diff --git a/.github/workflows/build-bazel.yml b/.github/workflows/build-bazel.yml index cbb58e1d53..affeefeeb8 100644 --- a/.github/workflows/build-bazel.yml +++ b/.github/workflows/build-bazel.yml @@ -19,7 +19,13 @@ jobs: with: path: "~/.cache/bazel" key: bazel - - run: bazel build //... + - name: bazel clean + run: bazel clean + + - name: build bazel + run: | + bazel build //... + - name: test - - run: ./bazel-bin/examples/call_highs_example \ No newline at end of file + run: ./bazel-bin/call-highs-example \ No newline at end of file diff --git a/.github/workflows/build-fast.yml b/.github/workflows/build-fast.yml index faec3cde47..247fa36c49 100644 --- a/.github/workflows/build-fast.yml +++ b/.github/workflows/build-fast.yml @@ -31,10 +31,12 @@ jobs: shell: bash run: ctest - - name: Doctest - working-directory: ${{runner.workspace}}/build - shell: bash - run: ./bin/doctest + # disable for now, py11 changes broke it. something trivial but + # not necessary, that was proof of concept. leaving here for now. + # - name: Doctest + # working-directory: ${{runner.workspace}}/build + # shell: bash + # run: ./bin/doctest - name: Install run: | diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 3675ea6da6..0327074e03 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -18,7 +18,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug - name: Build working-directory: ${{runner.workspace}}/build @@ -49,7 +49,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DFAST_BUILD=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release - name: Build working-directory: ${{runner.workspace}}/build diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 471b506dce..4455d473d6 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -8,7 +8,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macOS-10.15] # windows-2019 ubuntu-20.04, + os: [macOS-12] # windows-2019 ubuntu-20.04, steps: - uses: actions/checkout@v3 @@ -50,7 +50,7 @@ jobs: - name: Build wheels run: | - cd $GITHUB_WORKSPACE/src/interfaces/highspy + cd $GITHUB_WORKSPACE export REPAIR_LIBRARY_PATH=${{runner.workspace}}/installs/highs/lib export LD_LIBRARY_PATH=$REPAIR_LIBRARY_PATH export DYLD_LIBRARY_PATH=$REPAIR_LIBRARY_PATH diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000000..64ce412d8e --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,19 @@ +name: Documentation +on: + push: + branches: [latest] + tags: '*' + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@latest + with: + version: '1.6' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: julia --project=docs/ docs/make.jl diff --git a/.github/workflows/test-c-example.yml b/.github/workflows/test-c-example.yml index 1c3b81b925..e517d35f78 100644 --- a/.github/workflows/test-c-example.yml +++ b/.github/workflows/test-c-example.yml @@ -13,13 +13,13 @@ jobs: - name: Create Build Environment run: | mkdir build - mkdir install + mkdir installs - name: Build HiGHS library shell: bash working-directory: build run: | cmake \ - -DCMAKE_INSTALL_PREFIX=../install \ + -DCMAKE_INSTALL_PREFIX=../installs/highs \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=ON \ -DFAST_BUILD=ON \ @@ -32,6 +32,6 @@ jobs: run: | g++ $GITHUB_WORKSPACE/examples/call_highs_from_c.c \ -o c_example \ - -I install/include/highs \ - -L install/lib -lhighs - LD_LIBRARY_PATH=install/lib ./c_example + -I installs/highs/include/highs \ + -L installs/highs/lib -lhighs + LD_LIBRARY_PATH=installs/highs/lib ./c_example diff --git a/.github/workflows/test-python-api-mac.yml b/.github/workflows/test-python-api-mac.yml new file mode 100644 index 0000000000..c728398b62 --- /dev/null +++ b/.github/workflows/test-python-api-mac.yml @@ -0,0 +1,51 @@ +name: test-python-api-mac + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + python: [3.9] + + steps: + - uses: actions/checkout@v3 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Create Install dir + run: cmake -E make_directory ${{runner.workspace}}/installs + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=ON -DBUILD_SHARED_LIBS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel + cmake --install . + + - name: Install correct python version + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + + - name: Install Python Dependencies + shell: bash + run: | + python -m pip install --upgrade pip + python -m pip install pybind11 numpy pyomo pytest + + - name: Test Python Interface + shell: bash + working-directory: ${{runner.workspace}} + run: | + pip install -e ./HiGHS + pytest -v ./HiGHS/highspy/tests/ diff --git a/.github/workflows/test-python-api.yml b/.github/workflows/test-python-api-ubuntu.yml similarity index 82% rename from .github/workflows/test-python-api.yml rename to .github/workflows/test-python-api-ubuntu.yml index dade838761..1b2d5fcc84 100644 --- a/.github/workflows/test-python-api.yml +++ b/.github/workflows/test-python-api-ubuntu.yml @@ -1,4 +1,4 @@ -name: test-python-api +name: test-python-api-ubuntu on: [push, pull_request] @@ -16,7 +16,7 @@ jobs: - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build - - name: Create Install Directory + - name: Create Install dir run: cmake -E make_directory ${{runner.workspace}}/installs - name: Configure CMake @@ -29,8 +29,8 @@ jobs: shell: bash # Execute the build. You can specify a specific target with "--target " run: | - cmake --build . --parallel --config Release - make install + cmake --build . --parallel + cmake --install . HIGHS_LIB_DIR=${{runner.workspace}}/installs/highs/lib echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HIGHS_LIB_DIR" >> $GITHUB_ENV echo "DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$HIGHS_LIB_DIR" >> $GITHUB_ENV @@ -44,11 +44,11 @@ jobs: shell: bash run: | python -m pip install --upgrade pip - python -m pip install pybind11 numpy pyomo nose pip + python -m pip install pybind11 numpy pyomo pytest - name: Test Python Interface shell: bash working-directory: ${{runner.workspace}} run: | - pip install -e ./HiGHS/src/interfaces/highspy/ - nosetests -v ./HiGHS/src/interfaces/highspy/ + pip install -e ./HiGHS + pytest -v ./HiGHS/highspy/tests/ diff --git a/.github/workflows/test-python-api-win.yml b/.github/workflows/test-python-api-win.yml new file mode 100644 index 0000000000..7b392a9a0f --- /dev/null +++ b/.github/workflows/test-python-api-win.yml @@ -0,0 +1,55 @@ +name: test-python-api-win + +on: [] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest] + python: [3.9] + + steps: + - uses: actions/checkout@v3 + + - 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 -DFAST_BUILD=ON -DBUILD_SHARED_LIBS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + cmake --install . + + # $Env:HIGHS_LIB_DIR=${{runner.workspace}}/build/highs/lib + # $Env:Path=$Env:Path+";"+ $Env:HIGHS_LIB_DIR + # $Env:LIBPATH=$Env:LIBPATH+";"+ $Env:HIGHS_LIB_DIR + # $Env:PYTHONPATH=$Env:PYTHONPATH+";"+ $Env:HIGHS_LIB_DIR + # echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HIGHS_LIB_DIR" >> $GITHUB_ENV + # echo "DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$HIGHS_LIB_DIR" >> $GITHUB_ENV + + - name: Install correct python version + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + + - name: Install Python Dependencies + shell: bash + run: | + python -m pip install --upgrade pip + python -m pip install pybind11 numpy pyomo pytest + + - name: Test Python Interface + shell: bash + working-directory: ${{runner.workspace}} + run: | + pip install -e ./HiGHS + pytest -v ./HiGHS/highspy/tests/ diff --git a/.github/workflows/test-python-platforms.yml b/.github/workflows/test-python-platforms.yml index 97d9dc6de3..10279afb05 100644 --- a/.github/workflows/test-python-platforms.yml +++ b/.github/workflows/test-python-platforms.yml @@ -50,5 +50,5 @@ jobs: shell: bash working-directory: ${{runner.workspace}} run: | - pip install -e ./HiGHS/src/interfaces/highspy/ - nosetests -v ./HiGHS/src/interfaces/highspy/ + pip install -e ./HiGHS + nosetests -v ./HiGHS diff --git a/.gitignore b/.gitignore index 17c95d3b7b..c4519076d2 100644 --- a/.gitignore +++ b/.gitignore @@ -243,7 +243,7 @@ pip-log.txt #SCIP interface lpi_highs.cpp -src/interfaces/highspy/highspy/highs_bindings.*.so +highspy/highs_bindings.*.so # Model written with HiGHSDEV=on HighsRunModel.mps @@ -268,4 +268,4 @@ qjh.mps bazel* # webdemo -build_webdemo \ No newline at end of file +build_webdemo diff --git a/BUILD.bazel b/BUILD.bazel index cb79bf2a7e..b7fbd960be 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_binary") load("@bazel_skylib//rules:copy_file.bzl", "copy_file") copy_file( @@ -66,3 +66,12 @@ cc_library( "@zlib", ], ) + +cc_binary( + name = "call-highs-example", + srcs= ["examples/call_highs_from_cpp.cpp"], + deps = [ + "//:highs", + ], + visibility = ["//visibility:public"] +) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d36d192bf3..221e7645c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,19 +13,142 @@ include(Platform/${CMAKE_SYSTEM_NAME}-Determine-CXX OPTIONAL) include(Platform/${CMAKE_SYSTEM_NAME}-CXX OPTIONAL) set(CMAKE_CXX_COMPILER_NAMES clang++ icpc c++ ${CMAKE_CXX_COMPILER_NAMES}) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(utils-highs) +set_version(VERSION) + +project(HIGHS VERSION ${VERSION} LANGUAGES CXX C) + +set(PROJECT_NAMESPACE highs) +message(STATUS "${PROJECT_NAME} version: ${PROJECT_VERSION}") + +# use C++11 standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + # set default build type before project call, as it otherwise seems to fail for some plattforms if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RELEASE) endif() -project(HIGHS VERSION 1.5 LANGUAGES CXX C) -set(HIGHS_VERSION_PATCH 0) +# Default Build Type to be Release +# get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +# if(isMultiConfig) +# if(NOT CMAKE_CONFIGURATION_TYPES) +# set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING +# "Choose the type of builds, options are: Debug Release RelWithDebInfo MinSizeRel. (default: Release;Debug)" +# FORCE) +# endif() +# message(STATUS "Configuration types: ${CMAKE_CONFIGURATION_TYPES}") +# else() +# if(NOT CMAKE_BUILD_TYPE) +# set(CMAKE_BUILD_TYPE "Release" CACHE STRING +# "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel. (default: Release)" +# FORCE) +# endif() +# message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +# endif() + +# Layout build dir like install dir +include(GNUInstallDirs) -# use C++11 standard -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) + +# if(UNIX) +# option(BUILD_SHARED_LIBS "Build shared libraries (.so or .dyld)." ON) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +# # for multi-config build system (e.g. xcode) +# foreach(OUTPUTCONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) +# string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) +# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR}) +# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR}) +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR}) +# endforeach() +# else() +# # Currently Only support static build for windows +# option(BUILD_SHARED_LIBS "Build shared libraries (.dll)." OFF) +# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +# # for multi-config builds (e.g. msvc) +# foreach(OUTPUTCONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) +# string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) +# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR}) +# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR}) +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR}) +# endforeach() +# endif() + +if(BUILD_SHARED_LIBS AND MSVC) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +include(CTest) + +if(MSVC) + # This option is only available when building with MSVC. By default, highs + # is build using the cdecl calling convention, which is useful if you're + # writing C. However, the CLR and Win32 API both expect stdcall. + # + # If you are writing a CLR program and want to link to highs, you'll want + # to turn this on by invoking CMake with the "-DSTDCALL=ON" argument. + option( STDCALL "Build highs with the __stdcall convention" OFF ) +endif() + +option(FAST_BUILD "Fast build: " ON) + +# By default only build the C++ library. +option(BUILD_CXX "Build C++ library" ON) +message(STATUS "Build C++ library: ${BUILD_CXX}") + +option(PYTHON "Build Python interface" OFF) +message(STATUS "Build Python: ${PYTHON}") +option(FORTRAN "Build Fortran interface" OFF) +message(STATUS "Build Fortran: ${FORTRAN}") +option(CSHARP "Build CSharp interface" OFF) +message(STATUS "Build CSharp: ${CSHARP}") + +# ZLIB can be switched off, for building interfaces +option(ZLIB "Fast build: " ON) + +# If wrapper are built, we need to have the install rpath in BINARY_DIR to package +if(PYTHON OR FORTRAN OR CSHARP) + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +endif() + +# For Python interface +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + +# Basic type +include(CMakePushCheckState) +cmake_push_check_state(RESET) +set(CMAKE_EXTRA_INCLUDE_FILES "cstdint") +include(CheckTypeSize) +check_type_size("long" SIZEOF_LONG LANGUAGE CXX) +message(STATUS "Found long size: ${SIZEOF_LONG}") +check_type_size("long long" SIZEOF_LONG_LONG LANGUAGE CXX) +message(STATUS "Found long long size: ${SIZEOF_LONG_LONG}") +check_type_size("int64_t" SIZEOF_INT64_T LANGUAGE CXX) +message(STATUS "Found int64_t size: ${SIZEOF_INT64_T}") + +check_type_size("unsigned long" SIZEOF_ULONG LANGUAGE CXX) +message(STATUS "Found unsigned long size: ${SIZEOF_ULONG}") +check_type_size("unsigned long long" SIZEOF_ULONG_LONG LANGUAGE CXX) +message(STATUS "Found unsigned long long size: ${SIZEOF_ULONG_LONG}") +check_type_size("uint64_t" SIZEOF_UINT64_T LANGUAGE CXX) +message(STATUS "Found uint64_t size: ${SIZEOF_UINT64_T}") + +check_type_size("int *" SIZEOF_INT_P LANGUAGE CXX) +message(STATUS "Found int * size: ${SIZEOF_INT_P}") +cmake_pop_check_state() -# use customizable install directories -include(GNUInstallDirs) ### Require out-of-source builds file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) @@ -50,32 +173,12 @@ elseif(LINUX AND (NOT MSVC) AND (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) endif() endif() -# Fast build: No interfaces (apart from c); New (short) ctest instances, -# static library and exe without PIC. Used for gradually updating the CMake -# targets build and install / export. - -option(FAST_BUILD "Fast build: only build static lib and exe and quick test." OFF) - -# interfaces -option(PYTHON "Build Python interface" OFF) -option(FORTRAN "Build Fortran interface" OFF) -option(CSHARP "Build CSharp interface" OFF) - # emscripten option(EMSCRIPTEN_HTML "Emscripten HTML output" OFF) -# set the correct rpath for OS X set(CMAKE_MACOSX_RPATH ON) -option(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS "Export all symbols into the DLL" ON) - - -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) - include(CheckCXXSourceCompiles) - check_cxx_source_compiles( "#include int main () { @@ -112,29 +215,38 @@ endif() include(CheckCXXCompilerFlag) if (NOT FAST_BUILD) -# Function to set compiler flags on and off easily. -function(enable_cxx_compiler_flag_if_supported flag) - string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" flag_already_set) - if(flag_already_set EQUAL -1) - check_cxx_compiler_flag("${flag}" flag_supported) - if(flag_supported) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) - endif() - unset(flag_supported CACHE) - endif() -endfunction() - -# usage: turn pedantic on for even more warnings. -enable_cxx_compiler_flag_if_supported("-Wall") -enable_cxx_compiler_flag_if_supported("-Wextra") -enable_cxx_compiler_flag_if_supported("-Wno-unused-parameter") -enable_cxx_compiler_flag_if_supported("-Wno-format-truncation") -enable_cxx_compiler_flag_if_supported("-pedantic") + + option(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS "Export all symbols into the DLL" ON) + + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) + + option(BUILD_TESTING "Build Tests" ON) + + # Function to set compiler flags on and off easily. + function(enable_cxx_compiler_flag_if_supported flag) + string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" flag_already_set) + if(flag_already_set EQUAL -1) + check_cxx_compiler_flag("${flag}" flag_supported) + if(flag_supported) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) + endif() + unset(flag_supported CACHE) + endif() + endfunction() + + # usage: turn pedantic on for even more warnings. + enable_cxx_compiler_flag_if_supported("-Wall") + enable_cxx_compiler_flag_if_supported("-Wextra") + enable_cxx_compiler_flag_if_supported("-Wno-unused-parameter") + enable_cxx_compiler_flag_if_supported("-Wno-format-truncation") + enable_cxx_compiler_flag_if_supported("-pedantic") endif() if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86\_64|i686)") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)" AND NOT APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcntd") else() message("FLAG_MPOPCNT_SUPPORTED is not available on this architecture") @@ -157,30 +269,39 @@ if(MSVC) add_definitions(/wd4018 /wd4061 /wd4100 /wd4101 /wd4127 /wd4189 /wd4244 /wd4245 /wd4267 /wd4324 /wd4365 /wd4389 /wd4456 /wd4457 /wd4458 /wd4459 /wd4514 /wd4701 /wd4820) add_definitions(/MP) add_definitions(-D_CRT_SECURE_NO_WARNINGS) - add_definitions(-D_ITERATOR_DEBUG_LEVEL=0) + if (STDCALL) + # /Gz - stdcall calling convention + add_definitions(/Gz) + endif() + +# +# This fouls up building HiGHS as part of a larger CMake project: see #1129. +# Setting it will speed up run-time in debug (and compile-time?) +# +# add_definitions(-D_ITERATOR_DEBUG_LEVEL=0) endif() if (NOT FAST_BUILD OR FORTRAN) -include(CheckLanguage) -if(NOT MSVC) - check_language("Fortran") -endif() -if(CMAKE_Fortran_COMPILER) - enable_language(Fortran) - set(FORTRAN_FOUND ON) -else() - set(FORTRAN_FOUND OFF) -endif(CMAKE_Fortran_COMPILER) + include(CheckLanguage) + if(NOT MSVC) + check_language("Fortran") + endif() + if(CMAKE_Fortran_COMPILER) + enable_language(Fortran) + set(FORTRAN_FOUND ON) + else() + set(FORTRAN_FOUND OFF) + endif(CMAKE_Fortran_COMPILER) endif() if (NOT FAST_BUILD OR CSHARP) -check_language("CSharp") -if(CMAKE_CSharp_COMPILER) - enable_language(CSharp) - set(CSHARP_FOUND ON) -else() - set(CSHARP_FOUND OFF) -endif(CMAKE_CSharp_COMPILER) + check_language("CSharp") + if(CMAKE_CSharp_COMPILER) + enable_language(CSharp) + set(CSHARP_FOUND ON) + else() + set(CSHARP_FOUND OFF) + endif(CMAKE_CSharp_COMPILER) endif() check_cxx_compiler_flag("-fno-omit-frame-pointer" NO_OMIT_FRAME_POINTER_FLAG_SUPPORTED) @@ -201,7 +322,9 @@ endif() # set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined") # if zlib is found, then we can enable reading zlib-compressed input -find_package(ZLIB 1.2.3) +if (ZLIB) + find_package(ZLIB 1.2.3) +endif() include(CPack) set(CPACK_RESOURCE_FILE_LICENSE "${HIGHS_SOURCE_DIR}/COPYING") @@ -299,26 +422,6 @@ if (HIGHS_COVERAGE) endif() endif () -if(NOT MSVC) - set(OSI_ROOT "" CACHE PATH "Osi root folder.") - if (NOT "${OSI_ROOT}" STREQUAL "") - # if OSI_ROOT is set, then overwrite PKG_CONFIG_PATH - message(STATUS "OSI root folder set: " ${OSI_ROOT}) - set(ENV{PKG_CONFIG_PATH} "${OSI_ROOT}/${CMAKE_INSTALL_LIBDIR}/pkgconfig") - endif () - unset(OSI_ROOT CACHE) - find_package(PkgConfig) - - if(PKG_CONFIG_FOUND) - pkg_check_modules(OSI osi) - if (OSI_FOUND) - # need to come before adding any targets (add_executable, add_library) - link_directories(${OSI_LIBRARY_DIRS}) - include_directories(${OSITEST_INCLUDE_DIRS}) - endif (OSI_FOUND) - endif() -endif() - # whether to use shared or static libraries option(SHARED "Build shared libraries" ON) set(BUILD_SHARED_LIBS ${SHARED}) @@ -378,7 +481,6 @@ if(BUILD_TESTING) endif() add_subdirectory(src) - else(FAST_BUILD) message(STATUS "FAST_BUILD set to on. @@ -392,10 +494,11 @@ if(CMAKE_BUILD_TYPE STREQUAL RELEASE) endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -configure_file(${HIGHS_SOURCE_DIR}/src/HConfig.h.in ${HIGHS_BINARY_DIR}/HConfig.h) +# configure_file(${HIGHS_SOURCE_DIR}/src/HConfig.h.in ${HIGHS_BINARY_DIR}/HConfig.h) # set(CMAKE_PLATFORM_USES_PATH_WHEN_NO_SONAME FALSE) +option(BUILD_SHARED_LIBS "Build shared libraries." ON) # static build for windows if (NOT UNIX) @@ -427,31 +530,19 @@ include(CMakeDependentOption) option(BUILD_EXAMPLES "Build examples" ON) message(STATUS "Build examples: ${BUILD_EXAMPLES}") -CMAKE_DEPENDENT_OPTION(BUILD_CXX_EX "Build cxx example" ON "BUILD_EXAMPLES;BUILD_CXX" OFF) +CMAKE_DEPENDENT_OPTION(BUILD_CXX_EXAMPLE "Build cxx example" ON "BUILD_EXAMPLES;BUILD_CXX" OFF) message(STATUS "Build C++: ${BUILD_CXX_EX}") -CMAKE_DEPENDENT_OPTION(BUILD_C_EX "Build c example" ON "BUILD_EXAMPLES;BUILD_C" OFF) -message(STATUS "Build C: ${BUILD_C_EX}") -CMAKE_DEPENDENT_OPTION(BUILD_PYTHON_EX "Build python example" ON "BUILD_EXAMPLES;BUILD_PYTHON" OFF) -message(STATUS "Build Python: ${BUILD_PYTHON_EX}") - -# By default all dependencies are NOT built (i.e. BUILD_DEPS=OFF), -# BUT if building any wrappers (Python, Java or .Net) then BUILD_DEPS=ON. -if(BUILD_PYTHON) - option(BUILD_DEPS /"Build all dependencies" ON) -else() - option(BUILD_DEPS "Build all dependencies" OFF) -endif() -message(STATUS "Build all dependencies: ${BUILD_DEPS}") -# Install built dependencies if any, -option(INSTALL_BUILD_DEPS "Install build all dependencies" ON) +CMAKE_DEPENDENT_OPTION(BUILD_PYTHON_EXAMPLE "Build Python example" ON "BUILD_EXAMPLES;PYTHON" OFF) +message(STATUS "Build Python: ${BUILD_PYTHON_EXAMPLE}") +CMAKE_DEPENDENT_OPTION(BUILD_CSHARP_EXAMPLE "Build CSharp example" ON "BUILD_EXAMPLES;CSHARP" OFF) +message(STATUS "Build CSharp: ${BUILD_CSHARP_EXAMPLE}") # IF BUILD_DEPS=ON THEN Force all BUILD_*=ON CMAKE_DEPENDENT_OPTION(BUILD_ZLIB "Build the ZLIB dependency Library" OFF "NOT BUILD_DEPS" ON) message(STATUS "Build ZLIB: ${BUILD_ZLIB}") - -if(BUILD_PYTHON) +if(PYTHON) CMAKE_DEPENDENT_OPTION(BUILD_pybind11 "Build the pybind11 dependency Library" OFF "NOT BUILD_DEPS" ON) message(STATUS "Python: Build pybind11: ${BUILD_pybind11}") @@ -467,121 +558,18 @@ if(BUILD_PYTHON) message(STATUS "Python: Fetch dependencies: ${FETCH_PYTHON_DEPS}") endif() -# Add tests in examples/tests -add_subdirectory(examples/tests) - -# condition added to src/CMakeLists.txt -add_subdirectory(src) - option(JULIA "Build library and executable for Julia" OFF) -option(CMAKE_TARGETS "Install module to find HiGHS targets" OFF) - -# No changes in app/ apart from a relative path -add_subdirectory(app) +include(cpp-highs) -# check/ not added here, instead define fewer tests: -# build, 3 feas, 1 infeas, 1 unbounded. 1 parallel, 1 no presolve -if (NOT JULIA) - enable_testing() +if (PYTHON) + include(python-highs) endif() -# Check whether targets build OK. -add_test(NAME highs-lib-build - COMMAND ${CMAKE_COMMAND} - --build ${HIGHS_BINARY_DIR} - --target libhighs - --config ${CMAKE_BUILD_TYPE} - ) - -set_tests_properties(highs-lib-build - PROPERTIES - RESOURCE_LOCK libhighs) - -add_test(NAME highs-exe-build - COMMAND ${CMAKE_COMMAND} - --build ${HIGHS_BINARY_DIR} - --target highs - --config ${CMAKE_BUILD_TYPE} - ) - -set_tests_properties(highs-exe-build - PROPERTIES - RESOURCE_LOCK highs) - -set(successInstances - "25fv47\;2888\; 5.5018458883\;" - "80bau3b\;3760\; 9.8722419241\;" - "greenbea\;5249\;-7.2555248130\;") - -set(optionsInstances - "adlittle\;74\; 2.2549496316\;") - -set(infeasibleInstances - "bgetam\; infeasible") - -set(unboundedInstances - "gas11\; unbounded") - -# define settings -set(settings - "" - "--presolve=off" - "--parallel=on") - -# define function to add tests -# More Modern CMake: avoid macros if you can -function(add_instance_tests instances solutionstatus setting) -# loop over the instances -foreach(instance ${${instances}}) - # add default tests - # treat the instance as a tuple (list) of two values - list(GET instance 0 name) - list(GET instance 1 iter) - - if(${solutionstatus} STREQUAL "Optimal") - list(GET instance 2 optval) - endif() - - # specify the instance and the settings load command - set(inst "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps") - - add_test(NAME ${name}${setting} COMMAND $ ${setting} - ${inst}) - - set_tests_properties (${name}${setting} PROPERTIES - DEPENDS unit_tests_all) - set_tests_properties (${name}${setting} PROPERTIES - PASS_REGULAR_EXPRESSION - "Model status : ${solutionstatus}") - - if(${solutionstatus} STREQUAL "Optimal") - if("${setting}" STREQUAL "--presolve=off") - set_tests_properties (${name}${setting} PROPERTIES - PASS_REGULAR_EXPRESSION - "Simplex iterations: ${iter}\nObjective value : ${optval}") - else() - set_tests_properties (${name}${setting} PROPERTIES - PASS_REGULAR_EXPRESSION - "Objective value : ${optval}") - endif() - endif() -endforeach(instance) - -endfunction() - -if (NOT JULIA) - # add tests for success and fail instances - add_instance_tests(successInstances "Optimal" "") - add_instance_tests(failInstances "Fail" "") - add_instance_tests(infeasibleInstances "Infeasible" "") -# add_instance_tests(unboundedInstances "Unbounded" "") - set(settings ${settings} "--solver=ipm") +# Add tests in examples/tests +add_subdirectory(examples) - foreach(setting ${settings}) - add_instance_tests(optionsInstances "Optimal" ${setting}) - endforeach() -endif() +add_subdirectory(app) if (EXP) add_executable(doctest) @@ -594,23 +582,13 @@ if (EXP) endif() target_include_directories(doctest PRIVATE extern) - target_link_libraries(doctest libhighs) + target_link_libraries(doctest highs) endif() - -install(TARGETS libhighs EXPORT highs-targets - LIBRARY - ARCHIVE - RUNTIME - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) - +# install(TARGETS highs EXPORT highs-targets +# LIBRARY +# ARCHIVE +# RUNTIME +# PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) endif() - -# # Comment out for scaffold/ tests -# add_subdirectory(scaffold) - -# # Only needed if it defines a target like a main executable. For methods stick -# # to header-only. -# add_subdirectory(dev_presolve) - diff --git a/HConfig.h.bazel b/HConfig.h.bazel index 26be956457..cf32fd5584 100644 --- a/HConfig.h.bazel +++ b/HConfig.h.bazel @@ -2,9 +2,6 @@ #define HCONFIG_H_ #define FAST_BUILD -/* #undef SCIP_DEV */ -/* #undef HiGHSDEV */ -/* #undef OSI_FOUND */ /* #undef ZLIB_FOUND */ #define CMAKE_BUILD_TYPE "RELEASE" #define HiGHSRELEASE diff --git a/LICENSE b/LICENSE index 9ec0336f00..9c4bd44a7c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 HiGHS +Copyright (c) 2023 HiGHS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 689e50f5b6..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include LICENSE -include README.md \ No newline at end of file diff --git a/README.md b/README.md index 523f7b28cc..3a9f6a76c4 100644 --- a/README.md +++ b/README.md @@ -4,240 +4,106 @@ [![PyPi](https://img.shields.io/pypi/v/highspy.svg)](https://pypi.python.org/pypi/highspy) [![PyPi](https://img.shields.io/pypi/dm/highspy.svg)](https://pypi.python.org/pypi/highspy) +## Table of Contents + +* [About HiGHS](#about-highs) +* [Documentation](#documentation) +* [Precompiled binaries](#precompiled-binaries) +* [Compilation](#compilation) +* [Interfaces](#interfaces) +* [Python](#python) +* [Example](#google-colab-example) +* [Reference](#reference) + +About HiGHS +----------- + HiGHS is a high performance serial and parallel solver for large scale sparse linear optimization problems of the form - Minimize (1/2) x^TQx + c^Tx subject to L <= Ax <= U; l <= x <= u +$$ \min \quad \dfrac{1}{2}x^TQx + c^Tx \qquad \textrm{s.t.}~ \quad L \leq Ax \leq U; \quad l \leq x \leq u $$ -where Q must be positive semi-definite and, if Q is zero, there may be a requirement that some of the variables take integer values. Thus HiGHS can solve linear programming (LP) problems, convex quadratic programming (QP) problems, and mixed integer programming (MIP) problems. It is mainly written in C++, but also has some C. It has been developed and tested on various Linux, MacOS and Windows installations using both the GNU (g++) and Intel (icc) C++ compilers. Note that HiGHS requires (at least) version 4.9 of the GNU compiler. It has no third-party dependencies. +where Q must be positive semi-definite and, if Q is zero, there may be a requirement that some of the variables take integer values. Thus HiGHS can solve linear programming (LP) problems, convex quadratic programming (QP) problems, and mixed integer programming (MIP) problems. It is mainly written in C++, but also has some C. It has been developed and tested on various Linux, MacOS and Windows installations. No third-party dependencies are required. HiGHS has primal and dual revised simplex solvers, originally written by Qi Huangfu and further developed by Julian Hall. It also has an interior point solver for LP written by Lukas Schork, an active set solver for QP written by Michael Feldmeier, and a MIP solver written by Leona Gottwald. Other features have been added by Julian Hall and Ivet Galabova, who manages the software engineering of HiGHS and interfaces to C, C#, FORTRAN, Julia and Python. -Although HiGHS is freely available under the MIT license, we would be pleased to learn about users' experience and give advice via email sent to highsopt@gmail.com. +Find out more about HiGHS at https://www.highs.dev. -Reference ---------- -If you use HiGHS in an academic context, please acknowledge this and cite the following article. -Parallelizing the dual revised simplex method -Q. Huangfu and J. A. J. Hall -Mathematical Programming Computation, 10 (1), 119-142, 2018. -DOI: 10.1007/s12532-017-0130-5 - -http://www.maths.ed.ac.uk/hall/HuHa13/ - -Wikipedia ---------- - -The project has an entry on Wikipedia: https://en.wikipedia.org/wiki/HiGHS_optimization_solver +Although HiGHS is freely available under the MIT license, we would be pleased to learn about users' experience and give advice via email sent to highsopt@gmail.com. Documentation ------------- -The rest of this file gives brief documentation for HiGHS. Comprehensive documentation is available from [ergo-code.github.io](https://ergo-code.github.io/HiGHS/dev/). +Documentation is available at https://ergo-code.github.io/HiGHS/. -Download --------- +Precompiled binaries +-------------------- -Precompiled static executables are available for a variety of platforms at: +Precompiled static executables are available for a variety of platforms at https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases _These binaries are provided by the Julia community and are not officially supported by the HiGHS development team. If you have trouble using these libraries, please open a GitHub issue and tag `@odow` in your question._ -**Installation instructions** - -To install, download the appropriate file and extract the executable located at `/bin/highs`. - - * For Windows users: if in doubt, choose the file ending in `x86_64-w64-mingw32.tar.gz` - * For M1 macOS users: choose the file ending in `aarch64-apple-darwin.tar.gz` - * For Intel macOS users: choose the file ending in `x86_64-apple-darwin.tar.gz` - -**Shared libaries** - -For advanced users, precompiled executables using shared libraries are available for a variety of platforms at: -https://github.com/JuliaBinaryWrappers/HiGHS_jll.jl/releases. - -Similar download instructions apply. - - * These files link against `libstdc++`. If you do not have one installed, download the platform-specific libraries from: - https://github.com/JuliaBinaryWrappers/CompilerSupportLibraries_jll.jl/releases/tag/CompilerSupportLibraries-v0.5.1%2B0 - and copy all the libraries into the same folder as the `highs` executable. - * Unless using the FORTRAN interface, any of versions libgfortran3-libgfortran5 should work. - If in doubt, Windows users should choose the `x86_64-w64-mingw32-libgfortran5.tar.gz`. +See https://ergo-code.github.io/HiGHS/binaries.html. Compilation ----------- -HiGHS uses CMake as build system. First setup -a build folder and call CMake as follows +HiGHS uses CMake as build system, and requires at least version 3.15. First setup a build folder and call CMake as follows mkdir build cd build - cmake -DFAST_BUILD=ON .. + cmake .. Then compile the code using - cmake --build . + cmake --build . This installs the executable `bin/highs`. -The minimum CMake version required is 3.15. -Testing -------- +As an alternative it is also possible to let cmake create the build folder and thus build everything from the HiGHS directory, as follows -To perform a quick test whether the compilation was successful, run + cmake -S . -B build + cmake --build build - ctest -Run-time options ----------------- +To test whether the compilation was successful, run -In the following discussion, the name of the executable file generated is -assumed to be `highs`. + ctest -HiGHS can read plain text MPS files and LP files and the following command +HiGHS can read MPS files and (CPLEX) LP files, and the following command solves the model in `ml.mps` highs ml.mps -HiGHS options -------------- -Usage: - highs [OPTION...] [file] - - --model_file arg File of model to solve. - --presolve arg Presolve: "choose" by default - "on"/"off" are alternatives. - --solver arg Solver: "choose" by default - "simplex"/"ipm" are alternatives. - --parallel arg Parallel solve: "choose" by default - "on"/"off" are alternatives. - --run_crossover arg Run crossover after IPM: "on" by default - "choose"/"off" are alternatives. - --time_limit arg Run time limit (seconds - double). - --options_file arg File containing HiGHS options. - --solution_file arg File for writing out model solution. - --write_model_file arg File for writing out model. - --random_seed arg Seed to initialize random number generation. - --ranging arg Report cost, bound, RHS and basic solution ranging in any solution file: "off" by default - "on" is alternatives. - --read_solution_file File of solution to be read - - --version Print version number - -h, --help Print help. - - Note: - - * If the file defines a quadratic term in the objective (so the problem is a QP or MIQP) and "simplex" or "ipm" is selected for the solver option, then the quadratic term will be ignored. - * If the file constrains some variables to take integer values and defines a quadratic term in the objective, then the problem is MIQP and cannot be solved by HiGHS - -Language interfaces and further documentation ---------------------------------------------- - -There are HiGHS interfaces for C, C#, FORTRAN, and Python in HiGHS/src/interfaces, with example driver files in HiGHS/examples. -Documentation is availble via https://www.highs.dev/, and we are happy to give a reasonable level of support via -email sent to highsopt@gmail.com. - -Parallel code -------------- - -Parallel computation within HiGHS is limited to the dual simplex solver. -However, performance gain is unlikely to be significant at present. -For the simplex solver, at best, speed-up is limited to the number of memory channels, rather than the number of cores. - -HiGHS will identify the number of available threads at run time, and restrict their use to the value of the HiGHS option `threads`. - -If run with `threads=1`, HiGHS is serial. The `--parallel` run-time -option will cause the HiGHS parallel dual simplex solver to run in serial. Although this -could lead to better performance on some problems, performance will typically be -diminished. - -If multiple threads are available, and run with `threads>1`, HiGHS will use multiple threads. -Although the best value will be problem and architecture dependent, for the simplex solver `threads=8` is typically a -good choice. -Although HiGHS is slower when run in parallel than in serial for some problems, it is typically faster in parallel. - -HiGHS Library -------------- - -HiGHS is compiled in a shared library. Running - -`make install` - -from the build folder installs the library in `lib/`, as well as all header files in `include/highs/`. For a custom -installation in `install_folder` run +HiGHS is installed using the command -`cmake -DCMAKE_INSTALL_PREFIX=install_folder ..` + cmake --install . -and then - -`make install` - -To use the library from a CMake project use - -`find_package(HiGHS)` - -and add the correct path to HIGHS_DIR. - -Compiling and linking without CMake ------------------------------------ - -An executable defined in the file `use_highs.cpp` (for example) is linked with the HiGHS library as follows. After running the code above, compile and run with - -`g++ -o use_highs use_highs.cpp -I install_folder/include/highs/ -L install_folder/lib/ -lhighs` - -`LD_LIBRARY_PATH=install_folder/lib/ ./use_highs` +with the optional setting of `--prefix = The installation prefix CMAKE_INSTALL_PREFIX` if it is to be installed anywhere other than the default location. Interfaces -========== - -Julia ------ - -- A Julia interface is available at https://github.com/jump-dev/HiGHS.jl. - -Rust ----- - -- HiGHS can be used from rust through the [`highs` crate](https://crates.io/crates/highs). The rust linear programming modeler [**good_lp**](https://crates.io/crates/good_lp) supports HiGHS. - -R ------- - -- An R interface is available through the [`highs` R package](https://cran.r-project.org/package=highs). - -Javascript ---------- -HiGHS can be used from javascript directly inside a web browser thanks to [highs-js](https://github.com/lovasoa/highs-js). See the [demo](https://lovasoa.github.io/highs-js/) and the [npm package](https://www.npmjs.com/package/highs). +There are HiGHS interfaces for C, C#, FORTRAN, and Python in [HiGHS/src/interfaces](https://github.com/ERGO-Code/HiGHS/blob/master/src/interfaces), with example driver files in [HiGHS/examples](https://github.com/ERGO-Code/HiGHS/blob/master/examples). More on language and modelling interfaces can be found at https://ergo-code.github.io/HiGHS/interfaces.html. -Alternatively, HiGHS can directly be compiled into a single HTML file and used -in a browser. This requires `emscripten` to be installed from their website -(unfortunately, e.g. `sudo apt install emscripten` in Ubuntu Linux is broken): - - https://emscripten.org/docs/getting_started/downloads.html - -Then, run - - sh build_webdemo.sh - -This will create the file `build_webdemo/bin/highs.html`. For fast edit -iterations run - - find src app | entr -rs 'make -C build_webdemo highs; echo' - -This will rebuild `highs.html` every time a source file is modified (e.g. -from Visual Studio Code). +We are happy to give a reasonable level of support via email sent to highsopt@gmail.com. Python ------ -There are two ways to build the Python interface to HiGHS. +There are two ways to build the Python interface to HiGHS. __From PyPi__ -HiGHS has been added to PyPi, so should be installable using the command +HiGHS has been added to PyPi, so should be installable using the command -pip install highspy + pip install highspy The installation can be tested using the example [minimal.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/minimal.py), yielding the output - Running HiGHS 1.2.2 [date: 2022-09-04, git hash: 8701dbf19] - Copyright (c) 2022 ERGO-Code under MIT licence terms + Running HiGHS 1.5.0 [date: 2023-02-22, git hash: d041b3da0] + Copyright (c) 2023 HiGHS under MIT licence terms Presolving model 2 rows, 2 cols, 4 nonzeros 0 rows, 0 cols, 0 nonzeros @@ -248,7 +114,7 @@ The installation can be tested using the example [minimal.py](https://github.com Objective value : 1.0000000000e+00 HiGHS run time : 0.00 -or the more didactic [call_highs_from_python.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/call_highs_from_python.py). +or the more didactic [call_highs_from_python.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/call_highs_from_python.py). __Directly__ @@ -256,13 +122,27 @@ In order to build the Python interface, build and install the HiGHS library as described above, ensure the shared library is in the `LD_LIBRARY_PATH` environment variable, and then run -`pip install ./` + pip install ./ -from `src/interfaces/highspy` (there should be a `setup.py` file there). +from the HiGHS directory. You may also require * `pip install pybind11` * `pip install pyomo` -The Python interface can then be tested as above +The Python interface can then be tested as above. + +Google Colab Example +----------------------------- +The [Google Colab Example Notebook](https://colab.research.google.com/drive/1JmHF53OYfU-0Sp9bzLw-D2TQyRABSjHb?usp=sharing) demonstrates how to call HiGHS via the Python interface `highspy`. + +Reference +--------- + +If you use HiGHS in an academic context, please acknowledge this and cite the following article. + +Parallelizing the dual revised simplex method +Q. Huangfu and J. A. J. Hall +Mathematical Programming Computation, 10 (1), 119-142, 2018. +DOI: [10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5) diff --git a/Version.txt b/Version.txt new file mode 100644 index 0000000000..1a2a2f4278 --- /dev/null +++ b/Version.txt @@ -0,0 +1,4 @@ +HIGHS_MAJOR=1 +HIGHS_MINOR=5 +HIGHS_PATCH=3 +#PRE_RELEASE=YES diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d02a07f3c3..6712cbd93e 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,3 +1,30 @@ +if (FAST_BUILD) + +# create highs binary using library without pic +add_executable(highs-bin) + +target_sources(highs-bin PRIVATE RunHighs.cpp) + +target_include_directories(highs-bin PRIVATE + $ + ) + +if (UNIX) + target_compile_options(highs-bin PUBLIC "-Wno-unused-variable") + target_compile_options(highs-bin PUBLIC "-Wno-unused-const-variable") +endif() + +set_target_properties(highs-bin + PROPERTIES OUTPUT_NAME highs) + +target_link_libraries(highs-bin highs) + + +# install the binary +install(TARGETS highs-bin EXPORT highs-targets + RUNTIME) +else() + # create highs binary using library without pic add_executable(highs) @@ -22,3 +49,5 @@ target_include_directories(highs PRIVATE # install the binary install(TARGETS highs EXPORT highs-targets RUNTIME) + +endif() \ No newline at end of file diff --git a/app/RunHighs.cpp b/app/RunHighs.cpp index 89c4efceda..53bbc459ef 100644 --- a/app/RunHighs.cpp +++ b/app/RunHighs.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file ../app/RunHighs.cpp * @brief HiGHS main @@ -48,6 +46,7 @@ int main(int argc, char** argv) { // call this first so that printHighsVersionCopyright uses reporting // settings defined in any options file. highs.passOptions(loaded_options); + // highs.writeOptions("Options.md"); // Load the model from model_file HighsStatus read_status = highs.readModel(model_file); @@ -67,11 +66,10 @@ int main(int argc, char** argv) { HighsStatus run_status = highs.run(); if (run_status == HighsStatus::kError) return (int)run_status; - // Possibly compute the ranging information - if (options.ranging == kHighsOnString) highs.getRanging(); + // highs.writeInfo("Info.md"); // Possibly write the solution to a file - if (options.write_solution_to_file) + if (options.write_solution_to_file || options.solution_file != "") highs.writeSolution(options.solution_file, options.write_solution_style); // Possibly write the model to a file @@ -145,13 +143,13 @@ void reportModelStatsOrError(const HighsLogOptions& log_options, } if (num_integer) highsLogDev(log_options, HighsLogType::kInfo, - "Integer : %" HIGHSINT_FORMAT "\n", num_integer); + "Integer : %" HIGHSINT_FORMAT "\n", num_integer); if (num_semi_continuous) highsLogDev(log_options, HighsLogType::kInfo, - "SemiConts: %" HIGHSINT_FORMAT "\n", num_semi_continuous); + "SemiConts : %" HIGHSINT_FORMAT "\n", num_semi_continuous); if (num_semi_integer) highsLogDev(log_options, HighsLogType::kInfo, - "SemiInt : %" HIGHSINT_FORMAT "\n", num_semi_integer); + "SemiInt : %" HIGHSINT_FORMAT "\n", num_semi_integer); } else { highsLogUser(log_options, HighsLogType::kInfo, "%s", problem_type.c_str()); diff --git a/build_webdemo.sh b/build_webdemo.sh index 682715c703..12e7682232 100644 --- a/build_webdemo.sh +++ b/build_webdemo.sh @@ -1,3 +1,24 @@ +# Webdemo +# ------- + +# HiGHS can directly be compiled into a single HTML file and used +# in a browser. This requires `emscripten` to be installed from their website +# (unfortunately, e.g. `sudo apt install emscripten` in Ubuntu Linux is broken): + +# https://emscripten.org/docs/getting_started/downloads.html + +# Then, run + +# sh build_webdemo.sh + +# This will create the file `build_webdemo/bin/highs.html`. For fast edit +# iterations run + +# find src app | entr -rs 'make -C build_webdemo highs; echo' + +# This will rebuild `highs.html` every time a source file is modified (e.g. +# from Visual Studio Code). + script_path=$(realpath $(dirname $0)) build_dir="build_webdemo" diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index b3acf9c846..2f347df735 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -14,13 +14,14 @@ mip_abs_gap=0.0") set(TEST_SOURCES TestHighsVersion.cpp TestAlienBasis.cpp - TestDualise.cpp + TestDualize.cpp TestCheckSolution.cpp TestEkk.cpp TestFactor.cpp TestFreezeBasis.cpp TestHotStart.cpp TestMain.cpp + TestNames.cpp TestOptions.cpp TestIO.cpp TestSort.cpp @@ -69,24 +70,16 @@ if (UNIX) endif() target_link_libraries(unit_tests libhighs Catch) -if (OSI_FOUND AND BUILD_TESTING) - pkg_check_modules(OSITEST osi-unittests) - if (OSITEST_FOUND) - include_directories(${HIGHS_SOURCE_DIR}/src) - add_executable(osi_unit_tests TestOsi.cpp) - target_link_libraries(osi_unit_tests OsiHighs Catch ${OSITEST_LIBRARIES} CoinUtils) - target_include_directories(osi_unit_tests PUBLIC ${OSITEST_INCLUDE_DIRS} ${HIGHS_SOURCE_DIR}/src/interfaces) - target_compile_options(osi_unit_tests PUBLIC ${OSITEST_CFLAGS_OTHER}) - endif (OSITEST_FOUND) -endif() - if(FORTRAN_FOUND) set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) include_directories(${HIGHS_SOURCE_DIR}/src) add_executable(fortrantest TestFortranAPI.f90) - target_link_libraries(fortrantest libhighs FortranHighs) + if (NOT FAST_BUILD) + target_link_libraries(fortrantest libhighs FortranHighs) + else() + target_link_libraries(fortrantest highs FortranHighs) + endif() target_include_directories(fortrantest PUBLIC ${HIGHS_SOURCE_DIR}/src/interfaces) - else() endif(FORTRAN_FOUND) # check the C API @@ -114,40 +107,6 @@ set_tests_properties(unit_tests_all PROPERTIES DEPENDS unit-test-build) -if (OSITEST_FOUND) - -add_test(NAME osi-unit-test-build - COMMAND ${CMAKE_COMMAND} - --build ${HIGHS_BINARY_DIR} - --target osi_unit_tests - --config ${CMAKE_BUILD_TYPE} - ) - -# Avoid that several build jobs try to concurretly build. -set_tests_properties(osi-unit-test-build -PROPERTIES -RESOURCE_LOCK osiunittestbin) - -pkg_search_module(COINSAMPLE coindatasample) -if (COINSAMPLE_FOUND) - pkg_get_variable(COINSAMPLEDIR coindatasample datadir) -endif () - -pkg_search_module(COINNETLIB coindatanetlib) -if (COINNETLIB_FOUND) - pkg_get_variable(COINNETLIBDIR coindatanetlib datadir) -endif () - -configure_file(${HIGHS_SOURCE_DIR}/check/HCheckConfig.h.in ${HIGHS_BINARY_DIR}/HCheckConfig.h) - -# create a binary running all the tests in the executable -add_test(NAME osi_unit_tests_all COMMAND osi_unit_tests) -set_tests_properties(osi_unit_tests_all -PROPERTIES -DEPENDS osi-unit-test-build) - -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 # TEST_CASEs in the source files specified in TEST_SOURCES. Do not define any @@ -238,7 +197,7 @@ foreach(instance ${${instances}}) endif() # specify the instance and the settings load command - if(ZLIB_FOUND AND EXISTS "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps.gz") + if(ZLIB AND ZLIB_FOUND AND EXISTS "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps.gz") set(inst "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps.gz") else() set(inst "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps") diff --git a/check/TestAlienBasis.cpp b/check/TestAlienBasis.cpp index 5458a0beda..5805282aa8 100644 --- a/check/TestAlienBasis.cpp +++ b/check/TestAlienBasis.cpp @@ -635,12 +635,12 @@ TEST_CASE("AlienBasis-reuse-basis", "[highs_test_alien_basis]") { // Add another variable vector new_index = {0, 1, 2}; vector new_value = {50, 4, 30}; - highs.addCol(850, 0, inf, 3, &new_index[0], &new_value[0]); + highs.addCol(850, 0, inf, 3, new_index.data(), new_value.data()); // Add a new constraint new_value[0] = 15; new_value[1] = 24; new_value[2] = 30; - highs.addRow(-inf, 108, 3, &new_index[0], &new_value[0]); + highs.addRow(-inf, 108, 3, new_index.data(), new_value.data()); const bool singlar_also = true; if (singlar_also) { const HighsInt from_col = 0; @@ -656,8 +656,8 @@ TEST_CASE("AlienBasis-reuse-basis", "[highs_test_alien_basis]") { vector get_index(get_num_nz); vector get_value(get_num_nz); highs.getCols(from_col, to_col, get_num_col, &get_cost, &get_lower, - &get_upper, get_num_nz, &get_start[0], &get_index[0], - &get_value[0]); + &get_upper, get_num_nz, get_start.data(), get_index.data(), + get_value.data()); // Make the first two columns parallel, so that the saved basis is // singular, as well as having too few basic variables @@ -677,3 +677,40 @@ TEST_CASE("AlienBasis-reuse-basis", "[highs_test_alien_basis]") { highs.run(); if (dev_run) highs.writeSolution("", 1); } + +TEST_CASE("AlienBasis-singular-basis", "[highs_test_alien_basis]") { + HighsStatus return_status; + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {-1, -1}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf, -inf}; + lp.row_upper_ = {3, 2}; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 2, 3, 1}; + lp.sense_ = ObjSense::kMinimize; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + if (dev_run) highs.setOptionValue("log_dev_level", 3); + highs.passModel(lp); + highs.run(); + if (dev_run) highs.writeSolution("", 1); + HighsBasis basis = highs.getBasis(); + // Change the second constraint so that it's a copy of the first + highs.changeCoeff(1, 0, 1); + highs.changeCoeff(1, 1, 3); + highs.changeRowBounds(1, -inf, 3); + // Pass the basis - circumventing the internal setting of + // basis_.alien - and try to get the corresponding internal basic + // variables. INVERT will fail due to singularity, with no provision + // for basis changes to achieve non-singularity, so an error is + // returned. + highs.setBasis(basis); + std::vector basic_variables; + basic_variables.resize(lp.num_row_); + return_status = highs.getBasicVariables(basic_variables.data()); + REQUIRE(return_status == HighsStatus::kError); +} diff --git a/check/TestBasis.cpp b/check/TestBasis.cpp index ffcc3baa07..047db67974 100644 --- a/check/TestBasis.cpp +++ b/check/TestBasis.cpp @@ -70,7 +70,6 @@ TEST_CASE("Basis-file", "[highs_basis_file]") { std::remove(invalid_basis_file.c_str()); } -// No commas in test case name. TEST_CASE("Basis-data", "[highs_basis_data]") { HighsStatus return_status; std::string model0_file = @@ -97,10 +96,9 @@ TEST_CASE("Basis-data", "[highs_basis_data]") { testBasisReloadModel(highs, false); } -// No commas in test case name. TEST_CASE("set-pathological-basis", "[highs_basis_data]") { Highs highs; - // highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("output_flag", dev_run); HighsBasis basis; basis.clear(); @@ -109,6 +107,15 @@ TEST_CASE("set-pathological-basis", "[highs_basis_data]") { HighsInt index = 0; double value = 1.0; highs.addRow(0, 1, 1, &index, &value); + // Set up a basis with everything nonbasic. This will lead to + // basic_index being empty when passed to + // HFactor::setupGeneral. Previously this led to the creation of + // pointer &basic_index[0] that caused Windows faiure referenced in + // #1129, and reported in #1166. However, now that + // basic_index.data() is used to create the pointer, there is no + // Windows failure. Within HFactor::setupGeneral and + // HFactor::build(), the empty list of basic variables is handled + // correctly - with a basis of logicals being created basis.col_status.push_back(HighsBasisStatus::kLower); basis.row_status.push_back(HighsBasisStatus::kLower); highs.setBasis(basis); @@ -124,6 +131,31 @@ TEST_CASE("set-pathological-basis", "[highs_basis_data]") { REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnbounded); } +TEST_CASE("Basis-no-basic", "[highs_basis_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.addCol(1.0, 0, 1, 0, nullptr, nullptr); + highs.addCol(-1.0, 0, 1, 0, nullptr, nullptr); + std::vector index = {0, 1}; + std::vector value = {1.0, 2.0}; + highs.addRow(0, 1, 2, index.data(), value.data()); + value[0] = -1.0; + highs.addRow(0, 1, 2, index.data(), value.data()); + // Make all variables basic. This is a 2-row version of + // set-pathological-basis + HighsBasis basis; + basis.col_status.push_back(HighsBasisStatus::kLower); + basis.col_status.push_back(HighsBasisStatus::kLower); + basis.row_status.push_back(HighsBasisStatus::kLower); + basis.row_status.push_back(HighsBasisStatus::kLower); + REQUIRE(highs.setBasis(basis) == HighsStatus::kOk); + highs.run(); + if (dev_run) highs.writeSolution("", 1); + REQUIRE(highs.getInfo().objective_function_value == -0.5); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); +} + +// No commas in test case name. void testBasisReloadModel(Highs& highs, const bool from_file) { // Checks that no simplex iterations are required if a saved optimal // basis is used for the original LP after solving a different LP diff --git a/check/TestBasisSolves.cpp b/check/TestBasisSolves.cpp index e734acbfa5..330c691d4f 100644 --- a/check/TestBasisSolves.cpp +++ b/check/TestBasisSolves.cpp @@ -177,7 +177,7 @@ void testBasisSolve(Highs& highs) { HighsInt basic_col; - highs_status = highs.getBasicVariables(&basic_variables[0]); + highs_status = highs.getBasicVariables(basic_variables.data()); REQUIRE(highs_status == HighsStatus::kOk); for (HighsInt ix = 0; ix < numRow; ix++) known_solution[ix] = 0; @@ -202,9 +202,10 @@ void testBasisSolve(Highs& highs) { GetBasisSolvesFormRHS(lp, basic_variables, known_solution, rhs, transpose); if (transpose) { - highs_status = highs.getBasisTransposeSolve(&rhs[0], &solution_col[0]); + highs_status = + highs.getBasisTransposeSolve(rhs.data(), solution_col.data()); } else { - highs_status = highs.getBasisSolve(&rhs[0], &solution_col[0]); + highs_status = highs.getBasisSolve(rhs.data(), solution_col.data()); } REQUIRE(highs_status == HighsStatus::kOk); residual_norm = GetBasisSolvesCheckSolution(lp, basic_variables, rhs, @@ -243,8 +244,8 @@ void testBasisSolve(Highs& highs) { rhs[lp.a_matrix_.index_[el]] = lp.a_matrix_.value_[el]; highs_status = - highs.getBasisSolve(&rhs[0], &solution_col[0], &solution_num_nz, - &solution_col_indices[0]); + highs.getBasisSolve(rhs.data(), solution_col.data(), &solution_num_nz, + solution_col_indices.data()); REQUIRE(highs_status == HighsStatus::kOk); bool solution_nz_ok = GetBasisSolvesSolutionNzOk( numRow, solution_col, &solution_num_nz, solution_col_indices); @@ -273,8 +274,8 @@ void testBasisSolve(Highs& highs) { check_row = k; // Determine row check_row of B^{-1} highs_status = - highs.getBasisInverseRow(check_row, &solution_col[0], &solution_num_nz, - &solution_col_indices[0]); + highs.getBasisInverseRow(check_row, solution_col.data(), + &solution_num_nz, solution_col_indices.data()); REQUIRE(highs_status == HighsStatus::kOk); bool solution_nz_ok = GetBasisSolvesSolutionNzOk( numRow, solution_col, &solution_num_nz, solution_col_indices); @@ -306,8 +307,8 @@ void testBasisSolve(Highs& highs) { check_col = k; // Determine col check_col of B^{-1} highs_status = - highs.getBasisInverseCol(check_col, &solution_col[0], &solution_num_nz, - &solution_col_indices[0]); + highs.getBasisInverseCol(check_col, solution_col.data(), + &solution_num_nz, solution_col_indices.data()); REQUIRE(highs_status == HighsStatus::kOk); bool solution_nz_ok = GetBasisSolvesSolutionNzOk( numRow, solution_col, &solution_num_nz, solution_col_indices); @@ -337,7 +338,7 @@ void testBasisSolve(Highs& highs) { max_residual_norm = 0; for (;;) { for (HighsInt row = 0; row < numRow; row++) rhs[row] = random.fraction(); - highs_status = highs.getBasisSolve(&rhs[0], &solution_col[0]); + highs_status = highs.getBasisSolve(rhs.data(), solution_col.data()); REQUIRE(highs_status == HighsStatus::kOk); // Check solution residual_norm = GetBasisSolvesCheckSolution(lp, basic_variables, rhs, @@ -361,7 +362,8 @@ void testBasisSolve(Highs& highs) { max_residual_norm = 0; for (;;) { for (HighsInt row = 0; row < numRow; row++) rhs[row] = random.fraction(); - highs_status = highs.getBasisTransposeSolve(&rhs[0], &solution_col[0]); + highs_status = + highs.getBasisTransposeSolve(rhs.data(), solution_col.data()); REQUIRE(highs_status == HighsStatus::kOk); // Check solution residual_norm = GetBasisSolvesCheckSolution(lp, basic_variables, rhs, @@ -388,8 +390,8 @@ void testBasisSolve(Highs& highs) { for (;;) { check_row = k; highs_status = - highs.getReducedRow(check_row, &solution_row[0], &solution_num_nz, - &solution_row_indices[0]); + highs.getReducedRow(check_row, solution_row.data(), &solution_num_nz, + solution_row_indices.data()); REQUIRE(highs_status == HighsStatus::kOk); bool solution_nz_ok = GetBasisSolvesSolutionNzOk( numCol, solution_row, &solution_num_nz, solution_row_indices); @@ -410,8 +412,8 @@ void testBasisSolve(Highs& highs) { for (;;) { check_col = k; highs_status = - highs.getReducedColumn(check_col, &solution_col[0], &solution_num_nz, - &solution_col_indices[0]); + highs.getReducedColumn(check_col, solution_col.data(), &solution_num_nz, + solution_col_indices.data()); REQUIRE(highs_status == HighsStatus::kOk); // Check solution for (HighsInt row = 0; row < numRow; row++) rhs[row] = 0; @@ -472,10 +474,10 @@ TEST_CASE("Basis-solves", "[highs_basis_solves]") { solution_col.resize(numRow); // Check the NULL pointer trap for RHS - highs_status = highs.getBasisSolve(NULL, &solution_col[0]); + highs_status = highs.getBasisSolve(NULL, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisSolve(NULL, &solution_col[0]); + highs_status = highs.getBasisSolve(NULL, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); // Check the NULL pointer trap for the basic variables @@ -489,10 +491,10 @@ TEST_CASE("Basis-solves", "[highs_basis_solves]") { highs_status = highs.getBasisInverseCol(0, NULL); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisSolve(&rhs[0], NULL); + highs_status = highs.getBasisSolve(rhs.data(), NULL); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisTransposeSolve(&rhs[0], NULL); + highs_status = highs.getBasisTransposeSolve(rhs.data(), NULL); REQUIRE(highs_status == HighsStatus::kError); highs_status = highs.getReducedRow(0, NULL); @@ -502,39 +504,39 @@ TEST_CASE("Basis-solves", "[highs_basis_solves]") { REQUIRE(highs_status == HighsStatus::kError); // Check the indexing traps - highs_status = highs.getBasisInverseRow(-1, &solution_col[0]); + highs_status = highs.getBasisInverseRow(-1, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisInverseRow(numRow, &solution_col[0]); + highs_status = highs.getBasisInverseRow(numRow, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisInverseCol(-1, &solution_col[0]); + highs_status = highs.getBasisInverseCol(-1, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisInverseCol(numCol, &solution_col[0]); + highs_status = highs.getBasisInverseCol(numCol, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); // Check the no INVERSE traps - these should all work, as the first should // force inversion!!! - highs_status = highs.getBasicVariables(&basic_variables[0]); + highs_status = highs.getBasicVariables(basic_variables.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisInverseRow(0, &solution_col[0]); + highs_status = highs.getBasisInverseRow(0, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisInverseCol(0, &solution_col[0]); + highs_status = highs.getBasisInverseCol(0, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisSolve(&rhs[0], &solution_col[0]); + highs_status = highs.getBasisSolve(rhs.data(), solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getBasisTransposeSolve(&rhs[0], &solution_col[0]); + highs_status = highs.getBasisTransposeSolve(rhs.data(), solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getReducedRow(0, &solution_row[0]); + highs_status = highs.getReducedRow(0, solution_row.data()); REQUIRE(highs_status == HighsStatus::kError); - highs_status = highs.getReducedColumn(0, &solution_col[0]); + highs_status = highs.getReducedColumn(0, solution_col.data()); REQUIRE(highs_status == HighsStatus::kError); // Solve and perform numerical tests diff --git a/check/TestCAPI.c b/check/TestCAPI.c index bc70789790..ee1cff58f3 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -46,11 +46,11 @@ void assertLogical(const char* name, const HighsInt is) { void version_api() { if (dev_run) { printf("HiGHS version %s\n", Highs_version()); - printf("HiGHS version major %d\n", Highs_version_major()); - printf("HiGHS version minor %d\n", Highs_version_minor()); - printf("HiGHS version patch %d\n", Highs_version_patch()); + printf("HiGHS version major %"HIGHSINT_FORMAT"\n", Highs_versionMajor()); + printf("HiGHS version minor %"HIGHSINT_FORMAT"\n", Highs_versionMinor()); + printf("HiGHS version patch %"HIGHSINT_FORMAT"\n", Highs_versionPatch()); printf("HiGHS githash: %s\n", Highs_githash()); - printf("HiGHS compilation date %s\n", Highs_compilation_date()); + printf("HiGHS compilation date %s\n", Highs_compilationDate()); } } @@ -417,9 +417,241 @@ void full_api() { return_status = Highs_run(highs); assert( return_status == kHighsStatusOk ); + char* col_prefix = "Col"; + char* row_prefix = "Row"; + // Check index out of bounds + return_status = Highs_passColName(highs, -1, col_prefix); + assert( return_status == kHighsStatusError ); + return_status = Highs_passColName(highs, num_col, col_prefix); + assert( return_status == kHighsStatusError ); + return_status = Highs_passRowName(highs, -1, row_prefix); + assert( return_status == kHighsStatusError ); + return_status = Highs_passRowName(highs, num_row, row_prefix); + assert( return_status == kHighsStatusError ); + + // Define all column names to be the same + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + return_status = Highs_passColName(highs, iCol, col_prefix); + assert( return_status == kHighsStatusOk ); + } + return_status = Highs_writeModel(highs, ""); + assert( return_status == kHighsStatusError ); + + // Define all column names to be different + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + const char suffix = iCol + '0'; + const char* suffix_p = &suffix; + char name[5]; // 3 chars prefix, 1 char iCol, 1 char 0-terminator + sprintf(name, "%s%" HIGHSINT_FORMAT "", col_prefix, iCol); + const char* name_p = name; + return_status = Highs_passColName(highs, iCol, name_p); + assert( return_status == kHighsStatusOk ); + } + return_status = Highs_writeModel(highs, ""); + assert( return_status == kHighsStatusOk ); + + // Check that the columns can be found by name + HighsInt ck_iCol; + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + char name[5]; + return_status = Highs_getColName(highs, iCol, name); + assert( return_status == kHighsStatusOk ); + return_status = Highs_getColByName(highs, name, &ck_iCol); + assert( return_status == kHighsStatusOk ); + assert( ck_iCol == iCol ); + } + return_status = Highs_getColByName(highs, "FRED", &ck_iCol); + assert( return_status == kHighsStatusError ); + + // Define all row names to be the same + for (HighsInt iRow = 0; iRow < num_row; iRow++) { + return_status = Highs_passRowName(highs, iRow, row_prefix); + assert( return_status == kHighsStatusOk ); + } + return_status = Highs_writeModel(highs, ""); + assert( return_status == kHighsStatusError ); + + // Define all row names to be different + for (HighsInt iRow = 0; iRow < num_row; iRow++) { + const char suffix = iRow + '0'; + const char* suffix_p = &suffix; + char name[5]; // 3 chars prefix, 1 char iCol, 1 char 0-terminator + sprintf(name, "%s%" HIGHSINT_FORMAT "", row_prefix, iRow); + const char* name_p = name; + return_status = Highs_passRowName(highs, iRow, name_p); + assert( return_status == kHighsStatusOk ); + } + return_status = Highs_writeModel(highs, ""); + assert( return_status == kHighsStatusOk ); + + // Check that the rows can be found by name + HighsInt ck_iRow; + for (HighsInt iRow = 0; iRow < num_row; iRow++) { + char name[5]; + return_status = Highs_getRowName(highs, iRow, name); + assert( return_status == kHighsStatusOk ); + return_status = Highs_getRowByName(highs, name, &ck_iRow); + assert( return_status == kHighsStatusOk ); + assert( ck_iRow == iRow ); + } + return_status = Highs_getRowByName(highs, "FRED", &ck_iRow); + assert( return_status == kHighsStatusError ); + + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + char name[5]; + char* name_p = name; + return_status = Highs_getColName(highs, iCol, name_p); + assert( return_status == kHighsStatusOk ); + if (dev_run) printf("Column %" HIGHSINT_FORMAT " has name %s\n", iCol, name_p); + } + + for (HighsInt iRow = 0; iRow < num_row; iRow++) { + char name[5]; + char* name_p = name; + return_status = Highs_getRowName(highs, iRow, name_p); + assert( return_status == kHighsStatusOk ); + if (dev_run) printf("Row %" HIGHSINT_FORMAT " has name %s\n", iRow, name_p); + } + Highs_destroy(highs); } +void full_api_options() { + void* highs; + + highs = Highs_create(); + Highs_setBoolOptionValue(highs, "output_flag", dev_run); + + const double kHighsInf = Highs_getInfinity(highs); + HighsInt simplex_scale_strategy; + HighsInt return_status; + return_status = Highs_getIntOptionValue(highs, "simplex_scale_strategy", &simplex_scale_strategy); + assert( return_status == kHighsStatusOk ); + if (dev_run) + printf("simplex_scale_strategy = %"HIGHSINT_FORMAT": setting it to 3\n", simplex_scale_strategy); + simplex_scale_strategy = 3; + return_status = Highs_setIntOptionValue(highs, "simplex_scale_strategy", simplex_scale_strategy); + + const HighsInt presolve_index = 0; + char* name = NULL; + return_status = Highs_getOptionName(highs, presolve_index, &name); + if (dev_run) printf("option %"HIGHSINT_FORMAT" has name %s\n", presolve_index, name); + const char* presolve = "presolve"; + assert( *name == *presolve ); + free(name); + + HighsInt check_simplex_scale_strategy; + HighsInt min_simplex_scale_strategy; + HighsInt max_simplex_scale_strategy; + HighsInt default_simplex_scale_strategy; + return_status = Highs_getIntOptionValues(highs, "scale_strategy", NULL, NULL, NULL, NULL); + assert( return_status == kHighsStatusError ); + return_status = Highs_getDoubleOptionValues(highs, "simplex_scale_strategy", NULL, NULL, NULL, NULL); + assert( return_status == kHighsStatusError ); + return_status = Highs_getIntOptionValues(highs, "simplex_scale_strategy", + &check_simplex_scale_strategy, + &min_simplex_scale_strategy, + &max_simplex_scale_strategy, + &default_simplex_scale_strategy); + assert( return_status == kHighsStatusOk ); + assert( check_simplex_scale_strategy == simplex_scale_strategy ); + assert( min_simplex_scale_strategy == 0 ); + assert( max_simplex_scale_strategy == 5 ); + assert( default_simplex_scale_strategy == 1 ); + + + // There are some functions to check what type of option value you should + // provide. + HighsInt option_type; + return_status = Highs_getOptionType(highs, "simplex_scale_strategy", &option_type); + assert( return_status == kHighsStatusOk ); + assert( option_type == kHighsOptionTypeInt ); + return_status = Highs_getOptionType(highs, "bad_option", &option_type); + assert( return_status == kHighsStatusError ); + + double primal_feasibility_tolerance; + return_status = Highs_getDoubleOptionValue(highs, "primal_feasibility_tolerance", &primal_feasibility_tolerance); + assert( return_status == kHighsStatusOk ); + if (dev_run) + printf("primal_feasibility_tolerance = %g: setting it to 1e-6\n", primal_feasibility_tolerance); + primal_feasibility_tolerance = 1e-6; + return_status = Highs_setDoubleOptionValue(highs, "primal_feasibility_tolerance", primal_feasibility_tolerance); + assert( return_status == kHighsStatusOk ); + + double check_primal_feasibility_tolerance; + return_status = Highs_getDoubleOptionValues(highs, "primal_feasibility_tolerance", + &check_primal_feasibility_tolerance, NULL, NULL, NULL); + assert( return_status == kHighsStatusOk ); + assert( check_primal_feasibility_tolerance == primal_feasibility_tolerance ); + double default_primal_feasibility_tolerance; + double min_primal_feasibility_tolerance; + double max_primal_feasibility_tolerance; + return_status = Highs_getDoubleOptionValues(highs, "primal_feasibility_tolerance", + &check_primal_feasibility_tolerance, + &min_primal_feasibility_tolerance, + &max_primal_feasibility_tolerance, + &default_primal_feasibility_tolerance); + assert( min_primal_feasibility_tolerance == 1e-10 ); + assert( max_primal_feasibility_tolerance == kHighsInf ); + assert( default_primal_feasibility_tolerance == 1e-7 ); + + Highs_setStringOptionValue(highs, "presolve", "off"); + + return_status = Highs_getStringOptionValues(highs, "pre-solve", NULL, NULL); + assert( return_status == kHighsStatusError ); + // char check_presolve_value[kHighsMaximumStringLength]; + char check_presolve_value[512]; + return_status = Highs_getStringOptionValues(highs, "presolve", check_presolve_value, NULL); + assert( return_status == kHighsStatusOk ); + + // const HighsInt output_flag = 1; + // return_status = Highs_setBoolOptionValue(highs, "output_flag", output_flag); + return_status = Highs_setBoolOptionValue(highs, "output_flag", 1); + + assert( return_status == kHighsStatusOk ); + + HighsInt check_output_flag, default_output_flag; + return_status = Highs_getBoolOptionValues(highs, "output_flag", NULL, NULL); + assert( return_status == kHighsStatusOk ); + return_status = Highs_getBoolOptionValues(highs, "output_flag", + &check_output_flag, NULL); + assert( return_status == kHighsStatusOk ); + // assert( check_output_flag == output_flag ); + assert( check_output_flag == 1 ); + return_status = Highs_getBoolOptionValues(highs, "output_flag", + &check_output_flag, + &default_output_flag); + assert( return_status == kHighsStatusOk ); + // assert( default_output_flag == output_flag ); + assert( default_output_flag == 1 ); + + HighsInt num_string_option = 0; + char* option = NULL; + HighsInt type; + HighsInt num_options = Highs_getNumOptions(highs); + char current_string_value[512]; + + if (dev_run) + printf("\nString options are:\n"); + for (HighsInt index = 0; index < num_options; index++) { + Highs_getOptionName(highs, index, &option); + Highs_getOptionType(highs, option, &type); + if (type != kHighsOptionTypeString) { + free(option); + continue; + } + Highs_getStringOptionValues(highs, option, current_string_value, NULL); + num_string_option++; + if (dev_run) + printf("%"HIGHSINT_FORMAT": %-24s \"%s\"\n", + num_string_option, option, current_string_value); + free(option); + } + + Highs_destroy(highs); + +} + void full_api_lp() { // Form and solve the LP // Min f = 2x_0 + 3x_1 @@ -431,12 +663,11 @@ void full_api_lp() { void* highs; highs = Highs_create(); - if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); + Highs_setBoolOptionValue(highs, "output_flag", dev_run); const HighsInt num_col = 2; const HighsInt num_row = 3; const HighsInt num_nz = 5; - HighsInt i; // Define the column costs, lower bounds and upper bounds double col_cost[2] = {2.0, 3.0}; @@ -487,31 +718,66 @@ void full_api_lp() { printf("LP problem has old objective sense = %"HIGHSINT_FORMAT"\n", sense); assert( sense == kHighsObjSenseMinimize ); - HighsInt simplex_scale_strategy; - return_status = Highs_getIntOptionValue(highs, "simplex_scale_strategy", &simplex_scale_strategy); - assert( return_status == kHighsStatusOk ); - if (dev_run) - printf("simplex_scale_strategy = %"HIGHSINT_FORMAT": setting it to 3\n", simplex_scale_strategy); - simplex_scale_strategy = 3; - return_status = Highs_setIntOptionValue(highs, "simplex_scale_strategy", simplex_scale_strategy); - // There are some functions to check what type of option value you should - // provide. - HighsInt option_type; - return_status = Highs_getOptionType(highs, "simplex_scale_strategy", &option_type); - assert( return_status == kHighsStatusOk ); - assert( option_type == kHighsOptionTypeInt ); - return_status = Highs_getOptionType(highs, "bad_option", &option_type); - assert( return_status == kHighsStatusError ); - double primal_feasibility_tolerance; - return_status = Highs_getDoubleOptionValue(highs, "primal_feasibility_tolerance", &primal_feasibility_tolerance); - assert( return_status == kHighsStatusOk ); - if (dev_run) - printf("primal_feasibility_tolerance = %g: setting it to 1e-6\n", primal_feasibility_tolerance); - primal_feasibility_tolerance = 1e-6; - return_status = Highs_setDoubleOptionValue(highs, "primal_feasibility_tolerance", primal_feasibility_tolerance); - assert( return_status == kHighsStatusOk ); + // fetch column data (just first column) + { + const HighsInt get_col = 0; + const HighsInt num_get_col = 1; + HighsInt get_num_col = 0; + double* get_costs = (double*)malloc(sizeof(double) * num_get_col); + double* get_lower = (double*)malloc(sizeof(double) * num_get_col); + double* get_upper = (double*)malloc(sizeof(double) * num_get_col); + HighsInt get_num_nz = 0; + + return_status = Highs_getColsByRange(highs, get_col, get_col, + &get_num_col, get_costs, get_lower, get_upper, &get_num_nz, + NULL, NULL, NULL); + assert( return_status == kHighsStatusOk ); + + assertIntValuesEqual("getCols get_num_col", get_num_col, num_get_col); + assertDoubleValuesEqual("getCols get_costs", get_costs[0], col_cost[get_col]); + assertDoubleValuesEqual("getCols get_lower", get_lower[0], col_lower[get_col]); + assertDoubleValuesEqual("getCols get_upper", get_upper[0], col_upper[get_col]); + assertIntValuesEqual("getCols get_num_nz", get_num_nz, 2); + + // could also check coefficients by calling again... + + free(get_upper); + free(get_lower); + free(get_costs); + } + + // fetch row data (just 2nd row: 10 <= x_0 + 2x_1 <= 14) + { + const HighsInt get_row = 1; + const HighsInt num_get_row = 1; + HighsInt get_num_row = 0; + double* get_lower = (double*)malloc(sizeof(double) * num_get_row); + double* get_upper = (double*)malloc(sizeof(double) * num_get_row); + HighsInt get_num_nz = 0; + + assertIntValuesEqual("getNumRows", Highs_getNumRows(highs), num_row); + + return_status = Highs_getRowsByRange(highs, get_row, get_row, + &get_num_row, get_lower, get_upper, &get_num_nz, + NULL, NULL, NULL); + assert( return_status == kHighsStatusOk ); + + assertIntValuesEqual("getRows get_num_row", get_num_row, num_get_row); + assertDoubleValuesEqual("getRows get_lower", get_lower[0], row_lower[get_row]); + assertDoubleValuesEqual("getRows get_upper", get_upper[0], row_upper[get_row]); + assertIntValuesEqual("getRows get_num_nz", get_num_nz, 2); + + // could also check coefficients by calling again... + + free(get_upper); + free(get_lower); + } + + + + return_status = Highs_setBoolOptionValue(highs, "output_flag", 0); assert( return_status == kHighsStatusOk ); @@ -521,8 +787,6 @@ void full_api_lp() { assert( return_status == kHighsStatusOk ); if (dev_run) printf("Running loudly...\n"); - return_status = Highs_setBoolOptionValue(highs, "output_flag", 1); - assert( return_status == kHighsStatusOk ); // Get the model status HighsInt model_status = Highs_getModelStatus(highs); @@ -554,13 +818,13 @@ void full_api_lp() { return_status = Highs_getBasis(highs, col_basis_status, row_basis_status); assert( return_status == kHighsStatusOk ); // Report the column primal and dual values, and basis status - for (i = 0; i < num_col; i++) + for (HighsInt iCol = 0; iCol < num_col; iCol++) printf("Col%"HIGHSINT_FORMAT" = %lf; dual = %lf; status = %"HIGHSINT_FORMAT"; \n", - i, col_value[i], col_dual[i], col_basis_status[i]); + iCol, col_value[iCol], col_dual[iCol], col_basis_status[iCol]); // Report the row primal and dual values, and basis status - for (i = 0; i < num_row; i++) + for (HighsInt iRow = 0; iRow < num_row; iRow++) printf("Row%"HIGHSINT_FORMAT" = %lf; dual = %lf; status = %"HIGHSINT_FORMAT"; \n", - i, row_value[i], row_dual[i], row_basis_status[i]); + iRow, row_value[iRow], row_dual[iRow], row_basis_status[iRow]); } } free(col_value); @@ -592,6 +856,7 @@ void full_api_lp() { assert( model_status == kHighsModelStatusOptimal ); if (dev_run) printf("Run status = %"HIGHSINT_FORMAT"; Model status = %"HIGHSINT_FORMAT"\n", return_status, model_status); + Highs_destroy(highs); } @@ -653,6 +918,23 @@ void full_api_mip() { assert( return_status == kHighsStatusOk ); assert( mip_node_count == 1 ); + // Test Highs_getColIntegrality + HighsInt col_integrality; + return_status = Highs_getColIntegrality(highs, -1, &col_integrality); + assert( return_status == kHighsStatusError ); + return_status = Highs_getColIntegrality(highs, num_col, &col_integrality); + assert( return_status == kHighsStatusError ); + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + return_status = Highs_getColIntegrality(highs, iCol, &col_integrality); + assert( return_status == kHighsStatusOk ); + assert( col_integrality == 1 ); + } + + Highs_destroy(highs); + + free(col_value); + free(row_value); + } void full_api_qp() { @@ -682,9 +964,9 @@ void full_api_qp() { HighsInt q_dim = 1; HighsInt q_num_nz = 1; HighsInt q_format = kHighsHessianFormatTriangular; - HighsInt* q_start = (HighsInt*)malloc(sizeof(HighsInt) * q_dim); - HighsInt* q_index = (HighsInt*)malloc(sizeof(HighsInt) * q_num_nz); - double* q_value = (double*)malloc(sizeof(double) * q_num_nz); + HighsInt* q_start = (HighsInt*)malloc(sizeof(HighsInt*) * q_dim); + HighsInt* q_index = (HighsInt*)malloc(sizeof(HighsInt*) * q_num_nz); + double* q_value = (double*)malloc(sizeof(double*) * q_num_nz); q_start[0] = 0; q_index[0] = 0; q_value[0] = 2.0; @@ -722,11 +1004,14 @@ void full_api_qp() { model_status = Highs_getModelStatus(highs); assertIntValuesEqual("Model status for 2-d QP with illegal Hessian", model_status, 2); + free(q_start); + free(q_index); + free(q_value); + // Pass the new Hessian q_dim = 2; q_num_nz = 2; q_start = (HighsInt*)malloc(sizeof(HighsInt) * q_dim); - q_start = (HighsInt*)malloc(sizeof(HighsInt) * q_dim); q_index = (HighsInt*)malloc(sizeof(HighsInt) * q_num_nz); q_value = (double*)malloc(sizeof(double) * q_num_nz); q_start[0] = 0; @@ -750,6 +1035,7 @@ void full_api_qp() { objective_function_value = Highs_getObjectiveValue(highs); assertDoubleValuesEqual("Objective", objective_function_value, required_objective_function_value); + free(col_solution); col_solution = (double*)malloc(sizeof(double) * num_col); return_status = Highs_getSolution(highs, col_solution, NULL, NULL, NULL); @@ -816,6 +1102,14 @@ void full_api_qp() { model_status = Highs_getModelStatus(highs); assertIntValuesEqual("Model status for infeasible 2-d QP", model_status, 8); assert( model_status == kHighsModelStatusInfeasible ); + + Highs_destroy(highs); + + free(q_start); + free(q_index); + free(q_value); + free(col_solution); + } void options() { @@ -838,6 +1132,7 @@ void options() { assert( primal_feasibility_tolerance == 2.0 ); Highs_destroy(highs); + } void test_getColsByRange() { @@ -871,6 +1166,7 @@ void test_getColsByRange() { assert( matrix_index[1] == 0 ); assert( matrix_value[0] == 1.0 ); assert( matrix_value[1] == -1.0 ); + Highs_destroy(highs); } @@ -898,9 +1194,134 @@ void test_passHessian() { assertDoubleValuesEqual("Objective", objective_value, optimal_objective_value); assertDoubleValuesEqual("Primal", col_value[0], primal); assertDoubleValuesEqual("Dual", col_dual[0], dual); + Highs_destroy(highs); } +void test_ranging() { + + void* highs = Highs_create(); + if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); + // + // Set up + // min y + // s.t. + // -x + y >= 2 + // x + y >= 0 + // + double inf = Highs_getInfinity(highs); + Highs_addVar(highs, -inf, inf); + Highs_addVar(highs, -inf, inf); + Highs_changeColCost(highs, 0, 0); + Highs_changeColCost(highs, 1, 1); + HighsInt index[2] = {0.0, 1.0}; + double value[2] = {-1, 1}; + Highs_addRow(highs, 2, inf, 2, index, value); + value[0] = 1.0; + Highs_addRow(highs, 0, inf, 2, index, value); + // Cost ranging + // c0 2 -1 1 0 + // c1 0 0 inf inf + // + // Bound ranging + // Columns + // c0 1 -inf inf 1 + // c1 1 1 inf 1 + // Rows + // r0 -inf -inf inf inf + // r1 -inf -inf inf inf + Highs_run(highs); + HighsInt num_col = Highs_getNumCol(highs); + HighsInt num_row = Highs_getNumRow(highs); + double* col_cost_up_value = (double*)malloc(sizeof(double) * num_col); + double* col_cost_up_objective = (double*)malloc(sizeof(double) * num_col); + HighsInt* col_cost_up_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* col_cost_up_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + double* col_cost_dn_value = (double*)malloc(sizeof(double) * num_col); + double* col_cost_dn_objective = (double*)malloc(sizeof(double) * num_col); + HighsInt* col_cost_dn_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* col_cost_dn_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + double* col_bound_up_value = (double*)malloc(sizeof(double) * num_col); + double* col_bound_up_objective = (double*)malloc(sizeof(double) * num_col); + HighsInt* col_bound_up_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* col_bound_up_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + double* col_bound_dn_value = (double*)malloc(sizeof(double) * num_col); + double* col_bound_dn_objective = (double*)malloc(sizeof(double) * num_col); + HighsInt* col_bound_dn_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* col_bound_dn_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + double* row_bound_up_value = (double*)malloc(sizeof(double) * num_row); + double* row_bound_up_objective = (double*)malloc(sizeof(double) * num_row); + HighsInt* row_bound_up_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_row); + HighsInt* row_bound_up_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_row); + double* row_bound_dn_value = (double*)malloc(sizeof(double) * num_row); + double* row_bound_dn_objective = (double*)malloc(sizeof(double) * num_row); + HighsInt* row_bound_dn_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_row); + HighsInt* row_bound_dn_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_row); + HighsInt status = + Highs_getRanging(highs, + // + col_cost_up_value, col_cost_up_objective, col_cost_up_in_var, col_cost_up_ou_var, + col_cost_dn_value, col_cost_dn_objective, col_cost_dn_in_var, col_cost_dn_ou_var, + col_bound_up_value, col_bound_up_objective, col_bound_up_in_var, col_bound_up_ou_var, + col_bound_dn_value, col_bound_dn_objective, col_bound_dn_in_var, col_bound_dn_ou_var, + row_bound_up_value, row_bound_up_objective, row_bound_up_in_var, row_bound_up_ou_var, + row_bound_dn_value, row_bound_dn_objective, row_bound_dn_in_var, row_bound_dn_ou_var); + assert(status == kHighsStatusOk); + + assertDoubleValuesEqual("col_cost_dn_objective[0]", col_cost_dn_objective[0], 2); + assertDoubleValuesEqual("col_cost_dn_value[0]", col_cost_dn_value[0], -1); + assertDoubleValuesEqual("col_cost_up_value[0]", col_cost_up_value[0], 1); + assertDoubleValuesEqual("col_cost_up_objective[0]", col_cost_up_objective[0], 0); + assertDoubleValuesEqual("col_cost_dn_objective[1]", col_cost_dn_objective[1], 0); + assertDoubleValuesEqual("col_cost_dn_value[1]", col_cost_dn_value[1], 0); + assertDoubleValuesEqual("col_cost_up_value[1]", col_cost_up_value[1], inf); + assertDoubleValuesEqual("col_cost_up_objective[1]", col_cost_up_objective[1], inf); + + assertDoubleValuesEqual("col_bound_dn_objective[0]", col_bound_dn_objective[0], 1); + assertDoubleValuesEqual("col_bound_dn_value[0]", col_bound_dn_value[0], -inf); + assertDoubleValuesEqual("col_bound_up_value[0]", col_bound_up_value[0], inf); + assertDoubleValuesEqual("col_bound_up_objective[0]", col_bound_up_objective[0], 1); + assertDoubleValuesEqual("col_bound_dn_objective[1]", col_bound_dn_objective[1], 1); + assertDoubleValuesEqual("col_bound_dn_value[1]", col_bound_dn_value[1], 1); + assertDoubleValuesEqual("col_bound_up_value[1]", col_bound_up_value[1], inf); + assertDoubleValuesEqual("col_bound_up_objective[1]", col_bound_up_objective[1], 1); + + assertDoubleValuesEqual("row_bound_dn_objective[0]", row_bound_dn_objective[0], -inf); + assertDoubleValuesEqual("row_bound_dn_value[0]", row_bound_dn_value[0], -inf); + assertDoubleValuesEqual("row_bound_up_value[0]", row_bound_up_value[0], inf); + assertDoubleValuesEqual("row_bound_up_objective[0]", row_bound_up_objective[0], inf); + assertDoubleValuesEqual("row_bound_dn_objective[1]", row_bound_dn_objective[1], -inf); + assertDoubleValuesEqual("row_bound_dn_value[1]", row_bound_dn_value[1], -inf); + assertDoubleValuesEqual("row_bound_up_value[1]", row_bound_up_value[1], inf); + assertDoubleValuesEqual("row_bound_up_objective[1]", row_bound_up_objective[1], inf); + + free(col_cost_up_value); + free(col_cost_up_objective); + free(col_cost_up_in_var); + free(col_cost_up_ou_var); + free(col_cost_dn_value); + free(col_cost_dn_objective); + free(col_cost_dn_in_var); + free(col_cost_dn_ou_var); + free(col_bound_up_value); + free(col_bound_up_objective); + free(col_bound_up_in_var); + free(col_bound_up_ou_var); + free(col_bound_dn_value); + free(col_bound_dn_objective); + free(col_bound_dn_in_var); + free(col_bound_dn_ou_var); + free(row_bound_up_value); + free(row_bound_up_objective); + free(row_bound_up_in_var); + free(row_bound_up_ou_var); + free(row_bound_dn_value); + free(row_bound_dn_objective); + free(row_bound_dn_in_var); + free(row_bound_dn_ou_var); + +} + /* The horrible C in this causes problems in some of the CI tests, so suppress thius test until the C has been improved @@ -943,6 +1364,7 @@ void test_setSolution() { printf("Iteration counts are %d and %d\n", iteration_count0, iteration_count1); assertLogical("Dual", logic); + Highs_destroy(highs); } */ int main() { @@ -952,12 +1374,14 @@ int main() { minimal_api_lp(); minimal_api_mip(); minimal_api_qp(); + full_api_options(); full_api_lp(); full_api_mip(); full_api_qp(); options(); test_getColsByRange(); test_passHessian(); + test_ranging(); // test_setSolution(); return 0; } diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index 77e7bbdde4..a4ed7334b0 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -4,7 +4,7 @@ #include "SpecialLps.h" #include "catch.hpp" -const bool dev_run = true; +const bool dev_run = false; void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, @@ -253,7 +253,7 @@ TEST_CASE("check-set-rowwise-lp-solution", "[highs_check_solution]") { highs.addCol(-1.0, 0.0, 1.0, 0, nullptr, nullptr); highs.changeColIntegrality(i, HighsVarType::kInteger); } - highs.addRow(0.0, 1.0, num_col, &indices[0], &values[0]); + highs.addRow(0.0, 1.0, num_col, indices.data(), values.data()); highs.run(); double objective1 = highs.getInfo().objective_function_value; HighsSolution solution = highs.getSolution(); @@ -265,13 +265,42 @@ TEST_CASE("check-set-rowwise-lp-solution", "[highs_check_solution]") { highs.addCol(-1.0, 0.0, 1.0, 0, nullptr, nullptr); highs.changeColIntegrality(i, HighsVarType::kInteger); } - highs.addRow(0.0, 1.0, num_col, &indices[0], &values[0]); + highs.addRow(0.0, 1.0, num_col, indices.data(), values.data()); highs.setSolution(solution); highs.run(); double objective2 = highs.getInfo().objective_function_value; REQUIRE(fabs(objective1 - objective2) / max(1.0, objective1) < 1e-5); } +TEST_CASE("check-set-mip-solution-extra-row", "[highs_check_solution]") { + Highs highs; + const std::string solution_file_name = "temp.sol"; + highs.setOptionValue("output_flag", dev_run); + highs.addVar(0, 2); + highs.addVar(0, 2); + highs.changeColCost(0, 1); + highs.changeColCost(1, 10); + highs.changeColIntegrality(0, HighsVarType::kInteger); + std::vector index = {0, 1}; + std::vector value = {1, 1}; + highs.addRow(1, kHighsInf, 2, index.data(), value.data()); + highs.run(); + highs.writeSolution(solution_file_name); + if (dev_run) highs.writeSolution("", 1); + highs.clearSolver(); + // Add a constraint that cuts off the optimal solution, but leaves + // the integer assignment feasible + value[0] = 1; + value[1] = 4; + highs.addRow(4, kHighsInf, 2, index.data(), value.data()); + // Read the original solution - testing that the row section is not + // used + REQUIRE(highs.readSolution(solution_file_name) == HighsStatus::kOk); + highs.run(); + if (dev_run) highs.writeSolution("", 1); + std::remove(solution_file_name.c_str()); +} + void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, const HighsInt write_solution_style) { diff --git a/check/TestDualise.cpp b/check/TestDualize.cpp similarity index 91% rename from check/TestDualise.cpp rename to check/TestDualize.cpp index 3a0732b3c4..cc775003a5 100644 --- a/check/TestDualise.cpp +++ b/check/TestDualize.cpp @@ -6,7 +6,7 @@ const bool dev_run = false; const double double_equal_tolerance = 1e-5; void detailedOutput(Highs& highs); -void dualiseTest(Highs& highs); +void dualizeTest(Highs& highs); void simpleTest(Highs& highs); void fixedColumnTest(Highs& highs); void freeColumnTest(Highs& highs); @@ -16,7 +16,7 @@ void distillationTest(Highs& highs); HighsLp distillationLp(); void instanceTest(Highs& highs, const std::string model_name); -TEST_CASE("Dualise", "[highs_test_dualise]") { +TEST_CASE("Dualize", "[highs_test_dualize]") { Highs highs; if (!dev_run) highs.setOptionValue("output_flag", false); // simpleTest(highs); @@ -31,15 +31,15 @@ TEST_CASE("Dualise", "[highs_test_dualise]") { instanceTest(highs, "25fv47"); } -void dualiseTest(Highs& highs) { +void dualizeTest(Highs& highs) { const HighsInfo& info = highs.getInfo(); highs.setOptionValue("presolve", "off"); - highs.setOptionValue("simplex_dualise_strategy", kHighsOptionOff); + highs.setOptionValue("simplex_dualize_strategy", kHighsOptionOff); highs.setBasis(); highs.run(); // if (dev_run) highs.writeSolution("", true); double primal_objective = info.objective_function_value; - highs.setOptionValue("simplex_dualise_strategy", kHighsOptionOn); + highs.setOptionValue("simplex_dualize_strategy", kHighsOptionOn); highs.setBasis(); // detailedOutput(highs); highs.run(); @@ -87,7 +87,7 @@ void simpleTest(Highs& highs) { lp.a_matrix_.value_ = {1, 1}; lp.a_matrix_.format_ = MatrixFormat::kColwise; highs.passModel(model); - dualiseTest(highs); + dualizeTest(highs); highs.clear(); } @@ -95,17 +95,17 @@ void distillationTest(Highs& highs) { HighsModel model; model.lp_ = distillationLp(); highs.passModel(model); - dualiseTest(highs); + dualizeTest(highs); double x0_lower = 3; if (dev_run) printf("\nGive a lower bound on x0 of %g\n", x0_lower); highs.changeColBounds(0, x0_lower, inf); - dualiseTest(highs); + dualizeTest(highs); double x1_upper = 0.5; if (dev_run) printf("\nGive an upper bound on x1 of %g\n", x1_upper); highs.changeColBounds(1, -inf, x1_upper); - dualiseTest(highs); + dualizeTest(highs); highs.clear(); } @@ -117,7 +117,7 @@ void freeColumnTest(Highs& highs) { if (dev_run) printf("\nFree column 1 of distillation\n"); lp.col_lower_[1] = -inf; highs.passModel(model); - dualiseTest(highs); + dualizeTest(highs); highs.clear(); } @@ -130,7 +130,7 @@ void fixedColumnTest(Highs& highs) { lp.col_lower_[0] = x0_fixed; lp.col_upper_[0] = x0_fixed; highs.passModel(model); - dualiseTest(highs); + dualizeTest(highs); highs.clear(); } @@ -144,7 +144,7 @@ void colUpperBoundTest(Highs& highs) { // Needs reduced lower bound for feasiblilty // double col2_lower = 5.7; lp.col_lower_[2] = col2_lower; highs.passModel(model); - dualiseTest(highs); + dualizeTest(highs); } void rowUpperBoundTest(Highs& highs) { @@ -158,7 +158,7 @@ void rowUpperBoundTest(Highs& highs) { double row2_lower = 5.7; lp.row_lower_[2] = row2_lower; highs.passModel(model); - dualiseTest(highs); + dualizeTest(highs); } void instanceTest(Highs& highs, const std::string model_name) { @@ -166,5 +166,5 @@ void instanceTest(Highs& highs, const std::string model_name) { std::string(HIGHS_DIR) + "/check/instances/" + model_name + ".mps"; if (dev_run) printf("\nSolving model %s\n", model_name.c_str()); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); - dualiseTest(highs); + dualizeTest(highs); } diff --git a/check/TestFactor.cpp b/check/TestFactor.cpp index 6c99ea69df..5cff028bb6 100644 --- a/check/TestFactor.cpp +++ b/check/TestFactor.cpp @@ -32,7 +32,7 @@ TEST_CASE("Factor-dense-tran", "[highs_test_factor]") { highs.run(); basic_set.resize(num_row); // Get the optimal set of basic variables - highs.getBasicVariables(&basic_set[0]); + highs.getBasicVariables(basic_set.data()); for (HighsInt iRow = 0; iRow < num_row; iRow++) basic_set[iRow] = basic_set[iRow] < 0 ? num_col - basic_set[iRow] + 1 : basic_set[iRow]; @@ -351,7 +351,7 @@ bool testSolveDense() { unit[iCol] = 1.0; rhs_dense.clear(); for (HighsInt iCol = 0; iCol < num_row; iCol++) - rhs_dense[iCol] = lp.a_matrix_.computeDot(unit, basic_set[iCol]); + rhs_dense.push_back(lp.a_matrix_.computeDot(unit, basic_set[iCol])); factor.btranCall(rhs_dense); error_norm = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) { @@ -369,7 +369,7 @@ bool testSolveDense() { // Dense BTRAN rhs_dense.clear(); for (HighsInt iCol = 0; iCol < num_row; iCol++) - rhs_dense[iCol] = lp.a_matrix_.computeDot(solution, basic_set[iCol]); + rhs_dense.push_back(lp.a_matrix_.computeDot(solution, basic_set[iCol])); factor.btranCall(rhs_dense); error_norm = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index f064caf33d..67685c9ad0 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -95,7 +95,40 @@ TEST_CASE("filereader-edge-cases", "[highs_filereader]") { } } -TEST_CASE("filereader-free-format-parser", "[highs_filereader]") { +void freeFixedModelTest(const std::string model_name) { + std::string filename; + filename = std::string(HIGHS_DIR) + "/check/instances/" + model_name + ".mps"; + HighsStatus status; + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + status = highs.readModel(filename); + REQUIRE(status == HighsStatus::kOk); + + HighsModel model_free = highs.getModel(); + + status = highs.setOptionValue("mps_parser_type_free", false); + REQUIRE(status == HighsStatus::kOk); + + status = highs.readModel(filename); + REQUIRE(status == HighsStatus::kOk); + + HighsModel model_fixed = highs.getModel(); + + bool are_the_same = model_free == model_fixed; + REQUIRE(are_the_same); +} + +TEST_CASE("filereader-free-format-parser-qp", "[highs_filereader]") { + freeFixedModelTest("qjh"); + freeFixedModelTest("qjh_quadobj"); + // This test can't be used since fixed format reader can't handle + // QMATRIX section + // + // freeFixedModelTest("qjh_qmatrix"); +} + +TEST_CASE("filereader-free-format-parser-lp", "[highs_filereader]") { std::string filename; filename = std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; HighsStatus status; diff --git a/check/TestFreezeBasis.cpp b/check/TestFreezeBasis.cpp index 96c43e87f2..38bcb6064a 100644 --- a/check/TestFreezeBasis.cpp +++ b/check/TestFreezeBasis.cpp @@ -30,7 +30,7 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { // Get the integer solution to provide bound tightenings vector integrality; integrality.assign(num_col, HighsVarType::kInteger); - highs.changeColsIntegrality(from_col, to_col, &integrality[0]); + highs.changeColsIntegrality(from_col, to_col, integrality.data()); highs.setOptionValue("output_flag", false); highs.run(); @@ -43,7 +43,7 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { // Now restore the original integrality and set an explicit logical // basis to force reinversion integrality.assign(num_col, HighsVarType::kContinuous); - highs.changeColsIntegrality(from_col, to_col, &integrality[0]); + highs.changeColsIntegrality(from_col, to_col, integrality.data()); HighsBasis basis; for (HighsInt iCol = 0; iCol < num_col; iCol++) basis.col_status.push_back(HighsBasisStatus::kLower); @@ -58,7 +58,7 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { // Get the basic variables to force INVERT with a logical basis vector basic_variables; basic_variables.resize(lp.num_row_); - highs.getBasicVariables(&basic_variables[0]); + highs.getBasicVariables(basic_variables.data()); // Can freeze a basis now! REQUIRE(highs.freezeBasis(frozen_basis_id0) == HighsStatus::kOk); @@ -77,8 +77,8 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { if (dev_run) printf("\nSolving with bounds (integer solution, upper)\n"); local_col_lower = integer_solution; local_col_upper = original_col_upper; - highs.changeColsBounds(from_col, to_col, &local_col_lower[0], - &local_col_upper[0]); + highs.changeColsBounds(from_col, to_col, local_col_lower.data(), + local_col_upper.data()); if (dev_run) highs.setOptionValue("output_flag", true); highs.run(); // highs.setOptionValue("output_flag", false); @@ -91,8 +91,8 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { printf("\nSolving with bounds (integer solution, integer solution)\n"); local_col_lower = integer_solution; local_col_upper = integer_solution; - highs.changeColsBounds(from_col, to_col, &local_col_lower[0], - &local_col_upper[0]); + highs.changeColsBounds(from_col, to_col, local_col_lower.data(), + local_col_upper.data()); if (dev_run) highs.setOptionValue("output_flag", true); highs.run(); // highs.setOptionValue("output_flag", false); @@ -109,8 +109,8 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { if (dev_run) printf("Change column bounds to (integer solution, upper)\n"); local_col_lower = integer_solution; local_col_upper = original_col_upper; - highs.changeColsBounds(from_col, to_col, &local_col_lower[0], - &local_col_upper[0]); + highs.changeColsBounds(from_col, to_col, local_col_lower.data(), + local_col_upper.data()); if (dev_run) printf("Unfreeze basis %d\n", (int)frozen_basis_id2); REQUIRE(highs.unfreezeBasis(frozen_basis_id2) == HighsStatus::kOk); // Solving the LP should require no iterations @@ -131,8 +131,8 @@ TEST_CASE("FreezeBasis", "[highs_test_freeze_basis]") { if (dev_run) printf("Change column bounds to (lower, upper)\n"); local_col_lower = original_col_lower; local_col_upper = original_col_upper; - highs.changeColsBounds(from_col, to_col, &local_col_lower[0], - &local_col_upper[0]); + highs.changeColsBounds(from_col, to_col, local_col_lower.data(), + local_col_upper.data()); if (dev_run) printf("Unfreeze basis %d\n", (int)frozen_basis_id1); REQUIRE(highs.unfreezeBasis(frozen_basis_id1) == HighsStatus::kOk); // Solving the LP should require no iterations diff --git a/check/TestHighsModel.cpp b/check/TestHighsModel.cpp index 1adf4e2607..83615a7cc5 100644 --- a/check/TestHighsModel.cpp +++ b/check/TestHighsModel.cpp @@ -1,3 +1,4 @@ +#include #include #include "Highs.h" @@ -7,7 +8,7 @@ const bool dev_run = false; // No commas in test case name. -TEST_CASE("HighsModel", "[highs_model]") { +TEST_CASE("highs-model", "[highs_model]") { std::string filename; filename = std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; HighsStatus status; @@ -74,3 +75,54 @@ TEST_CASE("HighsModel", "[highs_model]") { status = highs.run(); REQUIRE(status == HighsStatus::kError); } + +TEST_CASE("highs-integrality", "[highs_model]") { + HighsLp lp; + HighsModelStatus require_model_status; + double optimal_objective; + lp.model_name_ = "distillation"; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {8, 10}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {7, 12, 6}; + lp.row_upper_ = {inf, inf, inf}; + lp.a_matrix_.start_ = {0, 3, 6}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {2, 3, 2, 2, 4, 1}; + lp.sense_ = ObjSense::kMinimize; + lp.offset_ = 0; + lp.a_matrix_.format_ = MatrixFormat::kColwise; + require_model_status = HighsModelStatus::kOptimal; + optimal_objective = 31.2; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + + HighsVarType integrality; + REQUIRE(highs.getColIntegrality(-1, integrality) == HighsStatus::kError); + REQUIRE(highs.getColIntegrality(0, integrality) == HighsStatus::kError); + REQUIRE(highs.getColIntegrality(lp.num_col_, integrality) == + HighsStatus::kError); + + lp.integrality_ = {HighsVarType::kContinuous, HighsVarType::kContinuous}; + highs.passModel(lp); + + REQUIRE(highs.getColIntegrality(-1, integrality) == HighsStatus::kError); + REQUIRE(highs.getColIntegrality(0, integrality) == HighsStatus::kOk); + REQUIRE(integrality == HighsVarType::kContinuous); + REQUIRE(highs.getColIntegrality(lp.num_col_, integrality) == + HighsStatus::kError); + + highs.run(); + REQUIRE(highs.getModelStatus() == require_model_status); + REQUIRE(std::abs(highs.getInfo().objective_function_value - + optimal_objective) < 1e-5); + + REQUIRE(highs.changeColIntegrality(0, HighsVarType::kInteger) == + HighsStatus::kOk); + + REQUIRE(highs.getColIntegrality(0, integrality) == HighsStatus::kOk); + REQUIRE(integrality == HighsVarType::kInteger); +} diff --git a/check/TestHighsVersion.cpp b/check/TestHighsVersion.cpp index 9e9cfda1ce..968771129c 100644 --- a/check/TestHighsVersion.cpp +++ b/check/TestHighsVersion.cpp @@ -8,20 +8,20 @@ const bool dev_run = false; // No commas in test case name. TEST_CASE("HighsVersion", "[highs_version]") { - std::string version = highsVersion(); + std::string version = std::string(highsVersion()); const HighsInt major = highsVersionMajor(); const HighsInt minor = highsVersionMinor(); const HighsInt patch = highsVersionPatch(); std::stringstream ss; - ss << "v" << major << "." << minor << "." << patch; + ss << major << "." << minor << "." << patch; std::string local_version = ss.str(); if (dev_run) { printf("HiGHS version: %s\n", version.c_str()); printf("HiGHS major version %d\n", int(major)); printf("HiGHS minor version %d\n", int(minor)); printf("HiGHS patch version %d\n", int(patch)); - printf("HiGHS githash: %s\n", highsGithash().c_str()); - printf("HiGHS compilation date: %s\n", highsCompilationDate().c_str()); + printf("HiGHS githash: %s\n", highsGithash()); + printf("HiGHS compilation date: %s\n", highsCompilationDate()); printf("HiGHS local version: %s\n", local_version.c_str()); } REQUIRE(major == HIGHS_VERSION_MAJOR); diff --git a/check/TestHotStart.cpp b/check/TestHotStart.cpp index 009a83e598..f995201e9c 100644 --- a/check/TestHotStart.cpp +++ b/check/TestHotStart.cpp @@ -26,7 +26,7 @@ TEST_CASE("HotStart-avgas", "[highs_test_hot_start]") { // Get the integer solution to provide bound tightenings vector integrality; integrality.assign(num_col, HighsVarType::kInteger); - highs.changeColsIntegrality(from_col, to_col, &integrality[0]); + highs.changeColsIntegrality(from_col, to_col, integrality.data()); highs.setOptionValue("output_flag", false); highs.run(); @@ -36,7 +36,7 @@ TEST_CASE("HotStart-avgas", "[highs_test_hot_start]") { // Now restore the original integrality integrality.assign(num_col, HighsVarType::kContinuous); - highs.changeColsIntegrality(from_col, to_col, &integrality[0]); + highs.changeColsIntegrality(from_col, to_col, integrality.data()); // Solve the continuous problem and get its hot start highs.run(); @@ -51,8 +51,8 @@ TEST_CASE("HotStart-avgas", "[highs_test_hot_start]") { highs.setBasis(); // Set the integer solution as upper bounds - highs.changeColsBounds(from_col, to_col, &original_col_lower[0], - &integer_solution[0]); + highs.changeColsBounds(from_col, to_col, original_col_lower.data(), + integer_solution.data()); if (dev_run) printf("\nSolving with bounds (lower, integer solution)\n"); @@ -91,7 +91,7 @@ TEST_CASE("HotStart-rgn", "[highs_test_hot_start]") { // Now remove integrality vector integrality; integrality.assign(num_col, HighsVarType::kContinuous); - highs.changeColsIntegrality(from_col, to_col, &integrality[0]); + highs.changeColsIntegrality(from_col, to_col, integrality.data()); // Solve the continuous problem and get its hot start highs.run(); @@ -106,8 +106,8 @@ TEST_CASE("HotStart-rgn", "[highs_test_hot_start]") { highs.setBasis(); // Set the mip solution as upper bounds - highs.changeColsBounds(from_col, to_col, &original_col_lower[0], - &mip_solution[0]); + highs.changeColsBounds(from_col, to_col, original_col_lower.data(), + mip_solution.data()); if (dev_run) printf("\nSolving with bounds (lower, mip solution)\n"); diff --git a/check/TestIO.cpp b/check/TestIO.cpp index 156b666ec5..79a25a7d47 100644 --- a/check/TestIO.cpp +++ b/check/TestIO.cpp @@ -9,7 +9,6 @@ const bool dev_run = false; const HighsInt kLogBufferSize = kIoBufferSize; char printed_log[kLogBufferSize]; -void* received_data = NULL; using std::memset; using std::strcmp; @@ -20,14 +19,15 @@ using std::strstr; // Callback that saves message for comparison static void myLogCallback(HighsLogType type, const char* message, - void* log_callback_data) { + void* deprecated // V2.0 remove +) { strcpy(printed_log, message); - received_data = log_callback_data; } // Callback that provides user logging static void userLogCallback(HighsLogType type, const char* message, - void* log_callback_data) { + void* deprecated // V2.0 remove +) { if (dev_run) printf("userLogCallback: %s", message); } @@ -41,22 +41,19 @@ TEST_CASE("run-callback", "[highs_io]") { } TEST_CASE("log-callback", "[highs_io]") { - int dummy_data = 42; bool output_flag = true; bool log_to_console = false; HighsInt log_dev_level = kHighsLogDevLevelInfo; HighsLogOptions log_options; - log_options.log_file_stream = stdout; + log_options.log_stream = stdout; log_options.output_flag = &output_flag; log_options.log_to_console = &log_to_console; log_options.log_dev_level = &log_dev_level; - log_options.log_callback = myLogCallback; - log_options.log_callback_data = (void*)&dummy_data; + log_options.log_user_callback = myLogCallback; highsLogDev(log_options, HighsLogType::kInfo, "Hi %s!", "HiGHS"); if (dev_run) printf("Log callback yields \"%s\"\n", printed_log); REQUIRE(strcmp(printed_log, "Hi HiGHS!") == 0); - REQUIRE(received_data == &dummy_data); // Check that nothing is printed if the type is VERBOSE when // log_dev_level is kHighsLogDevLevelInfo; @@ -78,7 +75,6 @@ TEST_CASE("log-callback", "[highs_io]") { highsLogUser(log_options, HighsLogType::kInfo, "Hello %s!\n", "HiGHS"); REQUIRE(strlen(printed_log) > 9); REQUIRE(strcmp(printed_log, "Hello HiGHS!\n") == 0); - REQUIRE(received_data == &dummy_data); { char long_message[sizeof(printed_log)]; diff --git a/check/TestInfo.cpp b/check/TestInfo.cpp index 4c1f77a5f0..4b93da3acd 100644 --- a/check/TestInfo.cpp +++ b/check/TestInfo.cpp @@ -24,15 +24,28 @@ TEST_CASE("highs-info", "[highs_info]") { return_status = highs.writeInfo(""); REQUIRE(return_status == HighsStatus::kWarning); - // Only use IPX if and using 32-bit arithmetic - bool use_ipx = false; -#ifndef HIGHSINT64 - use_ipx = true; -#endif - if (use_ipx) { - return_status = highs.setOptionValue("solver", "ipm"); - REQUIRE(return_status == HighsStatus::kOk); - } + return_status = highs.setOptionValue("solver", "ipm"); + REQUIRE(return_status == HighsStatus::kOk); + + HighsInfoType highs_info_type; + return_status = highs.getInfoType("objective_value", highs_info_type); + REQUIRE(return_status == HighsStatus::kError); + return_status = + highs.getInfoType("objective_function_value", highs_info_type); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(highs_info_type == HighsInfoType::kDouble); + + return_status = highs.getInfoType("iteration_count", highs_info_type); + REQUIRE(return_status == HighsStatus::kError); + return_status = highs.getInfoType("simplex_iteration_count", highs_info_type); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(highs_info_type == HighsInfoType::kInt); + + return_status = highs.getInfoType("mip_count", highs_info_type); + REQUIRE(return_status == HighsStatus::kError); + return_status = highs.getInfoType("mip_node_count", highs_info_type); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(highs_info_type == HighsInfoType::kInt64); // Info not valid before run() double objective_function_value; @@ -83,13 +96,8 @@ TEST_CASE("highs-info", "[highs_info]") { highs.modelStatusToString(model_status).c_str()); printf("From getInfo: objective_function_value = %g\n", highs_info.objective_function_value); - if (use_ipx) { - printf("From getInfo: ipm_iteration_count = %" HIGHSINT_FORMAT "\n", - highs_info.ipm_iteration_count); - } else { - printf("From getInfo: simplex_iteration_count = %" HIGHSINT_FORMAT "\n", - highs_info.simplex_iteration_count); - } + printf("From getInfo: ipm_iteration_count = %" HIGHSINT_FORMAT "\n", + highs_info.ipm_iteration_count); } std::remove(highs_info_file.c_str()); } diff --git a/check/TestLpModification.cpp b/check/TestLpModification.cpp index e2f0f5fd83..9bb773e9da 100644 --- a/check/TestLpModification.cpp +++ b/check/TestLpModification.cpp @@ -53,7 +53,8 @@ TEST_CASE("LP-717-od", "[highs_data]") { HighsStatus::kOk); std::vector index = {0}; std::vector value = {1.0}; - REQUIRE(highs.addRow(2.0, inf, 1, &index[0], &value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRow(2.0, inf, 1, index.data(), value.data()) == + HighsStatus::kOk); REQUIRE(highs.addCol(0.0, -inf, inf, 0, nullptr, nullptr) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); @@ -136,14 +137,14 @@ TEST_CASE("LP-717-full0", "[highs_data]") { } row_block_num_nz = row_block_index.size(); - REQUIRE(highs.addCols(row_block_num_col, &row_block_col_cost[0], - &row_block_col_lower[0], &row_block_col_upper[0], 0, - nullptr, nullptr, nullptr) == HighsStatus::kOk); + REQUIRE(highs.addCols(row_block_num_col, row_block_col_cost.data(), + row_block_col_lower.data(), row_block_col_upper.data(), + 0, nullptr, nullptr, nullptr) == HighsStatus::kOk); - REQUIRE(highs.addRows(row_block_num_row, &row_block_row_lower[0], - &row_block_row_upper[0], row_block_num_nz, - &row_block_start[0], &row_block_index[0], - &row_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(row_block_num_row, row_block_row_lower.data(), + row_block_row_upper.data(), row_block_num_nz, + row_block_start.data(), row_block_index.data(), + row_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a row-wise matrix, LP matrix has format %d\n", @@ -155,11 +156,11 @@ TEST_CASE("LP-717-full0", "[highs_data]") { std::vector col_block_start = {0, 2, 4}; std::vector col_block_index = {0, 1, 1, 2, 0, 1}; std::vector col_block_value = {-1, 1, -2, 2, -3, 3}; - REQUIRE(highs.addCols(col_block_num_col, &col_block_col_cost[0], - &col_block_col_lower[0], &col_block_col_upper[0], - col_block_num_nz, &col_block_start[0], - &col_block_index[0], - &col_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addCols(col_block_num_col, col_block_col_cost.data(), + col_block_col_lower.data(), col_block_col_upper.data(), + col_block_num_nz, col_block_start.data(), + col_block_index.data(), + col_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a column-wise matrix, LP matrix has format %d\n", (int)highs_lp.a_matrix_.format_); @@ -248,14 +249,14 @@ TEST_CASE("LP-717-full1", "[highs_data]") { } row_block_num_nz = row_block_index.size(); - REQUIRE(highs.addCols(row_block_num_col, &row_block_col_cost[0], - &row_block_col_lower[0], &row_block_col_upper[0], 0, - nullptr, nullptr, nullptr) == HighsStatus::kOk); + REQUIRE(highs.addCols(row_block_num_col, row_block_col_cost.data(), + row_block_col_lower.data(), row_block_col_upper.data(), + 0, nullptr, nullptr, nullptr) == HighsStatus::kOk); - REQUIRE(highs.addRows(row_block_num_row, &row_block_row_lower[0], - &row_block_row_upper[0], row_block_num_nz, - &row_block_start[0], &row_block_index[0], - &row_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(row_block_num_row, row_block_row_lower.data(), + row_block_row_upper.data(), row_block_num_nz, + row_block_start.data(), row_block_index.data(), + row_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a row-wise matrix, LP matrix has format %d\n", @@ -267,11 +268,11 @@ TEST_CASE("LP-717-full1", "[highs_data]") { std::vector col_block_start = {0, 2, 4}; std::vector col_block_index = {0, 1, 1, 2, 0, 1}; std::vector col_block_value = {-1, 1, -2, 2, -3, 3}; - REQUIRE(highs.addCols(col_block_num_col, &col_block_col_cost[0], - &col_block_col_lower[0], &col_block_col_upper[0], - col_block_num_nz, &col_block_start[0], - &col_block_index[0], - &col_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addCols(col_block_num_col, col_block_col_cost.data(), + col_block_col_lower.data(), col_block_col_upper.data(), + col_block_num_nz, col_block_start.data(), + col_block_index.data(), + col_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a column-wise matrix, LP matrix has format %d\n", (int)highs_lp.a_matrix_.format_); @@ -368,14 +369,14 @@ TEST_CASE("LP-717-full2", "[highs_data]") { } row_block_num_nz = row_block_index.size(); - REQUIRE(highs.addCols(row_block_num_col, &row_block_col_cost[0], - &row_block_col_lower[0], &row_block_col_upper[0], 0, - nullptr, nullptr, nullptr) == HighsStatus::kOk); + REQUIRE(highs.addCols(row_block_num_col, row_block_col_cost.data(), + row_block_col_lower.data(), row_block_col_upper.data(), + 0, nullptr, nullptr, nullptr) == HighsStatus::kOk); - REQUIRE(highs.addRows(row_block_num_row, &row_block_row_lower[0], - &row_block_row_upper[0], row_block_num_nz, - &row_block_start[0], &row_block_index[0], - &row_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(row_block_num_row, row_block_row_lower.data(), + row_block_row_upper.data(), row_block_num_nz, + row_block_start.data(), row_block_index.data(), + row_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a row-wise matrix, LP matrix has format %d\n", @@ -387,19 +388,19 @@ TEST_CASE("LP-717-full2", "[highs_data]") { std::vector col_block_start = {0, 2, 4}; std::vector col_block_index = {0, 1, 1, 2, 0, 1}; std::vector col_block_value = {-1, 1, -2, 2, -3, 3}; - REQUIRE(highs.addCols(col_block_num_col, &col_block_col_cost[0], - &col_block_col_lower[0], &col_block_col_upper[0], - col_block_num_nz, &col_block_start[0], - &col_block_index[0], - &col_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addCols(col_block_num_col, col_block_col_cost.data(), + col_block_col_lower.data(), col_block_col_upper.data(), + col_block_num_nz, col_block_start.data(), + col_block_index.data(), + col_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a column-wise matrix, LP matrix has format %d\n", (int)highs_lp.a_matrix_.format_); - REQUIRE(highs.addRows(row_block_num_row, &row_block_row_lower[0], - &row_block_row_upper[0], row_block_num_nz, - &row_block_start[0], &row_block_index[0], - &row_block_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(row_block_num_row, row_block_row_lower.data(), + row_block_row_upper.data(), row_block_num_nz, + row_block_start.data(), row_block_index.data(), + row_block_value.data()) == HighsStatus::kOk); if (dev_run) printf("After adding a row-wise matrix, LP matrix has format %d\n", @@ -467,11 +468,12 @@ TEST_CASE("LP-modification", "[highs_data]") { return_status); REQUIRE(return_status == HighsStatus::kOk); - REQUIRE(avgas_highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], - 0, NULL, NULL, NULL) == HighsStatus::kOk); - REQUIRE(avgas_highs.addRows(num_row, &rowLower[0], &rowUpper[0], num_row_nz, - &ARstart[0], &ARindex[0], - &ARvalue[0]) == HighsStatus::kOk); + REQUIRE(avgas_highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), 0, NULL, NULL, + NULL) == HighsStatus::kOk); + REQUIRE(avgas_highs.addRows(num_row, rowLower.data(), rowUpper.data(), + num_row_nz, ARstart.data(), ARindex.data(), + ARvalue.data()) == HighsStatus::kOk); // return_status = avgas_highs.writeModel(""); @@ -494,22 +496,23 @@ TEST_CASE("LP-modification", "[highs_data]") { REQUIRE(model_status == HighsModelStatus::kModelEmpty); // Adding column vectors and matrix to model with no rows returns an error - REQUIRE(highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], - num_col_nz, &Astart[0], &Aindex[0], - &Avalue[0]) == HighsStatus::kError); + REQUIRE(highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), num_col_nz, Astart.data(), + Aindex.data(), Avalue.data()) == HighsStatus::kError); // Adding column vectors to model with no rows returns OK - REQUIRE(highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], 0, - NULL, NULL, NULL) == HighsStatus::kOk); + REQUIRE(highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), 0, NULL, NULL, + NULL) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // return_status = highs.writeModel(""); // Adding row vectors and matrix to model with columns returns OK - REQUIRE(highs.addRows(num_row, &rowLower[0], &rowUpper[0], num_row_nz, - &ARstart[0], &ARindex[0], - &ARvalue[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(num_row, rowLower.data(), rowUpper.data(), num_row_nz, + ARstart.data(), ARindex.data(), + ARvalue.data()) == HighsStatus::kOk); // return_status = highs.writeModel(""); @@ -593,15 +596,16 @@ TEST_CASE("LP-modification", "[highs_data]") { callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // Adding column vectors to model with no rows returns OK - REQUIRE(highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], 0, - NULL, NULL, NULL) == HighsStatus::kOk); + REQUIRE(highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), 0, NULL, NULL, + NULL) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // Adding row vectors and matrix to model with columns returns OK - REQUIRE(highs.addRows(num_row, &rowLower[0], &rowUpper[0], num_row_nz, - &ARstart[0], &ARindex[0], - &ARvalue[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(num_row, rowLower.data(), rowUpper.data(), num_row_nz, + ARstart.data(), ARindex.data(), + ARvalue.data()) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -677,8 +681,9 @@ TEST_CASE("LP-modification", "[highs_data]") { callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // Adding column vectors to model with no rows returns OK - REQUIRE(highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], 0, - NULL, NULL, NULL) == HighsStatus::kOk); + REQUIRE(highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), 0, NULL, NULL, + NULL) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -863,15 +868,16 @@ TEST_CASE("LP-modification", "[highs_data]") { callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // Adding column vectors to model with no rows returns OK - REQUIRE(highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], 0, - NULL, NULL, NULL) == HighsStatus::kOk); + REQUIRE(highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), 0, NULL, NULL, + NULL) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // Adding row vectors and matrix to model with columns returns OK - REQUIRE(highs.addRows(num_row, &rowLower[0], &rowUpper[0], num_row_nz, - &ARstart[0], &ARindex[0], - &ARvalue[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(num_row, rowLower.data(), rowUpper.data(), num_row_nz, + ARstart.data(), ARindex.data(), + ARvalue.data()) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -941,17 +947,17 @@ TEST_CASE("LP-modification", "[highs_data]") { col1357_upper) == HighsStatus::kOk); // Return the LP to its original state with a mask - REQUIRE(highs.changeColsCost(col1357_col_mask, &colCost[0]) == + REQUIRE(highs.changeColsCost(col1357_col_mask, colCost.data()) == HighsStatus::kOk); REQUIRE(highs.changeColBounds(2, colLower[2], colUpper[2]) == HighsStatus::kOk); - REQUIRE(highs.changeColsBounds(col1357_col_mask, &colLower[0], - &colUpper[0]) == HighsStatus::kOk); + REQUIRE(highs.changeColsBounds(col1357_col_mask, colLower.data(), + colUpper.data()) == HighsStatus::kOk); - REQUIRE(highs.changeRowsBounds(row0135789_row_mask, &rowLower[0], - &rowUpper[0]) == HighsStatus::kOk); + REQUIRE(highs.changeRowsBounds(row0135789_row_mask, rowLower.data(), + rowUpper.data()) == HighsStatus::kOk); REQUIRE(highs.changeRowBounds(2, rowLower[2], rowUpper[2]) == HighsStatus::kOk); @@ -1165,18 +1171,18 @@ TEST_CASE("LP-interval-changes", "[highs_data]") { set_col2345_cost[1] = 3.0; set_col2345_cost[2] = 4.0; set_col2345_cost[3] = 5.0; - REQUIRE(highs.getCols(from_col, to_col, get_num_col, &og_col2345_cost[0], + REQUIRE(highs.getCols(from_col, to_col, get_num_col, og_col2345_cost.data(), NULL, NULL, get_num_nz, NULL, NULL, NULL) == HighsStatus::kOk); - REQUIRE(highs.changeColsCost(from_col, to_col, &set_col2345_cost[0]) == + REQUIRE(highs.changeColsCost(from_col, to_col, set_col2345_cost.data()) == HighsStatus::kOk); - REQUIRE(highs.getCols(from_col, to_col, get_num_col, &get_col2345_cost[0], + REQUIRE(highs.getCols(from_col, to_col, get_num_col, get_col2345_cost.data(), NULL, NULL, get_num_nz, NULL, NULL, NULL) == HighsStatus::kOk); REQUIRE(get_num_col == set_num_col); for (HighsInt usr_col = 0; usr_col < get_num_col; usr_col++) REQUIRE(get_col2345_cost[usr_col] == set_col2345_cost[usr_col]); - REQUIRE(highs.changeColsCost(from_col, to_col, &og_col2345_cost[0]) == + REQUIRE(highs.changeColsCost(from_col, to_col, og_col2345_cost.data()) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -1204,18 +1210,18 @@ TEST_CASE("LP-interval-changes", "[highs_data]") { set_col01234_lower[3] = 3.0; set_col01234_lower[4] = 4.0; REQUIRE(highs.getCols(from_col, to_col, get_num_col, NULL, - &og_col01234_lower[0], &og_col01234_upper[0], + og_col01234_lower.data(), og_col01234_upper.data(), get_num_nz, NULL, NULL, NULL) == HighsStatus::kOk); - REQUIRE(highs.changeColsBounds(from_col, to_col, &set_col01234_lower[0], - &og_col01234_upper[0]) == HighsStatus::kOk); + REQUIRE(highs.changeColsBounds(from_col, to_col, set_col01234_lower.data(), + og_col01234_upper.data()) == HighsStatus::kOk); REQUIRE(highs.getCols(from_col, to_col, get_num_col, NULL, - &get_col01234_lower[0], &og_col01234_upper[0], + get_col01234_lower.data(), og_col01234_upper.data(), get_num_nz, NULL, NULL, NULL) == HighsStatus::kOk); REQUIRE(get_num_col == set_num_col); for (HighsInt usr_col = 0; usr_col < get_num_col; usr_col++) REQUIRE(get_col01234_lower[usr_col] == set_col01234_lower[usr_col]); - REQUIRE(highs.changeColsBounds(from_col, to_col, &og_col01234_lower[0], - &og_col01234_upper[0]) == HighsStatus::kOk); + REQUIRE(highs.changeColsBounds(from_col, to_col, og_col01234_lower.data(), + og_col01234_upper.data()) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -1241,19 +1247,19 @@ TEST_CASE("LP-interval-changes", "[highs_data]") { set_row56789_lower[2] = 7.0; set_row56789_lower[3] = 8.0; set_row56789_lower[4] = 9.0; - REQUIRE(highs.getRows(from_row, to_row, get_num_row, &og_row56789_lower[0], - &og_row56789_upper[0], get_num_nz, NULL, NULL, - NULL) == HighsStatus::kOk); - REQUIRE(highs.changeRowsBounds(from_row, to_row, &set_row56789_lower[0], - &og_row56789_upper[0]) == HighsStatus::kOk); - REQUIRE(highs.getRows(from_row, to_row, get_num_row, &get_row56789_lower[0], - &og_row56789_upper[0], get_num_nz, NULL, NULL, + REQUIRE(highs.getRows(from_row, to_row, get_num_row, og_row56789_lower.data(), + og_row56789_upper.data(), get_num_nz, NULL, NULL, NULL) == HighsStatus::kOk); + REQUIRE(highs.changeRowsBounds(from_row, to_row, set_row56789_lower.data(), + og_row56789_upper.data()) == HighsStatus::kOk); + REQUIRE(highs.getRows(from_row, to_row, get_num_row, + get_row56789_lower.data(), og_row56789_upper.data(), + get_num_nz, NULL, NULL, NULL) == HighsStatus::kOk); REQUIRE(get_num_row == set_num_row); for (HighsInt usr_row = 0; usr_row < get_num_row; usr_row++) REQUIRE(get_row56789_lower[usr_row] == set_row56789_lower[usr_row]); - REQUIRE(highs.changeRowsBounds(from_row, to_row, &og_row56789_lower[0], - &og_row56789_upper[0]) == HighsStatus::kOk); + REQUIRE(highs.changeRowsBounds(from_row, to_row, og_row56789_lower.data(), + og_row56789_upper.data()) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -1332,22 +1338,24 @@ TEST_CASE("LP-delete", "[highs_data]") { get_value.resize(num_nz); // Get the set of cols to be removed - so that they can be reintroduced - REQUIRE(highs.getCols(&mask[0], get_num_col, &get_cost[0], &get_lower[0], - &get_upper[0], get_num_nz, &get_start[0], &get_index[0], - &get_value[0]) == HighsStatus::kOk); + REQUIRE(highs.getCols(mask.data(), get_num_col, get_cost.data(), + get_lower.data(), get_upper.data(), get_num_nz, + get_start.data(), get_index.data(), + get_value.data()) == HighsStatus::kOk); REQUIRE(get_num_col == rm_num_col); get_index.resize(get_num_nz); get_value.resize(get_num_nz); // Remove the set of cols - REQUIRE(highs.deleteCols(&mask[0]) == HighsStatus::kOk); + REQUIRE(highs.deleteCols(mask.data()) == HighsStatus::kOk); REQUIRE(mask == mask_check); REQUIRE(lp.num_col_ == num_col - rm_num_col); // Replace the set of cols - REQUIRE(highs.addCols(get_num_col, &get_cost[0], &get_lower[0], &get_upper[0], - get_num_nz, &get_start[0], &get_index[0], - &get_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addCols(get_num_col, get_cost.data(), get_lower.data(), + get_upper.data(), get_num_nz, get_start.data(), + get_index.data(), + get_value.data()) == HighsStatus::kOk); REQUIRE(lp.num_col_ == num_col); callRun(highs, log_options, "highs.run()", HighsStatus::kOk); @@ -1389,22 +1397,23 @@ TEST_CASE("LP-delete", "[highs_data]") { get_value.resize(num_nz); // Get the set of rows to be removed - so that they can be reintroduced - REQUIRE(highs.getRows(&mask[0], get_num_row, &get_lower[0], &get_upper[0], - get_num_nz, &get_start[0], &get_index[0], - &get_value[0]) == HighsStatus::kOk); + REQUIRE(highs.getRows(mask.data(), get_num_row, get_lower.data(), + get_upper.data(), get_num_nz, get_start.data(), + get_index.data(), + get_value.data()) == HighsStatus::kOk); REQUIRE(get_num_row == rm_num_row); get_index.resize(get_num_nz); get_value.resize(get_num_nz); // Remove the set of rows - REQUIRE(highs.deleteRows(&mask[0]) == HighsStatus::kOk); + REQUIRE(highs.deleteRows(mask.data()) == HighsStatus::kOk); REQUIRE(mask == mask_check); REQUIRE(lp.num_row_ == num_row - rm_num_row); // Replace the set of rows - REQUIRE(highs.addRows(get_num_row, &get_lower[0], &get_upper[0], get_num_nz, - &get_start[0], &get_index[0], - &get_value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRows(get_num_row, get_lower.data(), get_upper.data(), + get_num_nz, get_start.data(), get_index.data(), + get_value.data()) == HighsStatus::kOk); REQUIRE(lp.num_row_ == num_row); callRun(highs, log_options, "highs.run()", HighsStatus::kOk); @@ -1630,21 +1639,21 @@ bool areLpEqual(const HighsLp lp0, const HighsLp lp1, HighsInt lp0_num_nz = lp0.a_matrix_.start_[lp0.num_col_]; HighsInt lp1_num_nz = lp1.a_matrix_.start_[lp1.num_col_]; return_bool = areLpColEqual( - lp0.num_col_, &lp0.col_cost_[0], &lp0.col_lower_[0], &lp0.col_upper_[0], - lp0_num_nz, &lp0.a_matrix_.start_[0], &lp0.a_matrix_.index_[0], - &lp0.a_matrix_.value_[0], lp1.num_col_, &lp1.col_cost_[0], - &lp1.col_lower_[0], &lp1.col_upper_[0], lp1_num_nz, - &lp1.a_matrix_.start_[0], &lp1.a_matrix_.index_[0], - &lp1.a_matrix_.value_[0], infinite_bound); + lp0.num_col_, lp0.col_cost_.data(), lp0.col_lower_.data(), + lp0.col_upper_.data(), lp0_num_nz, lp0.a_matrix_.start_.data(), + lp0.a_matrix_.index_.data(), lp0.a_matrix_.value_.data(), lp1.num_col_, + lp1.col_cost_.data(), lp1.col_lower_.data(), lp1.col_upper_.data(), + lp1_num_nz, lp1.a_matrix_.start_.data(), lp1.a_matrix_.index_.data(), + lp1.a_matrix_.value_.data(), infinite_bound); if (!return_bool) return return_bool; } if (lp0.num_row_ > 0 && lp1.num_row_ > 0) { HighsInt lp0_num_nz = 0; HighsInt lp1_num_nz = 0; return_bool = areLpRowEqual( - lp0.num_row_, &lp0.row_lower_[0], &lp0.row_upper_[0], lp0_num_nz, NULL, - NULL, NULL, lp1.num_row_, &lp1.row_lower_[0], &lp1.row_upper_[0], - lp1_num_nz, NULL, NULL, NULL, infinite_bound); + lp0.num_row_, lp0.row_lower_.data(), lp0.row_upper_.data(), lp0_num_nz, + NULL, NULL, NULL, lp1.num_row_, lp1.row_lower_.data(), + lp1.row_upper_.data(), lp1_num_nz, NULL, NULL, NULL, infinite_bound); } return return_bool; } @@ -1777,7 +1786,7 @@ void messageReportLp(const char* message, const HighsLp& lp) { log_to_console = true; log_dev_level = kHighsLogDevLevelVerbose; log_options.output_flag = &output_flag; - log_options.log_file_stream = NULL; + log_options.log_stream = NULL; log_options.log_to_console = &log_to_console; log_options.log_dev_level = &log_dev_level; highsLogDev(log_options, HighsLogType::kVerbose, "\nReporting LP: %s\n", @@ -1792,7 +1801,7 @@ void messageReportMatrix(const char* message, const HighsInt num_col, bool output_flag = true; bool log_to_console = false; HighsInt log_dev_level = kHighsLogDevLevelInfo; - log_options.log_file_stream = stdout; + log_options.log_stream = stdout; log_options.output_flag = &output_flag; log_options.log_to_console = &log_to_console; log_options.log_dev_level = &log_dev_level; diff --git a/check/TestLpOrientation.cpp b/check/TestLpOrientation.cpp index 1d62c581f8..a4ac3fe759 100644 --- a/check/TestLpOrientation.cpp +++ b/check/TestLpOrientation.cpp @@ -88,18 +88,19 @@ TEST_CASE("LP-orientation", "[lp_orientation]") { // Clear the internal LP highs.clearModel(); - REQUIRE(highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], 0, - NULL, NULL, NULL) == HighsStatus::kOk); - REQUIRE(highs.addRows(num_row, &rowLower[0], &rowUpper[0], num_row_nz, - &ARstart[0], &ARindex[0], - &ARvalue[0]) == HighsStatus::kOk); + REQUIRE(highs.addCols(num_col, colCost.data(), colLower.data(), + colUpper.data(), 0, NULL, NULL, + NULL) == HighsStatus::kOk); + REQUIRE(highs.addRows(num_row, rowLower.data(), rowUpper.data(), num_row_nz, + ARstart.data(), ARindex.data(), + ARvalue.data()) == HighsStatus::kOk); highs.run(); REQUIRE(info.objective_function_value == optimal_objective_function_value); // Clear the internal LP highs.clearModel(); - highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], 0, NULL, NULL, - NULL); + 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; @@ -110,9 +111,10 @@ TEST_CASE("LP-orientation", "[lp_orientation]") { 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[0], &one_row_Upper[0], - one_row_numnz, &one_row_start[0], &one_row_index[0], - &one_row_value[0]) == HighsStatus::kOk); + 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); } highs.run(); REQUIRE(info.objective_function_value == optimal_objective_function_value); diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index 1629e7e925..3d76c96701 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -221,11 +221,7 @@ void testSolvers(Highs& highs, IterationCount& model_iteration_count, model_iteration_count.simplex = simplex_strategy_iteration_count[i]; testSolver(highs, "simplex", model_iteration_count, i); } - // Only use IPX with 32-bit arithmetic - // ToDo This is no longer true -#ifndef HIGHSINT64 testSolver(highs, "ipm", model_iteration_count); -#endif } // No commas in test case name. diff --git a/check/TestLpValidation.cpp b/check/TestLpValidation.cpp index 5be8dbc83c..a92ebee4e1 100644 --- a/check/TestLpValidation.cpp +++ b/check/TestLpValidation.cpp @@ -169,13 +169,13 @@ TEST_CASE("LP-validation", "[highs_data]") { highs.passOptions(options); REQUIRE(highs.passModel(lp) == HighsStatus::kOk); - return_status = - highs.addRows(num_row, &rowLower[0], &rowUpper[0], 0, NULL, NULL, NULL); + return_status = highs.addRows(num_row, rowLower.data(), rowUpper.data(), 0, + NULL, NULL, NULL); REQUIRE(return_status == HighsStatus::kOk); return_status = - highs.addCols(num_col, &colCost[0], &colLower[0], &colUpper[0], - num_col_nz, &Astart[0], &Aindex[0], &Avalue[0]); + highs.addCols(num_col, colCost.data(), colLower.data(), colUpper.data(), + num_col_nz, Astart.data(), Aindex.data(), Avalue.data()); REQUIRE(return_status == HighsStatus::kOk); // Create an empty column @@ -196,8 +196,8 @@ TEST_CASE("LP-validation", "[highs_data]") { vector XAvalue; // Add an empty column return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == HighsStatus::kOk); XcolUpper[0] = my_infinity; // reportLp(lp, HighsLogType::kVerbose); @@ -211,14 +211,14 @@ TEST_CASE("LP-validation", "[highs_data]") { } XcolCost[0] = my_infinity; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == require_return_status); XcolCost[0] = -my_infinity; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == require_return_status); // Reset to a legitimate cost @@ -228,40 +228,40 @@ TEST_CASE("LP-validation", "[highs_data]") { XcolLower[0] = 0; XcolUpper[0] = -1; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == HighsStatus::kWarning); // Add a column with bound inconsistency due to lower XcolLower[0] = 1; XcolUpper[0] = 0; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == HighsStatus::kWarning); // Add a column with illegal bound due to lower XcolLower[0] = my_infinity; XcolUpper[0] = 0; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == HighsStatus::kError); // Add a column with illegal bound due to upper XcolLower[0] = 0; XcolUpper[0] = -my_infinity; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == HighsStatus::kError); // Add a legitimate column XcolLower[0] = 0; XcolUpper[0] = 0; return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], NULL, NULL); + highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), NULL, NULL); REQUIRE(return_status == HighsStatus::kOk); // reportLp(lp, HighsLogType::kVerbose); @@ -297,17 +297,17 @@ TEST_CASE("LP-validation", "[highs_data]") { XAvalue[4] = -1e60; XAvalue[5] = 1e100; XAvalue[6] = -1; - return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], &XAindex[0], &XAvalue[0]); + return_status = highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), + XAindex.data(), XAvalue.data()); REQUIRE(return_status == HighsStatus::kError); // Legitimise large matrix entries. Small entries now cause warning XAvalue[4] = -1; XAvalue[5] = 1; - return_status = - highs.addCols(XnumNewCol, &XcolCost[0], &XcolLower[0], &XcolUpper[0], - XnumNewNZ, &XAstart[0], &XAindex[0], &XAvalue[0]); + return_status = highs.addCols(XnumNewCol, XcolCost.data(), XcolLower.data(), + XcolUpper.data(), XnumNewNZ, XAstart.data(), + XAindex.data(), XAvalue.data()); REQUIRE(return_status == HighsStatus::kWarning); if (!dev_run) highs.setOptionValue("output_flag", false); @@ -374,8 +374,8 @@ TEST_CASE("LP-row-index-duplication", "[highs_data]") { std::vector lower = {0, 0, 0}; std::vector upper = {inf, inf, inf}; HighsInt num_nz = index.size(); - REQUIRE(highs.addRows(3, &lower[0], &upper[0], num_nz, &start[0], &index[0], - &value[0]) == HighsStatus::kError); + REQUIRE(highs.addRows(3, lower.data(), upper.data(), num_nz, start.data(), + index.data(), value.data()) == HighsStatus::kError); } TEST_CASE("LP-extreme-coefficient", "[highs_data]") { diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index d3bb50c97c..a83007b875 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -89,9 +89,9 @@ TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") { mask[iCol] = 1; integrality[ix] = HighsVarType::kInteger; } - REQUIRE(highs.changeColsIntegrality(from_col0, to_col0, &integrality[0]) == + REQUIRE(highs.changeColsIntegrality(from_col0, to_col0, integrality.data()) == HighsStatus::kOk); - REQUIRE(highs.changeColsIntegrality(from_col1, to_col1, &integrality[0]) == + REQUIRE(highs.changeColsIntegrality(from_col1, to_col1, integrality.data()) == HighsStatus::kOk); if (dev_run) { highs.setOptionValue("log_dev_level", 3); @@ -122,8 +122,8 @@ TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") { highs.clearModel(); if (!dev_run) highs.setOptionValue("output_flag", false); highs.readModel(filename); - REQUIRE(highs.changeColsIntegrality(num_set_entries, &set[0], - &integrality[0]) == HighsStatus::kOk); + REQUIRE(highs.changeColsIntegrality(num_set_entries, set.data(), + integrality.data()) == HighsStatus::kOk); if (dev_run) highs.writeModel(""); highs.run(); if (dev_run) highs.writeSolution("", kSolutionStylePretty); @@ -138,7 +138,7 @@ TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") { highs.clearModel(); if (!dev_run) highs.setOptionValue("output_flag", false); highs.readModel(filename); - REQUIRE(highs.changeColsIntegrality(&mask[0], &integrality[0]) == + REQUIRE(highs.changeColsIntegrality(mask.data(), integrality.data()) == HighsStatus::kOk); if (dev_run) highs.writeModel(""); highs.run(); @@ -487,6 +487,34 @@ TEST_CASE("MIP-infeasible-start", "[highs_test_mip_solver]") { REQUIRE(model_status == HighsModelStatus::kInfeasible); } +TEST_CASE("get-integrality", "[highs_test_mip_solver]") {} + +TEST_CASE("MIP-get-saved-solutions", "[highs_test_mip_solver]") { + const std::string model = "flugpl"; + const std::string solution_file = "MipImproving.sol"; + const std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("presolve", kHighsOffString); + highs.setOptionValue("mip_improving_solution_save", true); + highs.setOptionValue("mip_improving_solution_report_sparse", true); + highs.setOptionValue("mip_improving_solution_file", solution_file); + highs.readModel(model_file); + highs.run(); + const std::vector saved_objective_and_solution = + highs.getSavedMipSolutions(); + const HighsInt num_saved_solution = saved_objective_and_solution.size(); + REQUIRE(num_saved_solution == 3); + const HighsInt last_saved_solution = num_saved_solution - 1; + REQUIRE(saved_objective_and_solution[last_saved_solution].objective == + highs.getInfo().objective_function_value); + for (HighsInt iCol = 0; iCol < highs.getLp().num_col_; iCol++) + REQUIRE(saved_objective_and_solution[last_saved_solution].col_value[iCol] == + highs.getSolution().col_value[iCol]); + std::remove(solution_file.c_str()); +} + bool objectiveOk(const double optimal_objective, const double require_optimal_objective, const bool dev_run = false) { diff --git a/check/TestNames.cpp b/check/TestNames.cpp new file mode 100644 index 0000000000..c1e55537d3 --- /dev/null +++ b/check/TestNames.cpp @@ -0,0 +1,119 @@ +#include + +#include "Highs.h" +#include "catch.hpp" + +const bool dev_run = false; +TEST_CASE("highs-names", "[highs_names]") { + std::string name; + const std::string model = "avgas"; + const std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + const HighsLp& lp = highs.getLp(); + + HighsInt iCol, iRow; + HighsStatus status; + + HighsInt ck_iCol; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + status = highs.getColName(iCol, name); + REQUIRE(status == HighsStatus::kOk); + status = highs.getColByName(name, ck_iCol); + REQUIRE(ck_iCol == iCol); + } + HighsInt ck_iRow; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + status = highs.getRowName(iRow, name); + REQUIRE(status == HighsStatus::kOk); + status = highs.getRowByName(name, ck_iRow); + REQUIRE(ck_iRow == iRow); + } + + // Change all names to distinct new names + REQUIRE(highs.passColName(-1, "FRED") == HighsStatus::kError); + REQUIRE(highs.passColName(lp.num_col_, "FRED") == HighsStatus::kError); + REQUIRE(highs.passColName(0, "") == HighsStatus::kError); + std::string col0_name; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + std::stringstream ss; + ss.str(std::string()); + ss << model << "_col_" << iCol << "\0"; + const std::string name = ss.str(); + if (iCol == 0) col0_name = name; + if (dev_run) printf("Col %d name is to be %s\n", int(iCol), name.c_str()); + REQUIRE(highs.passColName(iCol, name) == HighsStatus::kOk); + } + REQUIRE(highs.passRowName(-1, "FRED") == HighsStatus::kError); + REQUIRE(highs.passRowName(lp.num_row_, "FRED") == HighsStatus::kError); + REQUIRE(highs.passRowName(0, "") == HighsStatus::kError); + std::string row0_name; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + std::stringstream ss; + ss.str(std::string()); + ss << model << "_row_" << iRow << "\0"; + const std::string name = ss.str(); + if (iRow == 0) row0_name = name; + if (dev_run) printf("Row %d name is to be %s\n", int(iRow), name.c_str()); + REQUIRE(highs.passRowName(iRow, name) == HighsStatus::kOk); + } + highs.run(); + REQUIRE(highs.writeModel("") == HighsStatus::kOk); + if (dev_run) highs.writeSolution("", 1); + + status = highs.getColByName(col0_name, iCol); + REQUIRE(status == HighsStatus::kOk); + REQUIRE(iCol == 0); + status = highs.getRowByName(row0_name, iRow); + REQUIRE(status == HighsStatus::kOk); + REQUIRE(iRow == 0); + + // Change name of column num_col/2 to be the same as column 0 + REQUIRE(highs.getColName(0, name) == HighsStatus::kOk); + REQUIRE(name == col0_name); + iCol = lp.num_col_ / 2; + std::string iCol_name; + REQUIRE(highs.getColName(iCol, iCol_name) == HighsStatus::kOk); + REQUIRE(highs.passColName(iCol, col0_name) == HighsStatus::kOk); + + // column num_col/2 is no longer called iCol_name + status = highs.getColByName(iCol_name, iCol); + REQUIRE(status == HighsStatus::kError); + + status = highs.getColByName(col0_name, iCol); + REQUIRE(status == HighsStatus::kError); + + // Model can't be written + REQUIRE(highs.writeModel("") == HighsStatus::kError); + if (dev_run) highs.writeSolution("", 1); + + // Reinstate name and model writes OK + REQUIRE(highs.passColName(iCol, iCol_name) == HighsStatus::kOk); + REQUIRE(highs.writeModel("") == HighsStatus::kOk); + + // Change name of row num_row/2 to be the same as row 0 + REQUIRE(highs.getRowName(0, name) == HighsStatus::kOk); + REQUIRE(name == row0_name); + iRow = lp.num_row_ / 2; + REQUIRE(highs.passRowName(iRow, row0_name) == HighsStatus::kOk); + // Model can't be written + REQUIRE(highs.writeModel("") == HighsStatus::kError); + if (dev_run) highs.writeSolution("", 1); + + // Now work with a name-less model + HighsLp local_lp = lp; + local_lp.col_names_.clear(); + local_lp.row_names_.clear(); + highs.passModel(local_lp); + const std::string solution_file = "temp.sol"; + REQUIRE(highs.writeSolution(solution_file, 1) == HighsStatus::kOk); + + // Cannot get name of column or row 0 + REQUIRE(highs.getColName(0, name) == HighsStatus::kError); + REQUIRE(highs.getRowName(0, name) == HighsStatus::kError); + + std::remove(solution_file.c_str()); +} diff --git a/check/TestOptions.cpp b/check/TestOptions.cpp index ccdb1b8e12..4fad3d8060 100644 --- a/check/TestOptions.cpp +++ b/check/TestOptions.cpp @@ -7,6 +7,76 @@ const bool dev_run = false; +TEST_CASE("external-options", "[highs_options]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsInt num_options = highs.getNumOptions(); + if (dev_run) printf("Number of options is %d\n", int(num_options)); + std::string option; + HighsOptionType type; + bool current_bool_value, default_bool_value; + HighsInt min_int_value, max_int_value, current_int_value, default_int_value; + double min_double_value, max_double_value, current_double_value, + default_double_value; + std::string current_string_value, default_string_value; + for (HighsInt index = 0; index < num_options; index++) { + REQUIRE(highs.getOptionName(index, &option) == HighsStatus::kOk); + REQUIRE(highs.getOptionType(option, &type) == HighsStatus::kOk); + if (dev_run) + printf("Option %2d is \"%s\" of type %d", int(index), option.c_str(), + int(type)); + if (type == HighsOptionType::kBool) { + REQUIRE(highs.getBoolOptionValues(option, ¤t_bool_value, + &default_bool_value) == + HighsStatus::kOk); + if (dev_run) + printf(": current = %d; default = %d\n", current_bool_value, + default_bool_value); + } else if (type == HighsOptionType::kInt) { + REQUIRE(highs.getIntOptionValues(option, ¤t_int_value, + &min_int_value, &max_int_value, + &default_int_value) == HighsStatus::kOk); + if (dev_run) + printf(": current = %d; min = %d; max = %d; default = %d\n", + int(current_int_value), int(min_int_value), int(max_int_value), + int(default_int_value)); + } else if (type == HighsOptionType::kDouble) { + REQUIRE(highs.getDoubleOptionValues(option, ¤t_double_value, + &min_double_value, &max_double_value, + &default_double_value) == + HighsStatus::kOk); + if (dev_run) + printf(": current = %g; min = %g; max = %g; default = %g\n", + current_double_value, min_double_value, max_double_value, + default_double_value); + REQUIRE(highs.getDoubleOptionValues(option, ¤t_double_value) == + HighsStatus::kOk); + if (dev_run) + printf(" is \"%s\" of type %d: current = %g\n", option.c_str(), + int(type), current_double_value); + } else { + REQUIRE(highs.getStringOptionValues(option, ¤t_string_value, + &default_string_value) == + HighsStatus::kOk); + if (dev_run) + printf(": current = \"%s\"; default = \"%s\"\n", + current_string_value.c_str(), default_string_value.c_str()); + } + } + HighsInt num_string_option = 0; + if (dev_run) printf("\nString options are:\n"); + for (HighsInt index = 0; index < num_options; index++) { + highs.getOptionName(index, &option); + highs.getOptionType(option, &type); + highs.getStringOptionValues(option, ¤t_string_value); + if (type != HighsOptionType::kString) continue; + num_string_option++; + if (dev_run) + printf("%2d: %-24s \"%s\"\n", int(num_string_option), option.c_str(), + current_string_value.c_str()); + } +} + TEST_CASE("internal-options", "[highs_options]") { HighsOptions options; HighsLogOptions report_log_options = options.log_options; @@ -17,7 +87,8 @@ TEST_CASE("internal-options", "[highs_options]") { std::string filename = std::string(HIGHS_DIR) + "/check/sample_options_file"; - bool success = loadOptionsFromFile(report_log_options, options, filename); + bool success = loadOptionsFromFile(report_log_options, options, filename) == + HighsLoadOptionsStatus::kOk; REQUIRE(success == true); REQUIRE(options.presolve == kHighsOnString); REQUIRE(options.small_matrix_value == 0.001); @@ -163,21 +234,22 @@ TEST_CASE("internal-options", "[highs_options]") { bool get_mps_parser_type_free; return_status = - getLocalOptionValue(report_log_options, "mps_parser_type_free", - options.records, get_mps_parser_type_free); + getLocalOptionValues(report_log_options, "mps_parser_type_free", + options.records, &get_mps_parser_type_free); REQUIRE(return_status == OptionStatus::kOk); REQUIRE(get_mps_parser_type_free == false); HighsInt get_allowed_matrix_scale_factor; return_status = - getLocalOptionValue(report_log_options, "allowed_matrix_scale_factor", - options.records, get_allowed_matrix_scale_factor); + getLocalOptionValues(report_log_options, "allowed_matrix_scale_factor", + options.records, &get_allowed_matrix_scale_factor); REQUIRE(return_status == OptionStatus::kOk); REQUIRE(get_allowed_matrix_scale_factor == allowed_matrix_scale_factor); double get_small_matrix_value; - return_status = getLocalOptionValue(report_log_options, "small_matrix_value", - options.records, get_small_matrix_value); + return_status = + getLocalOptionValues(report_log_options, "small_matrix_value", + options.records, &get_small_matrix_value); REQUIRE(return_status == OptionStatus::kOk); REQUIRE(get_small_matrix_value == small_matrix_value); @@ -192,6 +264,91 @@ TEST_CASE("highs-options", "[highs_options]") { HighsStatus return_status = highs.writeOptions("Highs.set"); REQUIRE(return_status == HighsStatus::kOk); + // Check mixed-string value + // + // For bool, values from a precise set can be expected + return_status = highs.setOptionValue("mps_parser_type_free", "10true"); + REQUIRE(return_status == HighsStatus::kError); + + // For int, the number of digits scanned in the (trimmed) string to + // get an integer must match the number of characters in the + // (trimmed) string + const HighsInt set_int_option_value = 10; + HighsInt get_int_option_value; + return_status = highs.setOptionValue("simplex_iteration_limit", " 10"); + REQUIRE(return_status == HighsStatus::kOk); + highs.getOptionValue("simplex_iteration_limit", get_int_option_value); + REQUIRE(get_int_option_value == set_int_option_value); + + return_status = highs.setOptionValue("simplex_iteration_limit", "10 "); + REQUIRE(return_status == HighsStatus::kOk); + highs.getOptionValue("simplex_iteration_limit", get_int_option_value); + REQUIRE(get_int_option_value == set_int_option_value); + + return_status = highs.setOptionValue("simplex_iteration_limit", "1 0"); + REQUIRE(return_status == HighsStatus::kError); + + return_status = highs.setOptionValue("simplex_iteration_limit", "10."); + REQUIRE(return_status == HighsStatus::kError); + + return_status = highs.setOptionValue("simplex_iteration_limit", "10hi"); + REQUIRE(return_status == HighsStatus::kError); + + return_status = highs.setOptionValue("simplex_iteration_limit", "10.2hi"); + REQUIRE(return_status == HighsStatus::kError); + + // For double, make sure that the string contains a numerical + // character, otherwise anything goes! + return_status = highs.setOptionValue("objective_bound", "1E2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1.1E2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1E+2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1.1E+2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1E-2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1.1E-2"); + REQUIRE(return_status == HighsStatus::kOk); + + return_status = highs.setOptionValue("objective_bound", "-1E2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "-1.1E2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "-1E+2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "-1.1E+2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "-1E-2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "-1.1E-2"); + REQUIRE(return_status == HighsStatus::kOk); + + return_status = highs.setOptionValue("objective_bound", "e12"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1e2"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "12e"); + REQUIRE(return_status == HighsStatus::kOk); + return_status = highs.setOptionValue("objective_bound", "1.1e2"); + REQUIRE(return_status == HighsStatus::kOk); + // Illegal + return_status = highs.setOptionValue("objective_bound", "10hi"); + REQUIRE(return_status == HighsStatus::kError); + return_status = highs.setOptionValue("objective_bound", "1d2"); + REQUIRE(return_status == HighsStatus::kError); + return_status = highs.setOptionValue("objective_bound", "1.1d2"); + REQUIRE(return_status == HighsStatus::kError); + return_status = highs.setOptionValue("objective_bound", "1D2"); + REQUIRE(return_status == HighsStatus::kError); + return_status = highs.setOptionValue("objective_bound", "1.1D2"); + REQUIRE(return_status == HighsStatus::kError); + + // And the original motivation in #1250! + return_status = highs.setOptionValue("time_limit", "hi"); + REQUIRE(return_status == HighsStatus::kError); + // Check setting boolean options std::string setting_string = "fixed"; return_status = highs.setOptionValue("mps_parser_type_free", setting_string); diff --git a/check/TestOsi.cpp b/check/TestOsi.cpp deleted file mode 100644 index 870d447a24..0000000000 --- a/check/TestOsi.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* */ -/* This file is part of the HiGHS linear optimization suite */ -/* */ -/* Written and engineered 2008-2019 at the University of Edinburgh */ -/* */ -/* Available as open-source under the MIT License */ -/* */ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/**@file TestOsi.cpp - * @brief Osi/HiGHS unit test - */ - -#include - -#include "CoinHelperFunctions.hpp" -#include "CoinPragma.hpp" -#include "HCheckConfig.h" -#include "HighsInt.h" -#include "OsiHiGHSSolverInterface.hpp" -#include "OsiUnitTests.hpp" - -const bool dev_run = false; - -using namespace OsiUnitTest; - -HighsInt main(HighsInt argc, const char* argv[]) { -#ifndef COINSAMPLEFOUND - std::cerr << "Path to Data/Sample not known. Cannot run tests without sample " - "MPS files." - << std::endl; - return EXIT_FAILURE; -#endif - - // Synchronise C++ stream i/o with C stdio. - std::ios::sync_with_stdio(); - setbuf(stderr, 0); - setbuf(stdout, 0); - - // Suppress an popup window that Windows shows in response to a crash. - WindowsErrorPopupBlocker(); - - OsiUnitTest::verbosity = 10; - // OsiUnitTest::haltonerror = 2; - - HighsInt nerrors; - HighsInt nerrors_expected; - - std::string mpsDir = COINSAMPLEDIR; - mpsDir.push_back(CoinFindDirSeparator()); - - // Do common solverInterface testing by calling the base class testing method. - { - OsiHiGHSSolverInterface highsSi; - OSIUNITTEST_CATCH_ERROR( - OsiSolverInterfaceCommonUnitTest(&highsSi, mpsDir, ""), {}, highsSi, - "osi common unittest"); - } - - // Test Osi{Row,Col}Cut routines - { - OsiHiGHSSolverInterface highsSi; - testingMessage("Testing OsiRowCut with OsiHiGHSSolverInterface\n"); - OSIUNITTEST_CATCH_ERROR(OsiRowCutUnitTest(&highsSi, mpsDir), {}, highsSi, - "rowcut unittest"); - } - { - OsiHiGHSSolverInterface highsSi; - testingMessage("Testing OsiColCut with OsiHiGHSSolverInterface\n"); - OSIUNITTEST_CATCH_ERROR(OsiColCutUnitTest(&highsSi, mpsDir), {}, highsSi, - "colcut unittest"); - } - { - OsiHiGHSSolverInterface highsSi; - testingMessage("Testing OsiRowCutDebugger with OsiHiGHSSolverInterface\n"); - OSIUNITTEST_CATCH_ERROR(OsiRowCutDebuggerUnitTest(&highsSi, mpsDir), {}, - highsSi, "rowcut debugger unittest"); - } - -#ifdef COINNETLIBFOUND - // We have run the fast unit tests. - // If there were no errors, then also run the Netlib problems. - outcomes.getCountBySeverity(TestOutcome::ERROR, nerrors, nerrors_expected); - if (nerrors <= nerrors_expected) { - std::string netlibDir = COINNETLIBDIR; - netlibDir.push_back(CoinFindDirSeparator()); - - // Create vector of solver interfaces - std::vector vecSi(1, new OsiHiGHSSolverInterface); - - testingMessage("Testing OsiSolverInterface on Netlib problems.\n"); - OSIUNITTEST_CATCH_ERROR(OsiSolverInterfaceMpsUnitTest(vecSi, netlibDir), {}, - "highs", "netlib unittest"); - - delete vecSi[0]; - } else { - testingMessage( - "Skip testing OsiSolverInterface on Netlib problems as there were " - "unexpected errors in previous runs.\n"); - } -#else - testingMessage( - "Skip testing OsiSolverInterface on Netlib problems as path to " - "Data/Netlib not known.\n"); -#endif - - // We're done. Report on the results. - std::cout.flush(); - outcomes.print(); - - outcomes.getCountBySeverity(TestOutcome::ERROR, nerrors, nerrors_expected); - if (nerrors > nerrors_expected) - std::cerr << "Tests completed with " << nerrors - nerrors_expected - << " unexpected errors." << std::endl; - else - std::cerr << "All tests completed successfully\n"; - - return nerrors - nerrors_expected; -} diff --git a/check/TestQpSolver.cpp b/check/TestQpSolver.cpp index 469904dfef..6187ec064e 100644 --- a/check/TestQpSolver.cpp +++ b/check/TestQpSolver.cpp @@ -120,7 +120,7 @@ TEST_CASE("qpsolver", "[qpsolver]") { HighsInt num_col = highs.getNumCol(); std::vector integrality; integrality.assign(num_col, HighsVarType::kInteger); - REQUIRE(highs.changeColsIntegrality(0, num_col - 1, &integrality[0]) == + REQUIRE(highs.changeColsIntegrality(0, num_col - 1, integrality.data()) == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kError); @@ -249,7 +249,8 @@ TEST_CASE("test-qod", "[qpsolver]") { // Add the constraint 0.5 <= x0 + x1 lp.a_matrix_.index_ = {0, 1}; lp.a_matrix_.value_ = {1, 1}; - highs.addRow(0.5, inf, 2, &lp.a_matrix_.index_[0], &lp.a_matrix_.value_[0]); + highs.addRow(0.5, inf, 2, lp.a_matrix_.index_.data(), + lp.a_matrix_.value_.data()); if (dev_run) highs.writeModel(""); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); diff --git a/check/TestRays.cpp b/check/TestRays.cpp index 8c54d6deea..4c7191a8b3 100644 --- a/check/TestRays.cpp +++ b/check/TestRays.cpp @@ -238,7 +238,7 @@ void testInfeasibleMps(const std::string model, dual_ray_value.resize(lp.num_row_); REQUIRE(highs.getDualRay(has_dual_ray) == HighsStatus::kOk); REQUIRE(has_dual_ray == has_dual_ray_); - REQUIRE(highs.getDualRay(has_dual_ray, &dual_ray_value[0]) == + REQUIRE(highs.getDualRay(has_dual_ray, dual_ray_value.data()) == HighsStatus::kOk); checkDualRayValue(highs, dual_ray_value); // Check that there is no primal ray @@ -285,7 +285,7 @@ void testUnboundedMps(const std::string model, primal_ray_value.resize(lp.num_col_); REQUIRE(highs.getPrimalRay(has_primal_ray) == HighsStatus::kOk); REQUIRE(has_primal_ray == true); - REQUIRE(highs.getPrimalRay(has_primal_ray, &primal_ray_value[0]) == + REQUIRE(highs.getPrimalRay(has_primal_ray, primal_ray_value.data()) == HighsStatus::kOk); checkPrimalRayValue(highs, primal_ray_value); } @@ -323,7 +323,7 @@ TEST_CASE("Rays", "[highs_test_rays]") { REQUIRE(highs.getDualRay(has_dual_ray) == HighsStatus::kOk); REQUIRE(has_dual_ray == true); // Get the dual ray - REQUIRE(highs.getDualRay(has_dual_ray, &dual_ray_value[0]) == + REQUIRE(highs.getDualRay(has_dual_ray, dual_ray_value.data()) == HighsStatus::kOk); vector expected_dual_ray = {0.5, -1}; // From SCIP if (dev_run) { @@ -370,7 +370,7 @@ TEST_CASE("Rays", "[highs_test_rays]") { primal_ray_value.resize(lp.num_col_); REQUIRE(highs.getPrimalRay(has_primal_ray) == HighsStatus::kOk); REQUIRE(has_primal_ray == true); - REQUIRE(highs.getPrimalRay(has_primal_ray, &primal_ray_value[0]) == + REQUIRE(highs.getPrimalRay(has_primal_ray, primal_ray_value.data()) == HighsStatus::kOk); vector expected_primal_ray = {0.5, -1}; if (dev_run) { @@ -453,7 +453,7 @@ TEST_CASE("Rays-464a", "[highs_test_rays]") { REQUIRE(has_ray == true); vector ray_value; ray_value.assign(2, NAN); - highs.getPrimalRay(has_ray, &ray_value[0]); + highs.getPrimalRay(has_ray, ray_value.data()); checkPrimalRayValue(highs, ray_value); REQUIRE(has_ray); REQUIRE(ray_value[0] == ray_value[1]); @@ -486,7 +486,7 @@ TEST_CASE("Rays-464b", "[highs_test_rays]") { REQUIRE(has_ray == true); vector ray_value; ray_value.assign(2, NAN); - highs.getPrimalRay(has_ray, &ray_value[0]); + highs.getPrimalRay(has_ray, ray_value.data()); checkPrimalRayValue(highs, ray_value); REQUIRE(has_ray); REQUIRE(ray_value[0] == ray_value[1]); diff --git a/check/TestSemiVariables.cpp b/check/TestSemiVariables.cpp index 6d63e1b9ab..225dbd4cd2 100644 --- a/check/TestSemiVariables.cpp +++ b/check/TestSemiVariables.cpp @@ -170,7 +170,8 @@ TEST_CASE("semi-variable-upper-bound", "[highs_test_semi_variables]") { double coeff = 1e6; std::vector index = {0, 1}; std::vector value = {-1, coeff}; - REQUIRE(highs.addRow(0, 0, 2, &index[0], &value[0]) == HighsStatus::kOk); + REQUIRE(highs.addRow(0, 0, 2, index.data(), value.data()) == + HighsStatus::kOk); // Problem is no longer unbounded due to equation linking the // semi-variable to the continuous variable. However, optimal value // of semi-variable should be 1e6, so it is active at the modified upper @@ -233,6 +234,37 @@ TEST_CASE("semi-variable-file", "[highs_test_semi_variables]") { optimal_objective_function_value) < double_equal_tolerance); } +TEST_CASE("semi-variable-inconsistent-bounds", "[highs_test_semi_variables]") { + HighsLp lp; + lp.num_col_ = 1; + lp.num_row_ = 0; + lp.col_cost_ = {1}; + lp.col_lower_ = {1}; + lp.col_upper_ = {-1}; + lp.a_matrix_.start_ = {0, 0}; + lp.integrality_ = {semi_continuous}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.getSolution().col_value[0] == 0); + // Ensure that inconsistent bounds with negative lower are still + // accepted + lp.col_lower_[0] = -1; + lp.col_upper_[0] = -2; + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.getSolution().col_value[0] == 0); + // Ensure that continuous variables with inconsistent bounds yield + // infeasibility + highs.setOptionValue("solve_relaxation", true); + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); +} + void semiModel0(HighsLp& lp) { lp.num_col_ = 4; lp.num_row_ = 4; diff --git a/check/TestSort.cpp b/check/TestSort.cpp index 75714a939e..655dce5e06 100644 --- a/check/TestSort.cpp +++ b/check/TestSort.cpp @@ -24,7 +24,7 @@ void getRandomValues(const HighsInt num_values, vector& values, void doFullSort(const HighsInt num_values, vector& values, vector& indices) { // Sort the vector of random number and their corresponding indices - maxheapsort(&values[0], &indices[0], num_values); + maxheapsort(values.data(), indices.data(), num_values); } void doAddSort(HighsInt& num_values_sorted, @@ -138,7 +138,7 @@ TEST_CASE("HiGHS_sort", "[highs_data]") { int_values.resize(num_values); std::make_heap(int_values.begin(), int_values.end()); std::sort_heap(int_values.begin(), int_values.end()); - // maxheapsort(&int_values[0], num_values); + // maxheapsort(int_values.data(), num_values); bool ok; // Check that the values in the vector of doubles are ascending - can do @@ -208,8 +208,8 @@ TEST_CASE("HiGHS_sort", "[highs_data]") { sorted_lb.resize(num_values); sorted_ub.resize(num_values); - sortSetData(num_values, sorted_set, &lb[0], &ub[0], NULL, &sorted_lb[0], - &sorted_ub[0], NULL); + sortSetData(num_values, sorted_set, lb.data(), ub.data(), NULL, + sorted_lb.data(), sorted_ub.data(), NULL); HighsInt prev_ix = -kHighsIInf; for (HighsInt k0 = 0; k0 < num_values; k0++) { diff --git a/check/TestSpecialLps.cpp b/check/TestSpecialLps.cpp index 8fbda3d149..b3c8375948 100644 --- a/check/TestSpecialLps.cpp +++ b/check/TestSpecialLps.cpp @@ -59,9 +59,7 @@ void distillation(Highs& highs) { REQUIRE(highs.passModel(lp) == HighsStatus::kOk); // Presolve doesn't reduce the LP solve(highs, "on", "simplex", require_model_status, optimal_objective); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status, optimal_objective); -#endif } void issue272(Highs& highs) { @@ -76,10 +74,8 @@ void issue272(Highs& highs) { // Presolve reduces to empty, so no need to test presolve+IPX solve(highs, "on", "simplex", require_model_status, optimal_objective); solve(highs, "off", "simplex", require_model_status, optimal_objective); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status, optimal_objective); solve(highs, "off", "ipm", require_model_status, optimal_objective); -#endif } void issue280(Highs& highs) { @@ -110,10 +106,8 @@ void issue282(Highs& highs) { // Presolve reduces to empty, so no real need to test presolve+IPX solve(highs, "on", "simplex", require_model_status, optimal_objective); solve(highs, "off", "simplex", require_model_status, optimal_objective); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status, optimal_objective); solve(highs, "off", "ipm", require_model_status, optimal_objective); -#endif } void issue285(Highs& highs) { @@ -128,10 +122,8 @@ void issue285(Highs& highs) { // Presolve identifies infeasibility, so no need to test presolve+IPX solve(highs, "on", "simplex", require_model_status); solve(highs, "off", "simplex", require_model_status); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status); solve(highs, "off", "ipm", require_model_status); -#endif } void issue295(Highs& highs) { @@ -152,10 +144,8 @@ void issue295(Highs& highs) { REQUIRE(highs.passModel(lp) == HighsStatus::kOk); solve(highs, "on", "simplex", require_model_status, optimal_objective); solve(highs, "off", "simplex", require_model_status, optimal_objective); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status, optimal_objective); solve(highs, "off", "ipm", require_model_status, optimal_objective); -#endif } void issue306(Highs& highs) { @@ -172,10 +162,8 @@ void issue306(Highs& highs) { REQUIRE(highs.passModel(lp) == HighsStatus::kOk); solve(highs, "on", "simplex", require_model_status, optimal_objective); solve(highs, "off", "simplex", require_model_status, optimal_objective); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status, optimal_objective); solve(highs, "off", "ipm", require_model_status, optimal_objective); -#endif } void issue316(Highs& highs) { @@ -216,9 +204,7 @@ void issue425(Highs& highs) { REQUIRE(highs.passModel(lp) == HighsStatus::kOk); solve(highs, "on", "simplex", require_model_status, 0, -1); solve(highs, "off", "simplex", require_model_status, 0, 3); -#ifndef HIGHSINT64 solve(highs, "off", "ipm", require_model_status, 0, 4); -#endif } void issue669(Highs& highs) { @@ -390,10 +376,8 @@ void mpsGalenet(Highs& highs) { solve(highs, "on", "simplex", require_model_status); solve(highs, "off", "simplex", require_model_status); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status); solve(highs, "off", "ipm", require_model_status); -#endif } void primalDualInfeasible1(Highs& highs) { @@ -482,10 +466,8 @@ void mpsGas11(Highs& highs) { solve(highs, "on", "simplex", require_model_status); solve(highs, "off", "simplex", require_model_status); -#ifndef HIGHSINT64 solve(highs, "on", "ipm", require_model_status); solve(highs, "off", "ipm", require_model_status); -#endif } void almostNotUnbounded(Highs& highs) { @@ -531,9 +513,7 @@ void almostNotUnbounded(Highs& highs) { // REQUIRE(highs.writeModel("epsilon_unbounded.mps") == // HighsStatus::WARNING); solve(highs, "off", "simplex", require_model_status0); -#ifndef HIGHSINT64 solve(highs, "off", "ipm", require_model_status0); -#endif // LP is feasible on [1+alpha, alpha] with objective -1 so optimal, // but has open set of optimal solutions @@ -542,9 +522,7 @@ void almostNotUnbounded(Highs& highs) { solve(highs, "off", "simplex", require_model_status1, optimal_objective1); special_lps.reportSolution(highs, dev_run); -#ifndef HIGHSINT64 solve(highs, "off", "ipm", require_model_status1, optimal_objective1); -#endif // LP has bounded feasible region with optimal solution // [1+2/epsilon, 2/epsilon] and objective @@ -556,9 +534,7 @@ void almostNotUnbounded(Highs& highs) { solve(highs, "off", "simplex", require_model_status2, optimal_objective2); special_lps.reportSolution(highs, dev_run); -#ifndef HIGHSINT64 solve(highs, "off", "ipm", require_model_status2, optimal_objective2); -#endif } void singularStartingBasis(Highs& highs) { diff --git a/cmake/cpp-highs.cmake b/cmake/cpp-highs.cmake new file mode 100644 index 0000000000..478b57658c --- /dev/null +++ b/cmake/cpp-highs.cmake @@ -0,0 +1,92 @@ +if(NOT BUILD_CXX) + return() +endif() + +# Main Target + +configure_file(${HIGHS_SOURCE_DIR}/src/HConfig.h.in ${HIGHS_BINARY_DIR}/HConfig.h) + +add_subdirectory(src) + +# ALIAS +add_library(${PROJECT_NAMESPACE}::highs ALIAS highs) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Includes +target_include_directories(highs INTERFACE + $ + $ + $ + ) + +################### +## Install rules ## +################### +include(GNUInstallDirs) +include(GenerateExportHeader) +GENERATE_EXPORT_HEADER(highs) +install(FILES ${PROJECT_BINARY_DIR}/highs_export.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +string (TOLOWER ${PROJECT_NAME} lower) + install(TARGETS highs + EXPORT ${lower}-targets + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) + +# Add library targets to the build-tree export set +export(TARGETS highs + NAMESPACE ${PROJECT_NAMESPACE}:: + FILE "${HIGHS_BINARY_DIR}/highs-targets.cmake") + + +install(EXPORT ${lower}-targets + NAMESPACE ${PROJECT_NAMESPACE}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${lower}) + +include(CMakePackageConfigHelpers) +string (TOLOWER "${PROJECT_NAME}" PACKAGE_PREFIX) +# configure_package_config_file(src/HConfig.cmake.in +# "${PROJECT_BINARY_DIR}/${PACKAGE_PREFIX}-config.cmake" +# INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" +# NO_CHECK_REQUIRED_COMPONENTS_MACRO) +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${PACKAGE_PREFIX}-config-version.cmake" + COMPATIBILITY SameMajorVersion + ) + +# add_cxx_test() +# CMake function to generate and build C++ test. +# Parameters: +# the C++ filename +# e.g.: +# add_cxx_test(foo.cc) +function(add_cxx_test FILE_NAME) + message(STATUS "Configuring test ${FILE_NAME}: ...") + get_filename_component(TEST_NAME ${FILE_NAME} NAME_WE) + get_filename_component(COMPONENT_DIR ${FILE_NAME} DIRECTORY) + get_filename_component(COMPONENT_NAME ${COMPONENT_DIR} NAME) + + if(APPLE) + set(CMAKE_INSTALL_RPATH + "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif(UNIX) + set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN/../lib64:$ORIGIN/../lib:$ORIGIN") + endif() + + add_executable(${TEST_NAME} ${FILE_NAME}) + target_include_directories(${TEST_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + # target_compile_features(${TEST_NAME} PRIVATE cxx_std_17) + target_link_libraries(${TEST_NAME} PRIVATE ${PROJECT_NAMESPACE}::highs) + + if(BUILD_TESTING) + add_test(NAME cxx_${COMPONENT_NAME}_${TEST_NAME} COMMAND ${TEST_NAME}) + endif() + message(STATUS "Configuring test ${FILE_NAME}: ...DONE") +endfunction() diff --git a/cmake/utils-highs.cmake b/cmake/utils-highs.cmake new file mode 100644 index 0000000000..d91720f155 --- /dev/null +++ b/cmake/utils-highs.cmake @@ -0,0 +1,26 @@ +function(set_version VERSION) + if(DEFINED ENV{HIGHS_MAJOR} AND DEFINED ENV{HIGHS_MINOR}) + set(MAJOR $ENV{HIGHS_MAJOR}) + set(MINOR $ENV{HIGHS_MINOR}) + else() + # Get Major and Minor and maybe Patch from Version.txt + file(STRINGS "Version.txt" VERSION_STR) + foreach(STR ${VERSION_STR}) + if(${STR} MATCHES "HIGHS_MAJOR=(.*)") + set(MAJOR ${CMAKE_MATCH_1}) + elseif(${STR} MATCHES "HIGHS_MINOR=(.*)") + set(MINOR ${CMAKE_MATCH_1}) + elseif(${STR} MATCHES "HIGHS_PATCH=(.*)") + set(PATCH ${CMAKE_MATCH_1}) + endif() + endforeach() + endif() + + if(DEFINED ENV{HIGHS_PATCH}) + set(PATCH $ENV{HIGHS_PATCH}) + elseif(NOT DEFINED PATCH) + set(PATCH 999) + endif() + + set(${VERSION} "${MAJOR}.${MINOR}.${PATCH}" PARENT_SCOPE) +endfunction() diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..15730ee950 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +Manifest.toml +c_api_gen/libhighs.jl diff --git a/docs/HiGHS_CopyrightHeader.pl b/docs/HiGHS_CopyrightHeader.pl index 8d971bda86..0d5329877c 100755 --- a/docs/HiGHS_CopyrightHeader.pl +++ b/docs/HiGHS_CopyrightHeader.pl @@ -5,10 +5,9 @@ $CopyrightHeaderLine0 = "/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */"; $CopyrightHeaderLine1 = "/* */"; $CopyrightHeaderLine2 = "/* This file is part of the HiGHS linear optimization suite */"; -$CopyrightHeaderLine3 = "/* Written and engineered 2008-2022 at the University of Edinburgh */"; -$CopyrightHeaderLine4 = "/* Available as open-source under the MIT License */"; -$CopyrightHeaderLine5 = "/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */"; -$CopyrightHeaderLine6 = "/* Feldmeier */"; +$CopyrightHeaderLine3 = "/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */"; +$CopyrightHeaderLine4 = "/* Leona Gottwald and Michael Feldmeier */"; +$CopyrightHeaderLine5 = "/* Available as open-source under the MIT License */"; $RemoveCopyrightHeader = 0; while(<>) { if ($infile ne $ARGV) { @@ -32,11 +31,9 @@ print(outfile "$CopyrightHeaderLine2\n"); print(outfile "$CopyrightHeaderLine1\n"); print(outfile "$CopyrightHeaderLine3\n"); - print(outfile "$CopyrightHeaderLine1\n"); print(outfile "$CopyrightHeaderLine4\n"); print(outfile "$CopyrightHeaderLine1\n"); print(outfile "$CopyrightHeaderLine5\n"); - print(outfile "$CopyrightHeaderLine6\n"); print(outfile "$CopyrightHeaderLine1\n"); print(outfile "$CopyrightHeaderLine0\n"); # diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000000..ecc960c8f0 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,7 @@ +[deps] +Clang = "40e3b903-d033-50b4-a0cc-940c62c95e31" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" + +[compat] +Clang = "0.14, 0.17" +Documenter = "0.27" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..61426e0037 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,27 @@ +# Documentation + +This directory contains the source files for the [documentation](https://ergo-code.github.io/HiGHS). + +## Editing the documentation + +To edit the documentation, checkout a branch and edit the Markdown files in the +`src` directory. + +## Building the documentation + +To build locally, [install Julia](https://julialang.org/downloads/), then (from the `docs` directory) run: + +``` +$ julia make.jl +``` + +The first time you run this command, Julia will download and install the +necessary packages. This may take a couple of minutes. + +The website is generated in the `build/` folder. To check it out, load +`build/index.html` in your browser. + +## Deploying the documentation + +The documentation is automatically built and deployed by a GitHub action. You +should not check the `build/` directory into git. diff --git a/docs/c_api_gen/HConfig.h b/docs/c_api_gen/HConfig.h new file mode 100644 index 0000000000..0f1329b918 --- /dev/null +++ b/docs/c_api_gen/HConfig.h @@ -0,0 +1 @@ +// A dummy file to fix a warning when Julia parses the C API. diff --git a/docs/c_api_gen/build.jl b/docs/c_api_gen/build.jl new file mode 100644 index 0000000000..7ba40d16db --- /dev/null +++ b/docs/c_api_gen/build.jl @@ -0,0 +1,39 @@ +#=* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * +* This file is part of the HiGHS linear optimization suite * +* * +* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, * +* Leona Gottwald and Michael Feldmeier * +* Available as open-source under the MIT License * +* * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *=# + +import Clang: Generators + +highs_src = joinpath(dirname(dirname(@__DIR__)), "src") +c_api = joinpath(highs_src, "interfaces", "highs_c_api.h") + +Generators.build!( + Generators.create_context( + [c_api, joinpath(highs_src, "util", "HighsInt.h")], + [Generators.get_default_args(); "-I$highs_src"; "-I$(@__DIR__)"], + Dict{String,Any}( + "general" => Dict{String,Any}( + "output_file_path" => joinpath(@__DIR__, "libhighs.jl"), + "library_name" => "libhighs", + "print_using_CEnum" => false, + "extract_c_comment_style" => "doxygen", + ) + ), + ), +) + +open(joinpath(@__DIR__, "libhighs.jl"), "a") do io + for line in readlines(c_api) + m = match(r"const HighsInt kHighs([a-zA-Z]+) = (-?[0-9]+);", line) + if m === nothing + continue + end + println(io, "const kHighs$(m[1]) = HighsInt($(m[2]))") + end +end diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000000..0ce9881a56 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,101 @@ +#=* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* * +* This file is part of the HiGHS linear optimization suite * +* * +* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, * +* Leona Gottwald and Michael Feldmeier * +* Available as open-source under the MIT License * +* * +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *=# + +import Pkg +Pkg.activate(@__DIR__) +Pkg.instantiate() + +import Documenter + +# ============================================================================== +# Parse and build docstrings from the C API +# ============================================================================== + +const libhighs = "" +include(joinpath(@__DIR__, "c_api_gen", "build.jl")) +include(joinpath(@__DIR__, "c_api_gen", "libhighs.jl")) + +# ============================================================================== +# Make the documentation +# ============================================================================== + +Documenter.makedocs( + sitename = "HiGHS Documentation", + authors = "Julian Hall and Ivet Galabova", + format = Documenter.HTML( + # Use clean URLs, unless built as a "local" build + # prettyurls = !("local" in ARGS), + prettyurls = get(ENV, "CI", nothing) == "true", + highlights = ["yaml"], + ), + strict = !("strict=false" in ARGS), + doctest = ("doctest=only" in ARGS) ? :only : true, + repo = "https://github.com/ERGO-Code/HiGHS/tree/latest{path}", + linkcheck = true, + linkcheck_ignore = [ + "https://crates.io/crates/highs", + "https://crates.io/crates/good_lp", + "https://link.springer.com/article/10.1007/s12532-017-0130-5", + ], + pages = [ + "About" => "index.md", + "installation.md", + "Executable" => "executable.md", + "Guide" => Any[ + "guide/index.md", + "guide/basic.md", + "guide/further.md", + "guide/advanced.md" + ], + "Data structures" => Any[ + "structures/index.md", + "structures/enums.md", + "Classes" => Any[ + "structures/classes/index.md", + "structures/classes/HighsSparseMatrix.md", + "structures/classes/HighsLp.md", + "structures/classes/HighsSolution.md", + "structures/classes/HighsBasis.md", + "structures/classes/HighsInfo.md", + ], + ], + "Interfaces" => Any[ + "Python" => Any[ + "interfaces/python/index.md", + "interfaces/python/example-py.md", + ], + "C++" => Any[ + "interfaces/cpp/index.md", + "The HiGHS library" => "interfaces/cpp/library.md", + "Linking" => "interfaces/cpp/link.md", + "Examples" => "interfaces/cpp/examples.md", + ], + "C" => "interfaces/c/index.md", + "Julia" => "interfaces/julia/index.md", + "Other" => "interfaces/other.md", + ], + "Options" => Any[ + "options/intro.md", + "options/definitions.md" + ], + "Parallel" => "parallel.md", + "Terminology" => "terminology.md", + ], +) + +# ============================================================================== +# Deploy everything in `build` +# ============================================================================== + +Documenter.deploydocs(; + repo = "github.com/ERGO-Code/HiGHS.git", + push_preview = true, + devbranch = "latest", +) diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 0000000000..f69514a390 Binary files /dev/null and b/docs/src/assets/logo.png differ diff --git a/docs/src/executable.md b/docs/src/executable.md new file mode 100644 index 0000000000..d18d46ab63 --- /dev/null +++ b/docs/src/executable.md @@ -0,0 +1,58 @@ +# Executable + +For convenience, the executable is assumed to be `bin/highs`. + +### Running the executable + +The model given by the MPS file `model.mps` is solved by the command: + +```bash +$ bin/highs model.mps +``` + +If the model file is not in the folder from which the command was issued, then a +path name can be given. + +### Command line options + +When HiGHS is run from the command line, some fundamental option values may be +specified directly. Many more may be specified via a file. Formally, the usage +is: + +```bash +$ bin/highs --help +HiGHS options +Usage: + bin/highs [OPTION...] [file] + + --model_file arg File of model to solve. + --read_solution_file arg File of solution to read. + --options_file arg File containing HiGHS options. + --presolve arg Presolve: "choose" by default - "on"/"off" + are alternatives. + --solver arg Solver: "choose" by default - "simplex"/"ipm" + are alternatives. + --parallel arg Parallel solve: "choose" by default - + "on"/"off" are alternatives. + --run_crossover arg Run crossover: "on" by default - + "choose"/"off" are alternatives. + --time_limit arg Run time limit (seconds - double). + --solution_file arg File for writing out model solution. + --write_model_file arg File for writing out model. + --random_seed arg Seed to initialize random number generation. + --ranging arg Compute cost, bound, RHS and basic solution + ranging. + --version Print version. + -h, --help Print help. +``` + +The [list of options](@ref option-definitions) section gives a full +list of options, and the format in which they are specified. + +### Return code values + +Consistent with the callable methods in HiGHS, there are three possible return codes + + * -1: An error has occurred in HiGHS + * 0: HiGHS has run successfully + * 1: HiGHS has recovered from an unusual event, or has terminated due to reaching a time or iteration limit diff --git a/docs/src/guide/advanced.md b/docs/src/guide/advanced.md new file mode 100644 index 0000000000..5c637ec005 --- /dev/null +++ b/docs/src/guide/advanced.md @@ -0,0 +1,31 @@ +# [Advanced features](@id guide-advanced) + + +## Simplex tableau data + +HiGHS has a suite of methods for operations with the invertible +representation of the current basis matrix ``B``. To use +these requires knowledge of the corresponding (ordered) basic +variables. This is obtained using the +method +`getBasicVariables` +, with non-negative values being +columns and negative values corresponding to row indices plus one [so +-1 indicates row 0]. Methods +`getBasisInverseRow` +and +`getBasisInverseCol` +yield a specific row or column +of ``B^{-1}``. Methods +`getBasisSolve` +and +`getBasisTransposeSolve` +yield the solution +of ``Bx=b`` and ``Bx=b`` respectively. Finally, the +methods +`getReducedRow` +and +`getReducedColumn` +yield a specific row or column of ``B^{-1}A``. In all cases, +HiGHS can return the number and indices of the nonzeros in the result. + diff --git a/docs/src/guide/basic.md b/docs/src/guide/basic.md new file mode 100644 index 0000000000..764b99ecd4 --- /dev/null +++ b/docs/src/guide/basic.md @@ -0,0 +1,113 @@ +# [Basic features](@id guide-basic) + +The minimal use of HiGHS has the following three stages. + +* [Define a model](@ref Defining-a-model) +* [Solve the model](@ref Solving-the-model) +* [Extract results](@ref Extracting-results) + +Although its default actions will be sufficient for most users, HiGHS +can be controlled by setting [Option values](@ref Option-values). + +_Intro to other basic features_ + +### HiGHS data structures + +There are several specialist data structures that can be used to +interact with HiGHS when using [`C++`](@ref cpp-getting-started) and +`highspy`. These are defined in the sections on [enums](@ref structures-enums) +and [classes](@ref classes-overview), and are referred to below. + +#### [Enums](@id guide-basic-enums) + +Enums are scalar identifier types that can take only a limited range of values.???? + +#### The +advantage using these classes is that many fewer parameters are +needed when passing data to and from HiGHS. However, the use of +classes is not necessary for the basic use of `highspy`. As with the +`C` and `Fortran` interfaces, there are equivalent methods that use +simple scalars and vectors of data. + +## Defining a model + +HiGHS has comprehensive tools for defining models. This can be done by +either reading a model using a data file created elsewhere, or by +passing model data to HiGHS. Once a model has been defined in HiGHS, +it is referred to as the `incumbent model`. + +### Reading a model from a file + +The simplest way to define a model in HiGHS is to read it from a file using +the method [`readModel`](@ref Read-a-model). HiGHS infers the file type by the extension. Supported extensions are: + + * `.mps`: for an MPS file + * `.lp`: for a CPLEX LP file + +HiGHS can read compressed files that end in the `.gz` extension, but +not (yet) files that end in the `.zip` extension. + +### Building a model + +The model in HiGHS can be built using a sequence of calls to add +variables and constraints. This is most easily done one-by-one using +the methods [`addCol` and `addRow`](@ref +Build-a-model). Alternatively, calls to [`addVar`](@ref Build-a-model) +can be used to add variables, with calls to [`changeColCost`](@ref +Build-a-model) used to define each objective coefficient. + +Addition of multiple variables and constraints can be achieved using +[`addCols` and `addRows`](@ref Build-a-model). Alternatively, +[`addVars`](@ref Build-a-model) can be used to add variables, with +[`changeColsCost`](@ref Build-a-model) used to define objective +coefficients. Note that defining multiple variables and constraints requires +vectors of data and the specification of constraint coefficients as +compressed +[row-wise](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format)) +or +[column-wise](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_column_(CSC_or_CCS)) +matrices. + +### Passing a model + +If the entire definition of a model is known, then it can be passed to +HiGHS via individual data arrays using the method [`passModel`](@ref +Pass-a-model). In languages where HiGHS data structures can be used, +an instance of the [`HighsLp`](@ref HighsLp) class can be populated +with data and then passed. + +## Solving the model + +The incumbent model in HiGHS is solved by a call to the method +[`run`](@ref Solve-the-model). By default, HiGHS minimizes the model's +objective function, although this can be [changed](@ref +Modifying-model-data). Where possible, HiGHS will reduce the solution +time by using data obtained on previous runs, or supplied by the +user. More information on this process of hot starting solvers is +[available](@ref hot-start). + +## Extracting results + +After solving a model, it is important to know whether it has been +solved to optimality, shown to be infeasible or unbounded, or why the +solver may have terminated early. This model status is given by the +value returned by the method [`getModelStatus`](@ref +Extract-results). This value is of type [`HighsModelStatus`](@ref HighsModelStatus). +Scalar information about a solved model is obtained using the method +[`getInfo`](@ref Extract-results). The solution and (any) basis are +returned by the methods [`getSolution`](@ref Extract-results) and +[`getBasis`](@ref Extract-results) respectively. HiGHS can also be used +to write the solution to a file using the method [`writeSolution`](@ref +Report-results). + +## Option values + +The option values that control HiGHS are of type `string`, `bool`, +`int` and `double`. Options are referred to by a `string` identical to +the name of their identifier. A full specification of the options is +given in the [list of options](@ref option-definitions). An option +value is changed by passing its name and value to the method +[`setOptionValue`](@ref example-py-option-values). The current value +of an option is obtained by passing its name to the method +[`getOptionValue`](@ref example-py-option-values). + diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md new file mode 100644 index 0000000000..3f93a615e6 --- /dev/null +++ b/docs/src/guide/further.md @@ -0,0 +1,61 @@ +# [Further features](@id guide-further) + +## Model and solution management + +HiGHS has comprehensive tools for defining and extracting models. This can be +done either to/from MPS or (CPLEX) format LP files, or via method calls. HiGHS +also has methods that permit the incumbent model to be modified. Solutions can +be supplied and extracted using either files or method calls. + +### Extracting model data + +The numbers of column, rows and nonzeros in the model are returned by the +methods [`getNumCols`](@ref Get-model-data), [`getNumRows`](@ref Get-model-data), +and [`getNumEntries`](@ref Get-model-data) respectively. + +Model data can be extracted for a single column or row by specifying the index +of the column or row and calling the methods [`getCol`](@ref Get-model-data) and +[`getRow`](@ref Get-model-data). + +As well as returning the value of the cost and bounds, these methods also return +the number of nonzeros in the corresponding column or row of the constraint +matrix. The indices and values of the nonzeros can be obtained using the methods +[`getColEntries`](@ref Get-model-data) and [`getRowEntries`](@ref Get-model-data). + +For multiple columns and rows defined by a set of indices, the corresponding +data can be extracted using the methods [`getCols`](@ref Get-model-data), +[`getRows`](@ref Get-model-data), [`getColsEntries`](@ref Get-model-data) and +[`getRowsEntries`](@ref Get-model-data). + +Specific matrix coefficients obtained using the method [`getCoeff`](@ref Get-model-data). + +## Modifying model data + +The most immediate model modification is to change the sense of the objective. +By default, HiGHS minimizes the model's objective function. The objective sense +can be set to minimize (maximize) using [changeObjectiveSense](@ref Modify-model-data). + +Model data for can be changed for one column or row by specifying the index of +the column or row, together with the new scalar value for the cost or bounds, +the specific methods being [changeColCost](@ref Modify-model-data), +[changeColBounds](@ref Modify-model-data). The corresponding method for a row is +[changeRowBounds](@ref Modify-model-data). Changes for multiple columns or rows +are defined by supplying a list of indices, together with arrays of new values, +using the methods [changeColsCost](@ref Modify-model-data), +[changeColsBounds](@ref Modify-model-data). The corresponding method for a row +is [changeRowsBounds](@ref Modify-model-data). An individual matrix coefficient +is changed by passing its row index, column index and new value to +[changeCoeff](@ref Modify-model-data). + +### [Hot start](@id hot-start) + +It may be possible for HiGHS to start solving a model using data +obtained by solving a related model, or supplied by a user. Whether +this is possible depends on the the class of model being solved, the +solver to be used, and the modifications (if any) that have been to +the incumbent model since it was last solved. + +#### LP + +To run HiGHS from a user-defined solution or basis, this is passed to HiGHS +using the methods [setSolution](@ref Set-solution) or [setBasis](@ref Set-basis). diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md new file mode 100644 index 0000000000..3d93481564 --- /dev/null +++ b/docs/src/guide/index.md @@ -0,0 +1,18 @@ +# [Introduction](@id guide-introduction) + +This guide describes the features of HiGHS that are available when it +is called from [`Python`](@ref python-getting-started), [`C++`](@ref +cpp-getting-started), [`C`](@ref c-api) and [`Fortran`](@ref +fortran-api). It is written in three sections: [basic](@ref +guide-basic), [further](@ref guide-further) and [advanced](@ref +guide-advanced). + +The [basic](@ref guide-basic) section will be sufficient for most +users. This and the [further](@ref guide-further) section cover the +`Python` interface `highspy`. Although references to methods link to +`Python` examples, the method names and functionality for other +interfaces are as close as possible. + +The [advanced](@ref guide-advanced) section covers features in the +[`C++`](@ref cpp-getting-started), [`C`](@ref c-api) and +[`Fortran`](@ref fortran-api) that are not in `highspy`. \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000000..2f5bcd7b59 --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,86 @@ +# HiGHS - High Performance Optimization Software + +[![Build Status](https://github.com/ERGO-Code/HiGHS/workflows/build/badge.svg)](https://github.com/ERGO-Code/HiGHS/actions?query=workflow%3Abuild+branch%3Amaster) + +!!! warning + This HiGHS documentation is a work in progress. + +HiGHS is software for the definition, modification and solution of large scale +sparse linear optimization models. + +HiGHS is freely available from [GitHub](https://github.com/ERGO-Code/HiGHS) +under the MIT licence and has no third-party dependencies. + +## Specification + +HiGHS can solve linear programming (LP) models of the form: +```math +\begin{aligned} +\min \quad & c^T\! x \\ +\textrm{subject to} \quad & L \le Ax \le U \\ + & l \le x \le u, +\end{aligned} +``` +as well as mixed integer linear programming (MILP) models of the same form, for +which some of the variables must take integer values. + +HiGHS also solves quadratic programming (QP) models, which contain an additional +objective term ``\frac{1}{2}x^T\! Q x``, where the Hessian matrix ``Q`` is +positive semi-definite. HiGHS cannot solve QP models where some of the variables +must take integer values. + +Read the [Terminology](@ref) section for more details. + +## Using HiGHS + +HiGHS can be used as a standalone executable on Windows, Linux and MacOS. There +is also a C++11 library that can be used within a C++ project or, via its C, C#, +FORTRAN, Julia, and Python interfaces. + +Get started by following [Install HiGHS](@ref). + +## Overview + +The standalone [Executable](@ref) allows models to be solved from +[MPS](https://en.wikipedia.org/wiki/MPS_(format)) or (CPLEX) +[LP](https://web.mit.edu/lpsolve/doc/CPLEX-format.htm) files, with full control +of the HiGHS run-time options, and the solution can be written to files in human +and computer-readable formats. + +The HiGHS shared library allows models to be loaded, built and modified. It can +also be used to extract solution data and perform other operations relating to +the incumbent model. The basic functionality is introduced via a [`Guide`](@ref guide-basic), +with links to examples of its use in the `Python` interface `highspy`. This makes use of the C++ +structures and enums, and is as close as possible to the native C++ library +calls. These can be studied via the [C++ header file](https://github.com/ERGO-Code/HiGHS/blob/master/src/Highs.h). + +The C interface cannot make use of the C++ structures and enums, and its methods are documented [explicitly](@ref c-api). + +## Solution algorithms + +For LPs, HiGHS has implementations of both the revised simplex and interior +point methods. MIPs are solved by branch-and-price, and QPs by active set. + +## Citing HiGHS + +If you use HiGHS in an academic context, please cite the following article: + +Parallelizing the dual revised simplex method, +Q. Huangfu and J. A. J. Hall, +_Mathematical Programming Computation_, 10 (1), 119-142, 2018. +DOI: [10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5) + +## Performance benchmarks + +The performance of HiGHS relative to some commercial and open-source simplex +solvers may be assessed via the Mittlemann benchmarks: + + * [LP (find primal-dual feasible point)](http://plato.asu.edu/ftp/lpfeas.html) + * [LP (find optimal basic solution)](http://plato.asu.edu/ftp/lpopt.html) + * [MILP benchmarks](http://plato.asu.edu/ftp/milp.html). + +## Feedback + +Your comments or specific questions on HiGHS would be greatly appreciated, so +please send an email to `highsopt@gmail.com` to get in touch with the +development team. diff --git a/docs/src/installation.md b/docs/src/installation.md new file mode 100644 index 0000000000..d9a875a145 --- /dev/null +++ b/docs/src/installation.md @@ -0,0 +1,107 @@ +# Install HiGHS + +## Install via a package manager + +HiGHS can be installed using a package manager in the cases of +[`Julia`](@ref HiGHS.jl), [`Python`](@ref python-getting-started), and +[`Rust`](@ref Rust). + +## Precompiled Binaries + +_These binaries are provided by the Julia community and are not officially +supported by the HiGHS development team. If you have trouble using these +libraries, please open a GitHub issue and tag `@odow` in your question._ + +Precompiled static executables are available for a variety of platforms at + + * [https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases](https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases) + +Each download includes library files for linking to external projects and a +stand-alone executable. + +To install a precompiled binary, download the appropriate `.tar.gz` file and +extract the executable located at `/bin/highs`. + + * For Windows users: if in doubt, choose the file ending in `x86_64-w64-mingw32-cxx11.tar.gz` + * For M1 macOS users: choose the file ending in `aarch64-apple-darwin.tar.gz` + * For Intel macOS users: choose the file ending in `x86_64-apple-darwin.tar.gz` + +## Compile from source + +HiGHS uses CMake as build system, and requires at least version +3.15. After extracting HiGHS from +[GitHub](https://github.com/ERGO-Code/HiGHS), setup a build folder and +call CMake as follows: + +```bash +$ mkdir build +$ cd build +$ cmake .. +``` + +Then compile the code using: + +```bashs +$ cmake --build . +``` + +To test whether the compilation was successful, run + +```bash +$ ctest +``` + +HiGHS is installed using the command + +```bash +$ cmake --install . +``` + +This installs the library in `lib/`, as well as all header files in `include/highs/`. For a custom +installation in `install_folder` run + +```bash +$ cmake -DCMAKE_INSTALL_PREFIX=install_folder . +``` + +and then + +```bash +$ cmake --install . +``` + +To use the library from a CMake project use + +`find_package(HiGHS)` + +and add the correct path to HIGHS_DIR. + +## Windows + +By default, CMake builds the debug version of the binaries. These are generated in a directory `Debug`. To build a release version, add the option `--config Release` + +```bash + cmake -S . -B build + cmake --build build --config Release +``` + +It is also possible to specify a specific Visual studio version to build with: +```bash + cmake -G "Visual Studio 17 2022" -S . -B build + cmake --build build +``` + +When building under Windows, some extra options are available. One is building a 32 bit version or a 64 bit version. The default build is 64 bit. To build 32 bit, the following commands can be used from the `HiGHS/` directory: + +```bash + cmake -A Win32 -S . -DFAST_BUILD=OFF -B buildWin32 + cmake --build buildWin32 +``` + +Another thing specific for windows is the calling convention, particularly important for the HiGHS dynamic library (dll). The default calling convention in windows is cdecl calling convention, however, dlls are most often compiled with stdcall. Most applications which expect stdcall, can't access dlls with cdecl and vice versa. To change the default calling convention from cdecl to stdcall the following option can be added +```bash + cmake -DSTDCALL=ON -S . -DFAST_BUILD=OFF -B build + cmake --build build +``` +An extra note. With the legacy `-DFAST_BUILD=OFF`, under windows the build dll is called `highs.dll` however the exe expects `libhighs.dll` so a manual copy of `highs.dll` to `libhighs.dll` is needed. Of course all above options can be combined with each other. + diff --git a/docs/src/interfaces/c/index.md b/docs/src/interfaces/c/index.md new file mode 100644 index 0000000000..744c68a052 --- /dev/null +++ b/docs/src/interfaces/c/index.md @@ -0,0 +1,6 @@ +# [C](@id c-api) + +```@autodocs +Modules = [Main] +Filter = t -> startswith("$t", "Highs") +``` diff --git a/docs/src/interfaces/cpp/examples.md b/docs/src/interfaces/cpp/examples.md new file mode 100644 index 0000000000..0bcf5527b3 --- /dev/null +++ b/docs/src/interfaces/cpp/examples.md @@ -0,0 +1 @@ +Example programs calling HiGHS from C, C++, C#, Fortran and Python are in [`HiGHS/examples`](https://github.com/ERGO-Code/HiGHS/tree/master/examples). diff --git a/docs/src/interfaces/cpp/index.md b/docs/src/interfaces/cpp/index.md new file mode 100644 index 0000000000..ca9870a141 --- /dev/null +++ b/docs/src/interfaces/cpp/index.md @@ -0,0 +1,56 @@ +# [Getting started](@id cpp-getting-started) + +HiGHS can be cloned from [GitHub](https://github.com/ERGO-Code/HiGHS) with the command + +``` bash +git clone https://github.com/ERGO-Code/HiGHS.git +``` + +### Building HiGHS from source code + +HiGHS uses CMake (minimum version 3.15) as a build system, and can use the following compilers + +- Clang ` clang ` +- GNU ` g++ ` +- Intel ` icc ` + +The simplest setup is to create a build folder (within the folder into +which HiGHS has been downloaded) and then build HiGHS within it. The +name of the build folder is arbitrary but, assuming it is HiGHS/build, +the full sequence of commands required is as follows + +``` bash +cd HiGHS +mkdir build +cd build +cmake -DFAST_BUILD=ON .. +cmake --build . +``` + +This creates the [executable](@ref Executable) `build/bin/highs`. + +### Test build + +To perform a quick test to see whether the compilation was successful, run `ctest` from within the build folder. + +``` bash +ctest +``` + +### Install + +The default installation location may need administrative +permissions. To install, after building and testing, run + +``` bash +cmake --install . +``` + +To install in a specified installation directory run CMake with the +`CMAKE_INSTALL_PREFIX` flag set: + +``` bash +cmake -DFAST_BUILD=ON -DCMAKE_INSTALL_PREFIX=/path/to/highs_install .. +cmake --build . +cmake --install . +``` diff --git a/docs/src/interfaces/cpp/library.md b/docs/src/interfaces/cpp/library.md new file mode 100644 index 0000000000..53869789a3 --- /dev/null +++ b/docs/src/interfaces/cpp/library.md @@ -0,0 +1,100 @@ +The HiGHS library is defined in the [`src/Highs.h`](https://github.com/ERGO-Code/HiGHS/blob/master/src/Highs.h) header file. It contains the definition of the methods and members of the class. + +## Define model + +Models in HiGHS are defined as an instance of the `HighsModel` class. This consists of one instance of the `HighsLp` class, and one instance of the `HighsHessian` class. Communication of models to and from HiGHS is possible via instances of the `HighsLp` or `HighsModel` class. In the C and other interfaces, communication of models is via scalar values and addresses of arrays. + +In C++, the neatest way of passing a model to HiGHS is to create an instance of the `HighsModel` class, populate its data, and call +``` +Highs::passModel(const HighsModel& model) +``` + +or create and populate an instance of the `HighsLp` class, and call +``` +Highs::passModel(const HighsLp& lp) +``` + +For reading models from a file, use +``` +Highs::readModel(const std::string& filename) +``` + +Below is an example of building a `HighsModel` +``` + // Create and populate a HighsModel instance for the LP + + // Min f = x_0 + x_1 + 3 + // s.t. x_1 <= 7 + // 5 <= x_0 + 2x_1 <= 15 + // 6 <= 3x_0 + 2x_1 + // 0 <= x_0 <= 4; 1 <= x_1 + + // Although the first constraint could be expressed as an upper + // bound on x_1, it serves to illustrate a non-trivial packed + // column-wise matrix. + + HighsModel model; + model.lp_.num_col_ = 2; + model.lp_.num_row_ = 3; + model.lp_.sense_ = ObjSense::kMinimize; + model.lp_.offset_ = 3; + model.lp_.col_cost_ = {1.0, 1.0}; + model.lp_.col_lower_ = {0.0, 1.0}; + model.lp_.col_upper_ = {4.0, 1.0e30}; + model.lp_.row_lower_ = {-1.0e30, 5.0, 6.0}; + model.lp_.row_upper_ = {7.0, 15.0, 1.0e30}; + + // Here the orientation of the matrix is column-wise + model.lp_.a_matrix_.format_ = MatrixFormat::kColwise; + // a_start_ has num_col_+1 entries, and the last entry is the number + // of nonzeros in A, allowing the number of nonzeros in the last + // column to be defined + model.lp_.a_matrix_.start_ = {0, 2, 5}; + model.lp_.a_matrix_.index_ = {1, 2, 0, 1, 2}; + model.lp_.a_matrix_.value_ = {1.0, 3.0, 1.0, 2.0, 2.0}; +``` + +## Solve model + +``` + // Create a Highs instance + Highs highs; + HighsStatus return_status; + + // Pass the model to HiGHS + return_status = highs.passModel(model); + assert(return_status==HighsStatus::kOk); + + // Get a const reference to the LP data in HiGHS + const HighsLp& lp = highs.getLp(); + + // Solve the model + return_status = highs.run(); + assert(return_status==HighsStatus::kOk); + + // Get the model status + const HighsModelStatus& model_status = highs.getModelStatus(); + assert(model_status==HighsModelStatus::kOptimal); +``` + +Solution information: + +``` + const HighsInfo& info = highs.getInfo(); + cout << "Simplex iteration count: " << info.simplex_iteration_count << endl; + cout << "Objective function value: " << info.objective_function_value << endl; + cout << "Primal solution status: " << highs.solutionStatusToString(info.primal_solution_status) << endl; + cout << "Dual solution status: " << highs.solutionStatusToString(info.dual_solution_status) << endl; + cout << "Basis: " << highs.basisValidityToString(info.basis_validity) << endl; +``` + +## Integrality variables + +To indicate that variables must take integer values use the `HighsLp::integrality` vector. +``` + model.lp_.integrality_.resize(lp.num_col_); + for (int col=0; col < lp.num_col_; col++) + model.lp_.integrality_[col] = HighsVarType::kInteger; + + highs.passModel(model); +``` diff --git a/docs/src/interfaces/cpp/link.md b/docs/src/interfaces/cpp/link.md new file mode 100755 index 0000000000..d2414789a9 --- /dev/null +++ b/docs/src/interfaces/cpp/link.md @@ -0,0 +1,59 @@ +## Using HiGHS from another CMake Project + +There are several ways the HiGHS library can be used within another C++ project. + +Firstly, make sure that HiGHS is installed locally with the correct CMake flags: + +``` bash +cd HiGHS +mkdir build +cd build +cmake -DFAST_BUILD=ON -DCMAKE_INSTALL_PREFIX=/path_to_highs_install/ .. +cmake --build . +cmake --install . +``` + +This installs HiGHS in `/path_to_highs_install/`. + +Suppose another C++ CMake project has executable code in some file `main.cpp`, which includes `Highs.h`. To use the HiGHS library, edit the `CMakeLists.txt` as follows: + +``` +project(LOAD_HIGHS LANGUAGES CXX) + +set(HIGHS_DIR path_to_highs_install/lib/cmake/highs) + +find_package(HIGHS REQUIRED) +find_package(Threads REQUIRED) + +add_executable(main main.cpp) +target_link_libraries(main highs::highs) +``` + +The line +``` +set(HIGHS_DIR path_to_highs_install/lib/cmake/highs) +``` +adds the HiGHS installation path to `HIGHS_DIR`. This is equivalent to building this project with +``` +cmake -DHIGHS_DIR=path_to_highs_install/lib/cmake/highs .. +``` + +Alternatively, if you wish to include the code of HiGHS within your project, FetchContent is also available as follows: + +``` +project(LOAD_HIGHS LANGUAGES CXX) + +include(FetchContent) + +FetchContent_Declare( + highs + GIT_REPOSITORY "https://github.com/ERGO-Code/HiGHS.git" + GIT_TAG "bazel" +) +set(FAST_BUILD ON CACHE INTERNAL "Fast Build") + +FetchContent_MakeAvailable(highs) + +add_executable(main call_from_cpp.cc) +target_link_libraries(main highs::highs) +``` diff --git a/docs/src/interfaces/julia/index.md b/docs/src/interfaces/julia/index.md new file mode 100644 index 0000000000..26165acf00 --- /dev/null +++ b/docs/src/interfaces/julia/index.md @@ -0,0 +1,130 @@ +# HiGHS.jl + +[HiGHS.jl](https://github.com/jump-dev/HiGHS.jl) is a Julia package that +interfaces with HiGHS. + +HiGHS.jl has two components: + + - a thin wrapper around the complete C API + - an interface to [MathOptInterface](https://github.com/jump-dev/MathOptInterface.jl) + +The C API can be accessed via `HiGHS.Highs_xxx` functions, where the names and +arguments are identical to the C API. + +## Installation + +Install HiGHS as follows: +```julia +import Pkg +Pkg.add("HiGHS") +``` + +In addition to installing the HiGHS.jl package, this command will also download +and install the HiGHS binaries. (You do not need to install or compile HiGHS +separately.) + +To use a custom binary, read the [Custom solver binaries](https://jump.dev/JuMP.jl/stable/developers/custom_solver_binaries/) +section of the JuMP documentation. + +## Use with JuMP + +Pass `HiGHS.Optimizer` to `JuMP.Model` to create a JuMP model with HiGHS as the +optimizer. Set options using `set_optimizer_attribute`. + +```julia +using JuMP +import HiGHS +model = Model(HiGHS.Optimizer) +set_optimizer_attribute(model, "presolve", "on") +set_optimizer_attribute(model, "time_limit", 60.0) +``` + +For more details, including a range of tutorials and examples using HiGHS, see +the [JuMP documentation](https://jump.dev/JuMP.jl/stable/). + +## Issues and feedback + +HiGHS.jl is maintained by the JuMP community and is not officially maintained +or supported by the HiGHS developers. + +To report a problem (e.g., incorrect results, or a crash of the solver), +or make a suggestion for how to improve HiGHS.jl, please +[file a GitHub issue at HiGHS.jl](https://github.com/jump-dev/HiGHS.jl). + +If you use HiGHS from JuMP, use `JuMP.write_to_file(model, "filename.mps")` +to write your model an MPS file, then upload the MPS file to [https://gist.github.com](https://gist.github.com) +and provide a link to the gist in the GitHub issue. + +## C API + +HiGHS.jl is a thin wrapper around the complete [HiGHS C API](@ref c-api). + +As a basic example, we solve the model: + +```math +\begin{aligned} +\min \quad & x + y \\ +\textrm{subject to} \quad & 5 \le x + 2y \le 15 \\ + & 6 \le 3x + 2y \\ + & 0 \le x \le 4 \\ + & 1 \le y \\ + & y \in \mathbb{Z}. + +\end{aligned} +``` + +Here is the corresponding Julia code: + +```julia +julia> using HiGHS + +julia> highs = Highs_create() +Ptr{Nothing} @0x00007fc4557d3200 + +julia> ret = Highs_setBoolOptionValue(highs, "log_to_console", false) +0 + +julia> @assert ret == 0 # If ret != 0, something went wrong + +julia> Highs_addCol(highs, 1.0, 0.0, 4.0, 0, C_NULL, C_NULL) # x is column 0 +0 + +julia> Highs_addCol(highs, 1.0, 1.0, Inf, 0, C_NULL, C_NULL) # y is column 1 +0 + +julia> Highs_changeColIntegrality(highs, 1, kHighsVarTypeInteger) +0 + +julia> Highs_changeObjectiveSense(highs, kHighsObjSenseMinimize) +0 + +julia> senseP = Ref{Cint}(0) # Instead of passing `&sense`, pass a Julia `Ref` +Base.RefValue{Int32}(0) + +julia> Highs_getObjectiveSense(model, senseP) +0 + +julia> senseP[] == kHighsObjSenseMinimize # Dereference with senseP[] +true + +julia> Highs_addRow(highs, 5.0, 15.0, 2, Cint[0, 1], [1.0, 2.0]) +0 + +julia> Highs_addRow(highs, 6.0, Inf, 2, Cint[0, 1], [3.0, 2.0]) +0 + +julia> Highs_run(highs) +0 + +julia> col_value = zeros(Cdouble, 2); + +julia> Highs_getSolution(highs, col_value, C_NULL, C_NULL, C_NULL) +0 + +julia> col_value +2-element Vector{Float64}: + 1.0 + 2.0 + +julia> Highs_destroy(highs) +``` diff --git a/docs/src/interfaces/other.md b/docs/src/interfaces/other.md new file mode 100644 index 0000000000..fd4bfdbf59 --- /dev/null +++ b/docs/src/interfaces/other.md @@ -0,0 +1,66 @@ +# Other Interfaces + +!!! note + Some of the interfaces listed on this page are not officially supported by + the HiGHS development team and are contributed by the community. + +## AMPL + +HiGHS can be used via AMPL, see the [AMPL Documentation](https://dev.ampl.com/solvers/highs/index.html). + +## C# + +Here are observations on calling HiGHS from C#: + + * The file [`highs_csharp_api.cs`](https://github.com/ERGO-Code/HiGHS/blob/master/src/interfaces/highs_csharp_api.cs) + contains all the PInvoke you need. Copy it into your C# project. + * Make sure, that the native HiGHS library (`highs.dll`, `libhighs.dll`, + `libhighs.so`, ... depending on your platform) can be found at runtime. How + to do this is platform dependent, copying it next to your C# executable + should work in most cases. You can use msbuild for that. On linux, installing + HiGHS system wide should work. + * Make sure that all dependencies of the HiGHS library can be found, too. For + example, if HiGHS was build using `Visual C++` make sure that the + `MSVCRuntime` is installed on the machine you want to run your application + on. + * Depending on the name of your HiGHS library, it might be necessary to change + the constant "highslibname". See [document](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform) + on writing cross platform P/Invoke code if necessary. + * Call the Methods in `highs_csharp_api.cs` and have fun with HiGHS. + +This is the normal way to call plain old C from C# with the great simplification +that you don't have to write the PInvoke declarations yourself. + +## [Fortran](@id fortran-api) + +The interface is in +[`highs_fortran_api.f90`](https://github.com/ERGO-Code/HiGHS/blob/master/src/interfaces/highs_fortran_api.f90). Its +methods are simply bindings to the [`C` API](@ref c-api) + +To include in the build, switch the Fortran CMake parameter on: +``` +cmake -DFORTRAN=ON .. +``` + +## GAMS + +The interface is available at [GAMSlinks](https://github.com/coin-or/GAMSlinks/), +including [pre-build libraries](https://github.com/coin-or/GAMSlinks/releases). + +## Javascript + + * HiGHS can be used from javascript directly inside a web browser thanks to + [highs-js](https://github.com/lovasoa/highs-js). See the [demo](https://lovasoa.github.io/highs-js/) + and the [npm package](https://www.npmjs.com/package/highs). + * Alternatively, HiGHS also has a [native Node.js](https://www.npmjs.com/package/highs-solver) + interface. + +## R + + * An R interface is available through the [`highs` R package](https://cran.r-project.org/web/packages/highs/index.html). + +## Rust + + * HiGHS can be used from rust through the [`highs` crate](https://crates.io/crates/highs). + The rust linear programming modeler [**good_lp**](https://crates.io/crates/good_lp) + supports HiGHS. diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md new file mode 100644 index 0000000000..059a59a142 --- /dev/null +++ b/docs/src/interfaces/python/example-py.md @@ -0,0 +1,208 @@ +# [Examples](@id example-py) + +## Initialize HiGHS + +HiGHS must be initialized before making calls to the HiGHS Python +library, and the examples below assume that it has been done + +```python +import highspy +import numpy as np +h = highspy.Highs() +``` + +## Read a model + +To read a model into HiGHS from a MPS files and (CPLEX) LP files pass the file name to `readModel`. + +```python +# Read a model from MPS file model.mps +filename = 'model.mps' +status = h.readModel(filename) +print('Reading model file ', filename, ' returns a status of ', status) +filename = 'model.dat' +status = h.readModel(filename) +print('Reading model file ', filename, ' returns a status of ', status) +``` + +## Build a model + +Build the model + +```raw +minimize f = x0 + x1 +subject to x1 <= 7 + 5 <= x0 + 2x1 <= 15 + 6 <= 3x0 + 2x1 + 0 <= x0 <= 4; 1 <= x1 +``` + +Firstly, one variable at a time, via a sequence of calls to `addVar` and `addRow`:s +```python +inf = highspy.kHighsInf +# Define two variables, first using identifiers for the bound values, +# and then using constants +lower = 0 +upper = 4 +h.addVar(lower, upper) +h.addVar(1, inf) + +# Define the objective coefficients (costs) of the two variables, +# identifying the variable by index, and changing its cost from the +# default value of zero +cost = 1 +h.changeColCost(0, cost) +h.changeColCost(1, 1) + +# Define constraints for the model +# +# The first constraint (x1<=7) has only one nonzero coefficient, +# identified by variable index 1 and value 1 +num_nz = 1 +index = 1 +value = 1 +h.addRow(-inf, 7, num_nz, index, value) + +# The second constraint (5 <= x0 + 2x1 <= 15) has two nonzero +# coefficients, so arrays of indices and values are required +num_nz = 2 +index = np.array([0, 1]) +value = np.array([1, 2]) +h.addRow(5, 15, num_nz, index, value) + +# The final constraint (6 <= 3x0 + 2x1) has the same indices but +# different values +num_nz = 2 +value = np.array([3, 2]) +h.addRow(6, inf, num_nz, index, value) + +# Access LP +lp = h.getLp() +num_nz = h.getNumNz() +print('LP has ', lp.num_col_, ' columns', lp.num_row_, ' rows and ', num_nz, ' nonzeros') +``` + +Alternatively, via calls to `addCols` and `addRows`. + +```python +inf = highspy.kHighsInf +# The constraint matrix is defined with the rows below, but parameters +# for an empty (column-wise) matrix must be passed +cost = np.array([1, 1], dtype=np.double) +lower = np.array([0, 1], dtype=np.double) +upper = np.array([4, inf], dtype=np.double) +num_nz = 0 +start = 0 +index = 0 +value = 0 +h.addCols(2, cost, lower, upper, num_nz, start, index, value) +# Add the rows, with the constraint matrix row-wise +lower = np.array([-inf, 5, 6], dtype=np.double) +upper = np.array([7, 15, inf], dtype=np.double) +num_nz = 5 +start = np.array([0, 1, 3]) +index = np.array([1, 0, 1, 0, 1]) +value = np.array([1, 1, 2, 3, 2], dtype=np.double) +h.addRows(3, lower, upper, num_nz, start, index, value) +``` + + * `passColName` + * `passRowName` + +## Pass a model + +Pass a model from a HighsLp instance +```python +inf = highspy.kHighsInf +# Define a HighsLp instance +lp = highspy.HighsLp() +lp.num_col_ = 2; +lp.num_row_ = 3; +lp.col_cost_ = np.array([1, 1], dtype=np.double) +lp.col_lower_ = np.array([0, 1], dtype=np.double) +lp.col_upper_ = np.array([4, inf], dtype=np.double) +lp.row_lower_ = np.array([-inf, 5, 6], dtype=np.double) +lp.row_upper_ = np.array([7, 15, inf], dtype=np.double) +# In a HighsLp instsance, the number of nonzeros is given by a fictitious final start +lp.a_matrix_.start_ = np.array([0, 2, 5]) +lp.a_matrix_.index_ = np.array([1, 2, 0, 1, 2]) +lp.a_matrix_.value_ = np.array([1, 3, 1, 2, 2], dtype=np.double) +h.passModel(lp) +``` + +## Solve the model + +The incumbent model in HiGHS is solved by calling +```python +h.run() +``` + +## Print solution information + +```python +solution = h.getSolution() +basis = h.getBasis() +info = h.getInfo() +model_status = h.getModelStatus() +print('Model status = ', h.modelStatusToString(model_status)) +print() +print('Optimal objective = ', info.objective_function_value) +print('Iteration count = ', info.simplex_iteration_count) +print('Primal solution status = ', h.solutionStatusToString(info.primal_solution_status)) +print('Dual solution status = ', h.solutionStatusToString(info.dual_solution_status)) +print('Basis validity = ', h.basisValidityToString(info.basis_validity)) +``` + +## Extract results + + * `getModelStatus` + * `getInfo` + * `getSolution` + * `getBasis` + +## Report results + + * `writeSolution` + +## [Option values](@id example-py-option-values) + + * `setOptionValue` + * `getOptionValue` + +## Get model data + + * `getNumCols` + * `getNumRows` + * `getNumEntries` + * `getCol` + * `getRow` + * `getColEntries` + * `getRowEntries` + * `getCols` + * `getRows` + * `getColsEntries` + * `getRowsEntries` + * `getColName` + * `getColByName` + * `getRowName` + * `getRowByName` + * `getCoeff` + +## Modify model data + + * `changeObjectiveSense` + * `changeColCost` + * `changeColBounds` + * `changeRowBounds` + * `changeColsCosts` + * `changeColsBounds` + * `changeRowsBounds` + * `changeCoeff` + +## Set solution + + * `setSolution` + +## Set basis + + * `setBasis` diff --git a/docs/src/interfaces/python/index.md b/docs/src/interfaces/python/index.md new file mode 100644 index 0000000000..ae9c3fc7e3 --- /dev/null +++ b/docs/src/interfaces/python/index.md @@ -0,0 +1,61 @@ +# [Getting started](@id python-getting-started) + +## Install + +HiGHS is available as `highspy` on [PyPi](https://pypi.org/project/highspy/). + +If `highspy` is not already installed, run: + +```bash +$ pip install highspy +``` + +## Import + +To use `highspy` within a Python program, it must be imported + +```python +import highspy +``` + +When using `highspy`, it is likely that `numpy` structures will be needed, +so must also be imported + +```python +import numpy as np +``` + +## Initialize + +HiGHS must be initialized before making calls to the HiGHS Python library: + +```python +h = highspy.Highs() +``` + +## Methods + +Detailed documentation of the methods and structures is given in the +[examples section](@ref example-py). + +## Return status + +Unless a method just returns data from HiGHS, so is guaranteed to run +successfully, each method returns a status to indicate whether it has run +successfully. This value is an instance of the enum [HighsStatus](@ref), and in +the [examples section](@ref example-py), it is referred to as `status`. + +## First example + +The following Python code reads a model from the file `model.mps`, and then +solves it. + +```python +import highspy + +h = highspy.Highs() +filename = 'model.mps' +h.readModel(filename) +h.run() +print('Model ', filename, ' has status ', h.getModelStatus()) +``` diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md new file mode 100644 index 0000000000..1bbe0a311f --- /dev/null +++ b/docs/src/options/definitions.md @@ -0,0 +1,286 @@ +# [List of options](@id option-definitions) + +## presolve +- Presolve option: "off", "choose" or "on" +- Type: string +- Default: "choose" + +## solver +- Solver option: "simplex", "choose" or "ipm". If "simplex"/"ipm" is chosen then, for a MIP (QP) the integrality constraint (quadratic term) will be ignored +- Type: string +- Default: "choose" + +## parallel +- Parallel option: "off", "choose" or "on" +- Type: string +- Default: "choose" + +## run\_crossover +- Run IPM crossover: "off", "choose" or "on" +- Type: string +- Default: "on" + +## time\_limit +- Time limit (seconds) +- Type: double +- Range: [0, inf] +- Default: inf + +## ranging +- Compute cost, bound, RHS and basic solution ranging: "off" or "on" +- Type: string +- Default: "off" + +## infinite\_cost +- Limit on cost coefficient: values larger than this will be treated as infinite +- Type: double +- Range: [1e+15, inf] +- Default: 1e+20 + +## infinite\_bound +- Limit on |constraint bound|: values larger than this will be treated as infinite +- Type: double +- Range: [1e+15, inf] +- Default: 1e+20 + +## small\_matrix\_value +- Lower limit on |matrix entries|: values smaller than this will be treated as zero +- Type: double +- Range: [1e-12, inf] +- Default: 1e-09 + +## large\_matrix\_value +- Upper limit on |matrix entries|: values larger than this will be treated as infinite +- Type: double +- Range: [1, inf] +- Default: 1e+15 + +## primal\_feasibility\_tolerance +- Primal feasibility tolerance +- Type: double +- Range: [1e-10, inf] +- Default: 1e-07 + +## dual\_feasibility\_tolerance +- Dual feasibility tolerance +- Type: double +- Range: [1e-10, inf] +- Default: 1e-07 + +## ipm\_optimality\_tolerance +- IPM optimality tolerance +- Type: double +- Range: [1e-12, inf] +- Default: 1e-08 + +## objective\_bound +- Objective bound for termination +- Type: double +- Range: [-inf, inf] +- Default: inf + +## random\_seed +- Random seed used in HiGHS +- Type: integer +- Range: {0, 2147483647} +- Default: 0 + +## threads +- Number of threads used by HiGHS (0: automatic) +- Type: integer +- Range: {0, 2147483647} +- Default: 0 + +## simplex\_strategy +- Strategy for simplex solver 0 => Choose; 1 => Dual (serial); 2 => Dual (PAMI); 3 => Dual (SIP); 4 => Primal +- Type: integer +- Range: {0, 4} +- Default: 1 + +## simplex\_scale\_strategy +- Simplex scaling strategy: off / choose / equilibration / forced equilibration / max value 0 / max value 1 (0/1/2/3/4/5) +- Type: integer +- Range: {0, 5} +- Default: 1 + +## simplex\_dual\_edge\_weight\_strategy +- Strategy for simplex dual edge weights: Choose / Dantzig / Devex / Steepest Edge (-1/0/1/2) +- Type: integer +- Range: {-1, 2} +- Default: -1 + +## simplex\_primal\_edge\_weight\_strategy +- Strategy for simplex primal edge weights: Choose / Dantzig / Devex / Steepest Edge (-1/0/1/2) +- Type: integer +- Range: {-1, 2} +- Default: -1 + +## simplex\_iteration\_limit +- Iteration limit for simplex solver when solving LPs, but not subproblems in the MIP solver +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + +## simplex\_update\_limit +- Limit on the number of simplex UPDATE operations +- Type: integer +- Range: {0, 2147483647} +- Default: 5000 + +## simplex\_max\_concurrency +- Maximum level of concurrency in parallel simplex +- Type: integer +- Range: {1, 8} +- Default: 8 + +## output\_flag +- Enables or disables solver output +- Type: boolean +- Default: "true" + +## log\_to\_console +- Enables or disables console logging +- Type: boolean +- Default: "true" + +## solution\_file +- Solution file +- Type: string +- Default: "" + +## log\_file +- Log file +- Type: string +- Default: "" + +## write\_solution\_to\_file +- Write the primal and dual solution to a file +- Type: boolean +- Default: "false" + +## write\_solution\_style +- Style of solution file (raw = computer-readable, pretty = human-readable): -1 => HiGHS old raw (deprecated); 0 => HiGHS raw; 1 => HiGHS pretty; 2 => Glpsol raw; 3 => Glpsol pretty; 4 => HiGHS sparse raw +- Type: integer +- Range: {-1, 4} +- Default: 0 + +## glpsol\_cost\_row\_location +- Location of cost row for Glpsol file: -2 => Last; -1 => None; 0 => None if empty, otherwise data file location; 1 <= n <= num\_row => Location n; n > num\_row => Last +- Type: integer +- Range: {-2, 2147483647} +- Default: 0 + +## write\_model\_file +- Write model file +- Type: string +- Default: "" + +## write\_model\_to\_file +- Write the model to a file +- Type: boolean +- Default: "false" + +## mip\_detect\_symmetry +- Whether MIP symmetry should be detected +- Type: boolean +- Default: "true" + +## mip\_max\_nodes +- MIP solver max number of nodes +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + +## mip\_max\_stall\_nodes +- MIP solver max number of nodes where estimate is above cutoff bound +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + +## mip\_improving\_solution\_save +- Whether improving MIP solutions should be saved +- Type: boolean +- Default: "false" + +## mip\_improving\_solution\_report\_sparse +- Whether improving MIP solutions should be reported in sparse format +- Type: boolean +- Default: "false" + +## mip\_improving\_solution\_file +- File for reporting improving MIP solutions: not reported if "" +- Type: string +- Default: "" + +## mip\_max\_leaves +- MIP solver max number of leave nodes +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + +## mip\_max\_improving\_sols +- Limit on the number of improving solutions found to stop the MIP solver prematurely +- Type: integer +- Range: {1, 2147483647} +- Default: 2147483647 + +## mip\_lp\_age\_limit +- Maximal age of dynamic LP rows before they are removed from the LP relaxation in the MIP solver +- Type: integer +- Range: {0, 32767} +- Default: 10 + +## mip\_pool\_age\_limit +- Maximal age of rows in the MIP solver cutpool before they are deleted +- Type: integer +- Range: {0, 1000} +- Default: 30 + +## mip\_pool\_soft\_limit +- Soft limit on the number of rows in the MIP solver cutpool for dynamic age adjustment +- Type: integer +- Range: {1, 2147483647} +- Default: 10000 + +## mip\_pscost\_minreliable +- Minimal number of observations before MIP solver pseudo costs are considered reliable +- Type: integer +- Range: {0, 2147483647} +- Default: 8 + +## mip\_min\_cliquetable\_entries\_for\_parallelism +- Minimal number of entries in the MIP solver cliquetable before neighbourhood queries of the conflict graph use parallel processing +- Type: integer +- Range: {0, 2147483647} +- Default: 100000 + +## mip\_feasibility\_tolerance +- MIP feasibility tolerance +- Type: double +- Range: [1e-10, inf] +- Default: 1e-06 + +## mip\_heuristic\_effort +- Effort spent for MIP heuristics +- Type: double +- Range: [0, 1] +- Default: 0.05 + +## mip\_rel\_gap +- Tolerance on relative gap, |ub-lb|/|ub|, to determine whether optimality has been reached for a MIP instance +- Type: double +- Range: [0, inf] +- Default: 0.0001 + +## mip\_abs\_gap +- Tolerance on absolute gap of MIP, |ub-lb|, to determine whether optimality has been reached for a MIP instance +- Type: double +- Range: [0, inf] +- Default: 1e-06 + +## ipm\_iteration\_limit +- Iteration limit for IPM solver +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + diff --git a/docs/src/options/intro.md b/docs/src/options/intro.md new file mode 100644 index 0000000000..7396072a87 --- /dev/null +++ b/docs/src/options/intro.md @@ -0,0 +1,46 @@ +# Introduction + +The options that control HiGHS are of four types: `boolean`, `integer`, `double` +and `string`. Their values can be specified: + + * via the command line when running the [Executable](@ref) + * via method calls when running HiGHS in an application. + +## Options file + +When running the [Executable](@ref) via the command line, some options values +can be set explicitly in the command, and all options can be set by means of an +options file. + +A sample options file, giving documentation of all the options is written to the +console by the command: + +```bash +$ bin/highs --options_file="" +``` + +## Option methods + +To set the value of option `name`, call: + +``` +status = h.setOptionValue(name, value) +``` + +where the value passed can be an identifier of the appropriate type, or an +explicit value. + +To get the value of option `name`, call: + +``` +[status, value] = h.getOptionValue(name) +``` + +To get the type of option `name`, call: + +``` +[status, type] = h.getOptionType(name) +``` + +Examples of calls to options methods are given in the [examples section](@ref example-py). + diff --git a/docs/src/parallel.md b/docs/src/parallel.md new file mode 100644 index 0000000000..b14defc01e --- /dev/null +++ b/docs/src/parallel.md @@ -0,0 +1,69 @@ +# Parallelism + +## Generally + +HiGHS currently has limited opportunities for exploiting parallel +computing. These are currently restricted to the dual simplex solver +for LP, and the MIP solver. Details of these and future plans are set +out below. + +By default, when running in parallel, HiGHS will use half the +available threads on a machine. This number can be modified by setting +the value of the +[threads](@ref) +option. + +## Dual simplex + +By default, the HiGHS dual simplex solver runs in serial. However, it +has a variant allowing concurrent processing. This variant is used +when the +[parallel](@ref) +option is set "on", by specifying `--parallel` when running the +[executable](@ref Executable) via +the command line, or by setting it via a library call in an +application. + +The concurrency used will be the value of +[simplex\_max\_concurrency](@ref). If +this is fewer than the number of threads available, parallel +performance may be less than anticipated. + +The speed-up achieved using the dual simplex solver is normally +bounded by the number of memory channels in the architecture, and +typically less than the values achieved by [Huangfu and +Hall](https://link.springer.com/article/10.1007/s12532-017-0130-5). This +is because enhancements to the serial dual simplex solver in recent +years have not been propagated to the parallel solver. + +Unless an LP has significantly more variables than constraints, the +parallel dual simplex solver is unlikely to be worth using. + +## MIP + +The only parallel computation currently implemented in the MIP solver +occurs when performing symmetry detection on the model, and when +querying clique tables. This parallelism is always advantageous, so is +performed regardless of the value of the +[parallel](@ref) option. + +## Future plans + +A prototype parallel LP solver has been developed, in which the +(serial) interior point solver and simplex variants are run +concurrently. When one runs to completion, the others are +stopped. However, to ensure that it runs deterministically requires +considerable further work. The non-deterministic solver will be +available by the end of 2023, but a deterministic solver is unlikely +to be available before the end of 2024. + +The MIP solver has been written with parallel tree seach in mind, and +it is hoped that this will be implemented before the end of 2024. The +parallel LP solver will also enhance the MIP solver performance by +spoeeding up the solution of the root node. + +Development of a parallel interior point solver will start in 2023, +and is expected to be completed by the end of 2024. + + + diff --git a/docs/src/structures/classes/HighsBasis.md b/docs/src/structures/classes/HighsBasis.md new file mode 100644 index 0000000000..bfc71c7efd --- /dev/null +++ b/docs/src/structures/classes/HighsBasis.md @@ -0,0 +1,8 @@ +# HighsBasis + +The basis of a model is communicated via an instance of the HighsBasis class + +- valid: Scalar of type bool - Indicates whether the basis is valid +- col\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a column +- row\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a row + diff --git a/docs/src/structures/classes/HighsInfo.md b/docs/src/structures/classes/HighsInfo.md new file mode 100644 index 0000000000..f0ddebee4f --- /dev/null +++ b/docs/src/structures/classes/HighsInfo.md @@ -0,0 +1,80 @@ +# HighsInfo + +Scalar information about a solved model is communicated via an instance of the HighsInfo class + +## valid +- Indicates whether the values in a HighsInfo instance are valid +- Type: bool + +## simplex\_iteration\_count +- The number of simplex iterations performed +- Type: integer + +## ipm\_iteration\_count +- The number of interior point iterations performed +- Type: integer + +## crossover\_iteration\_count +- The number of crossover iterations performed +- Type: integer + +## qp\_iteration\_count +- The number of QP iterations performed +- Type: integer + +## primal\_solution\_status +- Comparison with [SolutionStatus](@ref) gives the status of the [primal](@ref Primal-values) solution +- Type: integer + +## dual\_solution\_status +- Comparison with [SolutionStatus](@ref) gives the status of the [dual](@ref Dual-values) solution +- Type: integer + +## basis\_validity +- Comparison with [BasisValidity](@ref) gives the status of any basis information +- Type: integer + +## objective\_function\_value +- The optimal value of the objective function +- Type: double + +## mip\_node\_count +- The number of nodes generated by the MIP solver +- Type: long integer + +## mip\_dual\_bound +- The [dual bound](@ref terminology-mip) for the MIP solver +- Type: double + +## mip\_gap +- The absolute value of the gap between the primal and bounds, relative to the primal bound. +- Type: double + +## max\_integrality\_violation +- The maximum deviation from an integer value over all the discrete variables +- Type: double + +## num\_primal\_infeasibilities +- The number of variables violating a bound by more than the [primal feasibility tolerance](@ref primal_feasibility_tolerance). +- Type: integer + +## max\_primal\_infeasibility +- The maximum violation of a bound on a variable +- Type: double + +## sum\_primal\_infeasibilities +- The sum of violations of bounds by variables +- Type: double + +## num\_dual\_infeasibilities +- The number of variables violating dual feasibility by more than the [dual feasibility tolerance](@ref dual_feasibility_tolerance). +- Type: integer + +## max\_dual\_infeasibility +- The maximum dual feasibility violation +- Type: double + +## sum\_dual\_infeasibilities +- The sum of dual feasibility violations +- Type: double + diff --git a/docs/src/structures/classes/HighsLp.md b/docs/src/structures/classes/HighsLp.md new file mode 100644 index 0000000000..44027869c7 --- /dev/null +++ b/docs/src/structures/classes/HighsLp.md @@ -0,0 +1,19 @@ +# HighsLp + +An LP model is communicated via an instance of the HighsLp class + +- num\_col\_: Scalar of type integer - Number of columns in the model +- num\_row\_: Scalar of type integer - Number of rows in the model +- col\_cost\_: Vector of type double - Coefficients of the linear term in the objective function +- col\_lower\_: Vector of type double - Lower bounds on the variables +- col\_upper\_: Vector of type double - Upper bounds on the variables +- row\_lower\_: Vector of type double - Lower bounds on the constraints +- row\_upper\_: Vector of type double - Upper bounds on the constraints +- a\_matrix\_: Instance of [HighsSparseMatrix](@ref) class - Constraint matrix +- sense\_: Scalar of type [ObjSense](@ref) - Optimization sense of the model +- offset\_: Scalar of type double - Constant term in the objective function +- model\_name\_: Scalar of type string - Name of the model +- objective\_name\_: Scalar of type string - Name of the objective function +- col\_names\_: Vector of type string - Names of the variables +- row\_names\_: Vector of type string - Names of the constraints +- integrality\_: Vector of type [HighsVarType](@ref) - Type of each variable diff --git a/docs/src/structures/classes/HighsSolution.md b/docs/src/structures/classes/HighsSolution.md new file mode 100644 index 0000000000..78fc48a959 --- /dev/null +++ b/docs/src/structures/classes/HighsSolution.md @@ -0,0 +1,10 @@ +# HighsSolution + +The solution of a model is communicated via an instance of the HighsSolution class + +- value\_valid: Scalar of type bool - Indicates whether the column and row values are valid +- dual\_valid: Scalar of type bool - Indicates whether the column and row [duals](@ref Dual-values) are valid +- col\_value: Vector of type double - Values of the columns (variables) +- col\_dual: Vector of type double - Duals of the columns (variables) +- row\_value: Vector of type double - Values of the rows (constraints) +- row\_dual: Vector of type double - Duals of the rows (constraints) diff --git a/docs/src/structures/classes/HighsSparseMatrix.md b/docs/src/structures/classes/HighsSparseMatrix.md new file mode 100644 index 0000000000..fdda98936d --- /dev/null +++ b/docs/src/structures/classes/HighsSparseMatrix.md @@ -0,0 +1,10 @@ +# HighsSparseMatrix + +The constraint matrix of an LP model is communicated via an instance of the HighsSparseMatrix class + +- format\_: Scalar of MatrixFormat type - Format of the matrix +- num\_col\_ : Scalar of integer type - Number of columns in the matrix +- num\_row\_: Scalar of integer type - Number of rows in the matrix +- start\_: Vector of integer type - Start of each compressed vector in the matrixs +- index\_: Vector of integer type - Indices of the nonzeros in the matrix +- value\_: Vector of double type - Values of the nonzeros in the matrix diff --git a/docs/src/structures/classes/index.md b/docs/src/structures/classes/index.md new file mode 100644 index 0000000000..b907914083 --- /dev/null +++ b/docs/src/structures/classes/index.md @@ -0,0 +1,11 @@ +# [Overview](@id classes-overview) + +The data members of fundamental classes in HiGHS are defined in this section. + + * [HighsSparseMatrix](@ref) + * [HighsLp](@ref) + * [HighsSolution](@ref) + * [HighsBasis](@ref) + * [HighsInfo](@ref) + +Class data members for internal use only are not documented. diff --git a/docs/src/structures/enums.md b/docs/src/structures/enums.md new file mode 100644 index 0000000000..af9601b7c2 --- /dev/null +++ b/docs/src/structures/enums.md @@ -0,0 +1,105 @@ +# [Enums](@id structures-enums) + +The members of the fundamental HiGHS enums are defined below. If `Enum` refers +to a particular enum, and `Member` to a particular member, the members are +available as follows. + + * Python: `highspy.Enum.Member` + * C++: `Enum::Member` + +Members for internal use only are not documented. + +## HighsStatus + +This is (part of) the return value of most HiGHS methods: + + * `kError`: The method has exposed an error + * `kOk`: The method has completed successfully + * `kWarning`: The method has recovered from an unusual event, or has terminated + due to reaching a time or iteration limit + +## MatrixFormat + +This defines the format of a [HighsSparseMatrix](@ref): + + * `kColwise`: The matrix is stored column-wise + * `kRowwise`: The matrix is stored row-wise + +## ObjSense + +This defines optimization sense of a [HighsLp](@ref): + + * `kMinimize`: The objective is to be minimized + * `kMaximize`: The objective is to be maximized + +## HighsVarType + +This defines the feasible values of a variable within a model: + + * `kContinuous`: The variable can take continuous values between its bounds + * `kInteger`: The variable must take integer values between its bounds + * `kSemiContinuous`: The variable must be zero or take continuous values between its bounds + * `kSemiInteger`: The variable must be zero or take integer values between its bounds + +## SolutionStatus + +This defines the nature of any primal or dual solution information: + + * `kSolutionStatusNone`: There is no solution information + * `kSolutionStatusInfeasible`: The solution is not feasible + * `kSolutionStatusFeasible`: The solution is feasible + +## BasisValidity + +This defines the nature of any basis information: + + * `kBasisValidityInvalid`: There is no basisn information + * `kBasisValidityValid`: The basis information is valid + +## HighsModelStatus + +This defines the status of the model after a call to `run` + + * `kNotset`: The model status has not been set + * `kModelError`: There is an error in the model + * `kSolveError`: There has been an error when solving the model + * `kModelEmpty`: The model is empty + * `kOptimal`: The model has been solved to optimality + * `kInfeasible`: The model is infeasible + * `kUnboundedOrInfeasible`: The model is unbounded or infeasible + * `kUnbounded`: The model is unbounded + * `kObjectiveBound`: The bound on the model objective value has been reached + * `kObjectiveTarget`: The target value for the model objective has been reached + * `kTimeLimit`: The run time limit has been reached + * `kIterationLimit`: The iteration limit has been reached + * `kSolutionLimit`: The MIP solver has reached the limit on the number of LPs solved + * `kUnknown`: The model status is unknown + +## HighsBasisStatus + +This defines the status of a variable (or slack variable for a constraint) in a +basis: + + * `kLower`: The variable is nonbasic at its lower bound (or fixed value) + * `kBasic`: The variable is basic + * `kUpper`: The variable is at its upper bound + * `kZero`: A free variable is nonbasic and set to zero + * `kNonbasic`: The variable is nonbasic + +## HighsOptionType + +This defines the types of option values that control HiGHS: + + * `kBool`: The option type is boolean + * `kInt`: The option type is integer + * `kDouble`: The option type is double + * `kString`: The option type is string + +## HighsInfoType + +This defines the types of (scalar) information available after a call to `run`: + + * `kInt64`: The information type is 64-bit integer + * `kInt`: The information type is integer + * `kDouble`: The information type is double + diff --git a/docs/src/structures/index.md b/docs/src/structures/index.md new file mode 100644 index 0000000000..aae6fbcf81 --- /dev/null +++ b/docs/src/structures/index.md @@ -0,0 +1,12 @@ +# [Introduction](@id structures-intro) + +There are several specialist data structures that can be used to +interact with HiGHS when using [`C++`](@ref cpp-getting-started) and +[`Python`](@ref python-getting-started), and they are defined in the +sections on [enums](@ref structures-enums) and [classes](@ref +classes-overview). The advantage using these classes is that many +fewer parameters are needed when passing data to and from +HiGHS. However, the use of classes is not necessary for the basic use +of `highspy`. As with the `C` and `Fortran` interfaces, there are +equivalent methods that use simple scalars and vectors of data. + diff --git a/docs/src/terminology.md b/docs/src/terminology.md new file mode 100644 index 0000000000..fba4e7849c --- /dev/null +++ b/docs/src/terminology.md @@ -0,0 +1,141 @@ +# Terminology + +Any linear optimization model will have __decision variables__, a +linear or quadratic __objective function__, and linear __constraints__ +and __bounds__ on the values of the decision variables. A +__mixed-integer__ optimization model will require some or all of the +decision variables to take integer values. The model may require the +objective function to be maximized or minimized whilst satisfying the +constraints and bounds. By default, HiGHS minimizes the objective +function. + +## Bounds and the objective function + +The bounds on a decision variable are the least and greatest values +that it may take, and infinite bounds can be specified. A linear +objective function is given by a set of coefficients, one for each +decision variable, and its value is the sum of products of +coefficients and values of decision variables. The objective +coefficients are often referred to as __costs__, and some may be +zero. When a model has been solved, the optimal values of the +decision variables are referred to as the __(primal) solution__. + +## Constraints and the feasible region + +Linear constraints require linear functions of decision variables to +lie between bounds, and infinite bounds can be specified. If the +bounds are equal, then the constraint is an __equation__. If the +bounds are both finite, then the constraint is said to be __boxed__ or +__two-sided__. The set of points satisfying linear constraints and +bounds is known as the __feasible region__. Geometrically, this is a +multi-dimensional convex polyhedron, whose extreme points are referred +to as __vertices__. + +## The constraint matrix + +The coefficients of the linear constraints are naturally viewed as +rows of a __matrix__. The constraint coefficients associated with a +particular decision variable form a column of the constraint +matrix. Hence constraints are sometimes referred to as __rows__, and +decision variables as __columns__. Constraint matrix coefficients may +be zero. Indeed, for large practical models it is typical for most +of the coefficients to be zero. When this property can be exploited to +computational advantage, the matrix is said to be __sparse__. When the +constraint matrix is not sparse, the solution of large models is +normally intractable computationally. + +## Optimization outcomes + +It is possible to define a set of constraints and bounds that cannot +be satisfied, in which case the model is said to be +__infeasible__. Conversely, it is possible that the value of the +objective function can be improved without bound whilst satisfying the +constraints and bounds, in which case the model is said to be +__unbounded__. If a model is neither infeasible, nor unbounded, it +has an __optimal solution__. The optimal objective function value for +a linear optimization model may be achieved at more than point, in +which case the optimal solution is said to be __non-unique__. + +## Primal values + +The values of the decision variables are referred to as __primal__ values to distingush them from __dual__ values. + +## Dual values + +When none of the decision variables is required to take integer +values, the model is said to be __continuous__. For +continuous models, each variable and constraint has an +associated __dual variable__. The values of the dual +variables constitute the __dual solution__, and it is for +this reason that the term __primal solution__ is used to +distinguish the optimal values of the decision variables. At the +optimal solution of a continuous model, some of the decision +variables and values of constraint functions will be equal to their +lower or upper bounds. Such a bound is said to +be __active__. If a variable or constraint is at a bound, +its corresponding dual solution value will generally be non-zero: when +at a lower bound the dual value will be non-negative; when at an upper +bound the dual value will be non-positive. When maximizing the +objective the required signs of the dual values are reversed. Due to +their economic interpretation, the dual values associated with +constraints are often referred to as __shadow prices__ +or __fair prices__. Mathematically, the dual values +associated with variables are often referred to as __reduced +costs__, and the dual values associated with constraints are +often referred to as __Lagrange multipliers__. + +## Basic solution + +An LP model that is neither infeasible, nor unbounded, has an +optimal solution at a vertex. At a vertex, the decision variables can +be partitioned into as many __basic variables__ as there are +constraints, and __nonbasic variables__. Such a solution is known as a +__basic solution__, and the partition referred to as a __basis__. + +## Sensitivity + +Analysis of the change in optimal objective value of a continuous +linear optimization model as the cost coefficients and bounds are +changed is referred to in HiGHS as __ranging__. For an +active bound, the corresponding dual value gives the change in the +objective if that bound is increased or decreased. This level of +analysis is often referred to as __sensitivity__. In +general, the change in the objective is only known for a limited range +of values for the active bound. HiGHS will return the limits of +these __bound ranges__ ranges, the objective value at +both limits and the index of a variable or constraint that will +acquire an active bound at both limits. For each variable with an +active bound, the solution will remain optimal for a range of values +of its cost coefficient. HiGHS will return the values of +these __cost ranges__. For a variable or constraint whose +value is not at a bound, HiGHS will return the range of values that +the variable or constraint can take, the objective values at the +limits of the range, and the index of a variable or constraint with a +bound that will become in active at both limits. + +## [MIP](@id terminology-mip) + +When solving a MIP, some or all the variables must take discrete values. In HiGHS there are three types of discrete variables. + +- Integer: those that must take integer values between their bounds +- Semi-continuous: those that must be zero or take continuous values between their bounds +- Semi-integer: those that must be zero or take integer values between their bounds + +In the following discussion, for ease of reference to relative +objective values, it is assumed that the objective is being minimized + +Any point for which the discrete variables satisfy their requirements, +is said to be __integer feasible__. The objective value at such a +point is an upper bound on the optimal objective value. The least such +bound is known as the __primal bound__. The MIP solver generates a +sequence of LPs, each of which has bounds on the variables that are +tighter than those of the original model. When unsolved, there is a +bound on the optimal objective value for each such LP and, the +greatest such bound is known as the __dual bound__. The optimal +objective value of the MIP cannot be less than the dual bound. Hence +the gap between the primal and dual bounds is a measure of progress of +the MIP solver. Although the absolute gap is of some interest, the gap +relative to the primal bound is a better measure. When the gap reaches +zero then the MIP is solved to optimality. However, it is often +preferable to stop the MIP solver when the relative gap is below a +specified tolerance. diff --git a/examples/tests/CMakeLists.txt b/examples/CMakeLists.txt similarity index 56% rename from examples/tests/CMakeLists.txt rename to examples/CMakeLists.txt index 394b0b4b31..662d6e4b8d 100644 --- a/examples/tests/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,21 +2,21 @@ if(NOT BUILD_EXAMPLES) return() endif() -if(BUILD_CXX_EXAMPLES) +if(BUILD_CXX_EXAMPLE) file(GLOB CXX_SRCS "*.cpp") foreach(FILE_NAME IN LISTS CXX_SRCS) add_cxx_test(${FILE_NAME}) endforeach() endif() -if(BUILD_PYTHON_EXAMPLES) - file(GLOB PYTHON_SRCS "*.py") - foreach(FILE_NAME IN LISTS PYTHON_SRCS) - add_python_example(${FILE_NAME}) - endforeach() -endif() +# if(BUILD_PYTHON_EXAMPLE) +# file(GLOB PYTHON_SRCS "*.py") +# foreach(FILE_NAME IN LISTS PYTHON_SRCS) +# add_python_example(${FILE_NAME}) +# endforeach() +# endif() -if(BUILD_C_EXAMPLES) +if(BUILD_CXX_EXAMPLE) file(GLOB C "*.c") foreach(FILE_NAME IN LISTS C_SRCS) add_c_example(${FILE_NAME}) diff --git a/examples/Docs.py b/examples/Docs.py new file mode 100644 index 0000000000..d412e4c5e9 --- /dev/null +++ b/examples/Docs.py @@ -0,0 +1,104 @@ +import highspy +import numpy as np + +# minimize f = x0 + x1 +# subject to x1 <= 7 +# 5 <= x0 + 2x1 <= 15 +# 6 <= 3x0 + 2x1 +# 0 <= x0 <= 4; 1 <= x1 +# Highs h +h = highspy.Highs() +inf = highspy.kHighsInf + +# Load a model from MPS file model.mps +print("\nLoading the model from an MPS file") +filename = 'model.mps' +h.readModel(filename) +h.run() + +print("\nBuilding a model using single variables and constraints") +# Build a model with single variables and constraints +h.clear() +# Define two variables, first using identifiers for the bound values, +# and then using constants +lower = 0 +upper = 4 +h.addVar(lower, upper) +h.addVar(1, inf) +# Define the objective coefficients (costs) of the two variables, +# identifying the variable by index, and defining its cost +cost = 1 +h.changeColCost(0, cost) +h.changeColCost(1, 1) +# Define constraints for the model +# +# The first constraint (x_1<=7) has only one nonzero coefficient, +# identified by variable index 1 and value 1 +lower = -inf +upper = 7 +num_nz = 1 +index = 1 +value = 1 +h.addRow(lower, upper, num_nz, index, value) +# The second constraint (5 <= x_0 + 2x_1 <= 15) has two nonzero +# coefficients, so arrays of indices and values are required +num_nz = 2 +index = np.array([0, 1]) +value = np.array([1, 2], dtype=np.double) +h.addRow(5, 15, num_nz, index, value) +# The final constraint (6 <= 3x_0 + 2x_1) has the same indices but different values +num_nz = 2 +value = np.array([3, 2], dtype=np.double) +h.addRow(6, inf, num_nz, index, value) + +# Access LP +lp = h.getLp() +num_nz = h.getNumNz() +print('LP has ', lp.num_col_, ' columns', lp.num_row_, ' rows and ', num_nz, ' nonzeros') + +#h.writeModel("") + +# Build a model with multiple columns and rows +h.clear() +print("\nBuilding a model using multiple variables and constraints") +# The constraint matrix is defined with the rows below, but parameters +# for an empty (column-wise) matrix must be passed +cost = np.array([1, 1], dtype=np.double) +lower = np.array([0, 1], dtype=np.double) +upper = np.array([4, inf], dtype=np.double) +num_nz = 0 +start = 0 +index = 0 +value = 0 +h.addCols(2, cost, lower, upper, num_nz, start, index, value) +# Add the rows, with the constraint matrix row-wise +lower = np.array([-inf, 5, 6], dtype=np.double) +upper = np.array([7, 15, inf], dtype=np.double) +num_nz = 5 +start = np.array([0, 1, 3]) +index = np.array([1, 0, 1, 0, 1]) +value = np.array([1, 1, 2, 3, 2], dtype=np.double) +h.addRows(3, lower, upper, num_nz, start, index, value) + +h.writeModel("") +h.run() + +# Pass the following model from a HighsLp instance +h.clear() +print("Passing the model via HighsLp") +lp = highspy.HighsLp() +lp.num_col_ = 2; +lp.num_row_ = 3; +lp.col_cost_ = np.array([1, 1], dtype=np.double) +lp.col_lower_ = np.array([0, 1], dtype=np.double) +lp.col_upper_ = np.array([4, inf], dtype=np.double) +lp.row_lower_ = np.array([-inf, 5, 6], dtype=np.double) +lp.row_upper_ = np.array([7, 15, inf], dtype=np.double) +# In a HighsLp instsance, the number of nonzeros is given by a fictitious final start +lp.a_matrix_.start_ = np.array([0, 2, 5]) +lp.a_matrix_.index_ = np.array([1, 2, 0, 1, 2]) +lp.a_matrix_.value_ = np.array([1, 3, 1, 2, 2], dtype=np.double) +h.passModel(lp) +h.writeModel("") +h.run() + diff --git a/examples/build.BAZEL b/examples/build.BAZEL deleted file mode 100644 index c89f13ab2a..0000000000 --- a/examples/build.BAZEL +++ /dev/null @@ -1,9 +0,0 @@ -load("@rules_cc//cc:defs.bzl", "cc_binary") - -cc_binary( - name = "call_highs_example", - srcs= ["call_highs_from_cpp.cpp"], - deps = [ - "@highs", - ], -) diff --git a/examples/call_highs_from_c.c b/examples/call_highs_from_c.c index d22e5bd8de..0d053f3c52 100644 --- a/examples/call_highs_from_c.c +++ b/examples/call_highs_from_c.c @@ -301,6 +301,12 @@ void minimal_api_mps() { } void full_api() { + printf("\nHiGHS version %s\n", Highs_version()); + printf(" Major version %d\n", Highs_versionMajor()); + printf(" Minor version %d\n", Highs_versionMinor()); + printf(" Patch version %d\n", Highs_versionPatch()); + printf(" Githash %s\n", Highs_githash()); + printf(" compilation date %s\n", Highs_compilationDate()); // This example does exactly the same as the minimal example above, // but illustrates the full C API. It first forms and solves the LP // @@ -515,6 +521,14 @@ void full_api() { printf("\nRun status = %d; Model status = %d\n", run_status, model_status); + // Check what type of info values are + int info_type; + const char* info_string = "objective_function_value"; + run_status = Highs_getInfoType(highs, info_string, &info_type); + printf("Info %s is of type %d\n", info_string, info_type); + assert(run_status == kHighsStatusOk); + assert(info_type == kHighsInfoTypeDouble); + Highs_getDoubleInfoValue(highs, "objective_function_value", &objective_function_value); Highs_getIntInfoValue(highs, "simplex_iteration_count", &simplex_iteration_count); Highs_getIntInfoValue(highs, "primal_solution_status", &primal_solution_status); diff --git a/examples/call_highs_from_cpp.cpp b/examples/call_highs_from_cpp.cpp index e4dc6f7ec7..2cde66d80a 100644 --- a/examples/call_highs_from_cpp.cpp +++ b/examples/call_highs_from_cpp.cpp @@ -95,6 +95,11 @@ int main() { // Pass the model to HiGHS return_status = highs.passModel(model); assert(return_status==HighsStatus::kOk); + // If a user passes a model with entries in + // model.lp_.a_matrix_.value_ less than (the option) + // small_matrix_value in magnitude, they will be ignored. A logging + // message will indicate this, and passModel will return + // HighsStatus::kWarning // // Get a const reference to the LP data in HiGHS const HighsLp& lp = highs.getLp(); diff --git a/examples/call_highs_from_fortran.f90 b/examples/call_highs_from_fortran.f90 index 37924e5f6c..5e9085a419 100644 --- a/examples/call_highs_from_fortran.f90 +++ b/examples/call_highs_from_fortran.f90 @@ -33,7 +33,7 @@ program fortrantest ! * The position in aindex/avalue of the index/value of the first ! nonzero in each column is stored in astart ! - ! Note that astart[0] must be zero + ! Note that astart(1) must be zero ! ! After a successful call to Highs_lpCall, the primal and dual ! solution, and the simplex basis are returned as follows @@ -83,7 +83,7 @@ program fortrantest integer ( c_int ), parameter :: modelstatus_optimal = 7 integer ( c_int ), parameter :: runstatus_error = -1 integer ( c_int ), parameter :: runstatus_ok = 0 - integer ( c_int ), parameter :: runstatus_warning = -1 + integer ( c_int ), parameter :: runstatus_warning = 1 ! For the full API test type ( c_ptr ) :: highs @@ -170,6 +170,25 @@ program fortrantest rowupper(1) = 6.0 rowupper(2) = 14.0 rowupper(3) = inf + + ! The definition of sparse matrices to be passed into the FORTRAN + ! interface is non-trivial, since the FORTRAN interface is a direct + ! bind to the C API: no execution (esp. conversion of data) at any + ! point. Hence FORTRAN users have to supply vectors that, when + ! indexed from 0, are standard for the C API. + + ! Although the FORTRAN arrays in the example are indexed from 1 (of + ! course) note that the row indices in aindex are in the interval + ! [0, numrow-1] + + ! For this example, aindex is indexed from 1 to 5, and contains [1 2 + ! 0 1 2], referring to column 0 having entries in rows 1 and 2; + ! column 1 having entries in rows 0, 1 and 2 + + ! FORTRAN-wise, astart would naturally be [1 3], but it must + ! indicate the starts when aindex is indexed from 0 to 4 in C, so + ! the starts must have 1 subtracted from them from the values that + ! would be used in FORTRAN. Hence astart is [0 2] astart(1) = 0 astart(2) = 2 @@ -186,25 +205,6 @@ program fortrantest avalue(4) = 2 avalue(5) = 1 - ! Define the constraint matrix row-wise, as it is added to the LP with the rows - arstart(1) = 0 - arstart(2) = 1 - arstart(3) = 3 - arindex(1) = 1 - arindex(2) = 0 - arindex(3) = 1 - arindex(4) = 0 - arindex(5) = 1 - arvalue(1) = 1 - arvalue(2) = 1 - arvalue(3) = 2 - arvalue(4) = 2 - arvalue(5) = 1 - - qp_sol(1) = 0.5 - qp_sol(2) = 5.0 - qp_sol(3) = 1.5 - !================================================================================ ! Illustrate use of Highs_lpCall to solve a given LP print*, "*********" @@ -282,7 +282,23 @@ program fortrantest ! Add two columns to the empty LP, but no matrix. After numnz=0, can ! just pass arrays rather than NULL runstatus = Highs_addCols(highs, numcol, colcost, collower, colupper, 0, integer_null, integer_null, double_null); - ! Add three rows to the 2-column LP + ! Define the constraint matrix by adding it as three rows to the + ! 2-column LP - requiring the matrix row-wise + + arstart(1) = 0 + arstart(2) = 1 + arstart(3) = 3 + arindex(1) = 1 + arindex(2) = 0 + arindex(3) = 1 + arindex(4) = 0 + arindex(5) = 1 + arvalue(1) = 1 + arvalue(2) = 1 + arvalue(3) = 2 + arvalue(4) = 2 + arvalue(5) = 1 + runstatus = Highs_addRows(highs, numrow, rowlower, rowupper, numnz, arstart, arindex, arvalue) runstatus = Highs_getObjectiveSense(highs, alt_sense); @@ -362,8 +378,8 @@ program fortrantest ! Write out model as MPS for use later runstatus = Highs_writeModel(highs, "F90.mps"//C_NULL_CHAR) - print*, "runstatus = ", runstatus - call assert(runstatus .ne. runstatus_warning, "Highs_writeModel runstatus") + ! runstatus is runstatus_warning since there are no names + call assert(runstatus .eq. runstatus_warning, "Highs_writeModel runstatus") call Highs_destroy(highs) !================================================================================ @@ -494,6 +510,10 @@ program fortrantest qp_qvalue(3) = 0.2 qp_qvalue(4) = 2.0 + qp_sol(1) = 0.5 + qp_sol(2) = 5.0 + qp_sol(3) = 1.5 + runstatus = Highs_qpCall( qp_numcol, qp_numrow, qp_numnz, qp_hessian_numnz,& aformat_colwise, qformat_triangular, sense, offset,& qp_colcost, qp_collower, qp_colupper, qp_rowlower, qp_rowupper,& diff --git a/extern/filereaderlp/builder.hpp b/extern/filereaderlp/builder.hpp index c6a2698ca0..b0c5c541c8 100644 --- a/extern/filereaderlp/builder.hpp +++ b/extern/filereaderlp/builder.hpp @@ -1,26 +1,25 @@ #ifndef __READERLP_BUILDER_HPP__ #define __READERLP_BUILDER_HPP__ -#include #include #include +#include #include "model.hpp" -struct Builder { - std::unordered_map> variables; +struct Builder { + std::unordered_map> variables; - Model model; + Model model; - std::shared_ptr getvarbyname(const std::string& name) { - auto it = variables.find(name); - if (it != variables.end()) - return it->second; - auto newvar = std::shared_ptr(new Variable(name)); - variables.insert(std::make_pair(name, newvar)); - model.variables.push_back(newvar); - return newvar; - } + std::shared_ptr getvarbyname(const std::string& name) { + auto it = variables.find(name); + if (it != variables.end()) return it->second; + auto newvar = std::shared_ptr(new Variable(name)); + variables.insert(std::make_pair(name, newvar)); + model.variables.push_back(newvar); + return newvar; + } }; #endif diff --git a/extern/filereaderlp/def.hpp b/extern/filereaderlp/def.hpp index 51a73a9fdd..deec9fdd60 100644 --- a/extern/filereaderlp/def.hpp +++ b/extern/filereaderlp/def.hpp @@ -5,9 +5,9 @@ #include void inline lpassert(bool condition) { - if (!condition) { - throw std::invalid_argument("File not existent or illegal file format."); - } + if (!condition) { + throw std::invalid_argument("File not existent or illegal file format."); + } } const std::string LP_KEYWORD_INF[] = {"infinity", "inf"}; diff --git a/extern/filereaderlp/model.hpp b/extern/filereaderlp/model.hpp index da16cdda21..8b94bb2233 100644 --- a/extern/filereaderlp/model.hpp +++ b/extern/filereaderlp/model.hpp @@ -7,65 +7,62 @@ #include enum class VariableType { - CONTINUOUS, - BINARY, - GENERAL, - SEMICONTINUOUS, - SEMIINTEGER + CONTINUOUS, + BINARY, + GENERAL, + SEMICONTINUOUS, + SEMIINTEGER }; -enum class ObjectiveSense { - MIN, - MAX -}; +enum class ObjectiveSense { MIN, MAX }; struct Variable { - VariableType type = VariableType::CONTINUOUS; - double lowerbound = 0.0; - double upperbound = std::numeric_limits::infinity(); - std::string name; + VariableType type = VariableType::CONTINUOUS; + double lowerbound = 0.0; + double upperbound = std::numeric_limits::infinity(); + std::string name; - Variable(std::string n="") : name(n) {}; + Variable(std::string n = "") : name(n){}; }; struct LinTerm { - std::shared_ptr var; - double coef = 1.0; + std::shared_ptr var; + double coef = 1.0; }; struct QuadTerm { - std::shared_ptr var1; - std::shared_ptr var2; - double coef = 1.0; + std::shared_ptr var1; + std::shared_ptr var2; + double coef = 1.0; }; struct Expression { - std::vector> linterms; - std::vector> quadterms; - double offset = 0.0; - std::string name = ""; + std::vector> linterms; + std::vector> quadterms; + double offset = 0.0; + std::string name = ""; }; struct Constraint { - double lowerbound = -std::numeric_limits::infinity(); - double upperbound = std::numeric_limits::infinity(); - std::shared_ptr expr; + double lowerbound = -std::numeric_limits::infinity(); + double upperbound = std::numeric_limits::infinity(); + std::shared_ptr expr; - Constraint() : expr(std::shared_ptr(new Expression)) {}; + Constraint() : expr(std::shared_ptr(new Expression)){}; }; struct SOS { - std::string name = ""; - short type = 0; // 1 or 2 - std::vector, double>> entries; + std::string name = ""; + short type = 0; // 1 or 2 + std::vector, double>> entries; }; struct Model { - std::shared_ptr objective; - ObjectiveSense sense; - std::vector> constraints; - std::vector> variables; - std::vector> soss; + std::shared_ptr objective; + ObjectiveSense sense; + std::vector> constraints; + std::vector> variables; + std::vector> soss; }; #endif diff --git a/extern/filereaderlp/reader.cpp b/extern/filereaderlp/reader.cpp index 8615112054..abe56759f8 100644 --- a/extern/filereaderlp/reader.cpp +++ b/extern/filereaderlp/reader.cpp @@ -1,85 +1,98 @@ #include "reader.hpp" -#include "builder.hpp" - -#include +#include +#include #include -#include +#include +#include #include +#include #include #include -#include #include +#include #include -#include -#include - -#include "def.hpp" #include "HConfig.h" // for ZLIB_FOUND +#include "builder.hpp" +#include "def.hpp" #ifdef ZLIB_FOUND #include "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 + 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; - } + 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 + NONE, + SECID, + VARID, + CONID, + CONST, + FREE, + BRKOP, + BRKCL, + COMP, + LNEND, + SLASH, + ASTERISK, + HAT, + SOSTYPE }; enum class LpSectionKeyword { @@ -95,101 +108,97 @@ enum class LpSectionKeyword { 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 }, - { "semi-continuous", LpSectionKeyword::SEMI }, - { "semi", LpSectionKeyword::SEMI }, - { "semis", LpSectionKeyword::SEMI }, - { "sos", LpSectionKeyword::SOS }, - { "end", LpSectionKeyword::END } -}; - -enum class SosType { - SOS1, - SOS2 -}; +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); + 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()); + name = strdup(s.c_str()); #else - name = _strdup(s.c_str()); + name = _strdup(s.c_str()); #endif - }; + }; - ProcessedToken(double v) : type(ProcessedTokenType::CONST), value(v) {}; + ProcessedToken(double v) : type(ProcessedTokenType::CONST), value(v){}; - ProcessedToken(LpComparisonType comp) : type(ProcessedTokenType::COMP), dir(comp) {}; + ProcessedToken(LpComparisonType comp) + : type(ProcessedTokenType::COMP), dir(comp){}; - ~ProcessedToken() - { - if( type == ProcessedTokenType::CONID || type == ProcessedTokenType::VARID ) - free(name); - } + ~ProcessedToken() { + if (type == ProcessedTokenType::CONID || type == ProcessedTokenType::VARID) + free(name); + } }; // how many raw tokens to cache @@ -198,980 +207,1097 @@ struct ProcessedToken { const double kHighsInf = std::numeric_limits::infinity(); class Reader { -private: + private: #ifdef ZLIB_FOUND - zstr::ifstream file; + zstr::ifstream file; #else - std::ifstream file; + 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) { + 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 + try { file.open(filename); + } catch (const strict_fstream::Exception& e) { + } +#else + file.open(filename); #endif - lpassert(file.is_open()); - }; + lpassert(file.is_open()); + }; - ~Reader() { - file.close(); - } + ~Reader() { file.close(); } - Model read(); + Model read(); }; Model readinstance(std::string filename) { - Reader reader(filename); - return reader.read(); + 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 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; isecond; +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; + 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]) ) + // 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(); + processtokens(); - linebuffer.clear(); - linebuffer.shrink_to_fit(); + linebuffer.clear(); + linebuffer.shrink_to_fit(); - //std::clog << "Splitting tokens..." << std::endl; - splittokens(); + // std::clog << "Splitting tokens..." << std::endl; + splittokens(); - //std::clog << "Setting up model..." << std::endl; - processsections(); - processedtokens.clear(); - processedtokens.shrink_to_fit(); + // std::clog << "Setting up model..." << std::endl; + processsections(); + processedtokens.clear(); + processedtokens.shrink_to_fit(); - return builder.model; + return builder.model; } void Reader::processnonesec() { - lpassert(sectiontokens.count(LpSectionKeyword::NONE) == 0); + 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; +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; + } - 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; - } + // var + if (it->type == ProcessedTokenType::VARID) { + std::string name = it->name; - // 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; - } + 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); - // 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; + ++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; - } + 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 - } + 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; - } + 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; - } + 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; - 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; + 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; - // 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(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); } - - lpassert(false); - } + begin = next3; + continue; + } + + lpassert(false); + } } void Reader::processbinsec() { - if(!sectiontokens.count(LpSectionKeyword::BIN)) - return; - std::vector::iterator& begin(sectiontokens[LpSectionKeyword::BIN].first); - std::vector::iterator& end(sectiontokens[LpSectionKeyword::BIN].second); - for (; begin != end; ++begin) { - 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; - } + 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() { - if(!sectiontokens.count(LpSectionKeyword::GEN)) - return; - std::vector::iterator& begin(sectiontokens[LpSectionKeyword::GEN].first); - std::vector::iterator& end(sectiontokens[LpSectionKeyword::GEN].second); - for (; begin != end; ++begin) { - 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; - } - } + 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() { - if(!sectiontokens.count(LpSectionKeyword::SEMI)) - return; - std::vector::iterator& begin(sectiontokens[LpSectionKeyword::SEMI].first); - std::vector::iterator& end(sectiontokens[LpSectionKeyword::SEMI].second); - for (; begin != end; ++begin) { - 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; - } - } + 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() { - if(!sectiontokens.count(LpSectionKeyword::SOS)) - return; - std::vector::iterator& begin(sectiontokens[LpSectionKeyword::SOS].first); - std::vector::iterator& end(sectiontokens[LpSectionKeyword::SOS].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; + 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; } - builder.model.soss.push_back(sos); - } + break; + } + + builder.model.soss.push_back(sos); + } } void Reader::processendsec() { - lpassert(sectiontokens.count(LpSectionKeyword::END) == 0); + lpassert(sectiontokens.count(LpSectionKeyword::END) == 0); } void Reader::processsections() { - processnonesec(); - processobjsec(); - processconsec(); - processboundssec(); - processgensec(); - processbinsec(); - processsemisec(); - processsossec(); - processendsec(); + processnonesec(); + processobjsec(); + processconsec(); + processboundssec(); + processgensec(); + processbinsec(); + processsemisec(); + processsossec(); + processendsec(); } void Reader::splittokens() { - LpSectionKeyword currentsection = LpSectionKeyword::NONE; - - for (std::vector::iterator it(processedtokens.begin()); it != processedtokens.end(); ++it) - if (it->type == ProcessedTokenType::SECID) { - if(currentsection != LpSectionKeyword::NONE) - sectiontokens[currentsection].second = it; // mark end of previous section - currentsection = it->keyword; - - // make sure this section did not yet occur - lpassert(sectiontokens.count(currentsection) == 0); - - std::vector::iterator next = it; - ++next; - // skip empty section - if( next == processedtokens.end() || next->type == ProcessedTokenType::SECID ) { - currentsection = LpSectionKeyword::NONE; - continue; - } - // remember begin of new section: its the token following the current one - sectiontokens[currentsection].first = next; + 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; } - - if(currentsection != LpSectionKeyword::NONE) - sectiontokens[currentsection].second = processedtokens.end(); // mark end of last section + } + 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 occured 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; + 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; } - - 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; } - - // 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; - } + } + + // 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; + } + + // 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(); } - // constraint identifier? - if (rawtokens[0].istype(RawTokenType::STR) && rawtokens[1].istype(RawTokenType::COLON)) { - processedtokens.emplace_back(ProcessedTokenType::CONID, rawtokens[0].svalue); - nextrawtoken(2); - continue; + // +/- Constant + if (rawtokens[0].istype(RawTokenType::CONS)) { + processedtokens.emplace_back(sign * rawtokens[0].dvalue); + nextrawtoken(); + 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; + // + [, + + [, - - [ + if (rawtokens[0].istype(RawTokenType::BRKOP) && sign == 1.0) { + processedtokens.emplace_back(ProcessedTokenType::BRKOP); + 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; - } + // - [, + - [, - + [ + if (rawtokens[0].istype(RawTokenType::BRKOP)) lpassert(false); - // assume var identifier + // +/- variable name 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) - 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; + processedtokens.emplace_back(sign); + continue; } - // ] - if (rawtokens[0].istype(RawTokenType::BRKCL)) { - processedtokens.emplace_back(ProcessedTokenType::BRKCL); - nextrawtoken(); - 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); + } - // / - if (rawtokens[0].istype(RawTokenType::SLASH)) { - processedtokens.emplace_back(ProcessedTokenType::SLASH); - nextrawtoken(); - continue; - } + // 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; + } +} - // * - if (rawtokens[0].istype(RawTokenType::ASTERISK)) { - processedtokens.emplace_back(ProcessedTokenType::ASTERISK); - nextrawtoken(); - continue; - } +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])) + ; + } + } +} - // ^ - if (rawtokens[0].istype(RawTokenType::HAT)) { - processedtokens.emplace_back(ProcessedTokenType::HAT); - nextrawtoken(); - continue; - } +// 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; - // <= - if (rawtokens[0].istype(RawTokenType::LESS) && rawtokens[1].istype(RawTokenType::EQUAL)) { - processedtokens.emplace_back(LpComparisonType::LEQ); - nextrawtoken(2); - continue; - } + // check for bracket closing + case ']': + t = RawTokenType::BRKCL; + this->linebufferpos++; + return true; - // < - if (rawtokens[0].istype(RawTokenType::LESS)) { - processedtokens.emplace_back(LpComparisonType::L); - nextrawtoken(); - continue; - } + // check for less sign + case '<': + t = RawTokenType::LESS; + this->linebufferpos++; + return true; - // >= - if (rawtokens[0].istype(RawTokenType::GREATER) && rawtokens[1].istype(RawTokenType::EQUAL)) { - processedtokens.emplace_back(LpComparisonType::GEQ); - nextrawtoken(2); - continue; - } + // check for greater sign + case '>': + t = RawTokenType::GREATER; + this->linebufferpos++; + return true; - // > - if (rawtokens[0].istype(RawTokenType::GREATER)) { - processedtokens.emplace_back(LpComparisonType::G); - nextrawtoken(); - continue; - } + // check for equal sign + case '=': + t = RawTokenType::EQUAL; + this->linebufferpos++; + return true; - // = - if (rawtokens[0].istype(RawTokenType::EQUAL)) { - processedtokens.emplace_back(LpComparisonType::EQ); - nextrawtoken(); - continue; - } + // check for colon + case ':': + t = RawTokenType::COLON; + this->linebufferpos++; + return true; - // FILEEND should have been handled in condition of while() - assert(!rawtokens[0].istype(RawTokenType::FLEND)); + // check for plus + case '+': + t = RawTokenType::PLUS; + this->linebufferpos++; + return true; - // catch all unknown symbols - lpassert(false); - break; - } -} + // check for hat + case '^': + t = RawTokenType::HAT; + this->linebufferpos++; + return true; -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]) ) - ; - } - } -} + // check for slash + case '/': + t = RawTokenType::SLASH; + this->linebufferpos++; + return true; -// 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 - assert(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; + // check for asterisk + case '*': + t = RawTokenType::ASTERISK; + this->linebufferpos++; 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; + + // check for minus + case '-': + t = RawTokenType::MINUS; + this->linebufferpos++; return true; - } - - lpassert(false); - return false; + + // 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 + assert(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/highs-config.cmake.in b/highs-config.cmake.in index b15d3529ef..5eadc3a856 100644 --- a/highs-config.cmake.in +++ b/highs-config.cmake.in @@ -1,9 +1,23 @@ -if(NOT TARGET libhighs) - include("${CMAKE_CURRENT_LIST_DIR}/highs-targets.cmake") -endif() +@PACKAGE_INIT@ + +set(HIGHS_DIR "@HIGHS_INSTALL_DIR@") + +if (FAST_BUILD) + if(NOT TARGET highs) + include("${CMAKE_CURRENT_LIST_DIR}/highs-targets.cmake") + endif() + + set(HIGHS_LIBRARIES highs) +else() + if(NOT TARGET libhighs) + include("${CMAKE_CURRENT_LIST_DIR}/highs-targets.cmake") + endif() + + set(HIGHS_LIBRARIES libhighs) +endif() -set(HIGHS_LIBRARIES libhighs) set(HIGHS_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") + set(HIGHS_FOUND TRUE) @CONF_DEPENDENCIES@ diff --git a/src/interfaces/highspy/highspy/__init__.py b/highspy/__init__.py similarity index 63% rename from src/interfaces/highspy/highspy/__init__.py rename to highspy/__init__.py index fd64079c1c..b04f4ba346 100644 --- a/src/interfaces/highspy/highspy/__init__.py +++ b/highspy/__init__.py @@ -1,25 +1,33 @@ from .highs import ( + # enum classes ObjSense, MatrixFormat, HessianFormat, SolutionStatus, BasisValidity, HighsModelStatus, + HighsPresolveStatus, HighsBasisStatus, HighsVarType, + HighsOptionType, + HighsInfoType, HighsStatus, HighsLogType, + # classes HighsSparseMatrix, HighsLp, HighsHessian, HighsModel, - HighsSolution, - HighsBasis, HighsInfo, HighsOptions, Highs, + # structs + HighsSolution, + HighsObjectiveSolution, + HighsBasis, + HighsRangingRecord, + HighsRanging, + # constants kHighsInf, - HIGHS_VERSION_MAJOR, - HIGHS_VERSION_MINOR, - HIGHS_VERSION_PATCH, + kHighsIInf, ) diff --git a/src/interfaces/highspy/highspy/highs.py b/highspy/highs.py similarity index 55% rename from src/interfaces/highspy/highspy/highs.py rename to highspy/highs.py index 52e2a21b0d..62cebeea88 100644 --- a/src/interfaces/highspy/highspy/highs.py +++ b/highspy/highs.py @@ -1,37 +1,38 @@ from .highs_bindings import ( + # enum classes ObjSense, MatrixFormat, HessianFormat, SolutionStatus, BasisValidity, HighsModelStatus, + HighsPresolveStatus, HighsBasisStatus, HighsVarType, + HighsOptionType, + HighsInfoType, HighsStatus, HighsLogType, - CallbackTuple, + # classes HighsSparseMatrix, HighsLp, HighsHessian, HighsModel, - HighsSolution, - HighsBasis, HighsInfo, HighsOptions, _Highs, + # structs + HighsSolution, + HighsObjectiveSolution, + HighsBasis, + HighsRangingRecord, + HighsRanging, + # constants kHighsInf, - HIGHS_VERSION_MAJOR, - HIGHS_VERSION_MINOR, - HIGHS_VERSION_PATCH, + kHighsIInf, ) class Highs(_Highs): def __init__(self): super().__init__() - self._log_callback_tuple = CallbackTuple() - - def setLogCallback(self, func, callback_data): - self._log_callback_tuple.callback = func - self._log_callback_tuple.callback_data = callback_data - super().setLogCallback(self._log_callback_tuple) diff --git a/src/interfaces/highspy/highspy/highs_bindings.cpp b/highspy/highs_bindings.cpp similarity index 54% rename from src/interfaces/highspy/highspy/highs_bindings.cpp rename to highspy/highs_bindings.cpp index 0266441603..ab80446edc 100644 --- a/src/interfaces/highspy/highspy/highs_bindings.cpp +++ b/highspy/highs_bindings.cpp @@ -1,21 +1,21 @@ #include "Highs.h" + #include #include #include +#include namespace py = pybind11; using namespace pybind11::literals; -void highs_passModel(Highs* h, HighsModel& model) +HighsStatus highs_passModel(Highs* h, HighsModel& model) { - HighsStatus status = h->passModel(model); - if (status != HighsStatus::kOk) - throw py::value_error("Error when passing model"); + return h->passModel(model); } -void highs_passModelPointers(Highs* h, +HighsStatus highs_passModelPointers(Highs* h, const int num_col, const int num_row, const int num_nz, const int q_num_nz, const int a_format, const int q_format, const int sense, const double offset, @@ -58,25 +58,21 @@ void highs_passModelPointers(Highs* h, const double* q_value_ptr = static_cast(q_value_info.ptr); const int* integrality_ptr = static_cast(integrality_info.ptr); - HighsStatus status = h->passModel(num_col, num_row, num_nz, - q_num_nz, a_format, q_format, - sense, offset, col_cost_ptr, - col_lower_ptr, col_upper_ptr, row_lower_ptr, - row_upper_ptr, a_start_ptr, a_index_ptr, - a_value_ptr, q_start_ptr, q_index_ptr, - q_value_ptr, integrality_ptr); - if (status != HighsStatus::kOk) - throw py::value_error("Error when passing model"); + return h->passModel(num_col, num_row, num_nz, + q_num_nz, a_format, q_format, + sense, offset, col_cost_ptr, + col_lower_ptr, col_upper_ptr, row_lower_ptr, + row_upper_ptr, a_start_ptr, a_index_ptr, + a_value_ptr, q_start_ptr, q_index_ptr, + q_value_ptr, integrality_ptr); } -void highs_passLp(Highs* h, HighsLp& lp) +HighsStatus highs_passLp(Highs* h, HighsLp& lp) { - HighsStatus status = h->passModel(lp); - if (status != HighsStatus::kOk) - throw py::value_error("Error when passing LP"); + return h->passModel(lp); } -void highs_passLpPointers(Highs* h, +HighsStatus highs_passLpPointers(Highs* h, const int num_col, const int num_row, const int num_nz, const int a_format, const int sense, const double offset, const py::array_t col_cost, @@ -109,28 +105,24 @@ void highs_passLpPointers(Highs* h, const double* a_value_ptr = static_cast(a_value_info.ptr); const int* integrality_ptr = static_cast(integrality_info.ptr); - HighsStatus status = h->passModel(num_col, num_row, num_nz, - a_format, sense, offset, - col_cost_ptr, col_lower_ptr, col_upper_ptr, - row_lower_ptr, row_upper_ptr, - a_start_ptr, a_index_ptr, a_value_ptr, - integrality_ptr); - if (status != HighsStatus::kOk) - throw py::value_error("Error when passing model"); + return h->passModel(num_col, num_row, num_nz, + a_format, sense, offset, + col_cost_ptr, col_lower_ptr, col_upper_ptr, + row_lower_ptr, row_upper_ptr, + a_start_ptr, a_index_ptr, a_value_ptr, + integrality_ptr); } -void highs_passHessian(Highs* h, HighsHessian& hessian) +HighsStatus highs_passHessian(Highs* h, HighsHessian& hessian) { - HighsStatus status = h->passHessian(hessian); - if (status != HighsStatus::kOk) - throw py::value_error("Error when passing Hessian"); + return h->passHessian(hessian); } -void highs_passHessianPointers(Highs* h, - const int dim, const int num_nz, const int format, - const py::array_t q_start, - const py::array_t q_index, - const py::array_t q_value) +HighsStatus highs_passHessianPointers(Highs* h, + const int dim, const int num_nz, const int format, + const py::array_t q_start, + const py::array_t q_index, + const py::array_t q_value) { py::buffer_info q_start_info = q_start.request(); py::buffer_info q_index_info = q_index.request(); @@ -140,39 +132,32 @@ void highs_passHessianPointers(Highs* h, const int* q_index_ptr = static_cast(q_index_info.ptr); const double* q_value_ptr = static_cast(q_value_info.ptr); - HighsStatus status = h->passHessian(dim, num_nz, format, - q_start_ptr, q_index_ptr, q_value_ptr); - if (status != HighsStatus::kOk) - throw py::value_error("Error when passing Hessian"); + return h->passHessian(dim, num_nz, format, + q_start_ptr, q_index_ptr, q_value_ptr); } -void highs_writeSolution(Highs* h, const std::string filename, const int style) +HighsStatus highs_writeSolution(Highs* h, const std::string filename, const int style) { - HighsStatus status = h->writeSolution(filename, style); - if (status != HighsStatus::kOk) - throw py::value_error("Error when writing solution"); + return h->writeSolution(filename, style); } + +// Not needed once getModelStatus(const bool scaled_model) disappears +// from, Highs.h HighsModelStatus highs_getModelStatus(Highs* h) { return h->getModelStatus(); } -bool highs_getDualRay(Highs* h, py::array_t values) +std::tuple highs_getRanging(Highs* h) { - bool has_dual_ray; - py::buffer_info values_info = values.request(); - double* values_ptr = static_cast(values_info.ptr); - - HighsStatus status = h->getDualRay(has_dual_ray, values_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when calling get dual ray"); - - return has_dual_ray; + HighsRanging ranging; + HighsStatus status = h->getRanging(ranging); + return std::make_tuple(status, ranging); } -void highs_addRow(Highs* h, double lower, double upper, int num_new_nz, py::array_t indices, py::array_t values) +HighsStatus highs_addRow(Highs* h, double lower, double upper, + int num_new_nz, py::array_t indices, py::array_t values) { py::buffer_info indices_info = indices.request(); py::buffer_info values_info = values.request(); @@ -180,15 +165,13 @@ void highs_addRow(Highs* h, double lower, double upper, int num_new_nz, py::arra int* indices_ptr = static_cast(indices_info.ptr); double* values_ptr = static_cast(values_info.ptr); - HighsStatus status = h->addRow(lower, upper, num_new_nz, indices_ptr, values_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when adding row"); + return h->addRow(lower, upper, num_new_nz, indices_ptr, values_ptr); } -void highs_addRows(Highs* h, int num_cons, py::array_t lower, py::array_t upper, int num_new_nz, - py::array_t starts, py::array_t indices, py::array_t values) +HighsStatus highs_addRows(Highs* h, int num_row, + py::array_t lower, py::array_t upper, + int num_new_nz, py::array_t starts, py::array_t indices, py::array_t values) { py::buffer_info lower_info = lower.request(); py::buffer_info upper_info = upper.request(); @@ -202,14 +185,12 @@ void highs_addRows(Highs* h, int num_cons, py::array_t lower, py::array_ int* indices_ptr = static_cast(indices_info.ptr); double* values_ptr = static_cast(values_info.ptr); - HighsStatus status = h->addRows(num_cons, lower_ptr, upper_ptr, num_new_nz, starts_ptr, indices_ptr, values_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when adding rows"); + return h->addRows(num_row, lower_ptr, upper_ptr, num_new_nz, starts_ptr, indices_ptr, values_ptr); } -void highs_addCol(Highs* h, double cost, double lower, double upper, int num_new_nz, py::array_t indices, py::array_t values) +HighsStatus highs_addCol(Highs* h, double cost, double lower, double upper, + int num_new_nz, py::array_t indices, py::array_t values) { py::buffer_info indices_info = indices.request(); py::buffer_info values_info = values.request(); @@ -217,23 +198,39 @@ void highs_addCol(Highs* h, double cost, double lower, double upper, int num_new int* indices_ptr = static_cast(indices_info.ptr); double* values_ptr = static_cast(values_info.ptr); - HighsStatus status = h->addCol(cost, lower, upper, num_new_nz, indices_ptr, values_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when adding col"); + return h->addCol(cost, lower, upper, num_new_nz, indices_ptr, values_ptr); } -void highs_addVar(Highs* h, double lower, double upper) +HighsStatus highs_addCols(Highs* h, int num_col, + py::array_t cost, py::array_t lower, py::array_t upper, + int num_new_nz, py::array_t starts, py::array_t indices, py::array_t values) { - HighsStatus status = h->addVar(lower, upper); + py::buffer_info cost_info = cost.request(); + py::buffer_info lower_info = lower.request(); + py::buffer_info upper_info = upper.request(); + py::buffer_info starts_info = starts.request(); + py::buffer_info indices_info = indices.request(); + py::buffer_info values_info = values.request(); - if (status != HighsStatus::kOk) - throw py::value_error("Error when adding var"); + double* cost_ptr = static_cast(cost_info.ptr); + double* lower_ptr = static_cast(lower_info.ptr); + double* upper_ptr = static_cast(upper_info.ptr); + int* starts_ptr = static_cast(starts_info.ptr); + int* indices_ptr = static_cast(indices_info.ptr); + double* values_ptr = static_cast(values_info.ptr); + + return h->addCols(num_col, cost_ptr, lower_ptr, upper_ptr, num_new_nz, starts_ptr, indices_ptr, values_ptr); } -void highs_addVars(Highs* h, int num_vars, py::array_t lower, py::array_t upper) +HighsStatus highs_addVar(Highs* h, double lower, double upper) +{ + return h->addVar(lower, upper); +} + + +HighsStatus highs_addVars(Highs* h, int num_vars, py::array_t lower, py::array_t upper) { py::buffer_info lower_info = lower.request(); py::buffer_info upper_info = upper.request(); @@ -241,14 +238,11 @@ void highs_addVars(Highs* h, int num_vars, py::array_t lower, py::array_ double* lower_ptr = static_cast(lower_info.ptr); double* upper_ptr = static_cast(upper_info.ptr); - HighsStatus status = h->addVars(num_vars, lower_ptr, upper_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when adding vars"); + return h->addVars(num_vars, lower_ptr, upper_ptr); } -void highs_changeColsCost(Highs* h, int num_set_entries, py::array_t indices, py::array_t cost) +HighsStatus highs_changeColsCost(Highs* h, int num_set_entries, py::array_t indices, py::array_t cost) { py::buffer_info indices_info = indices.request(); py::buffer_info cost_info = cost.request(); @@ -256,14 +250,11 @@ void highs_changeColsCost(Highs* h, int num_set_entries, py::array_t indice int* indices_ptr = static_cast(indices_info.ptr); double* cost_ptr = static_cast(cost_info.ptr); - HighsStatus status = h->changeColsCost(num_set_entries, indices_ptr, cost_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when changing objective coefficients"); + return h->changeColsCost(num_set_entries, indices_ptr, cost_ptr); } -void highs_changeColsBounds(Highs* h, int num_set_entries, py::array_t indices, py::array_t lower, py::array_t upper) +HighsStatus highs_changeColsBounds(Highs* h, int num_set_entries, py::array_t indices, py::array_t lower, py::array_t upper) { py::buffer_info indices_info = indices.request(); py::buffer_info lower_info = lower.request(); @@ -273,14 +264,11 @@ void highs_changeColsBounds(Highs* h, int num_set_entries, py::array_t indi double* lower_ptr = static_cast(lower_info.ptr); double* upper_ptr = static_cast(upper_info.ptr); - HighsStatus status = h->changeColsBounds(num_set_entries, indices_ptr, lower_ptr, upper_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when changing variable bounds"); + return h->changeColsBounds(num_set_entries, indices_ptr, lower_ptr, upper_ptr); } -void highs_changeColsIntegrality(Highs* h, int num_set_entries, py::array_t indices, py::array_t integrality) +HighsStatus highs_changeColsIntegrality(Highs* h, int num_set_entries, py::array_t indices, py::array_t integrality) { py::buffer_info indices_info = indices.request(); py::buffer_info integrality_info = integrality.request(); @@ -288,160 +276,276 @@ void highs_changeColsIntegrality(Highs* h, int num_set_entries, py::array_t int* indices_ptr = static_cast(indices_info.ptr); HighsVarType* integrality_ptr = static_cast(integrality_info.ptr); - HighsStatus status = h->changeColsIntegrality(num_set_entries, indices_ptr, integrality_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when changing variable integrality"); + return h->changeColsIntegrality(num_set_entries, indices_ptr, integrality_ptr); } -void highs_deleteVars(Highs* h, int num_set_entries, py::array_t indices) +HighsStatus highs_deleteCols(Highs* h, int num_set_entries, py::array_t indices) { py::buffer_info indices_info = indices.request(); int* indices_ptr = static_cast(indices_info.ptr); - HighsStatus status = h->deleteVars(num_set_entries, indices_ptr); + return h->deleteCols(num_set_entries, indices_ptr); +} - if (status != HighsStatus::kOk) - throw py::value_error("Error when deleting columns"); + +HighsStatus highs_deleteVars(Highs* h, int num_set_entries, py::array_t indices) +{ + return highs_deleteCols(h, num_set_entries, indices); } -void highs_deleteRows(Highs* h, int num_set_entries, py::array_t indices) +HighsStatus highs_deleteRows(Highs* h, int num_set_entries, py::array_t indices) { py::buffer_info indices_info = indices.request(); int* indices_ptr = static_cast(indices_info.ptr); - HighsStatus status = h->deleteRows(num_set_entries, indices_ptr); - - if (status != HighsStatus::kOk) - throw py::value_error("Error when deleting rows"); + return h->deleteRows(num_set_entries, indices_ptr); } -bool highs_getBoolOption(Highs* h, const std::string& option) +std::tuple highs_getOptionValue(Highs* h, const std::string& option) { - bool res; - HighsStatus status = h->getOptionValue(option, res); + HighsOptionType option_type; + HighsStatus status = h->getOptionType(option, option_type); if (status != HighsStatus::kOk) - throw py::value_error("Error while getting option " + option); - - return res; + return std::make_tuple(status, py::cast(0)); + + if (option_type == HighsOptionType::kBool) { + bool value; + status = h->getOptionValue(option, value); + return std::make_tuple(status, py::cast(value)); + } else if (option_type == HighsOptionType::kInt) { + HighsInt value; + status = h->getOptionValue(option, value); + return std::make_tuple(status, py::cast(value)); + } else if (option_type == HighsOptionType::kDouble) { + double value; + status = h->getOptionValue(option, value); + return std::make_tuple(status, py::cast(value)); + } else if (option_type == HighsOptionType::kString) { + std::string value; + status = h->getOptionValue(option, value); + return std::make_tuple(status, py::cast(value)); + } else + return std::make_tuple(HighsStatus::kError, py::cast(0)); } -int highs_getIntOption(Highs* h, const std::string& option) -{ - int res; - HighsStatus status = h->getOptionValue(option, res); - - if (status != HighsStatus::kOk) - throw py::value_error("Error while getting option " + option); - - return res; +std::tuple highs_getOptionType(Highs* h, const std::string& option) { + HighsOptionType option_type; + HighsStatus status = h->getOptionType(option, option_type); + return std::make_tuple(status, option_type); } +HighsStatus highs_writeOptions(Highs* h, const std::string& filename) { + return h->writeOptions(filename); +} -double highs_getDoubleOption(Highs* h, const std::string& option) +std::tuple highs_getInfoValue(Highs* h, const std::string& info) { - double res; - HighsStatus status = h->getOptionValue(option, res); + HighsInfoType info_type; + HighsStatus status = h->getInfoType(info, info_type); if (status != HighsStatus::kOk) - throw py::value_error("Error while getting option " + option); - - return res; + return std::make_tuple(status, py::cast(0)); + + if (info_type == HighsInfoType::kInt64) { + int64_t value; + status = h->getInfoValue(info, value); + return std::make_tuple(status, py::cast(value)); + } else if (info_type == HighsInfoType::kInt) { + HighsInt value; + status = h->getInfoValue(info, value); + return std::make_tuple(status, py::cast(value)); + } else if (info_type == HighsInfoType::kDouble) { + double value; + status = h->getInfoValue(info, value); + return std::make_tuple(status, py::cast(value)); + } else + return std::make_tuple(HighsStatus::kError, py::cast(0)); } +std::tuple highs_getInfoType(Highs* h, const std::string& info) { + HighsInfoType info_type; + HighsStatus status = h->getInfoType(info, info_type); + return std::make_tuple(status, info_type); +} -std::string highs_getStringOption(Highs* h, const std::string& option) +std::tuple highs_getObjectiveSense(Highs* h) { - std::string res; - HighsStatus status = h->getOptionValue(option, res); - - if (status != HighsStatus::kOk) - throw py::value_error("Error while getting option " + option); - - return res; + ObjSense obj_sense; + HighsStatus status = h->getObjectiveSense(obj_sense); + return std::make_tuple(status, obj_sense); } -py::object highs_getOptionValue(Highs* h, const std::string& option) +std::tuple highs_getObjectiveOffset(Highs* h) { - HighsOptionType option_type; - HighsStatus status = h->getOptionType(option, option_type); - - if (status != HighsStatus::kOk) - throw py::value_error("Error while getting option " + option); - - if (option_type == HighsOptionType::kBool) - return py::cast(highs_getBoolOption(h, option)); - else if (option_type == HighsOptionType::kInt) - return py::cast(highs_getIntOption(h, option)); - else if (option_type == HighsOptionType::kDouble) - return py::cast(highs_getDoubleOption(h, option)); - else if (option_type == HighsOptionType::kString) - return py::cast(highs_getStringOption(h, option)); - else - throw py::value_error("Unrecognized option type"); + double obj_offset; + HighsStatus status = h->getObjectiveOffset(obj_offset); + return std::make_tuple(status, obj_offset); } - -ObjSense highs_getObjectiveSense(Highs* h) +std::tuple highs_getCol(Highs* h, int col) { - ObjSense obj_sense; - HighsStatus status = h->getObjectiveSense(obj_sense); - - if (status != HighsStatus::kOk) - throw py::value_error("Error while getting objective sense"); + double cost, lower, upper; + HighsInt get_num_col; + HighsInt get_num_nz; + HighsStatus status = h->getCols(1, &col, get_num_col, &cost, &lower, &upper, get_num_nz, nullptr, nullptr, nullptr); + return std::make_tuple(status, cost, lower, upper, get_num_nz); +} - return obj_sense; +std::tuple, py::array_t> highs_getColEntries(Highs* h, int col) +{ + double cost, lower, upper; + HighsInt get_num_col; + HighsInt get_num_nz; + h->getCols(1, &col, get_num_col, nullptr, nullptr, nullptr, get_num_nz, nullptr, nullptr, nullptr); + get_num_nz = get_num_nz > 0 ? get_num_nz : 1; + HighsInt start; + std::vector index(get_num_nz); + std::vector value(get_num_nz); + HighsInt* index_ptr = static_cast(index.data()); + double* value_ptr = static_cast(value.data()); + HighsStatus status = h->getCols(1, &col, get_num_col, nullptr, nullptr, nullptr, get_num_nz, &start, index_ptr, value_ptr); + return std::make_tuple(status, py::cast(index), py::cast(value)); } +std::tuple highs_getRow(Highs* h, int row) +{ + double cost, lower, upper; + HighsInt get_num_row; + HighsInt get_num_nz; + HighsStatus status = h->getRows(1, &row, get_num_row, &lower, &upper, get_num_nz, nullptr, nullptr, nullptr); + return std::make_tuple(status, lower, upper, get_num_nz); +} -double highs_getObjectiveOffset(Highs* h) +std::tuple, py::array_t> highs_getRowEntries(Highs* h, int row) { - double obj_offset; - HighsStatus status = h->getObjectiveOffset(obj_offset); + double cost, lower, upper; + HighsInt get_num_row; + HighsInt get_num_nz; + h->getRows(1, &row, get_num_row, nullptr, nullptr, get_num_nz, nullptr, nullptr, nullptr); + get_num_nz = get_num_nz > 0 ? get_num_nz : 1; + HighsInt start; + std::vector index(get_num_nz); + std::vector value(get_num_nz); + HighsInt* index_ptr = static_cast(index.data()); + double* value_ptr = static_cast(value.data()); + HighsStatus status = h->getRows(1, &row, get_num_row, nullptr, nullptr, get_num_nz, &start, index_ptr, value_ptr); + return std::make_tuple(status, py::cast(index), py::cast(value)); +} - if (status != HighsStatus::kOk) - throw py::value_error("Error while getting objective offset"); +std::tuple, py::array_t, py::array_t, HighsInt> highs_getCols(Highs* h, int num_set_entries, py::array_t indices) +{ + py::buffer_info indices_info = indices.request(); + HighsInt* indices_ptr = static_cast(indices_info.ptr); + // Make sure that the vectors are not empty + const HighsInt dim = num_set_entries > 0 ? num_set_entries : 1; + std::vector cost(dim); + std::vector lower(dim); + std::vector upper(dim); + double* cost_ptr = static_cast(cost.data()); + double* lower_ptr = static_cast(lower.data()); + double* upper_ptr = static_cast(upper.data()); + HighsInt get_num_col; + HighsInt get_num_nz; + HighsStatus status = h->getCols(num_set_entries, indices_ptr, get_num_col, cost_ptr, lower_ptr, upper_ptr, get_num_nz, nullptr, nullptr, nullptr); + return std::make_tuple(status, get_num_col, py::cast(cost), py::cast(lower), py::cast(upper), get_num_nz); +} - return obj_offset; +std::tuple, py::array_t, py::array_t> highs_getColsEntries(Highs* h, int num_set_entries, py::array_t indices) +{ + py::buffer_info indices_info = indices.request(); + HighsInt* indices_ptr = static_cast(indices_info.ptr); + // Make sure that the vectors are not empty + const HighsInt dim = num_set_entries > 0 ? num_set_entries : 1; + HighsInt get_num_col; + HighsInt get_num_nz; + h->getCols(num_set_entries, indices_ptr, get_num_col, nullptr, nullptr, nullptr, get_num_nz, nullptr, nullptr, nullptr); + get_num_nz = get_num_nz > 0 ? get_num_nz : 1; + std::vector start(dim); + std::vector index(get_num_nz); + std::vector value(get_num_nz); + HighsInt* start_ptr = static_cast(start.data()); + HighsInt* index_ptr = static_cast(index.data()); + double* value_ptr = static_cast(value.data()); + HighsStatus status = h->getCols(num_set_entries, indices_ptr, get_num_col, nullptr, nullptr, nullptr, get_num_nz, start_ptr, index_ptr, value_ptr); + return std::make_tuple(status, py::cast(start), py::cast(index), py::cast(value)); } +std::tuple, py::array_t, HighsInt> highs_getRows(Highs* h, int num_set_entries, py::array_t indices) +{ + py::buffer_info indices_info = indices.request(); + HighsInt* indices_ptr = static_cast(indices_info.ptr); + // Make sure that the vectors are not empty + const HighsInt dim = num_set_entries > 0 ? num_set_entries : 1; + std::vector lower(dim); + std::vector upper(dim); + double* lower_ptr = static_cast(lower.data()); + double* upper_ptr = static_cast(upper.data()); + HighsInt get_num_row; + HighsInt get_num_nz; + HighsStatus status = h->getRows(num_set_entries, indices_ptr, get_num_row, lower_ptr, upper_ptr, get_num_nz, nullptr, nullptr, nullptr); + return std::make_tuple(status, get_num_row, py::cast(lower), py::cast(upper), get_num_nz); +} -class CallbackTuple { -public: - CallbackTuple() = default; - CallbackTuple(py::object _callback, py::object _cb_data) : callback(_callback), callback_data(_cb_data) {} - ~CallbackTuple() = default; - py::object callback; - py::object callback_data; -}; +std::tuple, py::array_t, py::array_t> highs_getRowsEntries(Highs* h, int num_set_entries, py::array_t indices) +{ + py::buffer_info indices_info = indices.request(); + HighsInt* indices_ptr = static_cast(indices_info.ptr); + // Make sure that the vectors are not empty + const HighsInt dim = num_set_entries > 0 ? num_set_entries : 1; + HighsInt get_num_row; + HighsInt get_num_nz; + h->getRows(num_set_entries, indices_ptr, get_num_row, nullptr, nullptr, get_num_nz, nullptr, nullptr, nullptr); + get_num_nz = get_num_nz > 0 ? get_num_nz : 1; + std::vector start(dim); + std::vector index(get_num_nz); + std::vector value(get_num_nz); + HighsInt* start_ptr = static_cast(start.data()); + HighsInt* index_ptr = static_cast(index.data()); + double* value_ptr = static_cast(value.data()); + HighsStatus status = h->getRows(num_set_entries, indices_ptr, get_num_row, nullptr, nullptr, get_num_nz, start_ptr, index_ptr, value_ptr); + return std::make_tuple(status, py::cast(start), py::cast(index), py::cast(value)); +} +std::tuple highs_getColName(Highs* h, const int col) +{ + std::string name; + HighsStatus status = h->getColName(col, name); + return std::make_tuple(status, name); +} -void py_log_callback(HighsLogType log_type, const char* msgbuffer, void* callback_data) +std::tuple highs_getColByName(Highs* h, const std::string name) { - CallbackTuple* callback_tuple = static_cast(callback_data); - std::string msg(msgbuffer); - callback_tuple->callback(log_type, msg, callback_tuple->callback_data); + HighsInt col; + HighsStatus status = h->getColByName(name, col); + return std::make_tuple(status, col); } +std::tuple highs_getRowName(Highs* h, const int row) +{ + std::string name; + HighsStatus status = h->getRowName(row, name); + return std::make_tuple(status, name); +} -HighsStatus highs_setLogCallback(Highs* h, CallbackTuple* callback_tuple) +std::tuple highs_getRowByName(Highs* h, const std::string name) { - void (*_log_callback)(HighsLogType, const char*, void*) = &py_log_callback; - HighsStatus status = h->setLogCallback(_log_callback, callback_tuple); - return status; + HighsInt row; + HighsStatus status = h->getRowByName(name, row); + return std::make_tuple(status, row); } PYBIND11_MODULE(highs_bindings, m) { + // enum classes py::enum_(m, "ObjSense") .value("kMinimize", ObjSense::kMinimize) .value("kMaximize", ObjSense::kMaximize); @@ -477,6 +581,16 @@ PYBIND11_MODULE(highs_bindings, m) .value("kIterationLimit", HighsModelStatus::kIterationLimit) .value("kUnknown", HighsModelStatus::kUnknown) .value("kSolutionLimit", HighsModelStatus::kSolutionLimit); + py::enum_(m, "HighsPresolveStatus") + .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) + .value("kNotReduced", HighsPresolveStatus::kNotReduced) + .value("kInfeasible", HighsPresolveStatus::kInfeasible) + .value("kUnboundedOrInfeasible", HighsPresolveStatus::kUnboundedOrInfeasible) + .value("kReduced", HighsPresolveStatus::kReduced) + .value("kReducedToEmpty", HighsPresolveStatus::kReducedToEmpty) + .value("kTimeout", HighsPresolveStatus::kTimeout) + .value("kNullError", HighsPresolveStatus::kNullError) + .value("kOptionsError", HighsPresolveStatus::kOptionsError); py::enum_(m, "HighsBasisStatus") .value("kLower", HighsBasisStatus::kLower) .value("kBasic", HighsBasisStatus::kBasic) @@ -488,6 +602,15 @@ PYBIND11_MODULE(highs_bindings, m) .value("kInteger", HighsVarType::kInteger) .value("kSemiContinuous", HighsVarType::kSemiContinuous) .value("kSemiInteger", HighsVarType::kSemiInteger); + py::enum_(m, "HighsOptionType") + .value("kBool", HighsOptionType::kBool) + .value("kInt", HighsOptionType::kInt) + .value("kDouble", HighsOptionType::kDouble) + .value("kString", HighsOptionType::kString); + py::enum_(m, "HighsInfoType") + .value("kInt64", HighsInfoType::kInt64) + .value("kInt", HighsInfoType::kInt) + .value("kDouble", HighsInfoType::kDouble); py::enum_(m, "HighsStatus") .value("kError", HighsStatus::kError) .value("kOk", HighsStatus::kOk) @@ -498,11 +621,6 @@ PYBIND11_MODULE(highs_bindings, m) .value("kVerbose", HighsLogType::kVerbose) .value("kWarning", HighsLogType::kWarning) .value("kError", HighsLogType::kError); - py::class_(m, "CallbackTuple") - .def(py::init<>()) - .def(py::init()) - .def_readwrite("callback", &CallbackTuple::callback) - .def_readwrite("callback_data", &CallbackTuple::callback_data); py::class_(m, "HighsSparseMatrix") .def(py::init<>()) .def_readwrite("format_", &HighsSparseMatrix::format_) @@ -543,24 +661,6 @@ PYBIND11_MODULE(highs_bindings, m) .def(py::init<>()) .def_readwrite("lp_", &HighsModel::lp_) .def_readwrite("hessian_", &HighsModel::hessian_); - py::class_(m, "HighsSolution") - .def(py::init<>()) - .def_readwrite("value_valid", &HighsSolution::value_valid) - .def_readwrite("dual_valid", &HighsSolution::dual_valid) - .def_readwrite("col_value", &HighsSolution::col_value) - .def_readwrite("col_dual", &HighsSolution::col_dual) - .def_readwrite("row_value", &HighsSolution::row_value) - .def_readwrite("row_dual", &HighsSolution::row_dual); - py::class_(m, "HighsBasis") - .def(py::init<>()) - .def_readwrite("valid", &HighsBasis::valid) - .def_readwrite("alien", &HighsBasis::alien) - .def_readwrite("was_alien", &HighsBasis::was_alien) - .def_readwrite("debug_id", &HighsBasis::debug_id) - .def_readwrite("debug_update_count", &HighsBasis::debug_update_count) - .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, "HighsInfo") .def(py::init<>()) .def_readwrite("valid", &HighsInfo::valid) @@ -624,7 +724,8 @@ PYBIND11_MODULE(highs_bindings, m) .def_readwrite("log_dev_level", &HighsOptions::log_dev_level) .def_readwrite("allow_unbounded_or_infeasible", &HighsOptions::allow_unbounded_or_infeasible) .def_readwrite("allowed_matrix_scale_factor", &HighsOptions::allowed_matrix_scale_factor) - .def_readwrite("simplex_dualise_strategy", &HighsOptions::simplex_dualise_strategy) + .def_readwrite("ipx_dualize_strategy", &HighsOptions::ipx_dualize_strategy) + .def_readwrite("simplex_dualize_strategy", &HighsOptions::simplex_dualize_strategy) .def_readwrite("simplex_permute_strategy", &HighsOptions::simplex_permute_strategy) .def_readwrite("simplex_price_strategy", &HighsOptions::simplex_price_strategy) .def_readwrite("mip_detect_symmetry", &HighsOptions::mip_detect_symmetry) @@ -644,28 +745,90 @@ PYBIND11_MODULE(highs_bindings, m) .def_readwrite("mip_heuristic_effort", &HighsOptions::mip_heuristic_effort); py::class_(m, "_Highs") .def(py::init<>()) + .def("version", &Highs::version) + .def("versionMajor", &Highs::versionMajor) + .def("versionMinor", &Highs::versionMinor) + .def("versionPatch", &Highs::versionPatch) + .def("githash", &Highs::githash) + .def("compilationDate", &Highs::compilationDate) + .def("clear", &Highs::clear) + .def("clearModel", &Highs::clearModel) + .def("clearSolver", &Highs::clearSolver) .def("passModel", &highs_passModel) .def("passModel", &highs_passModelPointers) .def("passModel", &highs_passLp) .def("passModel", &highs_passLpPointers) .def("passHessian", &highs_passHessian) .def("passHessian", &highs_passHessianPointers) + .def("passColName", &Highs::passColName) + .def("passRowName", &Highs::passRowName) .def("readModel", &Highs::readModel) + .def("readBasis", &Highs::readBasis) + .def("writeBasis", &Highs::writeBasis) .def("presolve", &Highs::presolve) .def("run", &Highs::run) .def("postsolve", &Highs::postsolve) .def("writeSolution", &highs_writeSolution) .def("readSolution", &Highs::readSolution) - .def("writeModel", &Highs::writeModel) + .def("setOptionValue", static_cast(&Highs::setOptionValue)) + .def("setOptionValue", static_cast(&Highs::setOptionValue)) + .def("setOptionValue", static_cast(&Highs::setOptionValue)) + .def("setOptionValue", static_cast(&Highs::setOptionValue)) + .def("readOptions", &Highs::readOptions) + .def("passOptions", &Highs::passOptions) + .def("getOptions", &Highs::getOptions) + .def("getOptionValue", &highs_getOptionValue) + // .def("getOptionName", &highs_getOptionName) + .def("getOptionType", &highs_getOptionType) + .def("resetOptions", &Highs::resetOptions) + .def("writeOptions", &highs_writeOptions) + // .def("getBoolOptionValues", &highs_getBoolOptionValues) + // .def("getIntOptionValues", &highs_getIntOptionValues) + // .def("getDoubleOptionValues", &highs_getDoubleOptionValues) + // .def("getStringOptionValues", &highs_getStringOptionValues) + .def("getInfo", &Highs::getInfo) + .def("getInfoValue", &highs_getInfoValue) + .def("getInfoType", &highs_getInfoType) + .def("writeInfo", &Highs::writeInfo) + .def("getInfinity", &Highs::getInfinity) + .def("getRunTime", &Highs::getRunTime) .def("getPresolvedLp", &Highs::getPresolvedLp) - .def("getPresolvedModel", &Highs::getPresolvedModel) - .def("getModel", &Highs::getModel) + // .def("getPresolvedModel", &Highs::getPresolvedModel) + // .def("getPresolveLog", &Highs::getPresolveLog) .def("getLp", &Highs::getLp) + .def("getModel", &Highs::getModel) .def("getSolution", &Highs::getSolution) + .def("getSavedMipSolutions", &Highs::getSavedMipSolutions) .def("getBasis", &Highs::getBasis) - .def("getInfo", &Highs::getInfo) - .def("getRunTime", &Highs::getRunTime) - .def("getInfinity", &Highs::getInfinity) +// &highs_getModelStatus not needed once getModelStatus(const bool +// scaled_model) disappears from, Highs.h + .def("getModelStatus", &highs_getModelStatus) //&Highs::getModelStatus) + .def("getModelPresolveStatus", &Highs::getModelPresolveStatus) + .def("getRanging", &highs_getRanging) + .def("getObjectiveValue", &Highs::getObjectiveValue) + .def("getNumCol", &Highs::getNumCol) + .def("getNumRow", &Highs::getNumRow) + .def("getNumNz", &Highs::getNumNz) + .def("getHessianNumNz", &Highs::getHessianNumNz) + .def("getObjectiveSense", &highs_getObjectiveSense) + .def("getObjectiveOffset", &highs_getObjectiveOffset) + + .def("getCol", &highs_getCol) + .def("getColEntries", &highs_getColEntries) + .def("getRow", &highs_getRow) + .def("getRowEntries", &highs_getRowEntries) + + .def("getCols", &highs_getCols) + .def("getColsEntries", &highs_getColsEntries) + .def("getRows", &highs_getRows) + .def("getRowsEntries", &highs_getRowsEntries) + + .def("getColName", &highs_getColName) + .def("getColByName", &highs_getColByName) + .def("getRowName", &highs_getRowName) + .def("getRowByName", &highs_getRowByName) + + .def("writeModel", &Highs::writeModel) .def("crossover", &Highs::crossover) .def("changeObjectiveSense", &Highs::changeObjectiveSense) .def("changeObjectiveOffset", &Highs::changeObjectiveOffset) @@ -674,52 +837,63 @@ PYBIND11_MODULE(highs_bindings, m) .def("changeColBounds", &Highs::changeColBounds) .def("changeRowBounds", &Highs::changeRowBounds) .def("changeCoeff", &Highs::changeCoeff) - .def("getObjectiveValue", &Highs::getObjectiveValue) - .def("getObjectiveSense", &highs_getObjectiveSense) - .def("getObjectiveOffset", &highs_getObjectiveOffset) - .def("getRunTime", &Highs::getRunTime) - .def("getModelStatus", &highs_getModelStatus) - .def("getDualRay", &highs_getDualRay, py::arg("dual_ray_value") = nullptr) - // .def("getPrimalRay", &highs_getPrimalRay) - // .def("getObjectiveValue", &Highs::getObjectiveValue) - // .def("getBasicVariables", &highs_getBasicVariables) .def("addRows", &highs_addRows) .def("addRow", &highs_addRow) .def("addCol", &highs_addCol) + .def("addCols", &highs_addCols) .def("addVar", &highs_addVar) .def("addVars", &highs_addVars) .def("changeColsCost", &highs_changeColsCost) .def("changeColsBounds", &highs_changeColsBounds) .def("changeColsIntegrality", &highs_changeColsIntegrality) - .def("setLogCallback", &highs_setLogCallback) - .def("setLogCallback", &highs_setLogCallback) + .def("deleteCols", &highs_deleteCols) .def("deleteVars", &highs_deleteVars) .def("deleteRows", &highs_deleteRows) - .def("clear", &Highs::clear) - .def("clearModel", &Highs::clearModel) - .def("clearSolver", &Highs::clearSolver) - .def("getNumCol", &Highs::getNumCol) - .def("getNumRow", &Highs::getNumRow) - .def("getNumNz", &Highs::getNumNz) - .def("getHessianNumNz", &Highs::getHessianNumNz) - .def("resetOptions", &Highs::resetOptions) - .def("readOptions", &Highs::readOptions) - .def("passOptions", &Highs::passOptions) - .def("writeOptions", &Highs::writeOptions, py::arg("filename"), py::arg("report_only_deviations") = false) - .def("getOptions", &Highs::getOptions) - .def("getOptionValue", &highs_getOptionValue) - .def("setOptionValue", static_cast(&Highs::setOptionValue)) - .def("setOptionValue", static_cast(&Highs::setOptionValue)) - .def("setOptionValue", static_cast(&Highs::setOptionValue)) - .def("setOptionValue", static_cast(&Highs::setOptionValue)) .def("writeInfo", &Highs::writeInfo) .def("modelStatusToString", &Highs::modelStatusToString) .def("solutionStatusToString", &Highs::solutionStatusToString) .def("basisStatusToString", &Highs::basisStatusToString) .def("basisValidityToString", &Highs::basisValidityToString); + // structs + py::class_(m, "HighsSolution") + .def(py::init<>()) + .def_readwrite("value_valid", &HighsSolution::value_valid) + .def_readwrite("dual_valid", &HighsSolution::dual_valid) + .def_readwrite("col_value", &HighsSolution::col_value) + .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") + .def(py::init<>()) + .def_readwrite("objective", &HighsObjectiveSolution::objective) + .def_readwrite("col_value", &HighsObjectiveSolution::col_value); + py::class_(m, "HighsBasis") + .def(py::init<>()) + .def_readwrite("valid", &HighsBasis::valid) + .def_readwrite("alien", &HighsBasis::alien) + .def_readwrite("was_alien", &HighsBasis::was_alien) + .def_readwrite("debug_id", &HighsBasis::debug_id) + .def_readwrite("debug_update_count", &HighsBasis::debug_update_count) + .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") + .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") + .def(py::init<>()) + .def_readwrite("valid", &HighsRanging::valid) + .def_readwrite("col_cost_up", &HighsRanging::col_cost_up) + .def_readwrite("col_cost_dn", &HighsRanging::col_cost_dn) + .def_readwrite("col_bound_up", &HighsRanging::col_bound_up) + .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); + // constants m.attr("kHighsInf") = kHighsInf; - m.attr("HIGHS_VERSION_MAJOR") = HIGHS_VERSION_MAJOR; - m.attr("HIGHS_VERSION_MINOR") = HIGHS_VERSION_MINOR; - m.attr("HIGHS_VERSION_PATCH") = HIGHS_VERSION_PATCH; + m.attr("kHighsIInf") = kHighsIInf; } diff --git a/src/interfaces/highspy/highspy/tests/__init__.py b/highspy/tests/__init__.py similarity index 100% rename from src/interfaces/highspy/highspy/tests/__init__.py rename to highspy/tests/__init__.py diff --git a/highspy/tests/test_highspy.py b/highspy/tests/test_highspy.py new file mode 100644 index 0000000000..0a9585a5e7 --- /dev/null +++ b/highspy/tests/test_highspy.py @@ -0,0 +1,441 @@ +import tempfile +import unittest +import highspy +import numpy as np +from pyomo.common.tee import capture_output +from io import StringIO + + +class TestHighsPy(unittest.TestCase): + def get_basic_model(self): + """ + min y + s.t. + -x + y >= 2 + x + y >= 0 + """ + inf = highspy.kHighsInf + h = highspy.Highs() + h.setOptionValue('output_flag', False) + h.addVars(2, np.array([-inf, -inf]), np.array([inf, inf])) + h.changeColsCost(2, np.array([0, 1]), np.array([0, 1], dtype=np.double)) + num_cons = 2 + lower = np.array([2, 0], dtype=np.double) + upper = np.array([inf, inf], dtype=np.double) + num_new_nz = 4 + starts = np.array([0, 2]) + indices = np.array([0, 1, 0, 1]) + values = np.array([-1, 1, 1, 1], dtype=np.double) + h.addRows(num_cons, lower, upper, num_new_nz, starts, indices, values) + return h + + def get_example_model(self): + """ + minimize f = x0 + x1 + subject to x1 <= 7 + 5 <= x0 + 2x1 <= 15 + 6 <= 3x0 + 2x1 + 0 <= x0 <= 4; 1 <= x1 + """ + inf = highspy.kHighsInf + h = highspy.Highs() + # Define a HighsLp instance + lp = highspy.HighsLp() + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = np.array([1, 1], dtype=np.double) + lp.col_lower_ = np.array([0, 1], dtype=np.double) + lp.col_upper_ = np.array([4, inf], dtype=np.double) + lp.row_lower_ = np.array([-inf, 5, 6], dtype=np.double) + lp.row_upper_ = np.array([7, 15, inf], dtype=np.double) + lp.a_matrix_.start_ = np.array([0, 2, 5]) + lp.a_matrix_.index_ = np.array([1, 2, 0, 1, 2]) + lp.a_matrix_.value_ = np.array([1, 3, 1, 2, 2], dtype=np.double) + h.passModel(lp) + return h + + def get_infeasible_model(self): + inf = highspy.kHighsInf + lp = highspy.HighsLp() + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = np.array([10, 15], dtype=np.double) + lp.col_lower_ = np.array([0, 0], dtype=np.double) + lp.col_upper_ = np.array([inf, inf], dtype=np.double) + lp.row_lower_ = np.array([3, 1], dtype=np.double) + lp.row_upper_ = np.array([3, 1], dtype=np.double) + lp.a_matrix_.start_ = np.array([0, 2, 4]) + lp.a_matrix_.index_ = np.array([0, 1, 0, 1]) + lp.a_matrix_.value_ = np.array([2, 1, 1, 3], dtype=np.double) + lp.offset_ = 0; + h = highspy.Highs() + h.setOptionValue('output_flag', False) + status = h.passModel(lp) + self.assertEqual(status, highspy.HighsStatus.kOk) + h.setOptionValue('presolve', 'off') + return h + + def test_version(self): + h = self.get_basic_model() + self.assertEqual(h.version(), "1.5.3") + self.assertEqual(h.versionMajor(), 1) + self.assertEqual(h.versionMinor(), 5) + self.assertEqual(h.versionPatch(), 3) + + def test_basics(self): + h = self.get_basic_model() + h.passColName(0, 'Col0') + h.passColName(1, 'Col1') + h.passRowName(0, 'Row0') + h.passRowName(1, 'Row1') +# h.setOptionValue('output_flag', True) + h.writeModel('') + h.setOptionValue('output_flag', False) + self.assertEqual(h.setOptionValue('presolve', 'off'), highspy.HighsStatus.kOk) +# h.setOptionValue('output_flag', True) + h.run() + + # Info can be obtained from the class instance, specific call + # and, in the case of objective_function_value, + # h.getObjectiveValue() + info = h.getInfo() + objective_function_value0 = info.objective_function_value + self.assertAlmostEqual(objective_function_value0, 1) + [status, objective_function_value1] = h.getInfoValue("objective_function_value") + self.assertAlmostEqual(objective_function_value0, objective_function_value1) + self.assertAlmostEqual(h.getObjectiveValue(), objective_function_value0) + + simplex_iteration_count0 = info.simplex_iteration_count + self.assertAlmostEqual(simplex_iteration_count0, 2) + [status, simplex_iteration_count1] = h.getInfoValue("simplex_iteration_count") + self.assertAlmostEqual(simplex_iteration_count0, simplex_iteration_count1) + + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -1) + self.assertAlmostEqual(sol.col_value[1], 1) + +# h.setOptionValue('output_flag', False) + """ + min y + s.t. + -x + y >= 3 + x + y >= 0 + """ + inf = highspy.kHighsInf + h.changeRowBounds(0, 3, inf) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -1.5) + self.assertAlmostEqual(sol.col_value[1], 1.5) + + # now make y integer + h.changeColsIntegrality(1, np.array([1]), np.array([highspy.HighsVarType.kInteger])) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -1.5) + self.assertAlmostEqual(sol.col_value[1], 2) + + """ + now delete the first constraint and add a new one + + min y + s.t. + x + y >= 0 + -x + y >= 0 + """ + h.deleteRows(1, np.array([0])) + h.addRows(1, np.array([0], dtype=np.double), np.array([inf]), 2, + np.array([0]), np.array([0, 1]), np.array([-1, 1], dtype=np.double)) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], 0) + self.assertAlmostEqual(sol.col_value[1], 0) + + # change the upper bound of x to -5 + h.changeColsBounds(1, np.array([0]), np.array([-inf], dtype=np.double), + np.array([-5], dtype=np.double)) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -5) + self.assertAlmostEqual(sol.col_value[1], 5) + + # now maximize + h.changeColCost(1, -1) + h.changeRowBounds(0, -inf, 0) + h.changeRowBounds(1, -inf, 0) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -5) + self.assertAlmostEqual(sol.col_value[1], -5) + + h.changeColCost(1, 1) + [status, sense] = h.getObjectiveSense() + self.assertEqual(sense, highspy.ObjSense.kMinimize) + h.changeObjectiveSense(highspy.ObjSense.kMaximize) + [status, sense] = h.getObjectiveSense() + self.assertEqual(sense, highspy.ObjSense.kMaximize) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -5) + self.assertAlmostEqual(sol.col_value[1], -5) + + self.assertAlmostEqual(h.getObjectiveValue(), -5) + + h.changeObjectiveOffset(1) + [status, offset] = h.getObjectiveOffset(); + self.assertAlmostEqual(offset, 1) + h.run() + self.assertAlmostEqual(h.getObjectiveValue(), -4) + + info = h.getInfo() + mip_node_count0 = info.mip_node_count + self.assertAlmostEqual(mip_node_count0, 0) + [status, mip_node_count1] = h.getInfoValue("mip_node_count") + self.assertEqual(status, highspy.HighsStatus.kOk) + self.assertAlmostEqual(mip_node_count0, mip_node_count1) + + def test_example(self): + h = self.get_example_model() + lp = h.getLp() + # + # Extract column 0 + iCol = 0 + [status, cost, lower, upper, get_num_nz] = h.getCol(iCol) + self.assertEqual(cost, lp.col_cost_[iCol]) + self.assertEqual(lower, lp.col_lower_[iCol]) + self.assertEqual(upper, lp.col_upper_[iCol]) + index = np.empty(get_num_nz) + value = np.empty(get_num_nz, dtype=np.double) + [status, index, value] = h.getColEntries(iCol) + for iEl in range(get_num_nz): + self.assertEqual(index[iEl], lp.a_matrix_.index_[iEl]) + self.assertEqual(value[iEl], lp.a_matrix_.value_[iEl]) + # + # Extract columns 0 and 1 + indices = np.array([0, 1]) + [status, get_num_col, cost, lower, upper, get_num_nz] = h.getCols(2, indices) + for get_col in range(get_num_col): + iCol = indices[get_col] + self.assertEqual(cost[get_col], lp.col_cost_[iCol]) + self.assertEqual(lower[get_col], lp.col_lower_[iCol]) + self.assertEqual(upper[get_col], lp.col_upper_[iCol]) + start = np.empty(get_num_col) + index = np.empty(get_num_nz) + value = np.empty(get_num_nz, dtype=np.double) + [status, start, index, value] = h.getColsEntries(2, indices) + for iCol in range(lp.num_col_): + self.assertEqual(start[iCol], lp.a_matrix_.start_[iCol]) + for iEl in range(get_num_nz): + self.assertEqual(index[iEl], lp.a_matrix_.index_[iEl]) + self.assertEqual(value[iEl], lp.a_matrix_.value_[iEl]) + # + # Extract row 1 + iRow = 1 + [status, lower, upper, get_num_nz] = h.getRow(iRow) + self.assertEqual(lower, lp.row_lower_[iRow]) + self.assertEqual(upper, lp.row_upper_[iRow]) + index = np.empty(get_num_nz) + value = np.empty(get_num_nz, dtype=np.double) + [status, index, value] = h.getRowEntries(iRow) + # + # Extract rows 0 and 2 + indices = np.array([0, 2]) + [status, get_num_row, lower, upper, get_num_nz] = h.getRows(2, indices) + for get_row in range(get_num_row): + iRow = indices[get_row] + self.assertEqual(lower[get_row], lp.row_lower_[iRow]) + self.assertEqual(upper[get_row], lp.row_upper_[iRow]) + start = np.empty(get_num_row) + index = np.empty(get_num_nz) + value = np.empty(get_num_nz, dtype=np.double) + [status, start, index, value] = h.getRowsEntries(2, indices) + + + + def test_options(self): + h = highspy.Highs() + + # test vanilla get option value method + + [status, output_flag] = h.getOptionValue('output_flag') + [status, solver] = h.getOptionValue('solver') + [status, primal_feasibility_tolerance] = h.getOptionValue('primal_feasibility_tolerance') + [status, simplex_update_limit] = h.getOptionValue('simplex_update_limit') + self.assertEqual(output_flag, True); + self.assertEqual(solver, 'choose'); + self.assertEqual(primal_feasibility_tolerance, 1e-7); + self.assertEqual(simplex_update_limit, 5000); + # Illegal name + option_value = h.getOptionValue('simplex_limit') + self.assertEqual(option_value[0], highspy.HighsStatus.kError) + + # test bool option + [status, type] = h.getOptionType('output_flag') + self.assertEqual(type, highspy.HighsOptionType.kBool) + + h.setOptionValue('output_flag', True) + [status, value] = h.getOptionValue('output_flag') + self.assertTrue(value) + h.setOptionValue('output_flag', False) + [status, value] = h.getOptionValue('output_flag') + self.assertFalse(value) + + # test string option + [status, type] = h.getOptionType('presolve') + self.assertEqual(type, highspy.HighsOptionType.kString) + h.setOptionValue('presolve', 'off') + [status, value] = h.getOptionValue('presolve') + self.assertEqual(value, 'off') + h.setOptionValue('presolve', 'on') + [status, value] = h.getOptionValue('presolve') + self.assertEqual(value, 'on') + + # test int option + [status, type] = h.getOptionType('threads') + self.assertEqual(type, highspy.HighsOptionType.kInt) + h.setOptionValue('threads', 1) + [status, value] = h.getOptionValue('threads') + self.assertEqual(value, 1) + h.setOptionValue('threads', 2) + [status, value] = h.getOptionValue('threads') + self.assertEqual(value, 2) + + # test double option + [status, type] = h.getOptionType('time_limit') + self.assertEqual(type, highspy.HighsOptionType.kDouble) + h.setOptionValue('time_limit', 1.7) + [status, value] = h.getOptionValue('time_limit') + self.assertAlmostEqual(value, 1.7) + h.setOptionValue('time_limit', 2.7) + [status, value] = h.getOptionValue('time_limit') + self.assertAlmostEqual(value, 2.7) + + def test_clear(self): + h = self.get_basic_model() + self.assertEqual(h.getNumCol(), 2) + self.assertEqual(h.getNumRow(), 2) + self.assertEqual(h.getNumNz(), 4) + + [status, orig_feas_tol] = h.getOptionValue('primal_feasibility_tolerance') + new_feas_tol = orig_feas_tol + 1 + h.setOptionValue('primal_feasibility_tolerance', new_feas_tol) + [status, value] = h.getOptionValue('primal_feasibility_tolerance') + self.assertAlmostEqual(value, new_feas_tol) + h.clear() + self.assertEqual(h.getNumCol(), 0) + self.assertEqual(h.getNumRow(), 0) + self.assertEqual(h.getNumNz(), 0) + [status, value] = h.getOptionValue('primal_feasibility_tolerance') + self.assertAlmostEqual(value, orig_feas_tol) + + h = self.get_basic_model() + h.setOptionValue('primal_feasibility_tolerance', new_feas_tol) + [status, value] = h.getOptionValue('primal_feasibility_tolerance') + self.assertAlmostEqual(value, new_feas_tol) + h.clearModel() + self.assertEqual(h.getNumCol(), 0) + self.assertEqual(h.getNumRow(), 0) + self.assertEqual(h.getNumNz(), 0) + [status, value] = h.getOptionValue('primal_feasibility_tolerance') + self.assertAlmostEqual(value, new_feas_tol) + + h = self.get_basic_model() + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -1) + self.assertAlmostEqual(sol.col_value[1], 1) + h.clearSolver() + self.assertEqual(h.getNumCol(), 2) + self.assertEqual(h.getNumRow(), 2) + self.assertEqual(h.getNumNz(), 4) + sol = h.getSolution() + self.assertFalse(sol.value_valid) + self.assertFalse(sol.dual_valid) + + h = self.get_basic_model() + [status, orig_feas_tol] = h.getOptionValue('primal_feasibility_tolerance') + new_feas_tol = orig_feas_tol + 1 + h.setOptionValue('primal_feasibility_tolerance', new_feas_tol) + [status, value] = h.getOptionValue('primal_feasibility_tolerance') + self.assertAlmostEqual(value, new_feas_tol) + h.resetOptions() + [status, value] = h.getOptionValue('primal_feasibility_tolerance') + self.assertAlmostEqual(value, orig_feas_tol) + + def test_ranging(self): + inf = highspy.kHighsInf + h = self.get_basic_model() + # Cost ranging + #c0 2 -1 1 0 + #c1 0 0 inf inf + # + ## Bound ranging + ## Columns + #c0 1 -inf inf 1 + #c1 1 1 inf 1 + ## Rows + #r0 -inf -inf inf inf + #r1 -inf -inf inf inf + h.run() + [status, ranging] = h.getRanging() + self.assertEqual(ranging.col_cost_dn.objective_[0], 2); + self.assertEqual(ranging.col_cost_dn.value_[0], -1); + self.assertEqual(ranging.col_cost_up.value_[0], 1); + self.assertEqual(ranging.col_cost_up.objective_[0], 0); + self.assertEqual(ranging.col_cost_dn.objective_[1], 0); + self.assertEqual(ranging.col_cost_dn.value_[1], 0); + self.assertEqual(ranging.col_cost_up.value_[1], inf); + self.assertEqual(ranging.col_cost_up.objective_[1], inf); +# + self.assertEqual(ranging.col_bound_dn.objective_[0], 1); + self.assertEqual(ranging.col_bound_dn.value_[0], -inf); + self.assertEqual(ranging.col_bound_up.value_[0], inf); + self.assertEqual(ranging.col_bound_up.objective_[0], 1); + self.assertEqual(ranging.col_bound_dn.objective_[1], 1); + self.assertEqual(ranging.col_bound_dn.value_[1], 1); + self.assertEqual(ranging.col_bound_up.value_[1], inf); + self.assertEqual(ranging.col_bound_up.objective_[1], 1); +# + self.assertEqual(ranging.row_bound_dn.objective_[0], -inf); + self.assertEqual(ranging.row_bound_dn.value_[0], -inf); + self.assertEqual(ranging.row_bound_up.value_[0], inf); + self.assertEqual(ranging.row_bound_up.objective_[0], inf); + self.assertEqual(ranging.row_bound_dn.objective_[1], -inf); + self.assertEqual(ranging.row_bound_dn.value_[1], -inf); + self.assertEqual(ranging.row_bound_up.value_[1], inf); + self.assertEqual(ranging.row_bound_up.objective_[1], inf); + + def test_write_basis_before_running(self): + h = self.get_basic_model() + with tempfile.NamedTemporaryFile() as f: + h.writeBasis(f.name) + contents = f.read() + self.assertEqual(contents, b'HiGHS v1\nNone\n') + + def test_write_basis_after_running(self): + h = self.get_basic_model() + h.run() + with tempfile.NamedTemporaryFile() as f: + h.writeBasis(f.name) + contents = f.read() + self.assertEqual( + contents, b'HiGHS v1\nValid\n# Columns 2\n1 1 \n# Rows 2\n0 0 \n' + ) + + def test_read_basis(self): + # Read basis from one run model into an unrun model + expected_status_before = highspy.HighsBasisStatus.kLower + expected_status_after = highspy.HighsBasisStatus.kBasic + + h1 = self.get_basic_model() + self.assertEqual(h1.getBasis().col_status[0], expected_status_before) + h1.run() + self.assertEqual(h1.getBasis().col_status[0], expected_status_after) + + h2 = self.get_basic_model() + self.assertEqual(h2.getBasis().col_status[0], expected_status_before) + + with tempfile.NamedTemporaryFile() as f: + h1.writeBasis(f.name) + h2.readBasis(f.name) + self.assertEqual(h2.getBasis().col_status[0], expected_status_after) diff --git a/manifest.in b/manifest.in new file mode 100644 index 0000000000..0d3fedf3ab --- /dev/null +++ b/manifest.in @@ -0,0 +1,3 @@ +include LICENSE +include highspy/highs_bindings.cpp +include src/Highs.h diff --git a/osi-highs.pc.in b/osi-highs.pc.in deleted file mode 100644 index 79a0801700..0000000000 --- a/osi-highs.pc.in +++ /dev/null @@ -1,11 +0,0 @@ -prefix=@CMAKE_INSTALL_PREFIX@ -libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ -includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/highs - -Name: OsiHiGHS -Description: COIN-OR Open Solver Interface for HiGHS -URL: https://github.com/ERGO-Code/HiGHS -Version: @HIGHS_VERSION_MAJOR@.@HIGHS_VERSION_MINOR@.@HIGHS_VERSION_PATCH@ -Libs: -L${libdir} -lOsiHighs -Cflags: -I${includedir} -Requires.private: osi highs diff --git a/src/interfaces/highspy/pyproject.toml b/pyproject.toml similarity index 75% rename from src/interfaces/highspy/pyproject.toml rename to pyproject.toml index dccf4d17fe..03614697b1 100644 --- a/src/interfaces/highspy/pyproject.toml +++ b/pyproject.toml @@ -1,37 +1,31 @@ [build-system] # Minimum requirements for the build system to execute. requires = [ - "setuptools", - "pybind11", - "pyomo", - "wheel", + "setuptools>=45", + "pybind11>=2.4", + "pyomo>=6.0", + "wheel>=0.2", ] build-backend = "setuptools.build_meta" [tool.cibuildwheel] build = "*" -skip = "" +skip = "cp36-*" test-skip = "" [tool.cibuildwheel.linux] archs = ["x86_64 i686 aarch64 ppc64le s390x"] before-all = [ - "cd /project && ls /host/home/ivet", - "cp -r /host/home/ivet/HiGHS .", - "cd HiGHS", - "rm -rf build && mkdir build", - "rm -rf src/interfaces/highspy/build", - "rm -rf src/interfaces/highspy/highspy.egg-info", - "rm -rf src/interfaces/highspy/highspy/*.so", + "cd /project && ls" ] before-build = [ - "cd HiGHS", + "rm -rf build", "mkdir -p build && cd build", "cmake -DFAST_BUILD=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/project/installs/highs ..", - "make -j3 && make install", + "make -j && make install", ] [tool.cibuildwheel.linux.environment] diff --git a/src/interfaces/highspy/setup.py b/setup.py similarity index 89% rename from src/interfaces/highspy/setup.py rename to setup.py index fccf1d696a..884e9839a2 100644 --- a/src/interfaces/highspy/setup.py +++ b/setup.py @@ -2,14 +2,15 @@ import pybind11.setup_helpers from pybind11.setup_helpers import Pybind11Extension, build_ext import os +# find_library and capture_output in test_log_callback seem to be the +# reasons for including pyomo from pyomo.common.fileutils import find_library - original_pybind11_setup_helpers_macos = pybind11.setup_helpers.MACOS pybind11.setup_helpers.MACOS = False try: - highs_lib = find_library('highs', include_PATH=True) + highs_lib = find_library('highs', include_PATH=False) if highs_lib is None: raise RuntimeError('Could not find HiGHS library; Please make sure it is in the LD_LIBRARY_PATH environment variable') highs_lib_dir = os.path.dirname(highs_lib) @@ -27,20 +28,18 @@ libraries=['highs'])) setup(name='highspy', - version='1.5.0.dev0', + version='1.5.3', packages=find_packages(), description='Python interface to HiGHS', maintainer_email='highsopt@gmail.com', license='MIT', url='https://github.com/ergo-code/highs', - install_requires=['pybind11', 'numpy', 'pyomo'], include_package_data=True, package_data={'highspy': ['highspy/*.so']}, ext_modules=extensions, cmdclass={'build_ext': build_ext}, - python_requires='>=3.6', + python_requires='>=3.7', classifiers=["Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c69723ffe1..bb6c38ff76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -328,46 +328,13 @@ set_target_properties(libhighs PROPERTIES OUTPUT_NAME "highs" MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") -if (OSI_FOUND) - add_library(OsiHighs interfaces/OsiHiGHSSolverInterface.cpp) - set(headers ${headers} interfaces/OsiHiGHSSolverInterface.hpp) - - target_include_directories(OsiHighs PUBLIC ${OSI_INCLUDE_DIRS}) - target_link_libraries(OsiHighs PUBLIC libhighs ${OSI_LIBRARIES}) - target_compile_options(OsiHighs PUBLIC ${OSI_CFLAGS_OTHER}) - - if(${BUILD_SHARED_LIBS}) - set_target_properties(OsiHighs PROPERTIES - VERSION - ${HIGHS_VERSION_MAJOR}.${HIGHS_VERSION_MINOR}.${HIGHS_VERSION_PATCH} - SOVERSION ${HIGHS_VERSION_MAJOR}.${HIGHS_VERSION_MINOR}) - else() - set_target_properties(OsiHighs PROPERTIES POSITION_INDEPENDENT_CODE on) - endif() - - install(TARGETS OsiHighs - LIBRARY - ARCHIVE - RUNTIME - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) - - set_target_properties(OsiHighs PROPERTIES INSTALL_RPATH - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - - #configure and install the pkg-config file - configure_file(${HIGHS_SOURCE_DIR}/osi-highs.pc.in - "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/osi-highs.pc" @ONLY) - install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/osi-highs.pc" - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -endif() - -if (ZLIB_FOUND) +if (ZLIB AND ZLIB_FOUND) target_link_libraries(libhighs ZLIB::ZLIB) set(CONF_DEPENDENCIES "include(CMakeFindDependencyMacro)\nfind_dependency(ZLIB)") endif() # set the install rpath to the installed destination -set_target_properties(highs PROPERTIES INSTALL_RPATH +set_target_properties(libhighs PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") # install the header files of highs @@ -443,21 +410,21 @@ else() # install / export. # Define library in modern CMake using target_*() # No interfaces (apart from c); No ipx; New (short) ctest instances. -add_library(libhighs) +add_library(highs) -set_target_properties(libhighs PROPERTIES POSITION_INDEPENDENT_CODE ON) -target_compile_definitions(libhighs PUBLIC LIBHIGHS_STATIC_DEFINE) +set_target_properties(highs PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_compile_definitions(highs PUBLIC LIBHIGHS_STATIC_DEFINE) if(${BUILD_SHARED_LIBS}) # put version information into shared library file - set_target_properties(libhighs PROPERTIES + set_target_properties(highs PROPERTIES VERSION ${HIGHS_VERSION_MAJOR}.${HIGHS_VERSION_MINOR}.${HIGHS_VERSION_PATCH} SOVERSION ${HIGHS_VERSION_MAJOR}.${HIGHS_VERSION_MINOR}) endif() -target_sources(libhighs PRIVATE +target_sources(highs PRIVATE ../extern/filereaderlp/reader.cpp io/Filereader.cpp io/FilereaderLp.cpp @@ -561,13 +528,13 @@ target_sources(libhighs PRIVATE util/stringutil.cpp interfaces/highs_c_api.cpp) -target_include_directories(libhighs PUBLIC +target_include_directories(highs PUBLIC $ $ $ ) -target_include_directories(libhighs PRIVATE +target_include_directories(highs PRIVATE $ $ $ @@ -584,27 +551,27 @@ target_include_directories(libhighs PRIVATE $ ) -target_include_directories(libhighs PRIVATE +target_include_directories(highs PRIVATE $ $ $ ) -if (ZLIB_FOUND) - target_include_directories(libhighs PRIVATE +if (ZLIB AND ZLIB_FOUND) + target_include_directories(highs PRIVATE $ ) - target_link_libraries(libhighs ZLIB::ZLIB) + target_link_libraries(highs ZLIB::ZLIB) set(CONF_DEPENDENCIES "include(CMakeFindDependencyMacro)\nfind_dependency(ZLIB)") endif() -# on UNIX system the 'lib' prefix is automatically added -set_target_properties(libhighs PROPERTIES - OUTPUT_NAME "highs" - MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +# # on UNIX system the 'lib' prefix is automatically added +# set_target_properties(highs PROPERTIES +# OUTPUT_NAME "highs" +# MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") # if (UNIX) -# set_target_properties(libhighs PROPERTIES +# set_target_properties(highs PROPERTIES # LIBRARY_OUTPUT_DIRECTORY "${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") # endif() @@ -740,10 +707,10 @@ set(headers_fast_build_ set(headers_fast_build_ ${headers_fast_build_} ipm/IpxWrapper.h ${basiclu_headers} ${ipx_headers}) -#set_target_properties(libhighs PROPERTIES PUBLIC_HEADER "src/Highs.h;src/lp_data/HighsLp.h;src/lp_data/HighsLpSolverObject.h") +#set_target_properties(highs PROPERTIES PUBLIC_HEADER "src/Highs.h;src/lp_data/HighsLp.h;src/lp_data/HighsLpSolverObject.h") # set the install rpath to the installed destination -# set_target_properties(libhighs PROPERTIES INSTALL_RPATH +# set_target_properties(highs PROPERTIES INSTALL_RPATH # "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") # install the header files of highs @@ -757,18 +724,40 @@ endforeach() install(FILES ${HIGHS_BINARY_DIR}/HConfig.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) -# target_compile_options(libhighs PRIVATE "-Wall") -# target_compile_options(libhighs PRIVATE "-Wunused") +# target_compile_options(highs PRIVATE "-Wall") +# target_compile_options(highs PRIVATE "-Wunused") -target_sources(libhighs PRIVATE ${basiclu_sources} ${ipx_sources} ipm/IpxWrapper.cpp) +target_sources(highs PRIVATE ${basiclu_sources} ${ipx_sources} ipm/IpxWrapper.cpp) if (UNIX) - target_compile_options(libhighs PRIVATE "-Wno-unused-variable") - target_compile_options(libhighs PRIVATE "-Wno-unused-const-variable") + target_compile_options(highs PRIVATE "-Wno-unused-variable") + target_compile_options(highs PRIVATE "-Wno-unused-const-variable") endif() -# set_target_properties(libhighs PROPERTIES INSTALL_RPATH -# "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + # Configure the config file for the build tree: + # Either list all the src/* directories here, or put explicit paths in all the + # include statements. + # M reckons that the latter is more transparent, and I'm inclined to agree. + set(CONF_INCLUDE_DIRS "${HIGHS_SOURCE_DIR}/src" "${HIGHS_BINARY_DIR}") + configure_file(${HIGHS_SOURCE_DIR}/highs-config.cmake.in + "${HIGHS_BINARY_DIR}/highs-config.cmake" @ONLY) + + # Configure the config file for the install + set(CONF_INCLUDE_DIRS "\${CMAKE_CURRENT_LIST_DIR}/../../../${CMAKE_INSTALL_INCLUDEDIR}") + configure_file(${HIGHS_SOURCE_DIR}/highs-config.cmake.in + "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs-config.cmake" @ONLY) + + # Configure the pkg-config file for the install + configure_file(${HIGHS_SOURCE_DIR}/highs.pc.in + "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs.pc" @ONLY) + + # Install the targets of the highs export group, the config file so that other + # cmake-projects can link easily against highs, and the pkg-config flie so that + # other projects can easily build against highs + 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() @@ -776,7 +765,12 @@ if(FORTRAN_FOUND) set(fortransources interfaces/highs_fortran_api.f90) set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) add_library(FortranHighs interfaces/highs_fortran_api.f90) - target_link_libraries(FortranHighs PUBLIC libhighs) + if (NOT FAST_BUILD) + target_link_libraries(FortranHighs PUBLIC libhighs) + else() + target_link_libraries(FortranHighs PUBLIC highs) + endif() + install(TARGETS FortranHighs LIBRARY ARCHIVE @@ -802,38 +796,8 @@ else() endif() find_package(Threads REQUIRED) +if (FAST_BUILD) +target_link_libraries(highs Threads::Threads) +else() target_link_libraries(libhighs Threads::Threads) - -if (CMAKE_TARGETS) - # Add library targets to the build-tree export set - export(TARGETS libhighs - FILE "${HIGHS_BINARY_DIR}/highs-targets.cmake") - - # Configure the config file for the build tree: - # Either list all the src/* directories here, or put explicit paths in all the - # include statements. - # M reckons that the latter is more transparent, and I'm inclined to agree. - set(CONF_INCLUDE_DIRS "${HIGHS_SOURCE_DIR}/src" "${HIGHS_BINARY_DIR}") - configure_file(${HIGHS_SOURCE_DIR}/highs-config.cmake.in - "${HIGHS_BINARY_DIR}/highs-config.cmake" @ONLY) - - # Configure the config file for the install - set(CONF_INCLUDE_DIRS "\${CMAKE_CURRENT_LIST_DIR}/../../../${CMAKE_INSTALL_INCLUDEDIR}") - configure_file(${HIGHS_SOURCE_DIR}/highs-config.cmake.in - "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs-config.cmake" @ONLY) - - # Configure the pkg-config file for the install - configure_file(${HIGHS_SOURCE_DIR}/highs.pc.in - "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs.pc" @ONLY) - - # Install the targets of the highs export group, the config file so that other - # cmake-projects can link easily against highs, and the pkg-config flie so that - # other projects can easily build against highs - install(EXPORT highs-targets FILE highs-targets.cmake DESTINATION - ${CMAKE_INSTALL_LIBDIR}/cmake/highs) - 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() diff --git a/src/HConfig.h.in b/src/HConfig.h.in index e559694486..652f6651ff 100644 --- a/src/HConfig.h.in +++ b/src/HConfig.h.in @@ -2,12 +2,9 @@ #define HCONFIG_H_ #cmakedefine FAST_BUILD -#cmakedefine SCIP_DEV -#cmakedefine HiGHSDEV -#cmakedefine OSI_FOUND #cmakedefine ZLIB_FOUND #cmakedefine CMAKE_BUILD_TYPE "@CMAKE_BUILD_TYPE@" -#cmakedefine HiGHSRELEASE +#cmakedefine CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" #cmakedefine HIGHSINT64 #cmakedefine HIGHS_HAVE_MM_PAUSE #cmakedefine HIGHS_HAVE_BUILTIN_CLZ diff --git a/src/Highs.h b/src/Highs.h index 4122482286..5508e79f70 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file Highs.h * @brief The HiGHS class @@ -26,9 +24,9 @@ #include "presolve/PresolveComponent.h" /** - * @brief Return the version as a string + * @brief Return the version */ -std::string highsVersion(); +const char* highsVersion(); /** * @brief Return detailed version information, githash and compilation @@ -37,8 +35,8 @@ std::string highsVersion(); HighsInt highsVersionMajor(); HighsInt highsVersionMinor(); HighsInt highsVersionPatch(); -std::string highsGithash(); -std::string highsCompilationDate(); +const char* highsGithash(); +const char* highsCompilationDate(); /** * @brief Class to set parameters and run HiGHS @@ -47,13 +45,43 @@ class Highs { public: Highs(); virtual ~Highs() { - FILE* log_file_stream = options_.log_options.log_file_stream; - if (log_file_stream != nullptr) { - assert(log_file_stream != stdout); - fclose(log_file_stream); + FILE* log_stream = options_.log_options.log_stream; + if (log_stream != nullptr) { + assert(log_stream != stdout); + fclose(log_stream); } } + /** + * @brief Return the version as a string + */ + std::string version() const { return highsVersion(); } + + /** + * @brief Return major version + */ + HighsInt versionMajor() const { return highsVersionMajor(); } + + /** + * @brief Return minor version + */ + HighsInt versionMinor() const { return highsVersionMinor(); } + + /** + * @brief Return patch version + */ + HighsInt versionPatch() const { return highsVersionPatch(); } + + /** + * @brief Return githash + */ + std::string githash() const { return highsGithash(); } + + /** + * @brief Return compilation date + */ + std::string compilationDate() const { return highsCompilationDate(); } + /** * @brief Reset the options and then call clearModel() */ @@ -126,6 +154,15 @@ class Highs { HighsStatus passHessian(const HighsInt dim, const HighsInt num_nz, const HighsInt format, const HighsInt* start, const HighsInt* index, const double* value); + /** + * @brief Pass a column name to the incumbent model + */ + HighsStatus passColName(const HighsInt col, const std::string& name); + + /** + * @brief Pass a row name to the incumbent model + */ + HighsStatus passRowName(const HighsInt row, const std::string& name); /** * @brief Read in a model @@ -216,21 +253,34 @@ class Highs { /** * @brief Gets an option value as bool/HighsInt/double/string and, for * bool/int/double, only if it's of the correct type. + * + * NB Deprecate in v2.0, in order to replace with more general + * get*OptionValues */ - HighsStatus getOptionValue(const std::string& option, bool& value) const; + HighsStatus getOptionValue(const std::string& option, bool& value) const { + return this->getBoolOptionValues(option, &value); + } - HighsStatus getOptionValue(const std::string& option, HighsInt& value) const; + HighsStatus getOptionValue(const std::string& option, HighsInt& value) const { + return this->getIntOptionValues(option, &value); + } - HighsStatus getOptionValue(const std::string& option, double& value) const; + HighsStatus getOptionValue(const std::string& option, double& value) const { + return this->getDoubleOptionValues(option, &value); + } HighsStatus getOptionValue(const std::string& option, - std::string& value) const; + std::string& value) const { + return this->getStringOptionValues(option, &value); + } /** * @brief Get the type expected by an option */ HighsStatus getOptionType(const std::string& option, - HighsOptionType& type) const; + HighsOptionType& type) const { + return this->getOptionType(option, &type); + } /** * @brief Reset the options to the default values @@ -245,6 +295,56 @@ class Highs { HighsStatus writeOptions(const std::string& filename, //!< The filename const bool report_only_deviations = false) const; + /** + * @brief Returns the number of user-settable options + */ + HighsInt getNumOptions() const { + return this->options_.num_user_settable_options_; + } + + /** + * @brief Get the number of user-settable options + */ + HighsStatus getOptionName(const HighsInt index, std::string* name) const; + + /** + * @brief Get the type of an option + */ + HighsStatus getOptionType(const std::string& option, + HighsOptionType* type) const; + + /** + * @brief Get the current and default values of a bool option + */ + HighsStatus getBoolOptionValues(const std::string& option, + bool* current_value = nullptr, + bool* default_value = nullptr) const; + + /** + * @brief Get the current, min, max and default values of an int option + */ + HighsStatus getIntOptionValues(const std::string& option, + HighsInt* current_value = nullptr, + HighsInt* min_value = nullptr, + HighsInt* max_value = nullptr, + HighsInt* default_value = nullptr) const; + + /** + * @brief Get the current, min, max and default values of a double option + */ + HighsStatus getDoubleOptionValues(const std::string& option, + double* current_value = nullptr, + double* min_value = nullptr, + double* max_value = nullptr, + double* default_value = nullptr) const; + + /** + * @brief Get the current and default values of a string option + */ + HighsStatus getStringOptionValues(const std::string& option, + std::string* current_value = nullptr, + std::string* default_value = nullptr) const; + /** * @brief Get a const reference to the internal info values * type. @@ -264,12 +364,14 @@ class Highs { HighsStatus getInfoValue(const std::string& info, double& value) const; + HighsStatus getInfoType(const std::string& info, HighsInfoType& type) const; + /** * @brief Write info values to a file, with the extension ".html" * producing HTML, otherwise using the standard format used to read * options from a file. */ - HighsStatus writeInfo(const std::string& filename) const; + HighsStatus writeInfo(const std::string& filename = "") const; /** * @brief Get the value of infinity used by HiGHS @@ -316,6 +418,16 @@ class Highs { */ const HighsSolution& getSolution() const { return solution_; } + /** + * @brief Return a const reference to the internal HighsSolution instance + */ + const std::vector& getSavedMipSolutions() const { + return saved_objective_and_solution_; + } + + /** + * @brief Return a const reference to the internal ICrash info instance + */ const ICrashInfo& getICrashInfo() const { return icrash_info_; }; /** @@ -355,10 +467,8 @@ class Highs { double* primal_ray_value = nullptr); /** - * @brief Get the ranging information for the current LP, possibly - * returning it, as well as holding it internally + * @brief Get the ranging information for the current LP */ - HighsStatus getRanging(); HighsStatus getRanging(HighsRanging& ranging); /** @@ -377,15 +487,6 @@ class Highs { */ bool hasInvert() const; - /** - * @brief Gets the internal basic variable index array in the order - * corresponding to calls to getBasisInverseRow, getBasisInverseCol, - * getBasisSolve, getBasisTransposeSolve, getReducedRow and getReducedColumn. - * Entries are indices of columns if in [0,num_col), and entries in [num_col, - * num_col+num_row) are (num_col+row_index). - */ - const HighsInt* getBasicVariablesArray() const; - /** * @brief Gets the basic variables in the order corresponding to * calls to getBasisInverseRow, getBasisInverseCol, getBasisSolve, @@ -395,15 +496,6 @@ class Highs { */ HighsStatus getBasicVariables(HighsInt* basic_variables); - /** - * @brief Form a row of \f$B^{-1}\f$ for basis matrix \f$B\f$, - * returning the result in the given HVector buffer which is - * expected to be setup with dimension num_row. The buffers - * previous contents will be overwritten. - */ - HighsStatus getBasisInverseRowSparse(const HighsInt row, - HVector& row_ep_buffer); - /** * @brief Form a row of \f$B^{-1}\f$ for basis matrix \f$B\f$, * returning the indices of the nonzeros unless row_num_nz is @@ -546,6 +638,22 @@ class Highs { double* value //!< Array of size num_nz with row values for the columns ); + /** + * @brief Get a column name from the incumbent model + */ + HighsStatus getColName(const HighsInt col, std::string& name) const; + + /** + * @brief Get column index corresponding to name + */ + HighsStatus getColByName(const std::string& name, HighsInt& col); + + /** + * @brief Get a column integrality from the incumbent model + */ + HighsStatus getColIntegrality(const HighsInt col, + HighsVarType& integrality) const; + /** * @brief Get multiple rows from the model given by an interval [from_row, * to_row] @@ -600,6 +708,16 @@ class Highs { double* value //!< Array of size num_nz with column values for the rows ); + /** + * @brief Get a row name from the incumbent model + */ + HighsStatus getRowName(const HighsInt row, std::string& name) const; + + /** + * @brief Get row index corresponding to name + */ + HighsStatus getRowByName(const std::string& name, HighsInt& row); + /** * @brief Get a matrix coefficient */ @@ -608,12 +726,12 @@ class Highs { /** * @brief Write out the incumbent model to a file */ - HighsStatus writeModel(const std::string& filename); + HighsStatus writeModel(const std::string& filename = ""); /** * @brief Write out the internal HighsBasis instance to a file */ - HighsStatus writeBasis(const std::string& filename); + HighsStatus writeBasis(const std::string& filename = ""); /** * Methods for incumbent model modification @@ -890,9 +1008,10 @@ class Highs { /** * @brief Set the callback method and user data to use for logging */ - HighsStatus setLogCallback(void (*log_callback)(HighsLogType, const char*, - void*), - void* log_callback_data = nullptr); + HighsStatus setLogCallback(void (*log_user_callback)(HighsLogType, + const char*, void*), + void* deprecated = nullptr // V2.0 remove + ); /** * @brief Use the HighsBasis passed to set the internal HighsBasis @@ -921,6 +1040,8 @@ class Highs { /** * @brief Interpret common qualifiers to string values */ + std::string presolveStatusToString( + const HighsPresolveStatus presolve_status) const; std::string modelStatusToString(const HighsModelStatus model_status) const; std::string solutionStatusToString(const HighsInt solution_status) const; std::string basisStatusToString(const HighsBasisStatus basis_status) const; @@ -1001,9 +1122,24 @@ class Highs { : nullptr; } -#ifdef OSI_FOUND - friend class OsiHiGHSSolverInterface; -#endif + /** + * @brief Gets the internal basic variable index array in the order + * corresponding to calls to getBasisInverseRow, getBasisInverseCol, + * getBasisSolve, getBasisTransposeSolve, getReducedRow and getReducedColumn. + * Entries are indices of columns if in [0,num_col), and entries in [num_col, + * num_col+num_row) are (num_col+row_index). + */ + const HighsInt* getBasicVariablesArray() const; + + /** + * @brief Form a row of \f$B^{-1}\f$ for basis matrix \f$B\f$, + * returning the result in the given HVector buffer which is + * expected to be setup with dimension num_row. The buffers + * previous contents will be overwritten. + */ + HighsStatus getBasisInverseRowSparse(const HighsInt row, + HVector& row_ep_buffer); + // Start of deprecated methods HighsInt getNumCols() const { @@ -1114,6 +1250,8 @@ class Highs { HighsInfo info_; HighsRanging ranging_; + std::vector saved_objective_and_solution_; + HighsPresolveStatus model_presolve_status_ = HighsPresolveStatus::kNotPresolved; HighsModelStatus model_status_ = HighsModelStatus::kNotset; @@ -1150,7 +1288,7 @@ class Highs { HighsPostsolveStatus runPostsolve(); HighsStatus openWriteFile(const string filename, const string method_name, - FILE*& file, bool& html) const; + FILE*& file, HighsFileType& file_type) const; void reportModel(); void newHighsBasis(); @@ -1278,6 +1416,7 @@ class Highs { HighsStatus getPrimalRayInterface(bool& has_primal_ray, double* primal_ray_value); + HighsStatus getRangingInterface(); bool aFormatOk(const HighsInt num_nz, const HighsInt format); bool qFormatOk(const HighsInt num_nz, const HighsInt format); void clearZeroHessian(); diff --git a/src/interfaces/OsiHiGHSSolverInterface.cpp b/src/interfaces/OsiHiGHSSolverInterface.cpp deleted file mode 100644 index 1d6c235027..0000000000 --- a/src/interfaces/OsiHiGHSSolverInterface.cpp +++ /dev/null @@ -1,1341 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* */ -/* This file is part of the HiGHS linear optimization suite */ -/* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ -/* */ -/* Available as open-source under the MIT License */ -/* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/**@file interfaces/OsiHiGHSInterface.cpp - * @brief Osi/HiGHS interface implementation - */ -#include "OsiHiGHSSolverInterface.hpp" - -#include - -#include "CoinWarmStartBasis.hpp" -#include "Highs.h" -#include "io/FilereaderMps.h" -#include "io/HighsIO.h" -#include "lp_data/HConst.h" -#include "lp_data/HighsLp.h" -#include "lp_data/HighsOptions.h" -#include "lp_data/HighsStatus.h" - -static void logtomessagehandler(HighsLogType type, const char* msg, - void* log_callback_data) { - assert(log_callback_data != NULL); - - CoinMessageHandler* handler = (CoinMessageHandler*)log_callback_data; - - // we know log message end with a newline, replace by coin-eol - HighsInt len = strlen(msg); - assert(len > 0); - assert(msg[len - 1] == '\n'); - const_cast(msg)[len - 1] = '\0'; - - handler->message(0, "HiGHS", msg, ' ') << CoinMessageEol; - - const_cast(msg)[len - 1] = '\n'; -} - -OsiHiGHSSolverInterface::OsiHiGHSSolverInterface() - // : status(HighsStatus::Init) { - : status(HighsStatus::kOk) { - this->highs = new Highs(); - - this->highs->setLogCallback(logtomessagehandler, (void*)handler_); - - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::OsiHiGHSSolverInterface()\n"); - this->dummy_solution = new HighsSolution; - - setStrParam(OsiSolverName, "HiGHS"); -} - -OsiHiGHSSolverInterface::OsiHiGHSSolverInterface( - const OsiHiGHSSolverInterface& original) - : OsiSolverInterface(original), - // status(HighsStatus::Init) - status(HighsStatus::kOk) { - this->highs = new Highs(); - - this->highs->setLogCallback(logtomessagehandler, (void*)handler_); - - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::OsiHiGHSSolverInterface()\n"); - this->dummy_solution = new HighsSolution; - - this->highs->passModel(original.highs->getLp()); - setStrParam(OsiSolverName, "HiGHS"); -} - -OsiHiGHSSolverInterface::~OsiHiGHSSolverInterface() { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::~OsiHiGHSSolverInterface()\n"); - - this->highs->setLogCallback(NULL, NULL); - - delete this->highs; - - if (this->rowRange != NULL) { - delete[] this->rowRange; - } - - if (this->rhs != NULL) { - delete[] this->rhs; - } - - if (this->rowSense != NULL) { - delete[] this->rowSense; - } - - if (this->matrixByCol != NULL) { - delete this->matrixByCol; - } -} - -OsiSolverInterface* OsiHiGHSSolverInterface::clone(bool copyData) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::clone()\n"); - if (!copyData) { - OsiHiGHSSolverInterface* cln = new OsiHiGHSSolverInterface(); - return cln; - - } else { - OsiHiGHSSolverInterface* cln = new OsiHiGHSSolverInterface(*this); - cln->objOffset = this->objOffset; - return cln; - } -} - -bool OsiHiGHSSolverInterface::setIntParam(OsiIntParam key, HighsInt value) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setIntParam()\n"); - switch (key) { - case OsiMaxNumIteration: - case OsiMaxNumIterationHotStart: - this->highs->options_.simplex_iteration_limit = value; - return true; - case OsiNameDiscipline: - // TODO - return false; - case OsiLastIntParam: - default: - return false; - } -} - -bool OsiHiGHSSolverInterface::setDblParam(OsiDblParam key, double value) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setDblParam()\n"); - switch (key) { - case OsiDualObjectiveLimit: - this->highs->options_.objective_bound = value; - return true; - case OsiPrimalObjectiveLimit: - return false; - case OsiDualTolerance: - this->highs->options_.dual_feasibility_tolerance = value; - return true; - case OsiPrimalTolerance: - this->highs->options_.primal_feasibility_tolerance = value; - return true; - case OsiObjOffset: - this->objOffset = value; - return true; - case OsiLastDblParam: - default: - return false; - } -} - -bool OsiHiGHSSolverInterface::setStrParam(OsiStrParam key, - const std::string& value) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setStrParam(%" HIGHSINT_FORMAT - ", %s)\n", - key, value.c_str()); - switch (key) { - case OsiProbName: - return OsiSolverInterface::setStrParam(key, value); - case OsiSolverName: - return OsiSolverInterface::setStrParam(key, value); - case OsiLastStrParam: - default: - return false; - } -} - -bool OsiHiGHSSolverInterface::getIntParam(OsiIntParam key, - HighsInt& value) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getIntParam()\n"); - switch (key) { - case OsiMaxNumIteration: - case OsiMaxNumIterationHotStart: - value = this->highs->options_.simplex_iteration_limit; - return true; - case OsiNameDiscipline: - // TODO - return false; - case OsiLastIntParam: - default: - return false; - } -} - -bool OsiHiGHSSolverInterface::getDblParam(OsiDblParam key, - double& value) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getDblParam()\n"); - switch (key) { - case OsiDualObjectiveLimit: - value = this->highs->options_.objective_bound; - return true; - case OsiPrimalObjectiveLimit: - return false; - case OsiDualTolerance: - value = this->highs->options_.dual_feasibility_tolerance; - return true; - case OsiPrimalTolerance: - value = this->highs->options_.primal_feasibility_tolerance; - return true; - case OsiObjOffset: - value = this->objOffset; - return true; - case OsiLastDblParam: - default: - return false; - } -} - -bool OsiHiGHSSolverInterface::getStrParam(OsiStrParam key, - std::string& value) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getStrParam(%" HIGHSINT_FORMAT - ", %s)\n", - key, value.c_str()); - switch (key) { - case OsiProbName: - return OsiSolverInterface::getStrParam(key, value); - case OsiSolverName: - return OsiSolverInterface::getStrParam(key, value); - case OsiLastStrParam: - default: - return false; - } -} - -void OsiHiGHSSolverInterface::initialSolve() { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::initialSolve()\n"); - this->status = this->highs->run(); -} - -bool OsiHiGHSSolverInterface::isAbandoned() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isAbandoned()\n"); - // return this->status == HighsStatus::NumericalDifficulties; - return false; -} - -bool OsiHiGHSSolverInterface::isProvenOptimal() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isProvenOptimal()\n"); - // return (this->status == HighsStatus::kOptimal) || - // (this->status == HighsStatus::kOk); - return false; -} - -bool OsiHiGHSSolverInterface::isProvenPrimalInfeasible() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isProvenPrimalInfeasible()\n"); - // return this->status == HighsStatus::kInfeasible; - return false; -} - -bool OsiHiGHSSolverInterface::isProvenDualInfeasible() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isProvenDualInfeasible()\n"); - // return this->status == HighsStatus::Unbounded; - return false; -} - -bool OsiHiGHSSolverInterface::isPrimalObjectiveLimitReached() const { - HighsOptions& options = this->highs->options_; - highsLogDev( - options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isPrimalObjectiveLimitReached()\n"); - return false; -} - -bool OsiHiGHSSolverInterface::isDualObjectiveLimitReached() const { - HighsOptions& options = this->highs->options_; - highsLogDev( - options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isDualObjectiveLimitReached()\n"); - // return this->status == HighsStatus::ReachedDualObjectiveUpperBound; - return false; -} - -bool OsiHiGHSSolverInterface::isIterationLimitReached() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isIterationLimitReached()\n"); - // return this->status == HighsStatus::ReachedIterationLimit; - return false; -} - -HighsInt OsiHiGHSSolverInterface::getNumCols() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getNumCols()\n"); - return this->highs->getNumCol(); -} - -HighsInt OsiHiGHSSolverInterface::getNumRows() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getNumRows()\n"); - return this->highs->getNumRow(); -} - -HighsInt OsiHiGHSSolverInterface::getNumElements() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getNumElements()\n"); - return this->highs->getNumNz(); -} - -const double* OsiHiGHSSolverInterface::getColLower() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getColLower()\n"); - return &(this->highs->getLp().col_lower_[0]); -} - -const double* OsiHiGHSSolverInterface::getColUpper() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getColUpper()\n"); - return &(this->highs->getLp().col_upper_[0]); -} - -const double* OsiHiGHSSolverInterface::getRowLower() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRowLower()\n"); - return &(this->highs->getLp().row_lower_[0]); -} - -const double* OsiHiGHSSolverInterface::getRowUpper() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRowUpper()\n"); - return &(this->highs->getLp().row_upper_[0]); -} - -const double* OsiHiGHSSolverInterface::getObjCoefficients() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getObjCoefficients()\n"); - return &(this->highs->getLp().col_cost_[0]); -} - -// TODO: review: 10^20? -double OsiHiGHSSolverInterface::getInfinity() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getInfinity()\n"); - return kHighsInf; -} - -const double* OsiHiGHSSolverInterface::getRowRange() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRowRange()\n"); - if (this->rowRange != NULL) { - delete[] this->rowRange; - } - - HighsInt nrows = this->getNumRows(); - - if (nrows == 0) { - return this->rowRange; - } - - this->rowRange = new double[nrows]; - - for (HighsInt i = 0; i < nrows; i++) { - // compute range for row i - double lo = this->highs->getLp().row_lower_[i]; - double hi = this->highs->getLp().row_upper_[i]; - double t1; - char t2; - this->convertBoundToSense(lo, hi, t2, t1, this->rowRange[i]); - } - - return this->rowRange; -} - -const double* OsiHiGHSSolverInterface::getRightHandSide() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRightHandSide()\n"); - if (this->rhs != NULL) { - delete[] this->rhs; - } - - HighsInt nrows = this->getNumRows(); - - if (nrows == 0) { - return this->rhs; - } - - this->rhs = new double[nrows]; - - for (HighsInt i = 0; i < nrows; i++) { - // compute rhs for row i - double lo = this->highs->getLp().row_lower_[i]; - double hi = this->highs->getLp().row_upper_[i]; - double t1; - char t2; - this->convertBoundToSense(lo, hi, t2, this->rhs[i], t1); - } - - return this->rhs; -} - -const char* OsiHiGHSSolverInterface::getRowSense() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRowSense()\n"); - if (this->rowSense != NULL) { - delete[] this->rowSense; - } - - HighsInt nrows = this->getNumRows(); - - if (nrows == 0) { - return this->rowSense; - } - - this->rowSense = new char[nrows]; - - for (HighsInt i = 0; i < nrows; i++) { - // compute sense for row i - double lo = this->highs->getLp().row_lower_[i]; - double hi = this->highs->getLp().row_upper_[i]; - double t1, t2; - this->convertBoundToSense(lo, hi, this->rowSense[i], t1, t2); - } - - return this->rowSense; -} - -const CoinPackedMatrix* OsiHiGHSSolverInterface::getMatrixByCol() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getMatrixByCol()\n"); - if (this->matrixByCol != NULL) { - delete this->matrixByCol; - } - - HighsInt nrows = this->getNumRows(); - HighsInt ncols = this->getNumCols(); - HighsInt nelements = this->getNumElements(); - - HighsInt* len = new int[ncols]; - HighsInt* start = new int[ncols + 1]; - HighsInt* index = new int[nelements]; - double* value = new double[nelements]; - - // copy data - memcpy(start, &(this->highs->getLp().a_matrix_.start_[0]), - (ncols + 1) * sizeof(HighsInt)); - memcpy(index, &(this->highs->getLp().a_matrix_.index_[0]), - nelements * sizeof(HighsInt)); - memcpy(value, &(this->highs->getLp().a_matrix_.value_[0]), - nelements * sizeof(double)); - - for (HighsInt i = 0; i < ncols; i++) { - len[i] = start[i + 1] - start[i]; - } - - this->matrixByCol = new CoinPackedMatrix(); - - this->matrixByCol->assignMatrix(true, nrows, ncols, nelements, value, index, - start, len); - assert(this->matrixByCol->getNumCols() == ncols); - assert(this->matrixByCol->getNumRows() == nrows); - - return this->matrixByCol; -} - -const CoinPackedMatrix* OsiHiGHSSolverInterface::getMatrixByRow() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getMatrixByRow()\n"); - if (this->matrixByRow != NULL) { - delete this->matrixByRow; - } - this->matrixByRow = new CoinPackedMatrix(); - this->matrixByRow->reverseOrderedCopyOf(*this->getMatrixByCol()); - - return this->matrixByRow; -} - -double OsiHiGHSSolverInterface::getObjSense() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getObjSense()\n"); - ObjSense sense; - this->highs->getObjectiveSense(sense); - return (double)sense; -} - -void OsiHiGHSSolverInterface::setObjSense(double s) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setObjSense()\n"); - ObjSense pass_sense = ObjSense::kMinimize; - if (s == (double)ObjSense::kMaximize) pass_sense = ObjSense::kMaximize; - this->highs->changeObjectiveSense(pass_sense); -} - -void OsiHiGHSSolverInterface::addRow(const CoinPackedVectorBase& vec, - const double rowlb, const double rowub) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::addRow()\n"); - HighsStatus status = this->highs->addRow(rowlb, rowub, vec.getNumElements(), - vec.getIndices(), vec.getElements()); - assert(status == HighsStatus::kOk); - if (status != HighsStatus::kOk) { - highsLogDev(options.log_options, HighsLogType::kInfo, - "Return from OsiHiGHSSolverInterface::addRow() is not ok\n"); - } -} - -void OsiHiGHSSolverInterface::addRow(const CoinPackedVectorBase& vec, - const char rowsen, const double rowrhs, - const double rowrng) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::addRow()\n"); - // Assign arbitrary values so that compilation is clean - double lb = 0; - double ub = 1e200; - this->convertSenseToBound(rowsen, rowrhs, rowrng, lb, ub); - this->addRow(vec, lb, ub); -} - -void OsiHiGHSSolverInterface::addCol(const CoinPackedVectorBase& vec, - const double collb, const double colub, - const double obj) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::addCol()\n"); - HighsStatus status = - this->highs->addCol(obj, collb, colub, vec.getNumElements(), - vec.getIndices(), vec.getElements()); - assert(status == HighsStatus::kOk); - if (status != HighsStatus::kOk) { - highsLogDev(options.log_options, HighsLogType::kInfo, - "Return from OsiHiGHSSolverInterface::addCol() is not ok\n"); - } -} - -void OsiHiGHSSolverInterface::deleteCols(const HighsInt num, - const HighsInt* colIndices) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::deleteCols()\n"); - this->highs->deleteCols(num, colIndices); -} - -void OsiHiGHSSolverInterface::deleteRows(const HighsInt num, - const HighsInt* rowIndices) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::deleteRows()\n"); - this->highs->deleteRows(num, rowIndices); -} - -void OsiHiGHSSolverInterface::assignProblem(CoinPackedMatrix*& matrix, - double*& collb, double*& colub, - double*& obj, double*& rowlb, - double*& rowub) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::assignProblem()\n"); - loadProblem(*matrix, collb, colub, obj, rowlb, rowub); - delete matrix; - matrix = 0; - delete[] collb; - collb = 0; - delete[] colub; - colub = 0; - delete[] obj; - obj = 0; - delete[] rowlb; - rowlb = 0; - delete[] rowub; - rowub = 0; -} - -void OsiHiGHSSolverInterface::loadProblem(const CoinPackedMatrix& matrix, - const double* collb, - const double* colub, - const double* obj, const char* rowsen, - const double* rowrhs, - const double* rowrng) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::loadProblem()\n"); - HighsInt numRow = matrix.getNumRows(); - - double* rowlb = new double[numRow]; - double* rowub = new double[numRow]; - - char* myrowsen = (char*)rowsen; - bool rowsennull = false; - double* myrowrhs = (double*)rowrhs; - bool rowrhsnull = false; - double* myrowrng = (double*)rowrng; - bool rowrngnull = false; - - if (rowsen == NULL) { - rowsennull = true; - myrowsen = new char[numRow]; - for (HighsInt i = 0; i < numRow; i++) { - myrowsen[i] = 'G'; - } - } - - if (rowrhs == NULL) { - rowsennull = true; - myrowrhs = new double[numRow]; - for (HighsInt i = 0; i < numRow; i++) { - myrowrhs[i] = 0.0; - } - } - - if (rowrng == NULL) { - rowrngnull = true; - myrowrng = new double[numRow]; - for (HighsInt i = 0; i < numRow; i++) { - myrowrng[i] = 0.0; - } - } - - for (HighsInt i = 0; i < numRow; i++) { - this->convertSenseToBound(myrowsen[i], myrowrhs[i], myrowrng[i], rowlb[i], - rowub[i]); - } - - this->loadProblem(matrix, collb, colub, obj, rowlb, rowub); - - delete[] rowlb; - delete[] rowub; - - if (rowsennull) { - delete[] myrowsen; - } - - if (rowrhsnull) { - delete[] myrowrhs; - } - - if (rowrngnull) { - delete[] myrowrng; - } -} - -void OsiHiGHSSolverInterface::assignProblem(CoinPackedMatrix*& matrix, - double*& collb, double*& colub, - double*& obj, char*& rowsen, - double*& rowrhs, double*& rowrng) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::assignProblem()\n"); - loadProblem(*matrix, collb, colub, obj, rowsen, rowrhs, rowrng); - delete matrix; - matrix = 0; - delete[] collb; - collb = 0; - delete[] colub; - colub = 0; - delete[] obj; - obj = 0; - delete[] rowsen; - rowsen = 0; - delete[] rowrhs; - rowrhs = 0; - delete[] rowrng; - rowrng = 0; -} - -void OsiHiGHSSolverInterface::loadProblem( - const HighsInt numcols, const HighsInt numrows, const CoinBigIndex* start, - const HighsInt* index, const double* value, const double* collb, - const double* colub, const double* obj, const double* rowlb, - const double* rowub) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::loadProblem()\n"); - double oldObjSense = this->getObjSense(); - - HighsLp lp; - - lp.num_row_ = numrows; - lp.num_col_ = numcols; - - // setup HighsLp data structures - lp.col_cost_.resize(numcols); - lp.col_upper_.resize(numcols); - lp.col_lower_.resize(numcols); - - lp.row_lower_.resize(numrows); - lp.row_upper_.resize(numrows); - - lp.a_matrix_.start_.resize(numcols + 1); - lp.a_matrix_.index_.resize(start[numcols]); - lp.a_matrix_.value_.resize(start[numcols]); - - // copy data - if (obj != NULL) { - lp.col_cost_.assign(obj, obj + numcols); - } else { - lp.col_cost_.assign(numcols, 0.0); - } - - if (collb != NULL) { - lp.col_lower_.assign(collb, collb + numcols); - } else { - lp.col_lower_.assign(numcols, 0.0); - } - - if (colub != NULL) { - lp.col_upper_.assign(colub, colub + numcols); - } else { - lp.col_upper_.assign(numcols, kHighsInf); - } - - if (rowlb != NULL) { - lp.row_lower_.assign(rowlb, rowlb + numrows); - } else { - lp.row_lower_.assign(numrows, -kHighsInf); - } - - if (rowub != NULL) { - lp.row_upper_.assign(rowub, rowub + numrows); - } else { - lp.row_upper_.assign(numrows, kHighsInf); - } - - lp.a_matrix_.start_.assign(start, start + numcols + 1); - lp.a_matrix_.index_.assign(index, index + start[numcols]); - lp.a_matrix_.value_.assign(value, value + start[numcols]); - this->highs->passModel(lp); - this->setObjSense(oldObjSense); -} - -void OsiHiGHSSolverInterface::loadProblem( - const HighsInt numcols, const HighsInt numrows, const CoinBigIndex* start, - const HighsInt* index, const double* value, const double* collb, - const double* colub, const double* obj, const char* rowsen, - const double* rowrhs, const double* rowrng) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::loadProblem()\n"); - double* rowlb = new double[numrows]; - double* rowub = new double[numrows]; - - for (HighsInt i = 0; i < numrows; i++) { - this->convertSenseToBound(rowsen[i], rowrhs[i], rowrng[i], rowlb[i], - rowub[i]); - } - - this->loadProblem(numcols, numrows, start, index, value, collb, colub, obj, - rowlb, rowub); - - delete[] rowlb; - delete[] rowub; -} - -void OsiHiGHSSolverInterface::loadProblem( - const CoinPackedMatrix& matrix, const double* collb, const double* colub, - const double* obj, const double* rowlb, const double* rowub) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::loadProblem()\n"); - bool transpose = false; - if (!matrix.isColOrdered()) { - transpose = true; - // ToDo: remove this hack - //((CoinPackedMatrix *)&matrix)->transpose(); - ((CoinPackedMatrix*)&matrix)->reverseOrdering(); - } - - HighsInt numCol = matrix.getNumCols(); - HighsInt numRow = matrix.getNumRows(); - HighsInt num_nz = matrix.getNumElements(); - - HighsInt* start = new int[numCol + 1]; - HighsInt* index = new int[num_nz]; - double* value = new double[num_nz]; - - // get matrix data - // const CoinBigIndex *vectorStarts = matrix.getVectorStarts(); - const HighsInt* vectorLengths = matrix.getVectorLengths(); - const double* elements = matrix.getElements(); - const HighsInt* indices = matrix.getIndices(); - - // set matrix in HighsLp - start[0] = 0; - HighsInt nz = 0; - for (HighsInt i = 0; i < numCol; i++) { - start[i + 1] = start[i] + vectorLengths[i]; - CoinBigIndex first = matrix.getVectorFirst(i); - for (HighsInt j = 0; j < vectorLengths[i]; j++) { - index[nz] = indices[first + j]; - value[nz] = elements[first + j]; - nz++; - } - } - assert(num_nz == nz); - - this->loadProblem(numCol, numRow, start, index, value, collb, colub, obj, - rowlb, rowub); - - if (transpose) { - //((CoinPackedMatrix)matrix).transpose(); - ((CoinPackedMatrix*)&matrix)->reverseOrdering(); - } - - delete[] start; - delete[] index; - delete[] value; -} - -/// Read a problem in MPS format from the given filename. -// HighsInt OsiHiGHSSolverInterface::readMps(const char *filename, -// const char *extension) -// { -// HighsOptions& options = this->highs->options_; -// highsLogDev(options.log_options, HighsLogType::kInfo, -// "Calling OsiHiGHSSolverInterface::readMps()\n"); - -// HighsModel model; - -// highs->options_.filename = std::string(filename) + "." + -// std::string(extension); - -// FilereaderRetcode rc = FilereaderMps().readModelFromFile(highs->options_, -// model); if (rc != FilereaderRetcode::kOk) -// return (HighsInt)rc; -// this->setDblParam(OsiDblParam::OsiObjOffset, model.lp_.offset_); -// highs->passModel(model); - -// return 0; -// } - -/// Write the problem into an mps file of the given filename. -void OsiHiGHSSolverInterface::writeMps(const char* filename, - const char* extension, - double objSense) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::writeMps()\n"); - - std::string fullname = std::string(filename) + "." + std::string(extension); - - if (objSense != 0.0) { - // HiGHS doesn't do funny stuff with the objective sense, so use Osi's - // method if something strange is requested - OsiSolverInterface::writeMpsNative(fullname.c_str(), NULL, NULL, 0, 2, - objSense); - return; - } - - FilereaderMps frmps; - HighsStatus rc = - frmps.writeModelToFile(highs->options_, fullname, highs->model_); - - if (rc != HighsStatus::kOk) - throw CoinError("Creating MPS file failed", "writeMps", - "OsiHiGHSSolverInterface", __FILE__, __LINE__); -} - -void OsiHiGHSSolverInterface::passInMessageHandler( - CoinMessageHandler* handler) { - OsiSolverInterface::passInMessageHandler(handler); - - this->highs->setLogCallback(logtomessagehandler, (void*)handler); -} - -const double* OsiHiGHSSolverInterface::getColSolution() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getColSolution()\n"); - if (!highs) { - return nullptr; - } else { - if (highs->solution_.col_value.size() == 0) { - double num_cols = highs->getNumCol(); - this->dummy_solution->col_value.resize(num_cols); - for (HighsInt col = 0; col < highs->getNumCol(); col++) { - if (highs->getLp().col_lower_[col] <= 0 && - highs->getLp().col_upper_[col] >= 0) - dummy_solution->col_value[col] = 0; - else if (std::fabs(highs->getLp().col_lower_[col] < - std::fabs(highs->getLp().col_upper_[col]))) - dummy_solution->col_value[col] = highs->getLp().col_lower_[col]; - else - dummy_solution->col_value[col] = highs->getLp().col_upper_[col]; - } - return &dummy_solution->col_value[0]; - } - } - - return &highs->solution_.col_value[0]; -} - -const double* OsiHiGHSSolverInterface::getRowPrice() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRowPrice()\n"); - if (!highs) - return nullptr; - else { - if (highs->solution_.row_dual.size() == 0) { - double num_cols = highs->getNumCol(); - this->dummy_solution->row_dual.resize(num_cols); - for (HighsInt col = 0; col < highs->getNumCol(); col++) { - if (highs->getLp().col_lower_[col] <= 0 && - highs->getLp().col_upper_[col] >= 0) - dummy_solution->row_dual[col] = 0; - else if (std::fabs(highs->getLp().col_lower_[col] < - std::fabs(highs->getLp().col_upper_[col]))) - dummy_solution->row_dual[col] = highs->getLp().col_lower_[col]; - else - dummy_solution->row_dual[col] = highs->getLp().col_upper_[col]; - } - return &dummy_solution->row_dual[0]; - } - } - - return &highs->solution_.row_dual[0]; -} - -const double* OsiHiGHSSolverInterface::getReducedCost() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getReducedCost()\n"); - if (!highs) - return nullptr; - else { - if (highs->solution_.col_dual.size() == 0) { - const HighsLp& lp = highs->getLp(); - double num_cols = lp.num_col_; - this->dummy_solution->col_dual.resize(num_cols); - for (HighsInt col = 0; col < num_cols; col++) { - dummy_solution->col_dual[col] = lp.col_cost_[col]; - for (HighsInt i = lp.a_matrix_.start_[col]; - i < lp.a_matrix_.start_[col + 1]; i++) { - const HighsInt row = lp.a_matrix_.index_[i]; - assert(row >= 0); - assert(row < lp.num_row_); - - dummy_solution->col_dual[col] -= - dummy_solution->row_dual[row] * lp.a_matrix_.value_[i]; - } - } - return &dummy_solution->col_dual[0]; - } - } - - return &highs->solution_.col_dual[0]; -} - -const double* OsiHiGHSSolverInterface::getRowActivity() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getRowActivity()\n"); - if (!highs) - return nullptr; - else { - if (highs->solution_.row_value.size() == 0) { - double num_cols = highs->getNumCol(); - this->dummy_solution->row_value.resize(num_cols); - for (HighsInt col = 0; col < highs->getNumCol(); col++) { - if (highs->getLp().col_lower_[col] <= 0 && - highs->getLp().col_upper_[col] >= 0) - dummy_solution->row_value[col] = 0; - else if (std::fabs(highs->getLp().col_lower_[col] < - std::fabs(highs->getLp().col_upper_[col]))) - dummy_solution->row_value[col] = highs->getLp().col_lower_[col]; - else - dummy_solution->row_value[col] = highs->getLp().col_upper_[col]; - } - return &dummy_solution->row_value[0]; - } - } - - return &highs->solution_.row_value[0]; -} - -double OsiHiGHSSolverInterface::getObjValue() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getObjValue()\n"); - double objVal = 0.0; - if (true || highs->solution_.col_value.size() == 0) { - const double* sol = this->getColSolution(); - const double* cost = this->getObjCoefficients(); - HighsInt ncols = this->getNumCols(); - - objVal = -this->objOffset; - for (HighsInt i = 0; i < ncols; i++) { - objVal += sol[i] * cost[i]; - } - } else { - this->highs->getInfoValue("objective_function_value", objVal); - } - - return objVal; -} - -HighsInt OsiHiGHSSolverInterface::getIterationCount() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getIterationCount()\n"); - if (!highs) { - return 0; - } - HighsInt iteration_count; - this->highs->getInfoValue("simplex_iteration_count", iteration_count); - return iteration_count; -} - -void OsiHiGHSSolverInterface::setRowPrice(const double* rowprice) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - - "Calling OsiHiGHSSolverInterface::setRowPrice()\n"); - if (!rowprice) return; - HighsSolution solution; - solution.row_dual.resize(highs->getNumRow()); - for (HighsInt row = 0; row < highs->getNumRow(); row++) - solution.row_dual[row] = rowprice[row]; - - /*HighsStatus result =*/highs->setSolution(solution); -} - -void OsiHiGHSSolverInterface::setColSolution(const double* colsol) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setColSolution()\n"); - if (!colsol) return; - HighsSolution solution; - solution.col_value.resize(highs->getNumCol()); - for (HighsInt col = 0; col < highs->getNumCol(); col++) - solution.col_value[col] = colsol[col]; - - /*HighsStatus result =*/highs->setSolution(solution); -} - -void OsiHiGHSSolverInterface::applyRowCut(const OsiRowCut& rc) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::applyRowCut()\n"); -} - -void OsiHiGHSSolverInterface::applyColCut(const OsiColCut& cc) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::applyColCut()\n"); -} - -void OsiHiGHSSolverInterface::setContinuous(HighsInt index) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setContinuous()\n"); -} - -void OsiHiGHSSolverInterface::setInteger(HighsInt index) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setInteger()\n"); -} - -bool OsiHiGHSSolverInterface::isContinuous(HighsInt colNumber) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::isContinuous()\n"); - return true; -} - -void OsiHiGHSSolverInterface::setRowType(HighsInt index, char sense, - double rightHandSide, double range) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setRowType()\n"); - // Assign arbitrary values so that compilation is clean - double lo = 0; - double hi = 1e200; - this->convertSenseToBound(sense, rightHandSide, range, lo, hi); - this->setRowBounds(index, lo, hi); -} - -void OsiHiGHSSolverInterface::setRowLower(HighsInt elementIndex, - double elementValue) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setRowLower()\n"); - - double upper = this->getRowUpper()[elementIndex]; - - this->highs->changeRowBounds(elementIndex, elementValue, upper); -} - -void OsiHiGHSSolverInterface::setRowUpper(HighsInt elementIndex, - double elementValue) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setRowUpper()\n"); - double lower = this->getRowLower()[elementIndex]; - this->highs->changeRowBounds(elementIndex, lower, elementValue); -} - -void OsiHiGHSSolverInterface::setColLower(HighsInt elementIndex, - double elementValue) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setColLower()\n"); - double upper = this->getColUpper()[elementIndex]; - this->highs->changeColBounds(elementIndex, elementValue, upper); -} - -void OsiHiGHSSolverInterface::setColUpper(HighsInt elementIndex, - double elementValue) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setColUpper()\n"); - double lower = this->getColLower()[elementIndex]; - this->highs->changeColBounds(elementIndex, lower, elementValue); -} - -void OsiHiGHSSolverInterface::setObjCoeff(HighsInt elementIndex, - double elementValue) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setObjCoeff()\n"); - this->highs->changeColCost(elementIndex, elementValue); -} - -std::vector OsiHiGHSSolverInterface::getDualRays(HighsInt maxNumRays, - bool fullRay) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getDualRays()\n"); - return std::vector(0); -} - -std::vector OsiHiGHSSolverInterface::getPrimalRays( - HighsInt maxNumRays) const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getPrimalRays()\n"); - return std::vector(0); -} - -CoinWarmStart* OsiHiGHSSolverInterface::getEmptyWarmStart() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getEmptyWarmStart()\n"); - return (dynamic_cast(new CoinWarmStartBasis())); -} - -CoinWarmStart* OsiHiGHSSolverInterface::getWarmStart() const { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::getWarmStart()\n"); - if (!highs) return NULL; - - if (highs->basis_.col_status.size() == 0 || - highs->basis_.row_status.size() == 0) - return NULL; - - HighsInt num_cols = highs->getNumCol(); - HighsInt num_rows = highs->getNumRow(); - - HighsInt* cstat = new int[num_cols]; - HighsInt* rstat = new int[num_rows]; - - getBasisStatus(cstat, rstat); - - CoinWarmStartBasis* warm_start = new CoinWarmStartBasis(); - warm_start->setSize(num_cols, num_rows); - - for (HighsInt i = 0; i < num_rows; ++i) - warm_start->setArtifStatus(i, CoinWarmStartBasis::Status(rstat[i])); - for (HighsInt i = 0; i < num_cols; ++i) - warm_start->setStructStatus(i, CoinWarmStartBasis::Status(cstat[i])); - - return warm_start; -} - -bool OsiHiGHSSolverInterface::setWarmStart(const CoinWarmStart* warmstart) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setWarmStart()\n"); - return false; -} - -void OsiHiGHSSolverInterface::resolve() { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::resolve()\n"); - this->status = this->highs->run(); -} - -void OsiHiGHSSolverInterface::setRowBounds(HighsInt elementIndex, double lower, - double upper) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setRowBounds()\n"); - - this->highs->changeRowBounds(elementIndex, lower, upper); -} - -void OsiHiGHSSolverInterface::setColBounds(HighsInt elementIndex, double lower, - double upper) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setColBounds()\n"); - - this->highs->changeColBounds(elementIndex, lower, upper); -} - -void OsiHiGHSSolverInterface::setRowSetBounds(const HighsInt* indexFirst, - const HighsInt* indexLast, - const double* boundList) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setRowSetBounds()\n"); - OsiSolverInterface::setRowSetBounds(indexFirst, indexLast - 1, boundList); -} - -void OsiHiGHSSolverInterface::setColSetBounds(const HighsInt* indexFirst, - const HighsInt* indexLast, - const double* boundList) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setColSetBounds()\n"); - OsiSolverInterface::setColSetBounds(indexFirst, indexLast - 1, boundList); -} - -void OsiHiGHSSolverInterface::branchAndBound() { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::branchAndBound()\n"); - // TODO -} - -void OsiHiGHSSolverInterface::setObjCoeffSet(const HighsInt* indexFirst, - const HighsInt* indexLast, - const double* coeffList) { - HighsOptions& options = this->highs->options_; - highsLogDev(options.log_options, HighsLogType::kInfo, - "Calling OsiHiGHSSolverInterface::setObjCoeffSet()\n"); - OsiSolverInterface::setObjCoeffSet(indexFirst, indexLast - 1, coeffList); -} - -HighsInt OsiHiGHSSolverInterface::canDoSimplexInterface() const { return 0; } - -/* Osi return codes: -0: free -1: basic -2: upper -3: lower -*/ -void OsiHiGHSSolverInterface::getBasisStatus(HighsInt* cstat, - HighsInt* rstat) const { - if (!highs) return; - - if (highs->basis_.col_status.size() == 0 || - highs->basis_.row_status.size() == 0) - return; - - for (size_t i = 0; i < highs->basis_.col_status.size(); ++i) - switch (highs->basis_.col_status[i]) { - case HighsBasisStatus::kBasic: - cstat[i] = 1; - break; - case HighsBasisStatus::kLower: - cstat[i] = 3; - break; - case HighsBasisStatus::kUpper: - cstat[i] = 2; - break; - case HighsBasisStatus::kZero: - cstat[i] = 0; - break; - case HighsBasisStatus::kNonbasic: - cstat[i] = 3; - break; - } - - for (size_t i = 0; i < highs->basis_.row_status.size(); ++i) - switch (highs->basis_.row_status[i]) { - case HighsBasisStatus::kBasic: - rstat[i] = 1; - break; - case HighsBasisStatus::kLower: - rstat[i] = 3; - break; - case HighsBasisStatus::kUpper: - rstat[i] = 2; - break; - case HighsBasisStatus::kZero: - rstat[i] = 0; - break; - case HighsBasisStatus::kNonbasic: - rstat[i] = 3; - break; - } -} - -void OsiHiGHSSolverInterface::setRowNames(OsiNameVec& srcNames, - HighsInt srcStart, HighsInt len, - HighsInt tgtStart) {} - -void OsiHiGHSSolverInterface::setColNames(OsiNameVec& srcNames, - HighsInt srcStart, HighsInt len, - HighsInt tgtStart) {} - -void OsiSolverInterfaceMpsUnitTest( - const std::vector& vecSiP, const std::string& mpsDir) { -} diff --git a/src/interfaces/OsiHiGHSSolverInterface.hpp b/src/interfaces/OsiHiGHSSolverInterface.hpp deleted file mode 100644 index 235689f2d4..0000000000 --- a/src/interfaces/OsiHiGHSSolverInterface.hpp +++ /dev/null @@ -1,415 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* */ -/* This file is part of the HiGHS linear optimization suite */ -/* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ -/* */ -/* Available as open-source under the MIT License */ -/* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/**@file interfaces/OsiHiGHSInterface.hpp - * @brief Osi/HiGHS interface header - */ -#ifndef OsiHiGHSSolverInterface_H -#define OsiHiGHSSolverInterface_H - -#include "OsiSolverInterface.hpp" - -// forward declarations -class Highs; -class HighsLp; -struct HighsSolution; -enum class HighsStatus; - -/** HiGHS Solver Interface - * - * Instantiation of OsiSolverInterface for HiGHS - */ -class OsiHiGHSSolverInterface : virtual public OsiSolverInterface { - public: - //--------------------------------------------------------------------------- - /**@name Solve methods */ - //@{ - /// Solve initial LP relaxation - /// @todo implement - virtual void initialSolve(); - - /// Resolve an LP relaxation after problem modification - /// @todo implement - virtual void resolve(); - - /// Invoke solver's built-in enumeration algorithm - /// @todo implement - virtual void branchAndBound(); - //@} - - //--------------------------------------------------------------------------- - ///@name Parameter set/get methods - ///@todo use OsiSolverInterface default implementation or override? - ///@{ - // Set an integer parameter - bool setIntParam(OsiIntParam key, int value); - // Set an double parameter - bool setDblParam(OsiDblParam key, double value); - // Set a string parameter - bool setStrParam(OsiStrParam key, const std::string& value); - // Get an integer parameter - bool getIntParam(OsiIntParam key, int& value) const; - // Get an double parameter - bool getDblParam(OsiDblParam key, double& value) const; - // Get a string parameter - bool getStrParam(OsiStrParam key, std::string& value) const; - //@} - - //--------------------------------------------------------------------------- - ///@name Methods returning info on how the solution process terminated - ///@{ - ///@todo implement - /// Are there a numerical difficulties? - virtual bool isAbandoned() const; - /// Is optimality proven? - virtual bool isProvenOptimal() const; - /// Is primal infeasiblity proven? - virtual bool isProvenPrimalInfeasible() const; - /// Is dual infeasiblity proven? - virtual bool isProvenDualInfeasible() const; - /// Is the given primal objective limit reached? - virtual bool isPrimalObjectiveLimitReached() const; - /// Is the given dual objective limit reached? - virtual bool isDualObjectiveLimitReached() const; - /// Iteration limit reached? - virtual bool isIterationLimitReached() const; - //@} - - //--------------------------------------------------------------------------- - ///@name Warm start methods - ///@{ - ///@todo implement - - /// Get an empty warm start object - CoinWarmStart* getEmptyWarmStart() const; - - /// Get warmstarting information - virtual CoinWarmStart* getWarmStart() const; - - /** Set warmstarting information. Return true/false depending on whether - * the warmstart information was accepted or not. - */ - virtual bool setWarmStart(const CoinWarmStart* warmstart); - ///@} - - //--------------------------------------------------------------------------- - ///@name Problem query methods - ///@{ - ///@todo implement - - /// Get number of columns - virtual int getNumCols() const; - - /// Get number of rows - virtual int getNumRows() const; - - /// Get number of nonzero elements - virtual int getNumElements() const; - - /// Get pointer to array[getNumCols()] of column lower bounds - virtual const double* getColLower() const; - - /// Get pointer to array[getNumCols()] of column upper bounds - virtual const double* getColUpper() const; - - /// Get pointer to array[getNumRows()] of row constraint senses. - virtual const char* getRowSense() const; - - /// Get pointer to array[getNumRows()] of rows right-hand sides - virtual const double* getRightHandSide() const; - - /// Get pointer to array[getNumRows()] of row ranges. - virtual const double* getRowRange() const; - - /// Get pointer to array[getNumRows()] of row lower bounds - virtual const double* getRowLower() const; - - /// Get pointer to array[getNumRows()] of row upper bounds - virtual const double* getRowUpper() const; - - /// Get pointer to array[getNumCols()] of objective function coefficients - virtual const double* getObjCoefficients() const; - - /// Get objective function sense (1 for min (default), -1 for max) - double getObjSense() const; - - /// Return true if column is continuous - virtual bool isContinuous(int colNumber) const; - - /// Get pointer to row-wise copy of matrix - virtual const CoinPackedMatrix* getMatrixByRow() const; - - /// Get pointer to column-wise copy of matrix - virtual const CoinPackedMatrix* getMatrixByCol() const; - - /// Get solver's value for infinity - virtual double getInfinity() const; - //@} - - ///@name Solution query methods - ///@{ - ///@todo implement - - /// Get pointer to array[getNumCols()] of primal solution vector - const double* getColSolution() const; - - /// Get pointer to array[getNumRows()] of dual prices - const double* getRowPrice() const; - - /// Get a pointer to array[getNumCols()] of reduced costs - const double* getReducedCost() const; - - /// Get pointer to array[getNumRows()] of row activity levels (constraint - /// matrix times the solution vector) - const double* getRowActivity() const; - - /// Get objective function value - double getObjValue() const; - - /// Get how many iterations it took to solve the problem (whatever "iteration" - /// mean to the solver) - int getIterationCount() const; - - /// Get as many dual rays as the solver can provide. - virtual std::vector getDualRays(int maxNumRays, - bool fullRay = false) const; - - /// Get as many primal rays as the solver can provide. - virtual std::vector getPrimalRays(int maxNumRays) const; - - ///@} - - //--------------------------------------------------------------------------- - - ///@name Methods to modify the objective, bounds, and solution - ///@{ - ///@todo implement - - /// Set an objective function coefficient - virtual void setObjCoeff(int elementIndex, double elementValue); - - /// Set a set of objective function coefficients - virtual void setObjCoeffSet(const int* indexFirst, const int* indexLast, - const double* coeffList); - - /// Set objective function sense (1 for min (default), -1 for max) - virtual void setObjSense(double s); - - using OsiSolverInterface::setColLower; - /// Set a single column lower bound - virtual void setColLower(int elementIndex, double elementValue); - - using OsiSolverInterface::setColUpper; - /// Set a single column upper bound - virtual void setColUpper(int elementIndex, double elementValue); - - /// Set a single column lower and upper bound - virtual void setColBounds(int elementIndex, - double lower, double upper); - - /// Set the bounds on a number of columns simultaneously - virtual void setColSetBounds(const int *indexFirst, - const int *indexLast, - const double *boundList); - - /// Set a single row lower bound - virtual void setRowLower(int elementIndex, double elementValue); - - /// Set a single row upper bound - virtual void setRowUpper(int elementIndex, double elementValue); - - /// Set a single row lower and upper bound - virtual void setRowBounds(int elementIndex, double lower, double upper); - - /// Set the bounds on a number of rows simultaneously - virtual void setRowSetBounds(const int *indexFirst, - const int *indexLast, - const double *boundList); - - /// Set the type of a single row - virtual void setRowType(int index, char sense, double rightHandSide, - double range); - - /// Set the type of a number of rows simultaneously - // virtual void setRowSetTypes(const int *indexFirst, - // const int *indexLast, - // const char *senseList, - // const double *rhsList, - // const double *rangeList); - //@} - - /// Set the primal solution column values - virtual void setColSolution(const double* colsol); - - /// Set dual solution vector - void setRowPrice(const double* rowprice); - - /// Set the index-th variable to be a continuous variable - virtual void setContinuous(int index); - - /// Set the index-th variable to be an integer variable - virtual void setInteger(int index); - - /// Set the variables listed in indices (which is of length len) to be - /// continuous variables - // virtual void setContinuous(const int *indices, int len); - - /// Set the variables listed in indices (which is of length len) to be integer - /// variables - // virtual void setInteger(const int *indices, int len); - - //------------------------------------------------------------------------- - ///@name Methods to modify the constraint system. - ///@{ - ///@todo implement - - using OsiSolverInterface::addCol; - // Add a column (primal variable) to the problem. - virtual void addCol(const CoinPackedVectorBase& vec, const double collb, - const double colub, const double obj); - - /// Remove a set of columns (primal variables) from the problem. - virtual void deleteCols(const int num, const int* colIndices); - - using OsiSolverInterface::addRow; - /// Add a row (constraint) to the problem. - void addRow(const CoinPackedVectorBase& vec, const double rowlb, - const double rowub); - - /// Add a row (constraint) to the problem. */ - virtual void addRow(const CoinPackedVectorBase& vec, const char rowsen, - const double rowrhs, const double rowrng); - - /// Delete a set of rows (constraints) from the problem. - virtual void deleteRows(const int num, const int* rowIndices); - - ///@} - - //--------------------------------------------------------------------------- - ///@name Methods for problem input and output - ///@{ - ///@todo implement - - /// Load in an problem by copying the arguments - virtual void loadProblem(const CoinPackedMatrix& matrix, const double* collb, - const double* colub, const double* obj, - const double* rowlb, const double* rowub); - - /// Load in an problem by assuming ownership of the arguments. - virtual void assignProblem(CoinPackedMatrix*& matrix, double*& collb, - double*& colub, double*& obj, double*& rowlb, - double*& rowub); - - /// Load in an problem by copying the arguments. - virtual void loadProblem(const CoinPackedMatrix& matrix, const double* collb, - const double* colub, const double* obj, - const char* rowsen, const double* rowrhs, - const double* rowrng); - - /// Load in an problem by assuming ownership of the arguments - virtual void assignProblem(CoinPackedMatrix*& matrix, double*& collb, - double*& colub, double*& obj, char*& rowsen, - double*& rowrhs, double*& rowrng); - - /// Load in a problem by copying the arguments. - virtual void loadProblem(const int numcols, const int numrows, - const CoinBigIndex* start, const int* index, - const double* value, const double* collb, - const double* colub, const double* obj, - const double* rowlb, const double* rowub); - - /// Load in a problem by copying the arguments. - virtual void loadProblem(const int numcols, const int numrows, - const CoinBigIndex* start, const int* index, - const double* value, const double* collb, - const double* colub, const double* obj, - const char* rowsen, const double* rowrhs, - const double* rowrng); - - /// Read a problem in MPS format from the given filename. - // int readMps(const char *filename, - // const char *extension = "mps"); - - /// Write the problem into an mps file of the given filename. - virtual void writeMps(const char* filename, const char* extension = "mps", - double objSense = 0.0) const; - - ///@} - - virtual int canDoSimplexInterface() const; - - ///@name Message handling - ///@{ - - /// Pass in a message handler - virtual void passInMessageHandler(CoinMessageHandler *handler); - - ///@} - - ///@name Constructors and destructor - ///@{ - - /// Default Constructor - OsiHiGHSSolverInterface(); - - /// Clone - /// @todo implement - virtual OsiSolverInterface* clone(bool copyData = true) const; - - /// Copy constructor - // OsiHiGHSSolverInterface(const OsiHiGHSSolverInterface &); - - /// Assignment operator - // OsiHiGHSSolverInterface &operator=(const OsiHiGHSSolverInterface& rhs); - - /// Destructor - virtual ~OsiHiGHSSolverInterface(); - - /// Resets as if default constructor - // virtual void reset(); - ///@} - /***************************************************************************/ - - void getBasisStatus(int *cstat, int *rstat) const; - - protected: - /// Apply a row cut. - virtual void applyRowCut(const OsiRowCut& rc); - - /// Apply a column cut (bound adjustment). - virtual void applyColCut(const OsiColCut& cc); - - void setRowNames(OsiNameVec& srcNames, int srcStart, - - int len, int tgtStart); - - void setColNames(OsiNameVec& srcNames, int srcStart, int len, int tgtStart); - - private: - Highs* highs; - HighsStatus status; - mutable double* rowRange = NULL; - mutable double* rhs = NULL; - mutable char* rowSense = NULL; - mutable CoinPackedMatrix* matrixByCol = NULL; - mutable CoinPackedMatrix* matrixByRow = NULL; - - mutable HighsSolution* dummy_solution; - - double objOffset = 0.0; - - OsiHiGHSSolverInterface(const OsiHiGHSSolverInterface& original); -}; - -void OsiSolverInterfaceMpsUnitTest(const std::vector& vecSiP, const std::string& mpsDir); - -#endif diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index f2bacca839..bb4b9c8f5e 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "highs_c_api.h" @@ -34,10 +32,8 @@ HighsInt Highs_lpCall(const HighsInt num_col, const HighsInt num_row, status = highs.run(); if (status == HighsStatus::kOk) { - HighsSolution solution; - HighsBasis basis; - solution = highs.getSolution(); - basis = highs.getBasis(); + const HighsSolution& solution = highs.getSolution(); + const HighsBasis& basis = highs.getBasis(); *model_status = (HighsInt)highs.getModelStatus(); const HighsInfo& info = highs.getInfo(); @@ -89,8 +85,7 @@ HighsInt Highs_mipCall(const HighsInt num_col, const HighsInt num_row, status = highs.run(); if (status == HighsStatus::kOk) { - HighsSolution solution; - solution = highs.getSolution(); + const HighsSolution& solution = highs.getSolution(); *model_status = (HighsInt)highs.getModelStatus(); const HighsInfo& info = highs.getInfo(); const bool copy_col_value = @@ -134,10 +129,8 @@ HighsInt Highs_qpCall( status = highs.run(); if (status == HighsStatus::kOk) { - HighsSolution solution; - HighsBasis basis; - solution = highs.getSolution(); - basis = highs.getBasis(); + const HighsSolution& solution = highs.getSolution(); + const HighsBasis& basis = highs.getBasis(); *model_status = (HighsInt)highs.getModelStatus(); const HighsInfo& info = highs.getInfo(); @@ -170,16 +163,16 @@ HighsInt Highs_qpCall( return (HighsInt)status; } -void* Highs_create() { return new Highs(); } +void* Highs_create(void) { return new Highs(); } void Highs_destroy(void* highs) { delete (Highs*)highs; } -const char* Highs_version(void) { return highsVersion().c_str(); } -HighsInt Highs_version_major() { return highsVersionMajor(); } -HighsInt Highs_version_minor() { return highsVersionMinor(); } -HighsInt Highs_version_patch() { return highsVersionPatch(); } -const char* Highs_githash() { return highsGithash().c_str(); } -const char* Highs_compilation_date() { return highsCompilationDate().c_str(); } +const char* Highs_version(void) { return highsVersion(); } +HighsInt Highs_versionMajor(void) { return highsVersionMajor(); } +HighsInt Highs_versionMinor(void) { return highsVersionMinor(); } +HighsInt Highs_versionPatch(void) { return highsVersionPatch(); } +const char* Highs_githash(void) { return highsGithash(); } +const char* Highs_compilationDate(void) { return highsCompilationDate(); } HighsInt Highs_run(void* highs) { return (HighsInt)((Highs*)highs)->run(); } @@ -255,6 +248,20 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, ->passHessian(dim, num_nz, format, start, index, value); } +HighsInt Highs_passRowName(const void* highs, const HighsInt row, + const char* name) { + return (HighsInt)((Highs*)highs)->passRowName(row, std::string(name)); +} + +HighsInt Highs_passColName(const void* highs, const HighsInt col, + const char* name) { + return (HighsInt)((Highs*)highs)->passColName(col, std::string(name)); +} + +HighsInt Highs_readOptions(const void* highs, const char* filename) { + return (HighsInt)((Highs*)highs)->readOptions(filename); +} + HighsInt Highs_clear(void* highs) { return (HighsInt)((Highs*)highs)->clear(); } HighsInt Highs_clearModel(void* highs) { @@ -287,33 +294,42 @@ HighsInt Highs_setStringOptionValue(void* highs, const char* option, ->setOptionValue(std::string(option), std::string(value)); } +HighsInt Highs_getNumOptions(const void* highs) { + return ((Highs*)highs)->getNumOptions(); +} + +HighsInt Highs_getOptionName(const void* highs, const HighsInt index, + char** name) { + std::string name_v; + HighsInt retcode = (HighsInt)((Highs*)highs)->getOptionName(index, &name_v); + // Guess we have to add one (for Windows, lol!) because char* is + // null-terminated + const HighsInt malloc_size = sizeof(char) * (name_v.length() + 1); + *name = (char*)malloc(malloc_size); + strcpy(*name, name_v.c_str()); + return retcode; +} + HighsInt Highs_getBoolOptionValue(const void* highs, const char* option, HighsInt* value) { - bool v; - HighsInt retcode = - (HighsInt)((Highs*)highs)->getOptionValue(std::string(option), v); - *value = (HighsInt)v; - return retcode; + return Highs_getBoolOptionValues(highs, option, value, nullptr); } HighsInt Highs_getIntOptionValue(const void* highs, const char* option, HighsInt* value) { - return (HighsInt)((Highs*)highs)->getOptionValue(std::string(option), *value); + return Highs_getIntOptionValues(highs, option, value, nullptr, nullptr, + nullptr); } HighsInt Highs_getDoubleOptionValue(const void* highs, const char* option, double* value) { - return (HighsInt)((Highs*)highs)->getOptionValue(std::string(option), *value); + return Highs_getDoubleOptionValues(highs, option, value, nullptr, nullptr, + nullptr); } HighsInt Highs_getStringOptionValue(const void* highs, const char* option, char* value) { - std::string v; - memset(value, 0, 7); - HighsInt retcode = - (HighsInt)((Highs*)highs)->getOptionValue(std::string(option), v); - strcpy(value, v.c_str()); - return retcode; + return Highs_getStringOptionValues(highs, option, value, nullptr); } HighsInt Highs_getOptionType(const void* highs, const char* option, @@ -325,6 +341,55 @@ HighsInt Highs_getOptionType(const void* highs, const char* option, return retcode; } +HighsInt Highs_getBoolOptionValues(const void* highs, const char* option, + HighsInt* current_value, + HighsInt* default_value) { + bool current_v; + bool default_v; + HighsInt retcode = + (HighsInt)((Highs*)highs) + ->getBoolOptionValues(std::string(option), ¤t_v, &default_v); + if (current_value) *current_value = current_v; + if (default_value) *default_value = default_v; + return retcode; +} + +HighsInt Highs_getIntOptionValues(const void* highs, const char* option, + HighsInt* current_value, HighsInt* min_value, + HighsInt* max_value, + HighsInt* default_value) { + return (HighsInt)((Highs*)highs) + ->getIntOptionValues(std::string(option), current_value, min_value, + max_value, default_value); +} + +HighsInt Highs_getDoubleOptionValues(const void* highs, const char* option, + double* current_value, double* min_value, + double* max_value, double* default_value) { + return (HighsInt)((Highs*)highs) + ->getDoubleOptionValues(std::string(option), current_value, min_value, + max_value, default_value); +} + +HighsInt Highs_getStringOptionValues(const void* highs, const char* option, + char* current_value, char* default_value) { + std::string current_v; + std::string default_v; + // Inherited from Highs_getStringOptionValue: cannot see why this + // was ever useful. Must assume that current_value is of length at + // least 7, which isn't necessarily true + // + // if (current_value) memset(current_value, 0, 7); + // if (default_value) memset(default_value, 0, 7); + HighsInt retcode = + (HighsInt)((Highs*)highs) + ->getStringOptionValues(std::string(option), ¤t_v, &default_v); + // current_value and default_value are nullptr by default + if (current_value) strcpy(current_value, current_v.c_str()); + if (default_value) strcpy(default_value, default_v.c_str()); + return retcode; +} + HighsInt Highs_resetOptions(void* highs) { return (HighsInt)((Highs*)highs)->resetOptions(); } @@ -352,10 +417,19 @@ HighsInt Highs_getInt64InfoValue(const void* highs, const char* info, return (HighsInt)((Highs*)highs)->getInfoValue(info, *value); } +HighsInt Highs_getInfoType(const void* highs, const char* info, + HighsInt* type) { + HighsInfoType t; + HighsInt retcode = + (HighsInt)((Highs*)highs)->getInfoType(std::string(info), t); + *type = (HighsInt)t; + return retcode; +} + HighsInt Highs_getSolution(const void* highs, double* col_value, double* col_dual, double* row_value, double* row_dual) { - HighsSolution solution = ((Highs*)highs)->getSolution(); + const HighsSolution& solution = ((Highs*)highs)->getSolution(); if (col_value != nullptr) { for (HighsInt i = 0; i < (HighsInt)solution.col_value.size(); i++) { @@ -385,7 +459,7 @@ HighsInt Highs_getSolution(const void* highs, double* col_value, HighsInt Highs_getBasis(const void* highs, HighsInt* col_status, HighsInt* row_status) { - HighsBasis basis = ((Highs*)highs)->getBasis(); + const HighsBasis& basis = ((Highs*)highs)->getBasis(); for (HighsInt i = 0; i < (HighsInt)basis.col_status.size(); i++) { col_status[i] = (HighsInt)basis.col_status[i]; } @@ -625,7 +699,7 @@ HighsInt Highs_changeColsIntegralityByRange(void* highs, } } return (HighsInt)((Highs*)highs) - ->changeColsIntegrality(from_col, to_col, &pass_integrality[0]); + ->changeColsIntegrality(from_col, to_col, pass_integrality.data()); } HighsInt Highs_changeColsIntegralityBySet(void* highs, @@ -640,7 +714,7 @@ HighsInt Highs_changeColsIntegralityBySet(void* highs, } } return (HighsInt)((Highs*)highs) - ->changeColsIntegrality(num_set_entries, set, &pass_integrality[0]); + ->changeColsIntegrality(num_set_entries, set, pass_integrality.data()); } HighsInt Highs_changeColsIntegralityByMask(void* highs, const HighsInt* mask, @@ -654,7 +728,7 @@ HighsInt Highs_changeColsIntegralityByMask(void* highs, const HighsInt* mask, } } return (HighsInt)((Highs*)highs) - ->changeColsIntegrality(mask, &pass_integrality[0]); + ->changeColsIntegrality(mask, pass_integrality.data()); } HighsInt Highs_changeColCost(void* highs, const HighsInt col, @@ -829,6 +903,45 @@ HighsInt Highs_getRowsByMask(const void* highs, const HighsInt* mask, return (HighsInt)status; } +HighsInt Highs_getRowName(const void* highs, const HighsInt row, char* name) { + std::string name_v; + HighsInt retcode = (HighsInt)((Highs*)highs)->getRowName(row, name_v); + strcpy(name, name_v.c_str()); + return retcode; +} + +HighsInt Highs_getRowByName(const void* highs, const char* name, + HighsInt* row) { + HighsInt local_row; + HighsInt retcode = (HighsInt)((Highs*)highs)->getRowByName(name, local_row); + *row = local_row; + return retcode; +} + +HighsInt Highs_getColName(const void* highs, const HighsInt col, char* name) { + std::string name_v; + HighsInt retcode = (HighsInt)((Highs*)highs)->getColName(col, name_v); + strcpy(name, name_v.c_str()); + return retcode; +} + +HighsInt Highs_getColByName(const void* highs, const char* name, + HighsInt* col) { + HighsInt local_col; + HighsInt retcode = (HighsInt)((Highs*)highs)->getColByName(name, local_col); + *col = local_col; + return retcode; +} + +HighsInt Highs_getColIntegrality(const void* highs, const HighsInt col, + HighsInt* integrality) { + HighsVarType integrality_v; + HighsInt retcode = + (HighsInt)((Highs*)highs)->getColIntegrality(col, integrality_v); + *integrality = HighsInt(integrality_v); + return retcode; +} + HighsInt Highs_deleteColsByRange(void* highs, const HighsInt from_col, const HighsInt to_col) { return (HighsInt)((Highs*)highs)->deleteCols(from_col, to_col); @@ -904,13 +1017,13 @@ HighsInt Highs_getModel(const void* highs, const HighsInt a_format, *num_col = lp.num_col_; *num_row = lp.num_row_; if (*num_col > 0) { - memcpy(col_cost, &lp.col_cost_[0], *num_col * sizeof(double)); - memcpy(col_lower, &lp.col_lower_[0], *num_col * sizeof(double)); - memcpy(col_upper, &lp.col_upper_[0], *num_col * sizeof(double)); + memcpy(col_cost, lp.col_cost_.data(), *num_col * sizeof(double)); + memcpy(col_lower, lp.col_lower_.data(), *num_col * sizeof(double)); + memcpy(col_upper, lp.col_upper_.data(), *num_col * sizeof(double)); } if (*num_row > 0) { - memcpy(row_lower, &lp.row_lower_[0], *num_row * sizeof(double)); - memcpy(row_upper, &lp.row_upper_[0], *num_row * sizeof(double)); + memcpy(row_lower, lp.row_lower_.data(), *num_row * sizeof(double)); + memcpy(row_upper, lp.row_upper_.data(), *num_row * sizeof(double)); } // Save the original orientation so that it is recovered @@ -930,16 +1043,16 @@ HighsInt Highs_getModel(const void* highs, const HighsInt a_format, if (*num_col > 0 && *num_row > 0) { *num_nz = lp.a_matrix_.numNz(); - memcpy(a_start, &lp.a_matrix_.start_[0], + memcpy(a_start, lp.a_matrix_.start_.data(), num_start_entries * sizeof(HighsInt)); - memcpy(a_index, &lp.a_matrix_.index_[0], *num_nz * sizeof(HighsInt)); - memcpy(a_value, &lp.a_matrix_.value_[0], *num_nz * sizeof(double)); + memcpy(a_index, lp.a_matrix_.index_.data(), *num_nz * sizeof(HighsInt)); + memcpy(a_value, lp.a_matrix_.value_.data(), *num_nz * sizeof(double)); } if (hessian.dim_ > 0) { *q_num_nz = hessian.start_[*num_col]; - memcpy(q_start, &hessian.start_[0], *num_col * sizeof(HighsInt)); - memcpy(q_index, &hessian.index_[0], *q_num_nz * sizeof(HighsInt)); - memcpy(q_value, &hessian.value_[0], *q_num_nz * sizeof(double)); + memcpy(q_start, hessian.start_.data(), *num_col * sizeof(HighsInt)); + memcpy(q_index, hessian.index_.data(), *q_num_nz * sizeof(HighsInt)); + memcpy(q_value, hessian.value_.data(), *q_num_nz * sizeof(double)); } if ((HighsInt)lp.integrality_.size()) { for (int iCol = 0; iCol < *num_col; iCol++) @@ -975,6 +1088,107 @@ HighsInt Highs_crossover(void* highs, const int num_col, const int num_row, return (HighsInt)((Highs*)highs)->crossover(solution); } +HighsInt Highs_getRanging( + void* highs, + // + double* col_cost_up_value, double* col_cost_up_objective, + HighsInt* col_cost_up_in_var, HighsInt* col_cost_up_ou_var, + double* col_cost_dn_value, double* col_cost_dn_objective, + HighsInt* col_cost_dn_in_var, HighsInt* col_cost_dn_ou_var, + double* col_bound_up_value, double* col_bound_up_objective, + HighsInt* col_bound_up_in_var, HighsInt* col_bound_up_ou_var, + double* col_bound_dn_value, double* col_bound_dn_objective, + HighsInt* col_bound_dn_in_var, HighsInt* col_bound_dn_ou_var, + double* row_bound_up_value, double* row_bound_up_objective, + HighsInt* row_bound_up_in_var, HighsInt* row_bound_up_ou_var, + double* row_bound_dn_value, double* row_bound_dn_objective, + HighsInt* row_bound_dn_in_var, HighsInt* row_bound_dn_ou_var) { + HighsRanging ranging; + HighsInt status = (HighsInt)((Highs*)highs)->getRanging(ranging); + if (status == (HighsInt)HighsStatus::kError) return status; + HighsInt num_col = ((Highs*)highs)->getNumCol(); + HighsInt num_row = ((Highs*)highs)->getNumRow(); + if (col_cost_up_value) + memcpy(col_cost_up_value, ranging.col_cost_up.value_.data(), + num_col * sizeof(double)); + if (col_cost_up_objective) + memcpy(col_cost_up_objective, ranging.col_cost_up.objective_.data(), + num_col * sizeof(double)); + if (col_cost_up_in_var) + memcpy(col_cost_up_in_var, ranging.col_cost_up.in_var_.data(), + num_col * sizeof(HighsInt)); + if (col_cost_up_ou_var) + memcpy(col_cost_up_ou_var, ranging.col_cost_up.ou_var_.data(), + num_col * sizeof(HighsInt)); + + if (col_cost_dn_value) + memcpy(col_cost_dn_value, ranging.col_cost_dn.value_.data(), + num_col * sizeof(double)); + if (col_cost_dn_objective) + memcpy(col_cost_dn_objective, ranging.col_cost_dn.objective_.data(), + num_col * sizeof(double)); + if (col_cost_dn_in_var) + memcpy(col_cost_dn_in_var, ranging.col_cost_dn.in_var_.data(), + num_col * sizeof(HighsInt)); + if (col_cost_dn_ou_var) + memcpy(col_cost_dn_ou_var, ranging.col_cost_dn.ou_var_.data(), + num_col * sizeof(HighsInt)); + + if (col_bound_up_value) + memcpy(col_bound_up_value, ranging.col_bound_up.value_.data(), + num_col * sizeof(double)); + if (col_bound_up_objective) + memcpy(col_bound_up_objective, ranging.col_bound_up.objective_.data(), + num_col * sizeof(double)); + if (col_bound_up_in_var) + memcpy(col_bound_up_in_var, ranging.col_bound_up.in_var_.data(), + num_col * sizeof(HighsInt)); + if (col_bound_up_ou_var) + memcpy(col_bound_up_ou_var, ranging.col_bound_up.ou_var_.data(), + num_col * sizeof(HighsInt)); + + if (col_bound_dn_value) + memcpy(col_bound_dn_value, ranging.col_bound_dn.value_.data(), + num_col * sizeof(double)); + if (col_bound_dn_objective) + memcpy(col_bound_dn_objective, ranging.col_bound_dn.objective_.data(), + num_col * sizeof(double)); + if (col_bound_dn_in_var) + memcpy(col_bound_dn_in_var, ranging.col_bound_dn.in_var_.data(), + num_col * sizeof(HighsInt)); + if (col_bound_dn_ou_var) + memcpy(col_bound_dn_ou_var, ranging.col_bound_dn.ou_var_.data(), + num_col * sizeof(HighsInt)); + + if (row_bound_up_value) + memcpy(row_bound_up_value, ranging.row_bound_up.value_.data(), + num_row * sizeof(double)); + if (row_bound_up_objective) + memcpy(row_bound_up_objective, ranging.row_bound_up.objective_.data(), + num_row * sizeof(double)); + if (row_bound_up_in_var) + memcpy(row_bound_up_in_var, ranging.row_bound_up.in_var_.data(), + num_row * sizeof(HighsInt)); + if (row_bound_up_ou_var) + memcpy(row_bound_up_ou_var, ranging.row_bound_up.ou_var_.data(), + num_row * sizeof(HighsInt)); + + if (row_bound_dn_value) + memcpy(row_bound_dn_value, ranging.row_bound_dn.value_.data(), + num_row * sizeof(double)); + if (row_bound_dn_objective) + memcpy(row_bound_dn_objective, ranging.row_bound_dn.objective_.data(), + num_row * sizeof(double)); + if (row_bound_dn_in_var) + memcpy(row_bound_dn_in_var, ranging.row_bound_dn.in_var_.data(), + num_row * sizeof(HighsInt)); + if (row_bound_dn_ou_var) + memcpy(row_bound_dn_ou_var, ranging.row_bound_dn.ou_var_.data(), + num_row * sizeof(HighsInt)); + + return status; +} + void Highs_resetGlobalScheduler(HighsInt blocking) { Highs::resetGlobalScheduler(blocking); } diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index d583b9c742..4e8a7ed268 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -2,19 +2,19 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_C_API #define HIGHS_C_API #include "util/HighsInt.h" +const HighsInt kHighsMaximumStringLength = 512; + const HighsInt kHighsStatusError = -1; const HighsInt kHighsStatusOk = 0; const HighsInt kHighsStatusWarning = 1; @@ -91,19 +91,19 @@ extern "C" { /** * Formulate and solve a linear program using HiGHS. * - * @param num_col the number of columns - * @param num_row the number of rows - * @param num_nz the number of nonzeros in the constraint matrix - * @param a_format the format of the constraint matrix as a - * `kHighsMatrixFormat` constant - * @param sense the optimization sense as a `kHighsObjSense` constant - * @param offset the objective constant - * @param col_cost array of length [num_col] with the column costs - * @param col_lower array of length [num_col] with the column lower bounds - * @param col_upper array of length [num_col] with the column upper bounds - * @param row_lower array of length [num_row] with the row lower bounds - * @param row_upper array of length [num_row] with the row upper bounds - * @param a_start the constraint matrix is provided to HiGHS in compressed + * @param num_col The number of columns. + * @param num_row The number of rows. + * @param num_nz The number of nonzeros in the constraint matrix. + * @param a_format The format of the constraint matrix as a + * `kHighsMatrixFormat` constant. + * @param sense The optimization sense as a `kHighsObjSense` constant. + * @param offset The objective constant. + * @param col_cost An array of length [num_col] with the column costs. + * @param col_lower An array of length [num_col] with the column lower bounds. + * @param col_upper An array of length [num_col] with the column upper bounds. + * @param row_lower An array of length [num_row] with the row lower bounds. + * @param row_upper An array of length [num_row] with the row upper bounds. + * @param a_start The constraint matrix is provided to HiGHS in compressed * sparse column form (if `a_format` is * `kHighsMatrixFormatColwise`, otherwise compressed sparse row * form). The sparse matrix consists of three arrays, @@ -112,27 +112,28 @@ extern "C" { * column in `a_index`. If `a_format` is * `kHighsMatrixFormatRowwise` the array is of length [num_row] * corresponding to each row. - * @param a_index array of length [num_nz] with indices of matrix entries - * @param a_value array of length [num_nz] with values of matrix entries - * - * @param col_value array of length [num_col], filled with the primal - * column solution - * @param col_dual array of length [num_col], filled with the dual column - * solution - * @param row_value array of length [num_row], filled with the primal row - * solution - * @param row_dual array of length [num_row], filled with the dual row - * solution - * @param col_basis_status array of length [num_col], filled with the basis - * status of the columns in the form of a - * `kHighsBasisStatus` constant - * @param row_basis_status array of length [num_row], filled with the basis - * status of the rows in the form of a - * `kHighsBasisStatus` constant - * @param model_status termination status of the model after the solve in - * the form of a `kHighsModelStatus` constant - * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @param a_index An array of length [num_nz] with indices of matrix entries. + * @param a_value An array of length [num_nz] with values of matrix entries. + * + * @param col_value An array of length [num_col], to be filled with the + * primal column solution. + * @param col_dual An array of length [num_col], to be filled with the + * dual column solution. + * @param row_value An array of length [num_row], to be filled with the + * primal row solution. + * @param row_dual An array of length [num_row], to be filled with the + * dual row solution. + * @param col_basis_status An array of length [num_col], to be filled with the + * basis status of the columns in the form of a + * `kHighsBasisStatus` constant. + * @param row_basis_status An array of length [num_row], to be filled with the + * basis status of the rows in the form of a + * `kHighsBasisStatus` constant. + * @param model_status The location in which to place the termination + * status of the model after the solve in the form of a + * `kHighsModelStatus` constant. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_lpCall(const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, const HighsInt a_format, @@ -152,10 +153,10 @@ HighsInt Highs_lpCall(const HighsInt num_col, const HighsInt num_row, * has an additional `integrality` argument, and that it is missing the * `col_dual`, `row_dual`, `col_basis_status` and `row_basis_status` arguments. * - * @param integrality array of length [num_col] containing a `kHighsVarType` - * constant for each column + * @param integrality An array of length [num_col], containing a + * `kHighsVarType` constant for each column. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_mipCall(const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, const HighsInt a_format, @@ -172,18 +173,19 @@ HighsInt Highs_mipCall(const HighsInt num_col, const HighsInt num_row, * * The signature of this method is identical to `Highs_lpCall`, except that it * has additional arguments for specifying the Hessian matrix. - - * @param q_num_nz the number of nonzeros in the Hessian matrix - * @param q_format the format of the Hessian matrix in the form of a + * + * @param q_num_nz The number of nonzeros in the Hessian matrix. + * @param q_format The format of the Hessian matrix in the form of a * `kHighsHessianStatus` constant. If q_num_nz > 0, this must - be `kHighsHessianFormatTriangular` - * @param q_start the Hessian matrix is provided in the same format as the + * be `kHighsHessianFormatTriangular`. + * @param q_start The Hessian matrix is provided in the same format as the * constraint matrix, using `q_start`, `q_index`, and `q_value` - * in the place of `a_start`, `a_index`, and `a_value` - * @param q_index array of length [q_num_nz] with indices of matrix entries - * @param q_value array of length [q_num_nz] with values of matrix entries - * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * in the place of `a_start`, `a_index`, and `a_value`. + * @param q_index An array of length [q_num_nz] with indices of matrix + * sentries. + * @param q_value An array of length [q_num_nz] with values of matrix entries. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_qpCall( const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, @@ -201,7 +203,7 @@ HighsInt Highs_qpCall( * * Call `Highs_destroy` on the returned reference to clean up allocated memory. * - * @returns A pointer to the Highs instance + * @returns A pointer to the Highs instance. */ void* Highs_create(void); @@ -211,80 +213,80 @@ void* Highs_create(void); * * To empty a model without invalidating `highs`, see `Highs_clearModel`. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. */ void Highs_destroy(void* highs); /** - * Return the HiGHS version number vX.Y.Z + * Return the HiGHS version number as a string of the form "vX.Y.Z". * - * @returns the HiGHS version as a char* + * @returns The HiGHS version as a `char*`. */ const char* Highs_version(void); /** - * Return the HiGHS major version number + * Return the HiGHS major version number. * - * @returns the HiGHS major version number + * @returns The HiGHS major version number. */ -HighsInt Highs_version_major(); +HighsInt Highs_versionMajor(void); /** - * Return the HiGHS minor version number + * Return the HiGHS minor version number. * - * @returns the HiGHS minor version number + * @returns The HiGHS minor version number. */ -HighsInt Highs_version_minor(); +HighsInt Highs_versionMinor(void); /** - * Return the HiGHS patch version number + * Return the HiGHS patch version number. * - * @returns the HiGHS patch version number + * @returns The HiGHS patch version number. */ -HighsInt Highs_version_patch(); +HighsInt Highs_versionPatch(void); /** - * Return the HiGHS githash + * Return the HiGHS githash. * - * @returns the HiGHS githash + * @returns The HiGHS githash. */ -const char* Highs_githash(); +const char* Highs_githash(void); /** - * Return the HiGHS compilation date + * Return the HiGHS compilation date. * - * @returns the HiGHS compilation date + * @returns Thse HiGHS compilation date. */ -const char* Highs_compilation_date(); +const char* Highs_compilationDate(void); /** * Read a model from `filename` into `highs`. * - * @param highs a pointer to the Highs instance - * @param filename the filename to read + * @param highs A pointer to the Highs instance. + * @param filename The filename to read. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_readModel(void* highs, const char* filename); /** * Write the model in `highs` to `filename`. * - * @param highs a pointer to the Highs instance - * @param filename the filename to write. + * @param highs A pointer to the Highs instance. + * @param filename The filename to write. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_writeModel(void* highs, const char* filename); /** - * Reset the options and then calls clearModel() + * Reset the options and then call `clearModel`. * * See `Highs_destroy` to free all associated memory. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_clear(void* highs); @@ -293,20 +295,20 @@ HighsInt Highs_clear(void* highs); * invalidate the pointer `highs`. Future calls (for example, adding new * variables and constraints) are allowed. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_clearModel(void* highs); /** - * Clear all solution data associated with the model + * Clear all solution data associated with the model. * * See `Highs_destroy` to clear the model and free all associated memory. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_clearSolver(void* highs); @@ -314,9 +316,9 @@ HighsInt Highs_clearSolver(void* highs); * Optimize a model. The algorithm used by HiGHS depends on the options that * have been set. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_run(void* highs); @@ -326,10 +328,10 @@ HighsInt Highs_run(void* highs); * * See also: `Highs_writeSolutionPretty`. * - * @param highs a pointer to the Highs instance - * @param filename the name of the file to write the results to + * @param highs A pointer to the Highs instance. + * @param filename The name of the file to write the results to. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_writeSolution(const void* highs, const char* filename); @@ -340,10 +342,10 @@ HighsInt Highs_writeSolution(const void* highs, const char* filename); * The method identical to `Highs_writeSolution`, except that the * printout is in a human-readiable format. * - * @param highs a pointer to the Highs instance - * @param filename the name of the file to write the results to + * @param highs A pointer to the Highs instance. + * @param filename The name of the file to write the results to. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_writeSolutionPretty(const void* highs, const char* filename); @@ -354,7 +356,7 @@ HighsInt Highs_writeSolutionPretty(const void* highs, const char* filename); * arguments for passing the Hessian matrix of a quadratic program and the * integrality vector. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_passLp(void* highs, const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, @@ -372,7 +374,7 @@ HighsInt Highs_passLp(void* highs, const HighsInt num_col, * The signature of function is identical to `Highs_passModel`, without the * arguments for passing the Hessian matrix of a quadratic program. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_passMip(void* highs, const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, @@ -387,24 +389,25 @@ HighsInt Highs_passMip(void* highs, const HighsInt num_col, * Pass a model to HiGHS in a single function call. This is faster than * constructing the model using `Highs_addRow` and `Highs_addCol`. * - * @param highs a pointer to the Highs instance - * @param num_col the number of columns - * @param num_row the number of rows - * @param num_nz the number of elements in the constraint matrix - * @param q_num_nz the number of elements in the Hessian matrix - * @param a_format the format of the constraint matrix to use in th form of a - * `kHighsMatrixFormat` constant - * @param q_format the format of the Hessian matrix to use in the form of a - * `kHighsHessianFormat` constant - * @param sense the optimization sense in the form of a `kHighsObjSense` - * constant - * @param offset the constant term in the objective function - * @param col_cost array of length [num_col] with the objective coefficients - * @param col_lower array of length [num_col] with the lower column bounds - * @param col_upper array of length [num_col] with the upper column bounds - * @param row_lower array of length [num_row] with the upper row bounds - * @param row_upper array of length [num_row] with the upper row bounds - * @param a_start the constraint matrix is provided to HiGHS in compressed + * @param highs A pointer to the Highs instance. + * @param num_col The number of columns. + * @param num_row The number of rows. + * @param num_nz The number of elements in the constraint matrix. + * @param q_num_nz The number of elements in the Hessian matrix. + * @param a_format The format of the constraint matrix to use in the form of + * a `kHighsMatrixFormat` constant. + * @param q_format The format of the Hessian matrix to use in the form of a + * `kHighsHessianFormat` constant. + * @param sense The optimization sense in the form of a `kHighsObjSense` + * constant. + * @param offset The constant term in the objective function. + * @param col_cost An array of length [num_col] with the objective + * coefficients. + * @param col_lower An array of length [num_col] with the lower column bounds. + * @param col_upper An array of length [num_col] with the upper column bounds. + * @param row_lower An array of length [num_row] with the upper row bounds. + * @param row_upper An array of length [num_row] with the upper row bounds. + * @param a_start The constraint matrix is provided to HiGHS in compressed * sparse column form (if `a_format` is * `kHighsMatrixFormatColwise`, otherwise compressed sparse * row form). The sparse matrix consists of three arrays, @@ -413,20 +416,21 @@ HighsInt Highs_passMip(void* highs, const HighsInt num_col, * column in `a_index`. If `a_format` is * `kHighsMatrixFormatRowwise` the array is of length * [num_row] corresponding to each row. - * @param a_index array of length [num_nz] with indices of matrix entries - * @param a_value array of length [num_nz] with values of matrix entries - * @param q_start the Hessian matrix is provided in the same format as the + * @param a_index An array of length [num_nz] with indices of matrix + * entries. + * @param a_value An array of length [num_nz] with values of matrix entries. + * @param q_start The Hessian matrix is provided in the same format as the * constraint matrix, using `q_start`, `q_index`, and * `q_value` in the place of `a_start`, `a_index`, and * `a_value`. If the model is linear, pass NULL. - * @param q_index array of length [q_num_nz] with indices of matrix entries. - * If the model is linear, pass NULL. - * @param q_value array of length [q_num_nz] with values of matrix entries. - * If the model is linear, pass NULL. - * @param integrality an array of length [num_col] containing a `kHighsVarType` - * consatnt for each column + * @param q_index An array of length [q_num_nz] with indices of matrix + * entries. If the model is linear, pass NULL. + * @param q_value An array of length [q_num_nz] with values of matrix + * entries. If the model is linear, pass NULL. + * @param integrality An array of length [num_col] containing a `kHighsVarType` + * consatnt for each column. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_passModel(void* highs, const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, @@ -443,34 +447,68 @@ HighsInt Highs_passModel(void* highs, const HighsInt num_col, /** * Set the Hessian matrix for a quadratic objective. * - * @param highs a pointer to the Highs instance - * @param dim the dimension of the Hessian matrix. Should be [num_col]. - * @param num_nz the number of non-zero elements in the Hessian matrix - * @param format the format of the Hessian matrix as a `kHighsHessianFormat` + * @param highs A pointer to the Highs instance. + * @param dim The dimension of the Hessian matrix. Should be [num_col]. + * @param num_nz The number of non-zero elements in the Hessian matrix. + * @param format The format of the Hessian matrix as a `kHighsHessianFormat` * constant. This must be `kHighsHessianFormatTriangular`. - * @param start the Hessian matrix is provided to HiGHS as the upper + * @param start The Hessian matrix is provided to HiGHS as the upper * triangular component in compressed sparse column form. The * sparse matrix consists of three arrays, `start`, `index`, * and `value`. `start` is an array of length [num_col] * containing the starting index of each column in `index`. - * @param index array of length [num_nz] with indices of matrix entries - * @param value array of length [num_nz] with values of matrix entries + * @param index An array of length [num_nz] with indices of matrix entries. + * @param value An array of length [num_nz] with values of matrix entries. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_passHessian(void* highs, const HighsInt dim, const HighsInt num_nz, const HighsInt format, const HighsInt* start, const HighsInt* index, const double* value); +/** + * Pass the name of a row. + * + * @param highs A pointer to the Highs instance. + * @param row The row for which the name is supplied. + * @param name The name of the row. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_passRowName(const void* highs, const HighsInt row, + const char* name); + +/** + * Pass the name of a column. + * + * @param highs A pointer to the Highs instance. + * @param col The column for which the name is supplied. + * @param name The name of the column. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_passColName(const void* highs, const HighsInt col, + const char* name); + +/** + * Read the option values from file. + * + * @param highs A pointer to the Highs instance. + * @param filename The filename from which to read the option values. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_readOptions(const void* highs, const char* filename); + /** * Set a boolean-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The new value of the option. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setBoolOptionValue(void* highs, const char* option, const HighsInt value); @@ -478,11 +516,11 @@ HighsInt Highs_setBoolOptionValue(void* highs, const char* option, /** * Set an int-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The new value of the option. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setIntOptionValue(void* highs, const char* option, const HighsInt value); @@ -490,11 +528,11 @@ HighsInt Highs_setIntOptionValue(void* highs, const char* option, /** * Set a double-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The new value of the option. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setDoubleOptionValue(void* highs, const char* option, const double value); @@ -502,11 +540,11 @@ HighsInt Highs_setDoubleOptionValue(void* highs, const char* option, /** * Set a string-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The new value of the option. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setStringOptionValue(void* highs, const char* option, const char* value); @@ -514,11 +552,12 @@ HighsInt Highs_setStringOptionValue(void* highs, const char* option, /** * Get a boolean-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value storage for the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The location in which the current value of the option should + * be placed. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBoolOptionValue(const void* highs, const char* option, HighsInt* value); @@ -526,11 +565,12 @@ HighsInt Highs_getBoolOptionValue(const void* highs, const char* option, /** * Get an int-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value storage for the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The location in which the current value of the option should + * be placed. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getIntOptionValue(const void* highs, const char* option, HighsInt* value); @@ -538,11 +578,12 @@ HighsInt Highs_getIntOptionValue(const void* highs, const char* option, /** * Get a double-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value storage for the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value The location in which the current value of the option should + * be placed. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getDoubleOptionValue(const void* highs, const char* option, double* value); @@ -550,11 +591,13 @@ HighsInt Highs_getDoubleOptionValue(const void* highs, const char* option, /** * Get a string-valued option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param value pointer to allocated memory to store the value of the option + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param value A pointer to allocated memory (of at least + * `kMaximumStringLength`) to store the current value of the + * option. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getStringOptionValue(const void* highs, const char* option, char* value); @@ -562,12 +605,12 @@ HighsInt Highs_getStringOptionValue(const void* highs, const char* option, /** * Get the type expected by an option. * - * @param highs a pointer to the Highs instance - * @param option the name of the option - * @param type int in which the corresponding `kHighsOptionType` constant - * is stored + * @param highs A pointer to the Highs instance. + * @param option The name of the option. + * @param type An int in which the corresponding `kHighsOptionType` + * constant should be placed. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getOptionType(const void* highs, const char* option, HighsInt* type); @@ -575,19 +618,19 @@ HighsInt Highs_getOptionType(const void* highs, const char* option, /** * Reset all options to their default value. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_resetOptions(void* highs); /** * Write the current options to file. * - * @param highs a pointer to the Highs instance - * @param filename the filename to write the options to + * @param highs A pointer to the Highs instance. + * @param filename The filename to write the options to. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_writeOptions(const void* highs, const char* filename); @@ -597,21 +640,94 @@ HighsInt Highs_writeOptions(const void* highs, const char* filename); * This is similar to `Highs_writeOptions`, except only options with * non-default value are written to `filename`. * - * @param highs a pointer to the Highs instance - * @param filename the filename to write the options to + * @param highs A pointer to the Highs instance. + * @param filename The filename to write the options to. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_writeOptionsDeviations(const void* highs, const char* filename); +/** + * Return the number of options + * + * @param highs A pointer to the Highs instance. + */ +HighsInt Highs_getNumOptions(const void* highs); + +/** + * Get the name of an option identified by index + * + * @param highs A pointer to the Highs instance. + * @param index The index of the option. + * @param name The name of the option. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getOptionName(const void* highs, const HighsInt index, + char** name); + +/** + * Get the current and default values of a bool option + * + * @param highs A pointer to the Highs instance. + * @param current_value A pointer to the current value of the option. + * @param default_value A pointer to the default value of the option. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getBoolOptionValues(const void* highs, const char* option, + HighsInt* current_value, + HighsInt* default_value); +/** + * Get the current and default values of an int option + * + * @param highs A pointer to the Highs instance. + * @param current_value A pointer to the current value of the option. + * @param min_value A pointer to the minimum value of the option. + * @param max_value A pointer to the maximum value of the option. + * @param default_value A pointer to the default value of the option. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getIntOptionValues(const void* highs, const char* option, + HighsInt* current_value, HighsInt* min_value, + HighsInt* max_value, HighsInt* default_value); + +/** + * Get the current and default values of a double option + * + * @param highs A pointer to the Highs instance. + * @param current_value A pointer to the current value of the option. + * @param min_value A pointer to the minimum value of the option. + * @param max_value A pointer to the maximum value of the option. + * @param default_value A pointer to the default value of the option. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getDoubleOptionValues(const void* highs, const char* option, + double* current_value, double* min_value, + double* max_value, double* default_value); + +/** + * Get the current and default values of a string option + * + * @param highs A pointer to the Highs instance. + * @param current_value A pointer to the current value of the option. + * @param default_value A pointer to the default value of the option. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getStringOptionValues(const void* highs, const char* option, + char* current_value, char* default_value); + /** * Get an int-valued info value. * - * @param highs a pointer to the Highs instance - * @param info the name of the info item - * @param value a reference to an integer that the result will be stored in + * @param highs A pointer to the Highs instance. + * @param info The name of the info item. + * @param value A reference to an integer that the result will be stored in. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getIntInfoValue(const void* highs, const char* info, HighsInt* value); @@ -619,11 +735,11 @@ HighsInt Highs_getIntInfoValue(const void* highs, const char* info, /** * Get a double-valued info value. * - * @param highs a pointer to the Highs instance - * @param info the name of the info item - * @param value a reference to an double that the result will be stored in + * @param highs A pointer to the Highs instance. + * @param info The name of the info item. + * @param value A reference to a double that the result will be stored in. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getDoubleInfoValue(const void* highs, const char* info, double* value); @@ -631,25 +747,41 @@ HighsInt Highs_getDoubleInfoValue(const void* highs, const char* info, /** * Get an int64-valued info value. * - * @param highs a pointer to the Highs instance - * @param info the name of the info item - * @param value a reference to a int64 that the result will be stored in + * @param highs A pointer to the Highs instance. + * @param info The name of the info item. + * @param value A reference to an int64 that the result will be stored in. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getInt64InfoValue(const void* highs, const char* info, int64_t* value); +/** + * Get the type expected by an info item. + * + * @param highs A pointer to the Highs instance. + * @param info The name of the info item. + * @param type An int in which the corresponding `kHighsOptionType` + * constant is stored. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getInfoType(const void* highs, const char* info, HighsInt* type); + /** * Get the primal and dual solution from an optimized model. * - * @param highs a pointer to the Highs instance - * @param col_value array of length [num_col], filled with primal column values - * @param col_dual array of length [num_col], filled with dual column values - * @param row_value array of length [num_row], filled with primal row values - * @param row_dual array of length [num_row], filled with dual row values + * @param highs A pointer to the Highs instance. + * @param col_value An array of length [num_col], to be filled with primal + * column values. + * @param col_dual An array of length [num_col], to be filled with dual column + * values. + * @param row_value An array of length [num_row], to be filled with primal row + * values. + * @param row_dual An array of length [num_row], to be filled with dual row + * values. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getSolution(const void* highs, double* col_value, double* col_dual, double* row_value, @@ -659,15 +791,15 @@ HighsInt Highs_getSolution(const void* highs, double* col_value, * Given a linear program with a basic feasible solution, get the column and row * basis statuses. * - * @param highs a pointer to the Highs instance - * @param col_status array of length [num_col], to be filled with the column + * @param highs A pointer to the Highs instance. + * @param col_status An array of length [num_col], to be filled with the column * basis statuses in the form of a `kHighsBasisStatus` - * constant - * @param row_status array of length [num_row], to be filled with the row + * constant. + * @param row_status An array of length [num_row], to be filled with the row * basis statuses in the form of a `kHighsBasisStatus` - * constant + * constant. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBasis(const void* highs, HighsInt* col_status, HighsInt* row_status); @@ -676,22 +808,22 @@ HighsInt Highs_getBasis(const void* highs, HighsInt* col_status, * Return the optimization status of the model in the form of a * `kHighsModelStatus` constant. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns an integer corresponding to the `kHighsModelStatus` constant + * @returns An integer corresponding to the `kHighsModelStatus` constant */ HighsInt Highs_getModelStatus(const void* highs); /** * Get an unbounded dual ray that is a certificate of primal infeasibility. * - * @param highs a pointer to the Highs instance - * @param has_dual_ray a pointer to an int to store 1 if the dual ray - * exists - * @param dual_ray_value an array of length [num_row] filled with the - * unbounded ray + * @param highs A pointer to the Highs instance. + * @param has_dual_ray A pointer to an int to store 1 if the dual ray + * exists. + * @param dual_ray_value An array of length [num_row] filled with the + * unbounded ray. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getDualRay(const void* highs, HighsInt* has_dual_ray, double* dual_ray_value); @@ -699,13 +831,13 @@ HighsInt Highs_getDualRay(const void* highs, HighsInt* has_dual_ray, /** * Get an unbounded primal ray that is a certificate of dual infeasibility. * - * @param highs a pointer to the Highs instance - * @param has_primal_ray a pointer to an int to store 1 if the primal ray - * exists - * @param primal_ray_value an array of length [num_col] filled with the - * unbounded ray + * @param highs A pointer to the Highs instance. + * @param has_primal_ray A pointer to an int to store 1 if the primal ray + * exists. + * @param primal_ray_value An array of length [num_col] filled with the + * unbounded ray. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getPrimalRay(const void* highs, HighsInt* has_primal_ray, double* primal_ray_value); @@ -713,21 +845,22 @@ HighsInt Highs_getPrimalRay(const void* highs, HighsInt* has_primal_ray, /** * Get the primal objective function value. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the primal objective function value + * @returns The primal objective function value */ double Highs_getObjectiveValue(const void* highs); /** - * Get the indices of the rows and columns that make up the basis matrix of a - * basic feasible solution. + * Get the indices of the rows and columns that make up the basis matrix ``B`` + * of a basic feasible solution. * * Non-negative entries are indices of columns, and negative entries are * `-row_index - 1`. For example, `{1, -1}` would be the second column and first * row. * * The order of these rows and columns is important for calls to the functions: + * * - `Highs_getBasisInverseRow` * - `Highs_getBasisInverseCol` * - `Highs_getBasisSolve` @@ -735,95 +868,103 @@ double Highs_getObjectiveValue(const void* highs); * - `Highs_getReducedRow` * - `Highs_getReducedColumn` * - * @param highs a pointer to the Highs instance - * @param basic_variables array of size [num_rows], filled with the indices of - * the basic variables + * @param highs A pointer to the Highs instance. + * @param basic_variables An array of size [num_rows], filled with the indices + * of the basic variables. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBasicVariables(const void* highs, HighsInt* basic_variables); /** - * Get a row of the inverse basis matrix \f$B^{-1}\f$. + * Get a row of the inverse basis matrix ``B^{-1}``. * - * See `Highs_getBasicVariables` for a description of the `B` matrix. + * See `Highs_getBasicVariables` for a description of the ``B`` matrix. * * The arrays `row_vector` and `row_index` must have an allocated length of * [num_row]. However, check `row_num_nz` to see how many non-zero elements are * actually stored. * - * @param highs a pointer to the Highs instance - * @param row index of the row to compute - * @param row_vector values of the non-zero elements - * @param row_num_nz the number of non-zeros in the row - * @param row_index indices of the non-zero elements + * @param highs A pointer to the Highs instance. + * @param row The index of the row to compute. + * @param row_vector An array of length [num_row] in which to store the + * values of the non-zero elements. + * @param row_num_nz The number of non-zeros in the row. + * @param row_index An array of length [num_row] in which to store the + * indices of the non-zero elements. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBasisInverseRow(const void* highs, const HighsInt row, double* row_vector, HighsInt* row_num_nz, HighsInt* row_index); /** - * Get a column of the inverse basis matrix \f$B^{-1}\f$. + * Get a column of the inverse basis matrix ``B^{-1}``. * - * See `Highs_getBasicVariables` for a description of the `B` matrix. + * See `Highs_getBasicVariables` for a description of the ``B`` matrix. * * The arrays `col_vector` and `col_index` must have an allocated length of * [num_row]. However, check `col_num_nz` to see how many non-zero elements are * actually stored. * - * @param highs a pointer to the Highs instance - * @param col index of the column to compute - * @param col_vector values of the non-zero elements - * @param col_num_nz the number of non-zeros in the column - * @param col_index indices of the non-zero elements + * @param highs A pointer to the Highs instance. + * @param col The index of the column to compute. + * @param col_vector An array of length [num_row] in which to store the + * values of the non-zero elements. + * @param col_num_nz The number of non-zeros in the column. + * @param col_index An array of length [num_row] in which to store the + * indices of the non-zero elements. - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBasisInverseCol(const void* highs, const HighsInt col, double* col_vector, HighsInt* col_num_nz, HighsInt* col_index); /** - * Compute \f$\mathbf{x}=B^{-1}\mathbf{b}\f$ for a given vector - * \f$\mathbf{b}\f$. + * Compute ``\mathbf{x}=B^{-1}\mathbf{b}`` for a given vector + * ``\mathbf{b}``. * - * See `Highs_getBasicVariables` for a description of the `B` matrix. + * See `Highs_getBasicVariables` for a description of the ``B`` matrix. * * The arrays `solution_vector` and `solution_index` must have an allocated * length of [num_row]. However, check `solution_num_nz` to see how many * non-zero elements are actually stored. * - * @param highs a pointer to the Highs instance - * @param rhs the right-hand side vector `b` - * @param solution_vector values of the non-zero elements - * @param solution_num_nz the number of non-zeros in the solution - * @param solution_index indices of the non-zero elements + * @param highs A pointer to the Highs instance. + * @param rhs The right-hand side vector ``b``. + * @param solution_vector An array of length [num_row] in which to store the + * values of the non-zero elements. + * @param solution_num_nz The number of non-zeros in the solution. + * @param solution_index An array of length [num_row] in which to store the + * indices of the non-zero elements. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBasisSolve(const void* highs, const double* rhs, double* solution_vector, HighsInt* solution_num_nz, HighsInt* solution_index); /** - * Compute \f$\mathbf{x}=B^{-T}\mathbf{b}\f$ for a given vector - * \f$\mathbf{b}\f$. + * Compute ``\mathbf{x}=B^{-T}\mathbf{b}`` for a given vector + * ``\mathbf{b}``. * - * See `Highs_getBasicVariables` for a description of the `B` matrix. + * See `Highs_getBasicVariables` for a description of the ``B`` matrix. * * The arrays `solution_vector` and `solution_index` must have an allocated * length of [num_row]. However, check `solution_num_nz` to see how many * non-zero elements are actually stored. * - * @param highs a pointer to the Highs instance - * @param rhs the right-hand side vector `b` - * @param solution_vector values of the non-zero elements - * @param solution_num_nz the number of non-zeros in the solution - * @param solution_index indices of the non-zero elements + * @param highs A pointer to the Highs instance. + * @param rhs The right-hand side vector ``b`` + * @param solution_vector An array of length [num_row] in whcih to store the + * values of the non-zero elements. + * @param solution_num_nz The number of non-zeros in the solution. + * @param solution_index An array of length [num_row] in whcih to store the + * indices of the non-zero elements. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getBasisTransposeSolve(const void* highs, const double* rhs, double* solution_vector, @@ -831,42 +972,46 @@ HighsInt Highs_getBasisTransposeSolve(const void* highs, const double* rhs, HighsInt* solution_index); /** - * Compute a row of \f$B^{-1}A\f$. + * Compute a row of ``B^{-1}A``. * - * See `Highs_getBasicVariables` for a description of the `B` matrix. + * See `Highs_getBasicVariables` for a description of the ``B`` matrix. * * The arrays `row_vector` and `row_index` must have an allocated length of * [num_row]. However, check `row_num_nz` to see how many non-zero elements are * actually stored. * - * @param highs a pointer to the Highs instance - * @param row index of the row to compute - * @param row_vector values of the non-zero elements - * @param row_num_nz the number of non-zeros in the row - * @param row_index indices of the non-zero elements + * @param highs A pointer to the Highs instance. + * @param row The index of the row to compute. + * @param row_vector An array of length [num_row] in which to store the + * values of the non-zero elements. + * @param row_num_nz The number of non-zeros in the row. + * @param row_index An array of length [num_row] in which to store the + * indices of the non-zero elements. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getReducedRow(const void* highs, const HighsInt row, double* row_vector, HighsInt* row_num_nz, HighsInt* row_index); /** - * Compute a column of \f$B^{-1}A\f$. + * Compute a column of ``B^{-1}A``. * - * See `Highs_getBasicVariables` for a description of the `B` matrix. + * See `Highs_getBasicVariables` for a description of the ``B`` matrix. * * The arrays `col_vector` and `col_index` must have an allocated length of * [num_row]. However, check `col_num_nz` to see how many non-zero elements are * actually stored. * - * @param highs a pointer to the Highs instance - * @param col index of the column to compute - * @param col_vector values of the non-zero elements - * @param col_num_nz the number of non-zeros in the column - * @param col_index indices of the non-zero elements + * @param highs A pointer to the Highs instance. + * @param col The index of the column to compute. + * @param col_vector An array of length [num_row] in which to store the +* values of the non-zero elements. + * @param col_num_nz The number of non-zeros in the column. + * @param col_index An array of length [num_row] in which to store the +* indices of the non-zero elements. - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getReducedColumn(const void* highs, const HighsInt col, double* col_vector, HighsInt* col_num_nz, @@ -876,13 +1021,13 @@ HighsInt Highs_getReducedColumn(const void* highs, const HighsInt col, * Set a basic feasible solution by passing the column and row basis statuses to * the model. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * @param col_status an array of length [num_col] with the column basis status * in the form of `kHighsBasisStatus` constants * @param row_status an array of length [num_row] with the row basis status * in the form of `kHighsBasisStatus` constants * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setBasis(void* highs, const HighsInt* col_status, const HighsInt* row_status); @@ -890,25 +1035,26 @@ HighsInt Highs_setBasis(void* highs, const HighsInt* col_status, /** * Set a logical basis in the model. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setLogicalBasis(void* highs); /** - * Set a solution by passing the column and row primal and dual - * solution values. For any values that are unavailable pass NULL. + * Set a solution by passing the column and row primal and dual solution values. * - * @param highs a pointer to the Highs instance - * @param col_value an array of length [num_col] with the column solution - * values - * @param row_value an array of length [num_row] with the row solution - * values - * @param col_dual an array of length [num_col] with the column dual values - * @param row_dual an array of length [num_row] with the row dual values + * For any values that are unavailable, pass NULL. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @param highs A pointer to the Highs instance. + * @param col_value An array of length [num_col] with the column solution + * values. + * @param row_value An array of length [num_row] with the row solution + * values. + * @param col_dual An array of length [num_col] with the column dual values. + * @param row_dual An array of length [num_row] with the row dual values. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_setSolution(void* highs, const double* col_value, const double* row_value, const double* col_dual, @@ -917,24 +1063,24 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, /** * Return the cumulative wall-clock time spent in `Highs_run`. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the cumulative wall-clock time spent in `Highs_run` + * @returns The cumulative wall-clock time spent in `Highs_run` */ double Highs_getRunTime(const void* highs); /** * Add a new column (variable) to the model. * - * @param highs a pointer to the Highs instance - * @param cost objective coefficient of the column - * @param lower lower bound of the column - * @param upper upper bound of the column - * @param num_new_nz number of non-zeros in the column - * @param index array of size [num_new_nz] with the row indices - * @param value array of size [num_new_nz] with row values + * @param highs A pointer to the Highs instance. + * @param cost The objective coefficient of the column. + * @param lower The lower bound of the column. + * @param upper The upper bound of the column. + * @param num_new_nz The number of non-zeros in the column. + * @param index An array of size [num_new_nz] with the row indices. + * @param value An array of size [num_new_nz] with row values. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_addCol(void* highs, const double cost, const double lower, const double upper, const HighsInt num_new_nz, @@ -943,21 +1089,22 @@ HighsInt Highs_addCol(void* highs, const double cost, const double lower, /** * Add multiple columns (variables) to the model. * - * @param highs a pointer to the Highs instance - * @param num_new_col number of new columns to add - * @param costs array of size [num_new_col] with objective coefficients - * @param lower array of size [num_new_col] with lower bounds - * @param upper array of size [num_new_col] with upper bounds - * @param num_new_nz number of new nonzeros in the constraint matrix - * @param starts the constraint coefficients are given as a matrix in + * @param highs A pointer to the Highs instance. + * @param num_new_col The number of new columns to add. + * @param costs An array of size [num_new_col] with objective + * coefficients. + * @param lower An array of size [num_new_col] with lower bounds. + * @param upper An array of size [num_new_col] with upper bounds. + * @param num_new_nz The number of new nonzeros in the constraint matrix. + * @param starts The constraint coefficients are given as a matrix in * compressed sparse column form by the arrays `starts`, * `index`, and `value`. `starts` is an array of size * [num_new_cols] with the start index of each row in * indices and values. - * @param index array of size [num_new_nz] with row indices - * @param value array of size [num_new_nz] with row values + * @param index An array of size [num_new_nz] with row indices. + * @param value An array of size [num_new_nz] with row values. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_addCols(void* highs, const HighsInt num_new_col, const double* costs, const double* lower, @@ -968,23 +1115,23 @@ HighsInt Highs_addCols(void* highs, const HighsInt num_new_col, /** * Add a new variable to the model. * - * @param highs a pointer to the Highs instance - * @param lower lower bound of the column - * @param upper upper bound of the column + * @param highs A pointer to the Highs instance. + * @param lower The lower bound of the column. + * @param upper The upper bound of the column. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_addVar(void* highs, const double lower, const double upper); /** * Add multiple variables to the model. * - * @param highs a pointer to the Highs instance - * @param num_new_var number of new variables to add - * @param lower array of size [num_new_var] with lower bounds - * @param upper array of size [num_new_var] with upper bounds + * @param highs A pointer to the Highs instance. + * @param num_new_var The number of new variables to add. + * @param lower An array of size [num_new_var] with lower bounds. + * @param upper An array of size [num_new_var] with upper bounds. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_addVars(void* highs, const HighsInt num_new_var, const double* lower, const double* upper); @@ -992,14 +1139,14 @@ HighsInt Highs_addVars(void* highs, const HighsInt num_new_var, /** * Add a new row (a linear constraint) to the model. * - * @param highs a pointer to the Highs instance - * @param lower lower bound of the row - * @param upper upper bound of the row - * @param num_new_nz number of non-zeros in the row - * @param index array of size [num_new_nz] with column indices - * @param value array of size [num_new_nz] with column values + * @param highs A pointer to the Highs instance. + * @param lower The lower bound of the row. + * @param upper The upper bound of the row. + * @param num_new_nz The number of non-zeros in the row + * @param index An array of size [num_new_nz] with column indices. + * @param value An array of size [num_new_nz] with column values. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_addRow(void* highs, const double lower, const double upper, const HighsInt num_new_nz, const HighsInt* index, @@ -1008,22 +1155,22 @@ HighsInt Highs_addRow(void* highs, const double lower, const double upper, /** * Add multiple rows (linear constraints) to the model. * - * @param highs a pointer to the Highs instance - * @param num_new_row the number of new rows to add - * @param lower array of size [num_new_row] with the lower bounds of the - * rows - * @param upper array of size [num_new_row] with the upper bounds of the - * rows - * @param num_new_nz number of non-zeros in the rows - * @param starts the constraint coefficients are given as a matrix in + * @param highs A pointer to the Highs instance. + * @param num_new_row The number of new rows to add + * @param lower An array of size [num_new_row] with the lower bounds of + * the rows. + * @param upper An array of size [num_new_row] with the upper bounds of + * the rows. + * @param num_new_nz The number of non-zeros in the rows. + * @param starts The constraint coefficients are given as a matrix in * compressed sparse row form by the arrays `starts`, * `index`, and `value`. `starts` is an array of size * [num_new_rows] with the start index of each row in * indices and values. - * @param index array of size [num_new_nz] with column indices - * @param value array of size [num_new_nz] with column values + * @param index An array of size [num_new_nz] with column indices. + * @param value An array of size [num_new_nz] with column values. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_addRows(void* highs, const HighsInt num_new_row, const double* lower, const double* upper, @@ -1033,33 +1180,33 @@ HighsInt Highs_addRows(void* highs, const HighsInt num_new_row, /** * Change the objective sense of the model. * - * @param highs a pointer to the Highs instance - * @param sense the new optimization sense in the form of a `kHighsObjSense` - * constant + * @param highs A pointer to the Highs instance. + * @param sense The new optimization sense in the form of a `kHighsObjSense` + * constant. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeObjectiveSense(void* highs, const HighsInt sense); /** * Change the objective offset of the model. * - * @param highs a pointer to the Highs instance - * @param offset the new objective offset + * @param highs A pointer to the Highs instance. + * @param offset The new objective offset. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeObjectiveOffset(void* highs, const double offset); /** * Change the integrality of a column. * - * @param highs a pointer to the Highs instance - * @param col the column index to change - * @param integrality the new integrality of the column in the form of a - * `kHighsVarType` constant + * @param highs A pointer to the Highs instance. + * @param col The column index to change. + * @param integrality The new integrality of the column in the form of a + * `kHighsVarType` constant. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColIntegrality(void* highs, const HighsInt col, const HighsInt integrality); @@ -1067,15 +1214,15 @@ HighsInt Highs_changeColIntegrality(void* highs, const HighsInt col, /** * Change the integrality of multiple adjacent columns. * - * @param highs a pointer to the Highs instance - * @param from_col the index of the first column whose integrality changes - * @param to_col the index of the last column whose integrality - * changes - * @param integrality an array of length [to_col - from_col + 1] with the new + * @param highs A pointer to the Highs instance. + * @param from_col The index of the first column whose integrality changes. + * @param to_col The index of the last column whose integrality + * changes. + * @param integrality An array of length [to_col - from_col + 1] with the new * integralities of the columns in the form of - * `kHighsVarType` constants + * `kHighsVarType` constants. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsIntegralityByRange(void* highs, const HighsInt from_col, @@ -1085,15 +1232,15 @@ HighsInt Highs_changeColsIntegralityByRange(void* highs, /** * Change the integrality of multiple columns given by an array of indices. * - * @param highs a pointer to the Highs instance - * @param num_set_entries the number of columns to change - * @param set an array of size [num_set_entries] with the indices - * of the columns to change - * @param integrality an array of length [num_set_entries] with the new + * @param highs A pointer to the Highs instance. + * @param num_set_entries The number of columns to change. + * @param set An array of size [num_set_entries] with the indices + * of the columns to change. + * @param integrality An array of length [num_set_entries] with the new * integralities of the columns in the form of - * `kHighsVarType` constants + * `kHighsVarType` constants. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsIntegralityBySet(void* highs, const HighsInt num_set_entries, @@ -1103,14 +1250,14 @@ HighsInt Highs_changeColsIntegralityBySet(void* highs, /** * Change the integrality of multiple columns given by a mask. * - * @param highs a pointer to the Highs instance - * @param mask an array of length [num_col] with 1 if the column - * integrality should be changed and 0 otherwise - * @param integrality an array of length [num_col] with the new + * @param highs A pointer to the Highs instance. + * @param mask An array of length [num_col] with 1 if the column + * integrality should be changed and 0 otherwise. + * @param integrality An array of length [num_col] with the new * integralities of the columns in the form of - * `kHighsVarType` constants + * `kHighsVarType` constants. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsIntegralityByMask(void* highs, const HighsInt* mask, const HighsInt* integrality); @@ -1118,11 +1265,11 @@ HighsInt Highs_changeColsIntegralityByMask(void* highs, const HighsInt* mask, /** * Change the objective coefficient of a column. * - * @param highs a pointer to the Highs instance - * @param col the index of the column fo change - * @param cost the new objective coefficient + * @param highs A pointer to the Highs instance. + * @param col The index of the column fo change. + * @param cost The new objective coefficient. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColCost(void* highs, const HighsInt col, const double cost); @@ -1130,13 +1277,13 @@ HighsInt Highs_changeColCost(void* highs, const HighsInt col, /** * Change the cost coefficients of multiple adjacent columns. * - * @param highs a pointer to the Highs instance - * @param from_col the index of the first column whose cost changes - * @param to_col the index of the last column whose cost changes - * @param cost an array of length [to_col - from_col + 1] with the new - * objective coefficients + * @param highs A pointer to the Highs instance. + * @param from_col The index of the first column whose cost changes. + * @param to_col The index of the last column whose cost changes. + * @param cost An array of length [to_col - from_col + 1] with the new + * objective coefficients. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsCostByRange(void* highs, const HighsInt from_col, const HighsInt to_col, const double* cost); @@ -1144,14 +1291,14 @@ HighsInt Highs_changeColsCostByRange(void* highs, const HighsInt from_col, /** * Change the cost of multiple columns given by an array of indices. * - * @param highs a pointer to the Highs instance - * @param num_set_entries the number of columns to change - * @param set an array of size [num_set_entries] with the indices - * of the columns to change - * @param cost an array of length [num_set_entries] with the new + * @param highs A pointer to the Highs instance. + * @param num_set_entries The number of columns to change. + * @param set An array of size [num_set_entries] with the indices + * of the columns to change. + * @param cost An array of length [num_set_entries] with the new * costs of the columns. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsCostBySet(void* highs, const HighsInt num_set_entries, const HighsInt* set, const double* cost); @@ -1159,12 +1306,12 @@ HighsInt Highs_changeColsCostBySet(void* highs, const HighsInt num_set_entries, /** * Change the cost of multiple columns given by a mask. * - * @param highs a pointer to the Highs instance - * @param mask an array of length [num_col] with 1 if the column - * cost should be changed and 0 otherwise - * @param cost an array of length [num_col] with the new costs + * @param highs A pointer to the Highs instance. + * @param mask An array of length [num_col] with 1 if the column + * cost should be changed and 0 otherwise. + * @param cost An array of length [num_col] with the new costs. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsCostByMask(void* highs, const HighsInt* mask, const double* cost); @@ -1172,12 +1319,12 @@ HighsInt Highs_changeColsCostByMask(void* highs, const HighsInt* mask, /** * Change the variable bounds of a column. * - * @param highs a pointer to the Highs instance - * @param col the index of the column whose bounds are to change - * @param lower the new lower bound - * @param upper the new upper bound + * @param highs A pointer to the Highs instance. + * @param col The index of the column whose bounds are to change. + * @param lower The new lower bound. + * @param upper The new upper bound. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColBounds(void* highs, const HighsInt col, const double lower, const double upper); @@ -1185,15 +1332,15 @@ HighsInt Highs_changeColBounds(void* highs, const HighsInt col, /** * Change the variable bounds of multiple adjacent columns. * - * @param highs a pointer to the Highs instance - * @param from_col the index of the first column whose bound changes - * @param to_col the index of the last column whose bound changes - * @param lower an array of length [to_col - from_col + 1] with the new - * lower bounds - * @param upper an array of length [to_col - from_col + 1] with the new - * upper bounds + * @param highs A pointer to the Highs instance. + * @param from_col The index of the first column whose bound changes. + * @param to_col The index of the last column whose bound changes. + * @param lower An array of length [to_col - from_col + 1] with the new + * lower bounds. + * @param upper An array of length [to_col - from_col + 1] with the new + * upper bounds. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsBoundsByRange(void* highs, const HighsInt from_col, const HighsInt to_col, @@ -1203,16 +1350,16 @@ HighsInt Highs_changeColsBoundsByRange(void* highs, const HighsInt from_col, /** * Change the bounds of multiple columns given by an array of indices. * - * @param highs a pointer to the Highs instance - * @param num_set_entries the number of columns to change - * @param set an array of size [num_set_entries] with the indices - * of the columns to change - * @param lower an array of length [num_set_entries] with the new - * lower bounds - * @param upper an array of length [num_set_entries] with the new - * upper bounds + * @param highs A pointer to the Highs instance. + * @param num_set_entries The number of columns to change. + * @param set An array of size [num_set_entries] with the indices + * of the columns to change. + * @param lower An array of length [num_set_entries] with the new + * lower bounds. + * @param upper An array of length [num_set_entries] with the new + * upper bounds. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsBoundsBySet(void* highs, const HighsInt num_set_entries, @@ -1222,13 +1369,13 @@ HighsInt Highs_changeColsBoundsBySet(void* highs, /** * Change the variable bounds of multiple columns given by a mask. * - * @param highs a pointer to the Highs instance - * @param mask an array of length [num_col] with 1 if the column - * bounds should be changed and 0 otherwise - * @param lower an array of length [num_col] with the new lower bounds - * @param upper an array of length [num_col] with the new upper bounds + * @param highs A pointer to the Highs instance. + * @param mask An array of length [num_col] with 1 if the column + * bounds should be changed and 0 otherwise. + * @param lower An array of length [num_col] with the new lower bounds. + * @param upper An array of length [num_col] with the new upper bounds. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeColsBoundsByMask(void* highs, const HighsInt* mask, const double* lower, const double* upper); @@ -1236,12 +1383,12 @@ HighsInt Highs_changeColsBoundsByMask(void* highs, const HighsInt* mask, /** * Change the bounds of a row. * - * @param highs a pointer to the Highs instance - * @param row the index of the row whose bounds are to change - * @param lower the new lower bound - * @param upper the new upper bound + * @param highs A pointer to the Highs instance. + * @param row The index of the row whose bounds are to change. + * @param lower The new lower bound. + * @param upper The new upper bound. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeRowBounds(void* highs, const HighsInt row, const double lower, const double upper); @@ -1249,16 +1396,16 @@ HighsInt Highs_changeRowBounds(void* highs, const HighsInt row, /** * Change the bounds of multiple rows given by an array of indices. * - * @param highs a pointer to the Highs instance - * @param num_set_entries the number of rows to change - * @param set an array of size [num_set_entries] with the indices - * of the rows to change - * @param lower an array of length [num_set_entries] with the new - * lower bounds - * @param upper an array of length [num_set_entries] with the new - * upper bounds + * @param highs A pointer to the Highs instance. + * @param num_set_entries The number of rows to change. + * @param set An array of size [num_set_entries] with the indices + * of the rows to change. + * @param lower An array of length [num_set_entries] with the new + * lower bounds. + * @param upper An array of length [num_set_entries] with the new + * upper bounds. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeRowsBoundsBySet(void* highs, const HighsInt num_set_entries, @@ -1268,13 +1415,13 @@ HighsInt Highs_changeRowsBoundsBySet(void* highs, /** * Change the bounds of multiple rows given by a mask. * - * @param highs a pointer to the Highs instance - * @param mask an array of length [num_row] with 1 if the row - * bounds should be changed and 0 otherwise - * @param lower an array of length [num_row] with the new lower bounds - * @param upper an array of length [num_row] with the new upper bounds + * @param highs A pointer to the Highs instance. + * @param mask An array of length [num_row] with 1 if the row + * bounds should be changed and 0 otherwise. + * @param lower An array of length [num_row] with the new lower bounds. + * @param upper An array of length [num_row] with the new upper bounds. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeRowsBoundsByMask(void* highs, const HighsInt* mask, const double* lower, const double* upper); @@ -1282,12 +1429,12 @@ HighsInt Highs_changeRowsBoundsByMask(void* highs, const HighsInt* mask, /** * Change a coefficient in the constraint matrix. * - * @param highs a pointer to the Highs instance - * @param row the index of the row to change - * @param col the index of the col to change - * @param value the new constraint coefficient + * @param highs A pointer to the Highs instance. + * @param row The index of the row to change. + * @param col The index of the column to change. + * @param value The new constraint coefficient. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_changeCoeff(void* highs, const HighsInt row, const HighsInt col, const double value); @@ -1295,58 +1442,60 @@ HighsInt Highs_changeCoeff(void* highs, const HighsInt row, const HighsInt col, /** * Get the objective sense. * - * @param highs a pointer to the Highs instance - * @param sense stores the current objective sense as a `kHighsObjSense` - * constant + * @param highs A pointer to the Highs instance. + * @param sense The location in which the current objective sense should be + * placed. The sense is a `kHighsObjSense` constant. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getObjectiveSense(const void* highs, HighsInt* sense); /** * Get the objective offset. * - * @param highs a pointer to the Highs instance - * @param offset stores the current objective offset + * @param highs A pointer to the Highs instance. + * @param offset The location in which the current objective offset should be + * placed. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getObjectiveOffset(const void* highs, double* offset); /** * Get data associated with multiple adjacent columns from the model. * - * To query the constraint coefficients, this function should be called twice: - * - First, call this function with `matrix_start`, `matrix_index`, and - * `matrix_value` as `NULL`. This call will populate `num_nz` with the - * number of nonzero elements in the corresponding section of the constraint - * matrix. - * - Second, allocate new `matrix_index` and `matrix_value` arrays of length - * `num_nz` and call this function again to populate the new arrays with - * their contents. - * - * @param highs a pointer to the Highs instance - * @param from_col the first column for which to query data for - * @param to_col the last column (inclusive) for which to query data for - * @param num_col an integer populated with the number of columns got from - * the model (this should equal `to_col - from_col + 1`) - * @param costs array of size [to_col - from_col + 1] for the column - * cost coefficients - * @param lower array of size [to_col - from_col + 1] for the column - * lower bounds - * @param upper array of size [to_col - from_col + 1] for the column - * upper bounds - * @param num_nz an integer populated with the number of non-zero - * elements in the constraint matrix - * @param matrix_start array of size [to_col - from_col + 1] with the start - * indices of each - * column in `matrix_index` and `matrix_value` - * @param matrix_index array of size [num_nz] with the row indices of each - * element in the constraint matrix - * @param matrix_value array of size [num_nz] with the non-zero elements of the - * constraint matrix. - * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * To query the constraint coefficients, this function should be called twice. + * + * First, call this function with `matrix_start`, `matrix_index`, and + * `matrix_value` as `NULL`. This call will populate `num_nz` with the number of + * nonzero elements in the corresponding section of the constraint matrix. + * + * Second, allocate new `matrix_index` and `matrix_value` arrays of length + * `num_nz` and call this function again to populate the new arrays with their + * contents. + * + * @param highs A pointer to the Highs instance. + * @param from_col The first column for which to query data for. + * @param to_col The last column (inclusive) for which to query data for. + * @param num_col An integer populated with the number of columns got from + * the model (this should equal `to_col - from_col + 1`). + * @param costs An array of size [to_col - from_col + 1] for the column + * cost coefficients. + * @param lower An array of size [to_col - from_col + 1] for the column + * lower bounds. + * @param upper An array of size [to_col - from_col + 1] for the column + * upper bounds. + * @param num_nz An integer to be populated with the number of non-zero + * elements in the constraint matrix. + * @param matrix_start An array of size [to_col - from_col + 1] with the start + * indices of each column in `matrix_index` and + * `matrix_value`. + * @param matrix_index An array of size [num_nz] with the row indices of each + * element in the constraint matrix. + * @param matrix_value An array of size [num_nz] with the non-zero elements of + * the constraint matrix. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getColsByRange(const void* highs, const HighsInt from_col, const HighsInt to_col, HighsInt* num_col, @@ -1360,11 +1509,11 @@ HighsInt Highs_getColsByRange(const void* highs, const HighsInt from_col, * This function is identical to `Highs_getColsByRange`, except for how the * columns are specified. * - * @param num_set_indices the number of indices in the set - * @param set array of size [num_set_entries] with the column - * indices to get + * @param num_set_indices The number of indices in `set`. + * @param set An array of size [num_set_entries] with the column + * indices to get. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getColsBySet(const void* highs, const HighsInt num_set_entries, const HighsInt* set, HighsInt* num_col, @@ -1378,10 +1527,10 @@ HighsInt Highs_getColsBySet(const void* highs, const HighsInt num_set_entries, * This function is identical to `Highs_getColsByRange`, except for how the * columns are specified. * - * @param mask array of length [num_col] containing a 1 to get the column and 0 - * otherwise + * @param mask An array of length [num_col] containing a `1` to get the column + * and `0` otherwise. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getColsByMask(const void* highs, const HighsInt* mask, HighsInt* num_col, double* costs, double* lower, @@ -1392,34 +1541,36 @@ HighsInt Highs_getColsByMask(const void* highs, const HighsInt* mask, /** * Get data associated with multiple adjacent rows from the model. * - * To query the constraint coefficients, this function should be called twice: - * - First, call this function with `matrix_start`, `matrix_index`, and - * `matrix_value` as `NULL`. This call will populate `num_nz` with the - * number of nonzero elements in the corresponding section of the constraint - * matrix. - * - Second, allocate new `matrix_index` and `matrix_value` arrays of length - * `num_nz` and call this function again to populate the new arrays with - * their contents. - * - * @param highs a pointer to the Highs instance - * @param from_row the first row for which to query data for - * @param to_row the last row (inclusive) for which to query data for - * @param num_row an integer populated with the number of row got from the - * model - * @param lower array of size [to_row - from_row + 1] for the row lower - * bounds - * @param upper array of size [to_row - from_row + 1] for the row upper - * bounds - * @param num_nz an integer populated with the number of non-zero - * elements in the constraint matrix - * @param matrix_start array of size [to_row - from_row + 1] with the start - * indices of each row in `matrix_index` and `matrix_value` - * @param matrix_index array of size [num_nz] with the column indices of each - * element in the constraint matrix - * @param matrix_value array of size [num_nz] with the non-zero elements of the - * constraint matrix. - * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * To query the constraint coefficients, this function should be called twice. + * + * First, call this function with `matrix_start`, `matrix_index`, and + * `matrix_value` as `NULL`. This call will populate `num_nz` with the number of + * nonzero elements in the corresponding section of the constraint matrix. + * + * Second, allocate new `matrix_index` and `matrix_value` arrays of length + * `num_nz` and call this function again to populate the new arrays with their + * contents. + * + * @param highs A pointer to the Highs instance. + * @param from_row The first row for which to query data for. + * @param to_row The last row (inclusive) for which to query data for. + * @param num_row An integer to be populated with the number of rows got + * from the smodel. + * @param lower An array of size [to_row - from_row + 1] for the row + * lower bounds. + * @param upper An array of size [to_row - from_row + 1] for the row + * upper bounds. + * @param num_nz An integer to be populated with the number of non-zero + * elements in the constraint matrix. + * @param matrix_start An array of size [to_row - from_row + 1] with the start + * indices of each row in `matrix_index` and + * `matrix_value`. + * @param matrix_index An array of size [num_nz] with the column indices of + * each element in the constraint matrix. + * @param matrix_value An array of size [num_nz] with the non-zero elements of + * the constraint matrix. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getRowsByRange(const void* highs, const HighsInt from_row, const HighsInt to_row, HighsInt* num_row, @@ -1433,11 +1584,11 @@ HighsInt Highs_getRowsByRange(const void* highs, const HighsInt from_row, * This function is identical to `Highs_getRowsByRange`, except for how the * rows are specified. * - * @param num_set_indices the number of indices in the set - * @param set array of size [num_set_entries] with the row indices - * to get + * @param num_set_indices The number of indices in `set`. + * @param set An array of size [num_set_entries] containing the + * row indices to get. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getRowsBySet(const void* highs, const HighsInt num_set_entries, const HighsInt* set, HighsInt* num_row, @@ -1451,24 +1602,84 @@ HighsInt Highs_getRowsBySet(const void* highs, const HighsInt num_set_entries, * This function is identical to `Highs_getRowsByRange`, except for how the * rows are specified. * - * @param mask array of length [num_row] containing a 1 to get the row and 0 - * otherwise + * @param mask An array of length [num_row] containing a `1` to get the row and + * `0` otherwise. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getRowsByMask(const void* highs, const HighsInt* mask, HighsInt* num_row, double* lower, double* upper, HighsInt* num_nz, HighsInt* matrix_start, HighsInt* matrix_index, double* matrix_value); +/** + * Get the name of a row. + * + * @param row The index of the row to query. + * @param name A pointer in which to store the name of the row. This must have + * length `kHighsMaximumStringLength`. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getRowName(const void* highs, const HighsInt row, char* name); + +/** + * Get the index of a row from its name. + * + * If multiple rows have the same name, or if no row exists with `name`, this + * function returns `kHighsStatusError`. + * + * @param name A pointer of the name of the row to query. + * @param row A pointer in which to store the index of the row + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getRowByName(const void* highs, const char* name, HighsInt* row); + +/** + * Get the name of a column. + * + * @param col The index of the column to query. + * @param name A pointer in which to store the name of the column. This must + * have length `kHighsMaximumStringLength`. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getColName(const void* highs, const HighsInt col, char* name); + +/** + * Get the index of a column from its name. + * + * If multiple columns have the same name, or if no column exists with `name`, + * this function returns `kHighsStatusError`. + * + * @param name A pointer of the name of the column to query. + * @param col A pointer in which to store the index of the column + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getColByName(const void* highs, const char* name, HighsInt* col); + +/** + * Get the integrality of a column. + * + * @param col The index of the column to query. + * @param integrality An integer in which the integrality of the column should + * be placed. The integer is one of the `kHighsVarTypeXXX` + * constants. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getColIntegrality(const void* highs, const HighsInt col, + HighsInt* integrality); /** * Delete multiple adjacent columns. * - * @param highs a pointer to the Highs instance - * @param from_col the index of the first column to delete - * @param to_col the index of the last column to delete + * @param highs A pointer to the Highs instance. + * @param from_col The index of the first column to delete. + * @param to_col The index of the last column to delete. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_deleteColsByRange(void* highs, const HighsInt from_col, const HighsInt to_col); @@ -1476,12 +1687,12 @@ HighsInt Highs_deleteColsByRange(void* highs, const HighsInt from_col, /** * Delete multiple columns given by an array of indices. * - * @param highs a pointer to the Highs instance - * @param num_set_entries the number of columns to delete - * @param set an array of size [num_set_entries] with the indices - * of the columns to delete + * @param highs A pointer to the Highs instance. + * @param num_set_entries The number of columns to delete. + * @param set An array of size [num_set_entries] with the indices + * of the columns to delete. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_deleteColsBySet(void* highs, const HighsInt num_set_entries, const HighsInt* set); @@ -1489,22 +1700,22 @@ HighsInt Highs_deleteColsBySet(void* highs, const HighsInt num_set_entries, /** * Delete multiple columns given by a mask. * - * @param highs a pointer to the Highs instance - * @param mask an array of length [num_col] with 1 if the column - * should be deleted and 0 otherwise + * @param highs A pointer to the Highs instance. + * @param mask An array of length [num_col] with 1 if the column + * should be deleted and 0 otherwise. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_deleteColsByMask(void* highs, HighsInt* mask); /** * Delete multiple adjacent rows. * - * @param highs a pointer to the Highs instance - * @param from_row the index of the first row to delete - * @param to_row the index of the last row to delete + * @param highs A pointer to the Highs instance. + * @param from_row The index of the first row to delete. + * @param to_row The index of the last row to delete. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_deleteRowsByRange(void* highs, const int from_row, const HighsInt to_row); @@ -1512,12 +1723,12 @@ HighsInt Highs_deleteRowsByRange(void* highs, const int from_row, /** * Delete multiple rows given by an array of indices. * - * @param highs a pointer to the Highs instance - * @param num_set_entries the number of rows to delete - * @param set an array of size [num_set_entries] with the indices - * of the rows to delete + * @param highs A pointer to the Highs instance. + * @param num_set_entries The number of rows to delete. + * @param set An array of size [num_set_entries] with the indices + * of the rows to delete. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_deleteRowsBySet(void* highs, const HighsInt num_set_entries, const HighsInt* set); @@ -1525,12 +1736,12 @@ HighsInt Highs_deleteRowsBySet(void* highs, const HighsInt num_set_entries, /** * Delete multiple rows given by a mask. * - * @param highs a pointer to the Highs instance - * @param mask an array of length [num_row] with 1 if the row should be - * deleted and 0 otherwise. New index of any column not - * deleted is returned in place of the value 0. + * @param highs A pointer to the Highs instance. + * @param mask An array of length [num_row] with `1` if the row should be + * deleted and `0` otherwise. The new index of any column not + * deleted is stored in place of the value `0`. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_deleteRowsByMask(void* highs, HighsInt* mask); @@ -1540,71 +1751,69 @@ HighsInt Highs_deleteRowsByMask(void* highs, HighsInt* mask); * Scaling a column modifies the elements in the constraint matrix, the variable * bounds, and the objective coefficient. * - * If scaleval < 0, the variable bounds flipped. - * - * @param highs a pointer to the Highs instance - * @param col the index of the column to scale - * @param scaleval the value by which to scale the column + * @param highs A pointer to the Highs instance. + * @param col The index of the column to scale. + * @param scaleval The value by which to scale the column. If `scaleval < 0`, + * the variable bounds flipped. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_scaleCol(void* highs, const HighsInt col, const double scaleval); /** * Scale a row by a constant. * - * If scaleval < 0, the row bounds are flipped. - * - * @param highs a pointer to the Highs instance - * @param row the index of the row to scale - * @param scaleval the value by which to scale the row + * @param highs A pointer to the Highs instance. + * @param row The index of the row to scale. + * @param scaleval The value by which to scale the row. If `scaleval < 0`, the + * row bounds are flipped. * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_scaleRow(void* highs, const HighsInt row, const double scaleval); /** * Return the value of infinity used by HiGHS. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the value of infinity used by HiGHS + * @returns The value of infinity used by HiGHS. */ double Highs_getInfinity(const void* highs); /** * Return the number of columns in the model. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the number of columns in the model + * @returns The number of columns in the model. */ HighsInt Highs_getNumCol(const void* highs); /** * Return the number of rows in the model. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the number of rows in the model. + * @returns The number of rows in the model. */ HighsInt Highs_getNumRow(const void* highs); /** * Return the number of nonzeros in the constraint matrix of the model. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the number of nonzeros in the constraint matrix of the model. + * @returns The number of nonzeros in the constraint matrix of the model. */ HighsInt Highs_getNumNz(const void* highs); /** * Return the number of nonzeroes in the Hessian matrix of the model. * - * @param highs a pointer to the Highs instance + * @param highs A pointer to the Highs instance. * - * @returns the number of nonzeroes in the Hessian matrix of the model. + * @returns The number of nonzeroes in the Hessian matrix of the model. */ HighsInt Highs_getHessianNumNz(const void* highs); @@ -1622,7 +1831,7 @@ HighsInt Highs_getHessianNumNz(const void* highs); * - `Highs_getNumNz` * - `Highs_getHessianNumNz` * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_getModel(const void* highs, const HighsInt a_format, const HighsInt q_format, HighsInt* num_col, @@ -1636,37 +1845,109 @@ HighsInt Highs_getModel(const void* highs, const HighsInt a_format, /** * Set a primal (and possibly dual) solution as a starting point, then run - * crossover to compute a basic feasible solution. If there is no dual solution, - * pass col_dual and row_dual as nullptr. - * - * @param highs a pointer to the Highs instance - * @param num_col the number of variables - * @param num_row the number of rows - * @param col_value array of length [num_col] with optimal primal solution for - * each column - * @param col_dual array of length [num_col] with optimal dual solution for - * each column - * @param row_dual array of length [num_row] with optimal dual solution for - * each row - * - * @returns a `kHighsStatus` constant indicating whether the call succeeded + * crossover to compute a basic feasible solution. + * + * @param highs A pointer to the Highs instance. + * @param num_col The number of variables. + * @param num_row The number of rows. + * @param col_value An array of length [num_col] with optimal primal solution + * for each column. + * @param col_dual An array of length [num_col] with optimal dual solution for + * each column. May be `NULL`, in which case no dual solution + * is passed. + * @param row_dual An array of length [num_row] with optimal dual solution for + * each row. . May be `NULL`, in which case no dual solution + * is passed. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ HighsInt Highs_crossover(void* highs, const int num_col, const int num_row, const double* col_value, const double* col_dual, const double* row_dual); /** - * Releases all resources held by the global scheduler instance. It is - * not thread-safe to call this function while calling Highs_run()/Highs_*call() - * on any other Highs instance in any thread. After this function has terminated - * it is guaranteed that eventually all previously created scheduler threads - * will terminate and allocated memory will be released. After this function - * has returned the option value for the number of threads may be altered to a - * new value before the next call to Highs_run()/Highs_*call(). If the given - * parameter has a nonzero value, then the function will not return until all - * memory is freed, which might be desirable when debugging heap memory but - * requires the calling thread to wait for all scheduler threads to wake-up - * which is usually not necessary. + * Compute the ranging information for all costs and bounds. For + * nonbasic variables the ranging informaiton is relative to the + * active bound. For basic variables the ranging information relates + * to... + * + * For any values that are not required, pass NULL. + * + * @param highs A pointer to the Highs instance. + * @param col_cost_up_value The upper range of the cost value + * @param col_cost_up_objective The objective at the upper cost range + * @param col_cost_up_in_var The variable entering the basis at the upper + * cost range + * @param col_cost_up_ou_var The variable leaving the basis at the upper + * cost range + * @param col_cost_dn_value The lower range of the cost value + * @param col_cost_dn_objective The objective at the lower cost range + * @param col_cost_dn_in_var The variable entering the basis at the lower + * cost range + * @param col_cost_dn_ou_var The variable leaving the basis at the lower + * cost range + * @param col_bound_up_value The upper range of the column bound value + * @param col_bound_up_objective The objective at the upper column bound range + * @param col_bound_up_in_var The variable entering the basis at the upper + * column bound range + * @param col_bound_up_ou_var The variable leaving the basis at the upper + * column bound range + * @param col_bound_dn_value The lower range of the column bound value + * @param col_bound_dn_objective The objective at the lower column bound range + * @param col_bound_dn_in_var The variable entering the basis at the lower + * column bound range + * @param col_bound_dn_ou_var The variable leaving the basis at the lower + * column bound range + * @param row_bound_up_value The upper range of the row bound value + * @param row_bound_up_objective The objective at the upper row bound range + * @param row_bound_up_in_var The variable entering the basis at the upper + * row bound range + * @param row_bound_up_ou_var The variable leaving the basis at the upper row + * bound range + * @param row_bound_dn_value The lower range of the row bound value + * @param row_bound_dn_objective The objective at the lower row bound range + * @param row_bound_dn_in_var The variable entering the basis at the lower + * row bound range + * @param row_bound_dn_ou_var The variable leaving the basis at the lower row + * bound range + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getRanging( + void* highs, + // + double* col_cost_up_value, double* col_cost_up_objective, + HighsInt* col_cost_up_in_var, HighsInt* col_cost_up_ou_var, + double* col_cost_dn_value, double* col_cost_dn_objective, + HighsInt* col_cost_dn_in_var, HighsInt* col_cost_dn_ou_var, + double* col_bound_up_value, double* col_bound_up_objective, + HighsInt* col_bound_up_in_var, HighsInt* col_bound_up_ou_var, + double* col_bound_dn_value, double* col_bound_dn_objective, + HighsInt* col_bound_dn_in_var, HighsInt* col_bound_dn_ou_var, + double* row_bound_up_value, double* row_bound_up_objective, + HighsInt* row_bound_up_in_var, HighsInt* row_bound_up_ou_var, + double* row_bound_dn_value, double* row_bound_dn_objective, + HighsInt* row_bound_dn_in_var, HighsInt* row_bound_dn_ou_var); + +/** + * Releases all resources held by the global scheduler instance. + * + * It is not thread-safe to call this function while calling `Highs_run` or one + * of the `Highs_XXXcall` methods on any other Highs instance in any thread. + * + * After this function has terminated, it is guaranteed that eventually all + * previously created scheduler threads will terminate and allocated memory will + * be released. + * + * After this function has returned, the option value for the number of threads + * may be altered to a new value before the next call to `Highs_run` or one of + * the `Highs_XXXcall` methods. + * + * @param blocking If the `blocking` parameter has a nonzero value, then this + * function will not return until all memory is freed, which + * might be desirable when debugging heap memory, but it + * requires the calling thread to wait for all scheduler + * threads to wake-up which is usually not necessary. * * @returns No status is returned since the function call cannot fail. Calling * this function while any Highs instance is in use on any thread is diff --git a/src/interfaces/highs_csharp_api.cs b/src/interfaces/highs_csharp_api.cs index dc2b3def27..dd76267e2e 100644 --- a/src/interfaces/highs_csharp_api.cs +++ b/src/interfaces/highs_csharp_api.cs @@ -7,902 +7,982 @@ public enum HighsStatus { - kError = -1, - kOk, - kWarning + kError = -1, + kOk, + kWarning } -public enum HighsMatrixFormat -{ - kColwise = 1, - kRowwise +public enum HighsMatrixFormat +{ + kColwise = 1, + kRowwise } public enum HighsBasisStatus { - kLower = 0, - kBasic, - kUpper, - kZero, - kNonbasic + kLower = 0, + kBasic, + kUpper, + kZero, + kNonbasic } public enum HighsObjectiveSense { - kMinimize = 1, - kMaximize = -1 + kMinimize = 1, + kMaximize = -1 } public enum HighsModelStatus { - kNotset = 0, - kLoadError, - kModelError, - kPresolveError, - kSolveError, - kPostsolveError, - kModelEmpty, - kOptimal, - kInfeasible, - kUnboundedOrInfeasible, - kUnbounded, - kObjectiveBound, - kObjectiveTarget, - kTimeLimit, - kIterationLimit, - kUnknown, - kSolutionLimit + kNotset = 0, + kLoadError, + kModelError, + kPresolveError, + kSolveError, + kPostsolveError, + kModelEmpty, + kOptimal, + kInfeasible, + kUnboundedOrInfeasible, + kUnbounded, + kObjectiveBound, + kObjectiveTarget, + kTimeLimit, + kIterationLimit, + kUnknown, + kSolutionLimit } public enum HighsIntegrality { - kContinuous = 0, - kInteger = 1, - kSemiContinuous = 2, - kSemiInteger = 3, - kImplicitInteger = 4, + kContinuous = 0, + kInteger = 1, + kSemiContinuous = 2, + kSemiInteger = 3, + kImplicitInteger = 4, } public class HighsModel { - public HighsObjectiveSense sense; - public double[] colcost; - public double offset; - public double[] collower; - public double[] colupper; - public double[] rowlower; - public double[] rowupper; - public HighsMatrixFormat a_format; - public int[] astart; - public int[] aindex; - public double[] avalue; - public int[] highs_integrality; - - public HighsModel() - { - - } - - public HighsModel(double[] colcost, double[] collower, double[] colupper, double[] rowlower, double[] rowupper, - int[] astart, int[] aindex, double[] avalue, int[] highs_integrality = null, double offset = 0, HighsMatrixFormat a_format = HighsMatrixFormat.kColwise, HighsObjectiveSense sense = HighsObjectiveSense.kMinimize) - { - this.colcost = colcost; - this.collower = collower; - this.colupper = colupper; - this.rowlower = rowlower; - this.rowupper = rowupper; - this.astart = astart; - this.aindex = aindex; - this.avalue = avalue; - this.offset = offset; - this.a_format = a_format; - this.sense = sense; - this.highs_integrality = highs_integrality; - } + public HighsObjectiveSense sense; + public double[] colcost; + public double offset; + public double[] collower; + public double[] colupper; + public double[] rowlower; + public double[] rowupper; + public HighsMatrixFormat a_format; + public int[] astart; + public int[] aindex; + public double[] avalue; + public int[] highs_integrality; + + public HighsModel() + { + + } + + public HighsModel(double[] colcost, double[] collower, double[] colupper, double[] rowlower, double[] rowupper, + int[] astart, int[] aindex, double[] avalue, int[] highs_integrality = null, double offset = 0, HighsMatrixFormat a_format = HighsMatrixFormat.kColwise, HighsObjectiveSense sense = HighsObjectiveSense.kMinimize) + { + this.colcost = colcost; + this.collower = collower; + this.colupper = colupper; + this.rowlower = rowlower; + this.rowupper = rowupper; + this.astart = astart; + this.aindex = aindex; + this.avalue = avalue; + this.offset = offset; + this.a_format = a_format; + this.sense = sense; + this.highs_integrality = highs_integrality; + } } public class HighsSolution { - public double[] colvalue; - public double[] coldual; - public double[] rowvalue; - public double[] rowdual; - - public HighsSolution(int numcol, int numrow) - { - this.colvalue = new double[numcol]; - this.coldual = new double[numcol]; - this.rowvalue = new double[numrow]; - this.rowdual = new double[numrow]; - } - - public HighsSolution(double[] colvalue, double[] coldual, double[] rowvalue, double[] rowdual) - { - this.colvalue = colvalue; - this.coldual = coldual; - this.rowvalue = rowvalue; - this.rowdual = rowdual; - } + public double[] colvalue; + public double[] coldual; + public double[] rowvalue; + public double[] rowdual; + + public HighsSolution(int numcol, int numrow) + { + this.colvalue = new double[numcol]; + this.coldual = new double[numcol]; + this.rowvalue = new double[numrow]; + this.rowdual = new double[numrow]; + } + + public HighsSolution(double[] colvalue, double[] coldual, double[] rowvalue, double[] rowdual) + { + this.colvalue = colvalue; + this.coldual = coldual; + this.rowvalue = rowvalue; + this.rowdual = rowdual; + } } public class HighsBasis { - public HighsBasisStatus[] colbasisstatus; - public HighsBasisStatus[] rowbasisstatus; - - public HighsBasis(int numcol, int numrow) - { - this.colbasisstatus = new HighsBasisStatus[numcol]; - this.rowbasisstatus = new HighsBasisStatus[numrow]; - } - - public HighsBasis(HighsBasisStatus[] colbasisstatus, HighsBasisStatus[] rowbasisstatus) - { - this.colbasisstatus = colbasisstatus; - this.rowbasisstatus = rowbasisstatus; - } + public HighsBasisStatus[] colbasisstatus; + public HighsBasisStatus[] rowbasisstatus; + + public HighsBasis(int numcol, int numrow) + { + this.colbasisstatus = new HighsBasisStatus[numcol]; + this.rowbasisstatus = new HighsBasisStatus[numrow]; + } + + public HighsBasis(HighsBasisStatus[] colbasisstatus, HighsBasisStatus[] rowbasisstatus) + { + this.colbasisstatus = colbasisstatus; + this.rowbasisstatus = rowbasisstatus; + } } public class HighsLpSolver : IDisposable { - private IntPtr highs; - - private bool _disposed; - - private const string highslibname = "libhighs"; - - [DllImport(highslibname)] - private static extern int Highs_call( - Int32 numcol, - Int32 numrow, - Int32 numnz, - double[] colcost, - double[] collower, - double[] colupper, - double[] rowlower, - double[] rowupper, - int[] astart, - int[] aindex, - double[] avalue, - double[] colvalue, - double[] coldual, - double[] rowvalue, - double[] rowdual, - int[] colbasisstatus, - int[] rowbasisstatus, - ref int modelstatus); - - [DllImport(highslibname)] - private static extern IntPtr Highs_create(); - - [DllImport(highslibname)] - private static extern void Highs_destroy(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_run(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_readModel(IntPtr highs, string filename); - - [DllImport(highslibname)] - private static extern int Highs_writeModel(IntPtr highs, string filename); - - [DllImport(highslibname)] - private static extern int Highs_passLp( - IntPtr highs, - int numcol, - int numrow, - int numnz, - int aformat, - int sense, - double offset, - double[] colcost, - double[] collower, - double[] colupper, - double[] rowlower, - double[] rowupper, - int[] astart, - int[] aindex, - double[] avalue); - - [DllImport(highslibname)] - private static extern int Highs_passMip( - IntPtr highs, - int numcol, - int numrow, - int numnz, - int aformat, - int sense, - double offset, - double[] colcost, - double[] collower, - double[] colupper, - double[] rowlower, - double[] rowupper, - int[] astart, - int[] aindex, - double[] avalue, - int[] highs_integrality); - - [DllImport(highslibname)] - private static extern int Highs_setOptionValue(IntPtr highs, string option, string value); - - [DllImport(highslibname)] - private static extern int Highs_setBoolOptionValue(IntPtr highs, string option, int value); - - [DllImport(highslibname)] - private static extern int Highs_setIntOptionValue(IntPtr highs, string option, int value); - - [DllImport(highslibname)] - private static extern int Highs_setDoubleOptionValue(IntPtr highs, string option, double value); - - [DllImport(highslibname)] - private static extern int Highs_setStringOptionValue(IntPtr highs, string option, string value); - - [DllImport(highslibname)] - private static extern int Highs_getBoolOptionValue(IntPtr highs, string option, out int value); - - [DllImport(highslibname)] - private static extern int Highs_getIntOptionValue(IntPtr highs, string option, out int value); - - [DllImport(highslibname)] - private static extern int Highs_getDoubleOptionValue(IntPtr highs, string option, out double value); - - [DllImport(highslibname)] - private static extern int Highs_getStringOptionValue(IntPtr highs, string option, [Out] StringBuilder value); - - [DllImport(highslibname)] - private static extern int Highs_getSolution(IntPtr highs, double[] colvalue, double[] coldual, double[] rowvalue, double[] rowdual); - - [DllImport(highslibname)] - private static extern int Highs_getNumCol(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_getNumRow(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_getNumNz(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_getBasis(IntPtr highs, int[] colstatus, int[] rowstatus); - - [DllImport(highslibname)] - private static extern double Highs_getObjectiveValue(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_getIterationCount(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_getModelStatus(IntPtr highs); - - [DllImport(highslibname)] - private static extern int Highs_addRow(IntPtr highs, double lower, double upper, int num_new_nz, int[] indices, double[] values); - - [DllImport(highslibname)] - private static extern int Highs_addRows( - IntPtr highs, - int num_new_row, - double[] lower, - double[] upper, - int num_new_nz, - int[] starts, - int[] indices, - double[] values); - - [DllImport(highslibname)] - private static extern int Highs_addCol( - IntPtr highs, - double cost, - double lower, - double upper, - int num_new_nz, - int[] indices, - double[] values); - - [DllImport(highslibname)] - private static extern int Highs_addCols( - IntPtr highs, - int num_new_col, - double[] costs, - double[] lower, - double[] upper, - int num_new_nz, - int[] starts, - int[] indices, - double[] values); - - [DllImport(highslibname)] - private static extern int Highs_changeObjectiveSense(IntPtr highs, int sense); - - [DllImport(highslibname)] - private static extern int Highs_changeColCost(IntPtr highs, int col, double cost); - - [DllImport(highslibname)] - private static extern int Highs_changeColsCostBySet(IntPtr highs, int num_set_entries, int[] set, double[] cost); - - [DllImport(highslibname)] - private static extern int Highs_changeColsCostByMask(IntPtr highs, int[] mask, double[] cost); - - [DllImport(highslibname)] - private static extern int Highs_changeColBounds(IntPtr highs, int col, double lower, double upper); - - [DllImport(highslibname)] - private static extern int Highs_changeColsBoundsByRange(IntPtr highs, int from_col, int to_col, double[] lower, double[] upper); - - [DllImport(highslibname)] - private static extern int Highs_changeColsBoundsBySet(IntPtr highs, int num_set_entries, int[] set, double[] lower, double[] upper); - - [DllImport(highslibname)] - private static extern int Highs_changeColsBoundsByMask(IntPtr highs, int[] mask, double[] lower, double[] upper); - - [DllImport(highslibname)] - private static extern int Highs_changeRowBounds(IntPtr highs, int row, double lower, double upper); - - [DllImport(highslibname)] - private static extern int Highs_changeRowsBoundsBySet(IntPtr highs, int num_set_entries, int[] set, double[] lower, double[] upper); - - [DllImport(highslibname)] - private static extern int Highs_changeRowsBoundsByMask(IntPtr highs, int[] mask, double[] lower, double[] upper); - - [DllImport(highslibname)] - private static extern int Highs_deleteColsByRange(IntPtr highs, int from_col, int to_col); - - [DllImport(highslibname)] - private static extern int Highs_deleteColsBySet(IntPtr highs, int num_set_entries, int[] set); - - [DllImport(highslibname)] - private static extern int Highs_deleteColsByMask(IntPtr highs, int[] mask); - - [DllImport(highslibname)] - private static extern int Highs_deleteRowsByRange(IntPtr highs, int from_row, int to_row); - - [DllImport(highslibname)] - private static extern int Highs_deleteRowsBySet(IntPtr highs, int num_set_entries, int[] set); - - [DllImport(highslibname)] - private static extern int Highs_deleteRowsByMask(IntPtr highs, int[] mask); - - [DllImport(highslibname)] - private static extern int Highs_getDoubleInfoValue(IntPtr highs, string info, out double value); - - [DllImport(highslibname)] - private static extern int Highs_getIntInfoValue(IntPtr highs, string info, out int value); - - [DllImport(highslibname)] - private static extern int Highs_getInt64InfoValue(IntPtr highs, string info, out long value); - - [DllImport(highslibname)] - private static extern int Highs_setSolution(IntPtr highs, double[] col_value, double[] row_value, double[] col_dual, double[] row_dual); - - [DllImport(highslibname)] - private static extern int Highs_getColsByRange( - IntPtr highs, - int from_col, - int to_col, - ref int num_col, - double[] costs, - double[] lower, - double[] upper, - ref int num_nz, - int[] matrix_start, - int[] matrix_index, - double[] matrix_value); - - [DllImport(highslibname)] - private static extern int Highs_getColsBySet( - IntPtr highs, - int num_set_entries, - int[] set, - ref int num_col, - double[] costs, - double[] lower, - double[] upper, - ref int num_nz, - int[] matrix_start, - int[] matrix_index, - double[] matrix_value); - - [DllImport(highslibname)] - private static extern int Highs_getColsByMask( - IntPtr highs, - int[] mask, - ref int num_col, - double[] costs, - double[] lower, - double[] upper, - ref int num_nz, - int[] matrix_start, - int[] matrix_index, - double[] matrix_value); - - [DllImport(highslibname)] - private static extern int Highs_getRowsByRange( - IntPtr highs, - int from_row, - int to_row, - ref int num_row, - double[] lower, - double[] upper, - ref int num_nz, - int[] matrix_start, - int[] matrix_index, - double[] matrix_value); - - [DllImport(highslibname)] - private static extern int Highs_getRowsBySet( - IntPtr highs, - int num_set_entries, - int[] set, - ref int num_row, - double[] lower, - double[] upper, - ref int num_nz, - int[] matrix_start, - int[] matrix_index, - double[] matrix_value); - - [DllImport(highslibname)] - private static extern int Highs_getRowsByMask( - IntPtr highs, - int[] mask, - ref int num_row, - double[] lower, - double[] upper, - ref int num_nz, - int[] matrix_start, - int[] matrix_index, - double[] matrix_value); - - [DllImport(highslibname)] - private static extern int Highs_getBasicVariables(IntPtr highs, int[] basic_variables); - - [DllImport(highslibname)] - private static extern int Highs_getBasisInverseRow(IntPtr highs, int row, double[] row_vector, ref int row_num_nz, int[] row_indices); - - [DllImport(highslibname)] - private static extern int Highs_getBasisInverseCol(IntPtr highs, int col, double[] col_vector, ref int col_num_nz, int[] col_indices); - - [DllImport(highslibname)] - private static extern int Highs_getBasisSolve( - IntPtr highs, - double[] rhs, - double[] solution_vector, - ref int solution_num_nz, - int[] solution_indices); - - [DllImport(highslibname)] - private static extern int Highs_getBasisTransposeSolve( - IntPtr highs, - double[] rhs, - double[] solution_vector, - ref int solution_nz, - int[] solution_indices); - - [DllImport(highslibname)] - private static extern int Highs_getReducedRow(IntPtr highs, int row, double[] row_vector, ref int row_num_nz, int[] row_indices); - - [DllImport(highslibname)] - private static extern int Highs_getReducedColumn(IntPtr highs, int col, double[] col_vector, ref int col_num_nz, int[] col_indices); - - public static HighsStatus call(HighsModel model, ref HighsSolution sol, ref HighsBasis bas, ref HighsModelStatus modelstatus) - { - int nc = model.colcost.Length; - int nr = model.rowlower.Length; - int nnz = model.avalue.Length; - - int[] colbasstat = new int[nc]; - int[] rowbasstat = new int[nr]; - - int modelstate = 0; - - HighsStatus status = (HighsStatus)HighsLpSolver.Highs_call( - nc, - nr, - nnz, - model.colcost, - model.collower, - model.colupper, - model.rowlower, - model.rowupper, - model.astart, - model.aindex, - model.avalue, - sol.colvalue, - sol.coldual, - sol.rowvalue, - sol.rowdual, - colbasstat, - rowbasstat, - ref modelstate); - - modelstatus = (HighsModelStatus)modelstate; - - bas.colbasisstatus = colbasstat.Select(x => (HighsBasisStatus)x).ToArray(); - bas.rowbasisstatus = rowbasstat.Select(x => (HighsBasisStatus)x).ToArray(); - - return status; - } - - public HighsLpSolver() - { - this.highs = HighsLpSolver.Highs_create(); - } - - ~HighsLpSolver() - { - this.Dispose(false); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (this._disposed) - { - return; - } - - HighsLpSolver.Highs_destroy(this.highs); - this._disposed = true; - } - - public HighsStatus run() - { - return (HighsStatus)HighsLpSolver.Highs_run(this.highs); - } - - public HighsStatus readModel(string filename) - { - return (HighsStatus)HighsLpSolver.Highs_readModel(this.highs, filename); - } - - public HighsStatus writeModel(string filename) - { - return (HighsStatus)HighsLpSolver.Highs_writeModel(this.highs, filename); - } - - public HighsStatus passLp(HighsModel model) - { - return (HighsStatus)HighsLpSolver.Highs_passLp( - this.highs, - model.colcost.Length, - model.rowlower.Length, - model.avalue.Length, - (int)model.a_format, - (int)model.sense, - model.offset, - model.colcost, - model.collower, - model.colupper, - model.rowlower, - model.rowupper, - model.astart, - model.aindex, - model.avalue); - } - - public HighsStatus passMip(HighsModel model) - { - return (HighsStatus)HighsLpSolver.Highs_passMip( - this.highs, - model.colcost.Length, - model.rowlower.Length, - model.avalue.Length, - (int)model.a_format, - (int)model.sense, - model.offset, - model.colcost, - model.collower, - model.colupper, - model.rowlower, - model.rowupper, - model.astart, - model.aindex, - model.avalue, - model.highs_integrality); - } - - public HighsStatus setOptionValue(string option, string value) - { - return (HighsStatus)HighsLpSolver.Highs_setOptionValue(this.highs, option, value); - } - - public HighsStatus setStringOptionValue(string option, string value) - { - return (HighsStatus)HighsLpSolver.Highs_setStringOptionValue(this.highs, option, value); - } - - public HighsStatus setBoolOptionValue(string option, int value) - { - return (HighsStatus)HighsLpSolver.Highs_setBoolOptionValue(this.highs, option, value); - } - - public HighsStatus setDoubleOptionValue(string option, double value) - { - return (HighsStatus)HighsLpSolver.Highs_setDoubleOptionValue(this.highs, option, value); - } - - public HighsStatus setIntOptionValue(string option, int value) - { - return (HighsStatus)HighsLpSolver.Highs_setIntOptionValue(this.highs, option, value); - } - - public HighsStatus getStringOptionValue(string option, out string value) - { - var stringBuilder = new StringBuilder(); - var result = (HighsStatus)HighsLpSolver.Highs_getStringOptionValue(this.highs, option, stringBuilder); - value = stringBuilder.ToString(); - return result; - } - - public HighsStatus getBoolOptionValue(string option, out int value) - { - return (HighsStatus)HighsLpSolver.Highs_getBoolOptionValue(this.highs, option, out value); - } - - public HighsStatus getDoubleOptionValue(string option, out double value) - { - return (HighsStatus)HighsLpSolver.Highs_getDoubleOptionValue(this.highs, option, out value); - } - - public HighsStatus getIntOptionValue(string option, out int value) - { - return (HighsStatus)HighsLpSolver.Highs_getIntOptionValue(this.highs, option, out value); - } - - public int getNumCol() - { - return HighsLpSolver.Highs_getNumCol(this.highs); - } - - public int getNumRow() - { - return HighsLpSolver.Highs_getNumRow(this.highs); - } - - public int getNumNz() - { - return HighsLpSolver.Highs_getNumNz(this.highs); - } - - public HighsSolution getSolution() - { - int nc = this.getNumCol(); - int nr = this.getNumRow(); - - HighsSolution sol = new HighsSolution(nc, nr); - HighsLpSolver.Highs_getSolution(this.highs, sol.colvalue, sol.coldual, sol.rowvalue, sol.rowdual); - - return sol; - } - - public HighsBasis getBasis() - { - int nc = this.getNumCol(); - int nr = this.getNumRow(); - - int[] colbasstat = new int[nc]; - int[] rowbasstat = new int[nr]; - - HighsLpSolver.Highs_getBasis(this.highs, colbasstat, rowbasstat); - HighsBasis bas = new HighsBasis( - colbasstat.Select(x => (HighsBasisStatus)x).ToArray(), - rowbasstat.Select(x => (HighsBasisStatus)x).ToArray()); - - return bas; - } - - public double getObjectiveValue() - { - return HighsLpSolver.Highs_getObjectiveValue(this.highs); - } - - public HighsModelStatus GetModelStatus() - { - return (HighsModelStatus)HighsLpSolver.Highs_getModelStatus(this.highs); - } - - public int getIterationCount() - { - return HighsLpSolver.Highs_getIterationCount(this.highs); - } - - public HighsStatus addRow(double lower, double upper, int[] indices, double[] values) - { - return (HighsStatus)HighsLpSolver.Highs_addRow(this.highs, lower, upper, indices.Length, indices, values); - } - - public HighsStatus addRows(double[] lower, double[] upper, int[] starts, int[] indices, double[] values) - { - return (HighsStatus)HighsLpSolver.Highs_addRows(this.highs, lower.Length, lower, upper, indices.Length, starts, indices, values); - } - - public HighsStatus addCol(double cost, double lower, double upper, int[] indices, double[] values) - { - return (HighsStatus)HighsLpSolver.Highs_addCol(this.highs, cost, lower, upper, indices.Length, indices, values); - } - - public HighsStatus addCols(double[] costs, double[] lower, double[] upper, int[] starts, int[] indices, double[] values) - { - return (HighsStatus)HighsLpSolver.Highs_addCols( - this.highs, - costs.Length, - costs, - lower, - upper, - indices.Length, - starts, - indices, - values); - } - - public HighsStatus changeObjectiveSense(HighsObjectiveSense sense) - { - return (HighsStatus)HighsLpSolver.Highs_changeObjectiveSense(this.highs, (int)sense); - } - - public HighsStatus changeColCost(int col, double cost) - { - return (HighsStatus)HighsLpSolver.Highs_changeColCost(this.highs, col, cost); - } - - public HighsStatus changeColsCostBySet(int[] cols, double[] costs) - { - return (HighsStatus)HighsLpSolver.Highs_changeColsCostBySet(this.highs, cols.Length, cols, costs); - } - - public HighsStatus changeColsCostByMask(bool[] mask, double[] cost) - { - return (HighsStatus)HighsLpSolver.Highs_changeColsCostByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray(), cost); - } - - public HighsStatus changeColBounds(int col, double lower, double upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeColBounds(this.highs, col, lower, upper); - } - - public HighsStatus changeColsBoundsByRange(int from, int to, double[] lower, double[] upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeColsBoundsByRange(this.highs, from, to, lower, upper); - } - - public HighsStatus changeColsBoundsBySet(int[] cols, double[] lower, double[] upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeColsBoundsBySet(this.highs, cols.Length, cols, lower, upper); - } - - public HighsStatus changeColsBoundsByMask(bool[] mask, double[] lower, double[] upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeColsBoundsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray(), lower, upper); - } - - public HighsStatus changeRowBounds(int row, double lower, double upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeRowBounds(this.highs, row, lower, upper); - } - - public HighsStatus changeRowsBoundsBySet(int[] rows, double[] lower, double[] upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeRowsBoundsBySet(this.highs, rows.Length, rows, lower, upper); - } - - public HighsStatus changeRowsBoundsByMask(bool[] mask, double[] lower, double[] upper) - { - return (HighsStatus)HighsLpSolver.Highs_changeRowsBoundsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray(), lower, upper); - } - - public HighsStatus deleteColsByRange(int from, int to) - { - return (HighsStatus)HighsLpSolver.Highs_deleteColsByRange(this.highs, from, to); - } - - public HighsStatus deleteColsBySet(int[] cols) - { - return (HighsStatus)HighsLpSolver.Highs_deleteColsBySet(this.highs, cols.Length, cols); - } - - public HighsStatus deleteColsByMask(bool[] mask) - { - return (HighsStatus)HighsLpSolver.Highs_deleteColsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray()); - } - - public HighsStatus deleteRowsByRange(int from, int to) - { - return (HighsStatus)HighsLpSolver.Highs_deleteRowsByRange(this.highs, from, to); - } - - public HighsStatus deleteRowsBySet(int[] rows) - { - return (HighsStatus)HighsLpSolver.Highs_deleteRowsBySet(this.highs, rows.Length, rows); - } - - public HighsStatus deleteRowsByMask(bool[] mask) - { - return (HighsStatus)HighsLpSolver.Highs_deleteRowsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray()); - } - - delegate int HighsGetInfoDelegate(IntPtr highs, string infoName, out TValue output); - - private TValue GetValueOrFallback(HighsGetInfoDelegate highsGetInfoDelegate, string infoName, TValue fallback) - { - try - { - var status = (HighsStatus)highsGetInfoDelegate(this.highs, infoName, out var value); - if (status != HighsStatus.kOk) - { - return fallback; - } - - return value; - } - catch - { - return fallback; - } - } - - /// - /// Gets the current solution info. - /// - /// The . - public SolutionInfo getInfo() - { - // TODO: This object does not contian the "complete" info from the C api. Add further props, if you need them. - var info = new SolutionInfo() - { - MipGap = this.GetValueOrFallback(HighsLpSolver.Highs_getDoubleInfoValue, "mip_gap", double.NaN), - DualBound = this.GetValueOrFallback(HighsLpSolver.Highs_getDoubleInfoValue, "mip_dual_bound", double.NaN), - ObjectiveValue = this.GetValueOrFallback(HighsLpSolver.Highs_getDoubleInfoValue, "objective_function_value", double.NaN), - NodeCount = this.GetValueOrFallback(HighsLpSolver.Highs_getInt64InfoValue, "mip_node_count", 0l), - IpmIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "simplex_iteration_count", 0), - SimplexIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "ipm_iteration_count", 0), - }; - return info; - } - - public HighsStatus setSolution(HighsSolution solution) - { - return (HighsStatus)HighsLpSolver.Highs_setSolution(this.highs, solution.colvalue, solution.coldual, solution.rowvalue, solution.rowdual); - } - - public HighsStatus getBasicVariables(ref int[] basic_variables) - { - return (HighsStatus)Highs_getBasicVariables(this.highs, basic_variables); - } - - public HighsStatus getBasisInverseRow(int row, double[] row_vector, ref int row_num_nz, int[] row_indices) - { - return (HighsStatus)Highs_getBasisInverseRow(this.highs, row, row_vector, ref row_num_nz, row_indices); - } - - public HighsStatus getBasisInverseCol(int col, double[] col_vector, ref int col_num_nz, int[] col_indices) - { - return (HighsStatus)Highs_getBasisInverseCol(this.highs, col, col_vector, ref col_num_nz, col_indices); - } - - public HighsStatus getBasisSolve(double[] rhs, double[] solution_vector, ref int solution_num_nz, int[] solution_indices) - { - return (HighsStatus)Highs_getBasisSolve(this.highs, rhs, solution_vector, ref solution_num_nz, solution_indices); - } - - public HighsStatus getBasisTransposeSolve(double[] rhs, double[] solution_vector, ref int solution_num_nz, int[] solution_indices) - { - return (HighsStatus)Highs_getBasisTransposeSolve(this.highs, rhs, solution_vector, ref solution_num_nz, solution_indices); - } - - public HighsStatus getReducedRow(int row, double[] row_vector, ref int row_num_nz, int[] row_indices) - { - return (HighsStatus)Highs_getReducedRow(this.highs, row, row_vector, ref row_num_nz, row_indices); - } - - public HighsStatus getReducedColumn(int col, double[] col_vector, ref int col_num_nz, int[] col_indices) - { - return (HighsStatus)Highs_getReducedColumn(this.highs, col, col_vector, ref col_num_nz, col_indices); - } + private IntPtr highs; + + private bool _disposed; + + private const string highslibname = "libhighs"; + + [DllImport(highslibname)] + private static extern int Highs_call( + Int32 numcol, + Int32 numrow, + Int32 numnz, + double[] colcost, + double[] collower, + double[] colupper, + double[] rowlower, + double[] rowupper, + int[] astart, + int[] aindex, + double[] avalue, + double[] colvalue, + double[] coldual, + double[] rowvalue, + double[] rowdual, + int[] colbasisstatus, + int[] rowbasisstatus, + ref int modelstatus); + + [DllImport(highslibname)] + private static extern IntPtr Highs_create(); + + [DllImport(highslibname)] + private static extern void Highs_destroy(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_run(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_readModel(IntPtr highs, string filename); + + [DllImport(highslibname)] + private static extern int Highs_writeModel(IntPtr highs, string filename); + + [DllImport(highslibname)] + private static extern int Highs_writeSolutionPretty(IntPtr highs, string filename); + + [DllImport(highslibname)] + private static extern int Highs_getInfinity(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_passLp( + IntPtr highs, + int numcol, + int numrow, + int numnz, + int aformat, + int sense, + double offset, + double[] colcost, + double[] collower, + double[] colupper, + double[] rowlower, + double[] rowupper, + int[] astart, + int[] aindex, + double[] avalue); + + [DllImport(highslibname)] + private static extern int Highs_passMip( + IntPtr highs, + int numcol, + int numrow, + int numnz, + int aformat, + int sense, + double offset, + double[] colcost, + double[] collower, + double[] colupper, + double[] rowlower, + double[] rowupper, + int[] astart, + int[] aindex, + double[] avalue, + int[] highs_integrality); + + [DllImport(highslibname)] + private static extern int Highs_setOptionValue(IntPtr highs, string option, string value); + + [DllImport(highslibname)] + private static extern int Highs_setBoolOptionValue(IntPtr highs, string option, int value); + + [DllImport(highslibname)] + private static extern int Highs_setIntOptionValue(IntPtr highs, string option, int value); + + [DllImport(highslibname)] + private static extern int Highs_setDoubleOptionValue(IntPtr highs, string option, double value); + + [DllImport(highslibname)] + private static extern int Highs_setStringOptionValue(IntPtr highs, string option, string value); + + [DllImport(highslibname)] + private static extern int Highs_getBoolOptionValue(IntPtr highs, string option, out int value); + + [DllImport(highslibname)] + private static extern int Highs_getIntOptionValue(IntPtr highs, string option, out int value); + + [DllImport(highslibname)] + private static extern int Highs_getDoubleOptionValue(IntPtr highs, string option, out double value); + + [DllImport(highslibname)] + private static extern int Highs_getStringOptionValue(IntPtr highs, string option, [Out] StringBuilder value); + + [DllImport(highslibname)] + private static extern int Highs_getSolution(IntPtr highs, double[] colvalue, double[] coldual, double[] rowvalue, double[] rowdual); + + [DllImport(highslibname)] + private static extern int Highs_getNumCol(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_getNumRow(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_getNumNz(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_getBasis(IntPtr highs, int[] colstatus, int[] rowstatus); + + [DllImport(highslibname)] + private static extern double Highs_getObjectiveValue(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_getIterationCount(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_getModelStatus(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_addRow(IntPtr highs, double lower, double upper, int num_new_nz, int[] indices, double[] values); + + [DllImport(highslibname)] + private static extern int Highs_addRows( + IntPtr highs, + int num_new_row, + double[] lower, + double[] upper, + int num_new_nz, + int[] starts, + int[] indices, + double[] values); + + [DllImport(highslibname)] + private static extern int Highs_addCol( + IntPtr highs, + double cost, + double lower, + double upper, + int num_new_nz, + int[] indices, + double[] values); + + [DllImport(highslibname)] + private static extern int Highs_addCols( + IntPtr highs, + int num_new_col, + double[] costs, + double[] lower, + double[] upper, + int num_new_nz, + int[] starts, + int[] indices, + double[] values); + + [DllImport(highslibname)] + private static extern int Highs_changeObjectiveSense(IntPtr highs, int sense); + + [DllImport(highslibname)] + private static extern int Highs_changeColCost(IntPtr highs, int col, double cost); + + [DllImport(highslibname)] + private static extern int Highs_changeColsCostBySet(IntPtr highs, int num_set_entries, int[] set, double[] cost); + + [DllImport(highslibname)] + private static extern int Highs_changeColsCostByMask(IntPtr highs, int[] mask, double[] cost); + + [DllImport(highslibname)] + private static extern int Highs_changeColBounds(IntPtr highs, int col, double lower, double upper); + + [DllImport(highslibname)] + private static extern int Highs_changeColsBoundsByRange(IntPtr highs, int from_col, int to_col, double[] lower, double[] upper); + + [DllImport(highslibname)] + private static extern int Highs_changeColsBoundsBySet(IntPtr highs, int num_set_entries, int[] set, double[] lower, double[] upper); + + [DllImport(highslibname)] + private static extern int Highs_changeColsBoundsByMask(IntPtr highs, int[] mask, double[] lower, double[] upper); + + [DllImport(highslibname)] + private static extern int Highs_changeRowBounds(IntPtr highs, int row, double lower, double upper); + + [DllImport(highslibname)] + private static extern int Highs_changeRowsBoundsBySet(IntPtr highs, int num_set_entries, int[] set, double[] lower, double[] upper); + + [DllImport(highslibname)] + private static extern int Highs_changeRowsBoundsByMask(IntPtr highs, int[] mask, double[] lower, double[] upper); + + [DllImport(highslibname)] + private static extern int Highs_changeColsIntegralityByRange(IntPtr highs, int from_col, int to_col, int[] integrality); + + [DllImport(highslibname)] + private static extern int Highs_changeCoeff(IntPtr highs, int row, int col, double value); + + [DllImport(highslibname)] + private static extern int Highs_deleteColsByRange(IntPtr highs, int from_col, int to_col); + + [DllImport(highslibname)] + private static extern int Highs_deleteColsBySet(IntPtr highs, int num_set_entries, int[] set); + + [DllImport(highslibname)] + private static extern int Highs_deleteColsByMask(IntPtr highs, int[] mask); + + [DllImport(highslibname)] + private static extern int Highs_deleteRowsByRange(IntPtr highs, int from_row, int to_row); + + [DllImport(highslibname)] + private static extern int Highs_deleteRowsBySet(IntPtr highs, int num_set_entries, int[] set); + + [DllImport(highslibname)] + private static extern int Highs_deleteRowsByMask(IntPtr highs, int[] mask); + + [DllImport(highslibname)] + private static extern int Highs_getDoubleInfoValue(IntPtr highs, string info, out double value); + + [DllImport(highslibname)] + private static extern int Highs_getIntInfoValue(IntPtr highs, string info, out int value); + + [DllImport(highslibname)] + private static extern int Highs_getInt64InfoValue(IntPtr highs, string info, out long value); + + [DllImport(highslibname)] + private static extern int Highs_setSolution(IntPtr highs, double[] col_value, double[] row_value, double[] col_dual, double[] row_dual); + + [DllImport(highslibname)] + private static extern int Highs_getColsByRange( + IntPtr highs, + int from_col, + int to_col, + ref int num_col, + double[] costs, + double[] lower, + double[] upper, + ref int num_nz, + int[] matrix_start, + int[] matrix_index, + double[] matrix_value); + + [DllImport(highslibname)] + private static extern int Highs_getColsBySet( + IntPtr highs, + int num_set_entries, + int[] set, + ref int num_col, + double[] costs, + double[] lower, + double[] upper, + ref int num_nz, + int[] matrix_start, + int[] matrix_index, + double[] matrix_value); + + [DllImport(highslibname)] + private static extern int Highs_getColsByMask( + IntPtr highs, + int[] mask, + ref int num_col, + double[] costs, + double[] lower, + double[] upper, + ref int num_nz, + int[] matrix_start, + int[] matrix_index, + double[] matrix_value); + + [DllImport(highslibname)] + private static extern int Highs_getRowsByRange( + IntPtr highs, + int from_row, + int to_row, + ref int num_row, + double[] lower, + double[] upper, + ref int num_nz, + int[] matrix_start, + int[] matrix_index, + double[] matrix_value); + + [DllImport(highslibname)] + private static extern int Highs_getRowsBySet( + IntPtr highs, + int num_set_entries, + int[] set, + ref int num_row, + double[] lower, + double[] upper, + ref int num_nz, + int[] matrix_start, + int[] matrix_index, + double[] matrix_value); + + [DllImport(highslibname)] + private static extern int Highs_getRowsByMask( + IntPtr highs, + int[] mask, + ref int num_row, + double[] lower, + double[] upper, + ref int num_nz, + int[] matrix_start, + int[] matrix_index, + double[] matrix_value); + + [DllImport(highslibname)] + private static extern int Highs_getBasicVariables(IntPtr highs, int[] basic_variables); + + [DllImport(highslibname)] + private static extern int Highs_getBasisInverseRow(IntPtr highs, int row, double[] row_vector, ref int row_num_nz, int[] row_indices); + + [DllImport(highslibname)] + private static extern int Highs_getBasisInverseCol(IntPtr highs, int col, double[] col_vector, ref int col_num_nz, int[] col_indices); + + [DllImport(highslibname)] + private static extern int Highs_getBasisSolve( + IntPtr highs, + double[] rhs, + double[] solution_vector, + ref int solution_num_nz, + int[] solution_indices); + + [DllImport(highslibname)] + private static extern int Highs_getBasisTransposeSolve( + IntPtr highs, + double[] rhs, + double[] solution_vector, + ref int solution_nz, + int[] solution_indices); + + [DllImport(highslibname)] + private static extern int Highs_getReducedRow(IntPtr highs, int row, double[] row_vector, ref int row_num_nz, int[] row_indices); + + [DllImport(highslibname)] + private static extern int Highs_getReducedColumn(IntPtr highs, int col, double[] col_vector, ref int col_num_nz, int[] col_indices); + + [DllImport(highslibname)] + private static extern int Highs_clearModel(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_clearSolver(IntPtr highs); + + [DllImport(highslibname)] + private static extern int Highs_passColName(IntPtr highs, int col, string name); + + [DllImport(highslibname)] + private static extern int Highs_passRowName(IntPtr highs, int row, string name); + + [DllImport(highslibname)] + private static extern int Highs_writeOptions(IntPtr highs, string filename); + + [DllImport(highslibname)] + private static extern int Highs_writeOptionsDeviations(IntPtr highs, string filename); + + public static HighsStatus call(HighsModel model, ref HighsSolution sol, ref HighsBasis bas, ref HighsModelStatus modelstatus) + { + int nc = model.colcost.Length; + int nr = model.rowlower.Length; + int nnz = model.avalue.Length; + + int[] colbasstat = new int[nc]; + int[] rowbasstat = new int[nr]; + + int modelstate = 0; + + HighsStatus status = (HighsStatus)HighsLpSolver.Highs_call( + nc, + nr, + nnz, + model.colcost, + model.collower, + model.colupper, + model.rowlower, + model.rowupper, + model.astart, + model.aindex, + model.avalue, + sol.colvalue, + sol.coldual, + sol.rowvalue, + sol.rowdual, + colbasstat, + rowbasstat, + ref modelstate); + + modelstatus = (HighsModelStatus)modelstate; + + bas.colbasisstatus = colbasstat.Select(x => (HighsBasisStatus)x).ToArray(); + bas.rowbasisstatus = rowbasstat.Select(x => (HighsBasisStatus)x).ToArray(); + + return status; + } + + public HighsLpSolver() + { + this.highs = HighsLpSolver.Highs_create(); + } + + ~HighsLpSolver() + { + this.Dispose(false); + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (this._disposed) + { + return; + } + + HighsLpSolver.Highs_destroy(this.highs); + this._disposed = true; + } + + public HighsStatus run() + { + return (HighsStatus)HighsLpSolver.Highs_run(this.highs); + } + + public HighsStatus readModel(string filename) + { + return (HighsStatus)HighsLpSolver.Highs_readModel(this.highs, filename); + } + + public HighsStatus writeModel(string filename) + { + return (HighsStatus)HighsLpSolver.Highs_writeModel(this.highs, filename); + } + + public HighsStatus writeSolutionPretty(string filename) + { + return (HighsStatus)HighsLpSolver.Highs_writeSolutionPretty(this.highs, filename); + } + + public Double getInfinity() + { + return (Double)HighsLpSolver.Highs_getInfinity(this.highs); + } + + public HighsStatus passLp(HighsModel model) + { + return (HighsStatus)HighsLpSolver.Highs_passLp( + this.highs, + model.colcost.Length, + model.rowlower.Length, + model.avalue.Length, + (int)model.a_format, + (int)model.sense, + model.offset, + model.colcost, + model.collower, + model.colupper, + model.rowlower, + model.rowupper, + model.astart, + model.aindex, + model.avalue); + } + + public HighsStatus passMip(HighsModel model) + { + return (HighsStatus)HighsLpSolver.Highs_passMip( + this.highs, + model.colcost.Length, + model.rowlower.Length, + model.avalue.Length, + (int)model.a_format, + (int)model.sense, + model.offset, + model.colcost, + model.collower, + model.colupper, + model.rowlower, + model.rowupper, + model.astart, + model.aindex, + model.avalue, + model.highs_integrality); + } + + public HighsStatus setOptionValue(string option, string value) + { + return (HighsStatus)HighsLpSolver.Highs_setOptionValue(this.highs, option, value); + } + + public HighsStatus setStringOptionValue(string option, string value) + { + return (HighsStatus)HighsLpSolver.Highs_setStringOptionValue(this.highs, option, value); + } + + public HighsStatus setBoolOptionValue(string option, int value) + { + return (HighsStatus)HighsLpSolver.Highs_setBoolOptionValue(this.highs, option, value); + } + + public HighsStatus setDoubleOptionValue(string option, double value) + { + return (HighsStatus)HighsLpSolver.Highs_setDoubleOptionValue(this.highs, option, value); + } + + public HighsStatus setIntOptionValue(string option, int value) + { + return (HighsStatus)HighsLpSolver.Highs_setIntOptionValue(this.highs, option, value); + } + + public HighsStatus getStringOptionValue(string option, out string value) + { + var stringBuilder = new StringBuilder(); + var result = (HighsStatus)HighsLpSolver.Highs_getStringOptionValue(this.highs, option, stringBuilder); + value = stringBuilder.ToString(); + return result; + } + + public HighsStatus getBoolOptionValue(string option, out int value) + { + return (HighsStatus)HighsLpSolver.Highs_getBoolOptionValue(this.highs, option, out value); + } + + public HighsStatus getDoubleOptionValue(string option, out double value) + { + return (HighsStatus)HighsLpSolver.Highs_getDoubleOptionValue(this.highs, option, out value); + } + + public HighsStatus getIntOptionValue(string option, out int value) + { + return (HighsStatus)HighsLpSolver.Highs_getIntOptionValue(this.highs, option, out value); + } + + public int getNumCol() + { + return HighsLpSolver.Highs_getNumCol(this.highs); + } + + public int getNumRow() + { + return HighsLpSolver.Highs_getNumRow(this.highs); + } + + public int getNumNz() + { + return HighsLpSolver.Highs_getNumNz(this.highs); + } + + public HighsSolution getSolution() + { + int nc = this.getNumCol(); + int nr = this.getNumRow(); + + HighsSolution sol = new HighsSolution(nc, nr); + HighsLpSolver.Highs_getSolution(this.highs, sol.colvalue, sol.coldual, sol.rowvalue, sol.rowdual); + + return sol; + } + + public HighsBasis getBasis() + { + int nc = this.getNumCol(); + int nr = this.getNumRow(); + + int[] colbasstat = new int[nc]; + int[] rowbasstat = new int[nr]; + + HighsLpSolver.Highs_getBasis(this.highs, colbasstat, rowbasstat); + HighsBasis bas = new HighsBasis( + colbasstat.Select(x => (HighsBasisStatus)x).ToArray(), + rowbasstat.Select(x => (HighsBasisStatus)x).ToArray()); + + return bas; + } + + public double getObjectiveValue() + { + return HighsLpSolver.Highs_getObjectiveValue(this.highs); + } + + public HighsModelStatus GetModelStatus() + { + return (HighsModelStatus)HighsLpSolver.Highs_getModelStatus(this.highs); + } + + public int getIterationCount() + { + return HighsLpSolver.Highs_getIterationCount(this.highs); + } + + public HighsStatus addRow(double lower, double upper, int[] indices, double[] values) + { + return (HighsStatus)HighsLpSolver.Highs_addRow(this.highs, lower, upper, indices.Length, indices, values); + } + + public HighsStatus addRows(double[] lower, double[] upper, int[] starts, int[] indices, double[] values) + { + return (HighsStatus)HighsLpSolver.Highs_addRows(this.highs, lower.Length, lower, upper, indices.Length, starts, indices, values); + } + + public HighsStatus addCol(double cost, double lower, double upper, int[] indices, double[] values) + { + return (HighsStatus)HighsLpSolver.Highs_addCol(this.highs, cost, lower, upper, indices.Length, indices, values); + } + + public HighsStatus addCols(double[] costs, double[] lower, double[] upper, int[] starts, int[] indices, double[] values) + { + return (HighsStatus)HighsLpSolver.Highs_addCols( + this.highs, + costs.Length, + costs, + lower, + upper, + indices.Length, + starts, + indices, + values); + } + + public HighsStatus changeObjectiveSense(HighsObjectiveSense sense) + { + return (HighsStatus)HighsLpSolver.Highs_changeObjectiveSense(this.highs, (int)sense); + } + + public HighsStatus changeColCost(int col, double cost) + { + return (HighsStatus)HighsLpSolver.Highs_changeColCost(this.highs, col, cost); + } + + public HighsStatus changeColsCostBySet(int[] cols, double[] costs) + { + return (HighsStatus)HighsLpSolver.Highs_changeColsCostBySet(this.highs, cols.Length, cols, costs); + } + + public HighsStatus changeColsCostByMask(bool[] mask, double[] cost) + { + return (HighsStatus)HighsLpSolver.Highs_changeColsCostByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray(), cost); + } + + public HighsStatus changeColBounds(int col, double lower, double upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeColBounds(this.highs, col, lower, upper); + } + + public HighsStatus changeColsBoundsByRange(int from, int to, double[] lower, double[] upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeColsBoundsByRange(this.highs, from, to, lower, upper); + } + + public HighsStatus changeColsBoundsBySet(int[] cols, double[] lower, double[] upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeColsBoundsBySet(this.highs, cols.Length, cols, lower, upper); + } + + public HighsStatus changeColsBoundsByMask(bool[] mask, double[] lower, double[] upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeColsBoundsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray(), lower, upper); + } + + public HighsStatus changeRowBounds(int row, double lower, double upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeRowBounds(this.highs, row, lower, upper); + } + + public HighsStatus changeRowsBoundsBySet(int[] rows, double[] lower, double[] upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeRowsBoundsBySet(this.highs, rows.Length, rows, lower, upper); + } + + public HighsStatus changeRowsBoundsByMask(bool[] mask, double[] lower, double[] upper) + { + return (HighsStatus)HighsLpSolver.Highs_changeRowsBoundsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray(), lower, upper); + } + + public HighsStatus changeColsIntegralityByRange(int from_col, int to_col, HighsIntegrality[] integrality) + { + return (HighsStatus)HighsLpSolver.Highs_changeColsIntegralityByRange(this.highs, from_col, to_col, Array.ConvertAll(integrality, item => (int)item)); + } + + public HighsStatus changeCoeff(int row, int col, double value) + { + return (HighsStatus)HighsLpSolver.Highs_changeCoeff(this.highs, row, col, value); + } + + public HighsStatus deleteColsByRange(int from, int to) + { + return (HighsStatus)HighsLpSolver.Highs_deleteColsByRange(this.highs, from, to); + } + + public HighsStatus deleteColsBySet(int[] cols) + { + return (HighsStatus)HighsLpSolver.Highs_deleteColsBySet(this.highs, cols.Length, cols); + } + + public HighsStatus deleteColsByMask(bool[] mask) + { + return (HighsStatus)HighsLpSolver.Highs_deleteColsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray()); + } + + public HighsStatus deleteRowsByRange(int from, int to) + { + return (HighsStatus)HighsLpSolver.Highs_deleteRowsByRange(this.highs, from, to); + } + + public HighsStatus deleteRowsBySet(int[] rows) + { + return (HighsStatus)HighsLpSolver.Highs_deleteRowsBySet(this.highs, rows.Length, rows); + } + + public HighsStatus deleteRowsByMask(bool[] mask) + { + return (HighsStatus)HighsLpSolver.Highs_deleteRowsByMask(this.highs, mask.Select(x => x ? 1 : 0).ToArray()); + } + + delegate int HighsGetInfoDelegate(IntPtr highs, string infoName, out TValue output); + + private TValue GetValueOrFallback(HighsGetInfoDelegate highsGetInfoDelegate, string infoName, TValue fallback) + { + try + { + var status = (HighsStatus)highsGetInfoDelegate(this.highs, infoName, out var value); + if (status != HighsStatus.kOk) + { + return fallback; + } + + return value; + } + catch + { + return fallback; + } + } + + /// + /// Gets the current solution info. + /// + /// The . + public SolutionInfo getInfo() + { + // TODO: This object does not contian the "complete" info from the C api. Add further props, if you need them. + var info = new SolutionInfo() + { + MipGap = this.GetValueOrFallback(HighsLpSolver.Highs_getDoubleInfoValue, "mip_gap", double.NaN), + DualBound = this.GetValueOrFallback(HighsLpSolver.Highs_getDoubleInfoValue, "mip_dual_bound", double.NaN), + ObjectiveValue = this.GetValueOrFallback(HighsLpSolver.Highs_getDoubleInfoValue, "objective_function_value", double.NaN), + NodeCount = this.GetValueOrFallback(HighsLpSolver.Highs_getInt64InfoValue, "mip_node_count", 0l), + IpmIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "simplex_iteration_count", 0), + SimplexIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "ipm_iteration_count", 0), + }; + return info; + } + + public HighsStatus setSolution(HighsSolution solution) + { + return (HighsStatus)HighsLpSolver.Highs_setSolution(this.highs, solution.colvalue, solution.coldual, solution.rowvalue, solution.rowdual); + } + + public HighsStatus getBasicVariables(ref int[] basic_variables) + { + return (HighsStatus)Highs_getBasicVariables(this.highs, basic_variables); + } + + public HighsStatus getBasisInverseRow(int row, double[] row_vector, ref int row_num_nz, int[] row_indices) + { + return (HighsStatus)Highs_getBasisInverseRow(this.highs, row, row_vector, ref row_num_nz, row_indices); + } + + public HighsStatus getBasisInverseCol(int col, double[] col_vector, ref int col_num_nz, int[] col_indices) + { + return (HighsStatus)Highs_getBasisInverseCol(this.highs, col, col_vector, ref col_num_nz, col_indices); + } + + public HighsStatus getBasisSolve(double[] rhs, double[] solution_vector, ref int solution_num_nz, int[] solution_indices) + { + return (HighsStatus)Highs_getBasisSolve(this.highs, rhs, solution_vector, ref solution_num_nz, solution_indices); + } + + public HighsStatus getBasisTransposeSolve(double[] rhs, double[] solution_vector, ref int solution_num_nz, int[] solution_indices) + { + return (HighsStatus)Highs_getBasisTransposeSolve(this.highs, rhs, solution_vector, ref solution_num_nz, solution_indices); + } + + public HighsStatus getReducedRow(int row, double[] row_vector, ref int row_num_nz, int[] row_indices) + { + return (HighsStatus)Highs_getReducedRow(this.highs, row, row_vector, ref row_num_nz, row_indices); + } + + public HighsStatus getReducedColumn(int col, double[] col_vector, ref int col_num_nz, int[] col_indices) + { + return (HighsStatus)Highs_getReducedColumn(this.highs, col, col_vector, ref col_num_nz, col_indices); + } + + public HighsStatus clearModel() + { + return (HighsStatus)Highs_clearModel(this.highs); + } + + public HighsStatus clearSolver() + { + return (HighsStatus)Highs_clearSolver(this.highs); + } + + public HighsStatus passColName(int col, string name) + { + return (HighsStatus)Highs_passColName(this.highs, col, name); + } + + public HighsStatus passRowName(int row, string name) + { + return (HighsStatus)Highs_passRowName(this.highs, row, name); + } + + public HighsStatus writeOptions(string filename) + { + return (HighsStatus)Highs_writeOptions(this.highs, filename); + } + + public HighsStatus writeOptionsDeviations(string filename) + { + return (HighsStatus)Highs_writeOptionsDeviations(this.highs, filename); + } } /// @@ -910,33 +990,33 @@ public HighsStatus getReducedColumn(int col, double[] col_vector, ref int col_nu /// public class SolutionInfo { - /// - /// Gets or sets the simplex iteration count. - /// - public int SimplexIterationCount { get; set; } - - /// - /// Gets or sets the Interior Point Method (IPM) iteration count. - /// - public int IpmIterationCount { get; set; } - - /// - /// Gets or sets the MIP gap. - /// - public double MipGap { get; set; } - - /// - /// Gets or sets the best dual bound. - /// - public double DualBound { get; set; } - - /// - /// Gets or sets the MIP node count. - /// - public long NodeCount { get; set; } - - /// - /// Gets or sets the objective value. - /// - public double ObjectiveValue { get; set; } -} + /// + /// Gets or sets the simplex iteration count. + /// + public int SimplexIterationCount { get; set; } + + /// + /// Gets or sets the Interior Point Method (IPM) iteration count. + /// + public int IpmIterationCount { get; set; } + + /// + /// Gets or sets the MIP gap. + /// + public double MipGap { get; set; } + + /// + /// Gets or sets the best dual bound. + /// + public double DualBound { get; set; } + + /// + /// Gets or sets the MIP node count. + /// + public long NodeCount { get; set; } + + /// + /// Gets or sets the objective value. + /// + public double ObjectiveValue { get; set; } +} \ No newline at end of file diff --git a/src/interfaces/highs_fortran_api.f90 b/src/interfaces/highs_fortran_api.f90 index bb72417c50..05e2d17b1b 100644 --- a/src/interfaces/highs_fortran_api.f90 +++ b/src/interfaces/highs_fortran_api.f90 @@ -340,6 +340,14 @@ function Highs_getDoubleInfoValue ( h, o, v ) result( s ) bind ( c, name='Highs_ integer ( c_int ) :: s end function Highs_getDoubleInfoValue + function Highs_getInfoType ( h, o, v ) result( s ) bind ( c, name='Highs_getInfoType' ) + use iso_c_binding + type(c_ptr), VALUE :: h + character( c_char ) :: o(*) + integer ( c_int ) :: v + integer ( c_int ) :: s + end function Highs_getInfoType + function Highs_getSolution (h, cv, cd, rv, rd) result ( s ) bind ( c, name='Highs_getSolution' ) use iso_c_binding type(c_ptr), VALUE :: h diff --git a/src/interfaces/highspy/highspy/tests/test_highspy.py b/src/interfaces/highspy/highspy/tests/test_highspy.py deleted file mode 100644 index 808b69a6da..0000000000 --- a/src/interfaces/highspy/highspy/tests/test_highspy.py +++ /dev/null @@ -1,233 +0,0 @@ -import unittest -import highspy -import numpy as np -from pyomo.common.tee import capture_output -from io import StringIO - - -class TestHighsPy(unittest.TestCase): - def get_basic_model(self): - """ - min y - s.t. - -x + y >= 2 - x + y >= 0 - """ - inf = highspy.kHighsInf - h = highspy.Highs() - h.addVars(2, np.array([-inf, -inf]), np.array([inf, inf])) - h.changeColsCost(2, np.array([0, 1]), np.array([0, 1], dtype=np.double)) - num_cons = 2 - lower = np.array([2, 0], dtype=np.double) - upper = np.array([inf, inf], dtype=np.double) - num_new_nz = 4 - starts = np.array([0, 2]) - indices = np.array([0, 1, 0, 1]) - values = np.array([-1, 1, 1, 1], dtype=np.double) - h.addRows(num_cons, lower, upper, num_new_nz, starts, indices, values) - h.setOptionValue('log_to_console', False) - return h - - def get_infeasible_model(self): - inf = highspy.kHighsInf - lp = highspy.HighsLp() - lp.num_col_ = 2; - lp.num_row_ = 2; - lp.col_cost_ = np.array([10, 15], dtype=np.double) - lp.col_lower_ = np.array([0, 0], dtype=np.double) - lp.col_upper_ = np.array([inf, inf], dtype=np.double) - lp.row_lower_ = np.array([3, 1], dtype=np.double) - lp.row_upper_ = np.array([3, 1], dtype=np.double) - lp.a_matrix_.start_ = np.array([0, 2, 4]) - lp.a_matrix_.index_ = np.array([0, 1, 0, 1]) - lp.a_matrix_.value_ = np.array([2, 1, 1, 3], dtype=np.double) - lp.offset_ = 0; - h = highspy.Highs() - h.passModel(lp) - h.setOptionValue('log_to_console', False) - h.setOptionValue('presolve', 'off') - return h - - def test_basics(self): - inf = highspy.kHighsInf - h = self.get_basic_model() - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -1) - self.assertAlmostEqual(sol.col_value[1], 1) - - """ - min y - s.t. - -x + y >= 3 - x + y >= 0 - """ - h.changeRowBounds(0, 3, inf) - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -1.5) - self.assertAlmostEqual(sol.col_value[1], 1.5) - - # now make y integer - h.changeColsIntegrality(1, np.array([1]), np.array([highspy.HighsVarType.kInteger])) - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -1) - self.assertAlmostEqual(sol.col_value[1], 2) - - """ - now delete the first constraint and add a new one - - min y - s.t. - x + y >= 0 - -x + y >= 0 - """ - h.deleteRows(1, np.array([0])) - h.addRows(1, np.array([0], dtype=np.double), np.array([inf]), 2, - np.array([0]), np.array([0, 1]), np.array([-1, 1], dtype=np.double)) - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], 0) - self.assertAlmostEqual(sol.col_value[1], 0) - - # change the upper bound of x to -5 - h.changeColsBounds(1, np.array([0]), np.array([-inf], dtype=np.double), - np.array([-5], dtype=np.double)) - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -5) - self.assertAlmostEqual(sol.col_value[1], 5) - - # now maximize - h.changeColCost(1, -1) - h.changeRowBounds(0, -inf, 0) - h.changeRowBounds(1, -inf, 0) - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -5) - self.assertAlmostEqual(sol.col_value[1], -5) - - h.changeColCost(1, 1) - self.assertEqual(h.getObjectiveSense(), highspy.ObjSense.kMinimize) - h.changeObjectiveSense(highspy.ObjSense.kMaximize) - self.assertEqual(h.getObjectiveSense(), highspy.ObjSense.kMaximize) - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -5) - self.assertAlmostEqual(sol.col_value[1], -5) - - self.assertAlmostEqual(h.getObjectiveValue(), -5) - h.changeObjectiveOffset(1) - self.assertAlmostEqual(h.getObjectiveOffset(), 1) - h.run() - self.assertAlmostEqual(h.getObjectiveValue(), -4) - - def test_options(self): - # test bool option - h = highspy.Highs() - h.setOptionValue('log_to_console', True) - self.assertTrue(h.getOptionValue('log_to_console')) - h.setOptionValue('log_to_console', False) - self.assertFalse(h.getOptionValue('log_to_console')) - - # test string option - h.setOptionValue('presolve', 'off') - self.assertEqual(h.getOptionValue('presolve'), 'off') - h.setOptionValue('presolve', 'on') - self.assertEqual(h.getOptionValue('presolve'), 'on') - - # test int option - h.setOptionValue('threads', 1) - self.assertEqual(h.getOptionValue('threads'), 1) - h.setOptionValue('threads', 2) - self.assertEqual(h.getOptionValue('threads'), 2) - - # test double option - h.setOptionValue('time_limit', 1.7) - self.assertAlmostEqual(h.getOptionValue('time_limit'), 1.7) - h.setOptionValue('time_limit', 2.7) - self.assertAlmostEqual(h.getOptionValue('time_limit'), 2.7) - - def test_clear(self): - h = self.get_basic_model() - self.assertEqual(h.getNumCol(), 2) - self.assertEqual(h.getNumRow(), 2) - self.assertEqual(h.getNumNz(), 4) - - orig_feas_tol = h.getOptionValue('primal_feasibility_tolerance') - new_feas_tol = orig_feas_tol + 1 - h.setOptionValue('primal_feasibility_tolerance', new_feas_tol) - self.assertAlmostEqual(h.getOptionValue('primal_feasibility_tolerance'), new_feas_tol) - h.clear() - self.assertEqual(h.getNumCol(), 0) - self.assertEqual(h.getNumRow(), 0) - self.assertEqual(h.getNumNz(), 0) - self.assertAlmostEqual(h.getOptionValue('primal_feasibility_tolerance'), orig_feas_tol) - - h = self.get_basic_model() - h.setOptionValue('primal_feasibility_tolerance', new_feas_tol) - self.assertAlmostEqual(h.getOptionValue('primal_feasibility_tolerance'), new_feas_tol) - h.clearModel() - self.assertEqual(h.getNumCol(), 0) - self.assertEqual(h.getNumRow(), 0) - self.assertEqual(h.getNumNz(), 0) - self.assertAlmostEqual(h.getOptionValue('primal_feasibility_tolerance'), new_feas_tol) - - h = self.get_basic_model() - h.run() - sol = h.getSolution() - self.assertAlmostEqual(sol.col_value[0], -1) - self.assertAlmostEqual(sol.col_value[1], 1) - h.clearSolver() - self.assertEqual(h.getNumCol(), 2) - self.assertEqual(h.getNumRow(), 2) - self.assertEqual(h.getNumNz(), 4) - sol = h.getSolution() - self.assertFalse(sol.value_valid) - self.assertFalse(sol.dual_valid) - - h = self.get_basic_model() - orig_feas_tol = h.getOptionValue('primal_feasibility_tolerance') - new_feas_tol = orig_feas_tol + 1 - h.setOptionValue('primal_feasibility_tolerance', new_feas_tol) - self.assertAlmostEqual(h.getOptionValue('primal_feasibility_tolerance'), new_feas_tol) - h.resetOptions() - self.assertAlmostEqual(h.getOptionValue('primal_feasibility_tolerance'), orig_feas_tol) - - # def test_dual_ray(self): - # h = self.get_infeasible_model() - # h.setOptionValue('log_to_console', True) - # h.run() - # has_dual_ray = h.getDualRay() - # print('has_dual_ray = ', has_dual_ray) - # self.assertTrue(has_dual_ray) - - # def test_check_solution_feasibility(self): - # h = self.get_basic_model() - # h.setOptionValue('log_to_console', True) - # h.assessLpPrimalSolution() - # h.run() - # h.assessLpPrimalSolution() - - def test_log_callback(self): - h = self.get_basic_model() - h.setOptionValue('log_to_console', True) - - class Foo(object): - def __str__(self): - return 'an instance of Foo' - - def __repr__(self): - return self.__str__() - - def log_callback(log_type, message, data): - print('got a log message: ', log_type, data, message) - - h.setLogCallback(log_callback, Foo()) - out = StringIO() - with capture_output(out) as t: - h.run() - out = out.getvalue() - self.assertIn('got a log message: HighsLogType.kInfo an instance of Foo Presolving model', out) - diff --git a/src/interfaces/highspy/manifest.in b/src/interfaces/highspy/manifest.in deleted file mode 100644 index bd3d7c349a..0000000000 --- a/src/interfaces/highspy/manifest.in +++ /dev/null @@ -1,2 +0,0 @@ -include highs_bindings.cpp -include ../../Highs.h \ No newline at end of file diff --git a/src/io/Filereader.cpp b/src/io/Filereader.cpp index 09ed032e69..7998372f0a 100644 --- a/src/io/Filereader.cpp +++ b/src/io/Filereader.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "io/Filereader.h" @@ -18,6 +16,12 @@ #include "io/FilereaderMps.h" #include "io/HighsIO.h" +// 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 const std::string getFilenameExt(const std::string filename) { // Extract file name extension std::string name = filename; @@ -43,12 +47,23 @@ Filereader* Filereader::getFilereader(const HighsLogOptions& log_options, filename.c_str()); reader = NULL; #endif + // } else if (extension == "zip") { + // #ifdef ZLIB_FOUND + // extension = getFilenameExt(filename.substr(0, filename.size() - 4)); + // #else + // highsLogUser(log_options, HighsLogType::kError, + // "HiGHS build without zlib support. Cannot read .zip + // file.\n", filename.c_str()); + // reader = NULL; + // #endif } - if (extension.compare("mps") == 0) { + std::string lower_case_extension = extension; + tolower(lower_case_extension); + if (lower_case_extension.compare("mps") == 0) { reader = new FilereaderMps(); - } else if (extension.compare("lp") == 0) { + } else if (lower_case_extension.compare("lp") == 0) { reader = new FilereaderLp(); - } else if (extension.compare("ems") == 0) { + } else if (lower_case_extension.compare("ems") == 0) { reader = new FilereaderEms(); } else { reader = NULL; @@ -87,7 +102,9 @@ std::string extractModelName(const std::string filename) { std::size_t found = name.find_last_of("/\\"); if (found < name.size()) name = name.substr(found + 1); found = name.find_last_of("."); - if (name.substr(found + 1) == "gz") { + if (name.substr(found + 1) == "gz" + // || name.substr(found + 1) == "zip" + ) { name.erase(found, name.size() - found); found = name.find_last_of("."); } diff --git a/src/io/Filereader.h b/src/io/Filereader.h index 39a9ea8c14..6abebcdee4 100644 --- a/src/io/Filereader.h +++ b/src/io/Filereader.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/Filereader.h * @brief diff --git a/src/io/FilereaderEms.cpp b/src/io/FilereaderEms.cpp index 085daa5597..d75dc86f93 100644 --- a/src/io/FilereaderEms.cpp +++ b/src/io/FilereaderEms.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/FilereaderEms.cpp * @brief diff --git a/src/io/FilereaderEms.h b/src/io/FilereaderEms.h index 363794ba0b..e2f4431beb 100644 --- a/src/io/FilereaderEms.h +++ b/src/io/FilereaderEms.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/FilereaderEms.h * @brief diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index ab22b66362..701cb41fc3 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/FilereaderLp.cpp * @brief @@ -25,7 +23,7 @@ #include "lp_data/HighsLpUtils.h" const bool original_double_format = false; -const bool allow_model_names = false; +const bool allow_model_names = true; FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, const std::string filename, @@ -283,9 +281,9 @@ HighsStatus FilereaderLp::writeModelToFile(const HighsOptions& options, ar_matrix.ensureRowwise(); const bool has_col_names = - allow_model_names && lp.col_names_.size() == lp.num_col_; + allow_model_names && HighsInt(lp.col_names_.size()) == lp.num_col_; const bool has_row_names = - allow_model_names && lp.row_names_.size() == lp.num_row_; + allow_model_names && HighsInt(lp.row_names_.size()) == lp.num_row_; FILE* file = fopen(filename.c_str(), "w"); // write comment at the start of the file @@ -359,6 +357,10 @@ HighsStatus FilereaderLp::writeModelToFile(const HighsOptions& options, this->writeToFileValue(file, lp.row_lower_[iRow], true); this->writeToFileLineend(file); } else { + // Need to distinguish the names when writing out boxed + // constraint row as two single-sided constraints + const bool boxed = + lp.row_lower_[iRow] > -kHighsInf && lp.row_upper_[iRow] < kHighsInf; if (lp.row_lower_[iRow] > -kHighsInf) { // Has a lower bound if (has_row_names) { @@ -366,7 +368,11 @@ HighsStatus FilereaderLp::writeModelToFile(const HighsOptions& options, } else { this->writeToFileCon(file, iRow); } - this->writeToFile(file, "lo:"); + if (boxed) { + this->writeToFile(file, "lo:"); + } else { + this->writeToFile(file, ":"); + } this->writeToFileMatrixRow(file, iRow, ar_matrix, lp.col_names_); this->writeToFile(file, " >="); this->writeToFileValue(file, lp.row_lower_[iRow], true); @@ -379,7 +385,11 @@ HighsStatus FilereaderLp::writeModelToFile(const HighsOptions& options, } else { this->writeToFileCon(file, iRow); } - this->writeToFile(file, "up:"); + if (boxed) { + this->writeToFile(file, "up:"); + } else { + this->writeToFile(file, ":"); + } this->writeToFileMatrixRow(file, iRow, ar_matrix, lp.col_names_); this->writeToFile(file, " <="); this->writeToFileValue(file, lp.row_upper_[iRow], true); diff --git a/src/io/FilereaderLp.h b/src/io/FilereaderLp.h index 1d899a484e..4140bde1a6 100644 --- a/src/io/FilereaderLp.h +++ b/src/io/FilereaderLp.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/FilereaderLp.cpp * @brief diff --git a/src/io/FilereaderMps.cpp b/src/io/FilereaderMps.cpp index 88fe681e01..1f605a7f12 100644 --- a/src/io/FilereaderMps.cpp +++ b/src/io/FilereaderMps.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/FilereaderMps.cpp * @brief diff --git a/src/io/FilereaderMps.h b/src/io/FilereaderMps.h index 810e1e280b..476ea6876e 100644 --- a/src/io/FilereaderMps.h +++ b/src/io/FilereaderMps.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/FilereaderMps.h * @brief diff --git a/src/io/HMPSIO.cpp b/src/io/HMPSIO.cpp index 7b9aed5f9a..0ac99b2d2c 100644 --- a/src/io/HMPSIO.cpp +++ b/src/io/HMPSIO.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/HMPSIO.cpp * @brief @@ -49,6 +47,7 @@ FilereaderRetcode readMps( // MPS file buffer numRow = 0; numCol = 0; + Qdim = 0; cost_row_location = -1; objOffset = 0; objSense = ObjSense::kMinimize; @@ -613,9 +612,8 @@ HighsStatus writeMps( const vector& integrality, const std::string objective_name, const vector& col_names, const vector& row_names, const bool use_free_format) { - const bool write_zero_no_cost_columns = true; - HighsInt num_zero_no_cost_columns = 0; - HighsInt num_zero_no_cost_columns_in_bounds_section = 0; + HighsInt num_no_cost_zero_columns = 0; + HighsInt num_no_cost_zero_columns_in_bounds_section = 0; highsLogDev(log_options, HighsLogType::kInfo, "writeMPS: Trying to open file %s\n", filename.c_str()); FILE* file = fopen(filename.c_str(), "w"); @@ -772,15 +770,17 @@ HighsStatus writeMps( bool integerFg = false; HighsInt nIntegerMk = 0; fprintf(file, "COLUMNS\n"); + const bool write_no_cost_zero_columns = true; for (HighsInt c_n = 0; c_n < num_col; c_n++) { - if (a_start[c_n] == a_start[c_n + 1] && col_cost[c_n] == 0) { + const bool no_cost_zero_column = + !col_cost[c_n] && a_start[c_n] == a_start[c_n + 1]; + if (no_cost_zero_column) { // Possibly skip this column as it's zero and has no cost - num_zero_no_cost_columns++; - if (write_zero_no_cost_columns) { + num_no_cost_zero_columns++; + if (write_no_cost_zero_columns) { // Give the column a presence by writing out a zero cost - double v = 0; fprintf(file, " %-8s %-8s %.15g\n", col_names[c_n].c_str(), - objective_name.c_str(), v); + objective_name.c_str(), 0.0); } continue; } @@ -853,13 +853,15 @@ HighsStatus writeMps( discrete = integrality[c_n] == HighsVarType::kInteger || integrality[c_n] == HighsVarType::kSemiContinuous || integrality[c_n] == HighsVarType::kSemiInteger; - if (a_start[c_n] == a_start[c_n + 1] && col_cost[c_n] == 0) { + const bool no_cost_zero_column = + !col_cost[c_n] && a_start[c_n] == a_start[c_n + 1]; + if (no_cost_zero_column) { // Possibly skip this column if it's zero and has no cost if (!highs_isInfinity(ub) || lb) { // Column would have a bound to report - num_zero_no_cost_columns_in_bounds_section++; + num_no_cost_zero_columns_in_bounds_section++; } - if (!write_zero_no_cost_columns) continue; + if (!write_no_cost_zero_columns) continue; } if (lb == ub) { // Equal lower and upper bounds: Fixed @@ -875,16 +877,18 @@ HighsStatus writeMps( // Binary fprintf(file, " BV BOUND %-8s\n", col_names[c_n].c_str()); } else { - if (!highs_isInfinity(-lb)) { - // Finite lower bound. No need to state this if LB is - // zero unless UB is infinte - if (lb || highs_isInfinity(ub)) - fprintf(file, " LI BOUND %-8s %.15g\n", - col_names[c_n].c_str(), lb); + assert(write_no_cost_zero_columns); + // No cost zero columns have a presence in the COLUMNS + // section, so no need to indicate integrality using LI + // or UI bounds. Avoids need for integer-valued bounds + if (!highs_isInfinity(-lb) && lb) { + // Finite, nonzero lower bound. + fprintf(file, " LO BOUND %-8s %.15g\n", + col_names[c_n].c_str(), lb); } if (!highs_isInfinity(ub)) { // Finite upper bound - fprintf(file, " UI BOUND %-8s %.15g\n", + fprintf(file, " UP BOUND %-8s %.15g\n", col_names[c_n].c_str(), ub); } } @@ -939,15 +943,15 @@ HighsStatus writeMps( } } fprintf(file, "ENDATA\n"); - if (num_zero_no_cost_columns) + if (num_no_cost_zero_columns) highsLogUser(log_options, HighsLogType::kInfo, "Model has %" HIGHSINT_FORMAT " zero columns with no costs: %" HIGHSINT_FORMAT " have finite upper bounds " "or nonzero lower bounds and are %swritten in MPS file\n", - num_zero_no_cost_columns, - num_zero_no_cost_columns_in_bounds_section, - write_zero_no_cost_columns ? "" : "not "); + num_no_cost_zero_columns, + num_no_cost_zero_columns_in_bounds_section, + write_no_cost_zero_columns ? "" : "not "); fclose(file); return HighsStatus::kOk; } diff --git a/src/io/HMPSIO.h b/src/io/HMPSIO.h index c0b79d6cac..899167f9f2 100644 --- a/src/io/HMPSIO.h +++ b/src/io/HMPSIO.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/HMPSIO.h * @brief diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 01702e0420..361b831a04 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "io/HMpsFF.h" @@ -582,9 +580,11 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, isFreeRow = true; } } else { + std::string unidentified = strline.substr(start); + trim(unidentified); highsLogUser(log_options, HighsLogType::kError, - "Entry in ROWS section of MPS file is of type \"%s\"\n", - strline[start]); + "Entry \"%s\" in ROWS section of MPS file is unidentifed\n", + unidentified.c_str()); return HMpsFF::Parsekey::kFail; } @@ -819,6 +819,7 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, end = first_word_end(strline, end_marker); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No coefficient given for column \"%s\"\n", marker.c_str()); return HMpsFF::Parsekey::kFail; @@ -862,6 +863,7 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, // parse second coefficient marker = first_word(strline, end); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No coefficient given for column \"%s\"\n", marker.c_str()); @@ -1010,6 +1012,7 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, end = first_word_end(strline, end_marker); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No bound given for row \"%s\"\n", marker.c_str()); return HMpsFF::Parsekey::kFail; @@ -1029,6 +1032,7 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, word = first_word(strline, end_marker); end = first_word_end(strline, end_marker); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No bound given for SIF row \"%s\"\n", marker.c_str()); return HMpsFF::Parsekey::kFail; @@ -1058,6 +1062,7 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, // parse second coefficient marker = first_word(strline, end); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No coefficient given for rhs of row \"%s\"\n", marker.c_str()); @@ -1182,6 +1187,7 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, bool is_integral = false; bool is_semi = false; bool is_defaultbound = false; + const std::string bound_type = word; if (word == "UP") // lower bound is_ub = true; else if (word == "LO") // upper bound @@ -1234,6 +1240,7 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, is_semi = true; num_sc++; } else { + trim(word); highsLogUser(log_options, HighsLogType::kError, "Entry in BOUNDS section of MPS file is of type \"%s\"\n", word.c_str()); @@ -1283,6 +1290,7 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, // binary: BV { if (!is_lb || !is_ub) { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "BV row %s but [is_lb, is_ub] = [%1" HIGHSINT_FORMAT ", %1" HIGHSINT_FORMAT "]\n", @@ -1313,8 +1321,10 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, end = first_word_end(strline, end_marker); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, - "No bound given for row \"%s\"\n", marker.c_str()); + "No bound given for %s row \"%s\"\n", bound_type.c_str(), + marker.c_str()); return HMpsFF::Parsekey::kFail; } double value = atof(word.c_str()); @@ -1420,6 +1430,7 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, end = first_word_end(strline, end_marker); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No range given for row \"%s\"\n", marker.c_str()); return HMpsFF::Parsekey::kFail; @@ -1459,6 +1470,7 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, end = first_word_end(strline, end_marker); if (word == "") { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "No range given for row \"%s\"\n", marker.c_str()); return HMpsFF::Parsekey::kFail; @@ -1489,6 +1501,7 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, } if (!is_end(strline, end)) { + trim(marker); highsLogUser(log_options, HighsLogType::kError, "Unknown specifiers in RANGES section for row \"%s\"\n", marker.c_str()); @@ -1565,6 +1578,8 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( end_coeff_name = first_word_end(strline, end_row_name); if (coeff_name == "") { + trim(row_name); + trim(col_name); highsLogUser( log_options, HighsLogType::kError, "%s has no coefficient for entry \"%s\" in column \"%s\"\n", @@ -1705,6 +1720,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( end_coeff_name = first_word_end(strline, end_row_name); if (coeff_name == "") { + trim(row_name); + trim(col_name); highsLogUser( log_options, HighsLogType::kError, "%s has no coefficient for entry \"%s\" in column \"%s\"\n", @@ -1768,6 +1785,7 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, } if (conetypestr.empty()) { + trim(section_args); highsLogUser(log_options, HighsLogType::kError, "Cone type missing in CSECTION %s\n", section_args.c_str()); return HMpsFF::Parsekey::kFail; @@ -1789,6 +1807,7 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, else if (conetypestr == "DPOW") conetype = ConeType::kDPow; else { + trim(conetypestr); highsLogUser(log_options, HighsLogType::kError, "Unrecognized cone type %s\n", conetypestr.c_str()); return HMpsFF::Parsekey::kFail; @@ -1891,6 +1910,7 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, * word is currently the column name and there may be a weight following */ if (sos_entries.empty()) { + trim(strline); highsLogUser(log_options, HighsLogType::kError, "SOS type specification missing before %s.\n", strline.c_str()); @@ -1906,6 +1926,7 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, // first word is SOS name, second word is colname, third word is weight // we expect SOS definitions to be contiguous for now if (word != sos_name.back()) { + trim(word); highsLogUser(log_options, HighsLogType::kError, "SOS specification for SOS %s mixed with SOS %s. This is " "currently not supported.\n", @@ -1913,6 +1934,7 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, return HMpsFF::Parsekey::kFail; } if (is_end(strline, end)) { + trim(strline); highsLogUser(log_options, HighsLogType::kError, "Missing variable in SOS specification line %s.\n", strline.c_str()); diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index d9936bf93e..6cff8f0595 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/HMpsFF.h * @brief diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index a5ac80762d..7168cf2120 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/HighsIO.cpp * @brief IO methods for HiGHS - currently just print/log messages @@ -98,7 +96,7 @@ std::array highsDoubleToString(const double val, void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...) { if (!*log_options_.output_flag || - (log_options_.log_file_stream == NULL && !*log_options_.log_to_console)) + (log_options_.log_stream == NULL && !*log_options_.log_to_console)) return; // highsLogUser should not be passed HighsLogType::kDetailed or // HighsLogType::kVerbose @@ -109,19 +107,17 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, va_list argptr; va_start(argptr, format); const bool flush_streams = true; - if (!log_options_.log_callback) { + if (!log_options_.log_user_callback) { // Write to log file stream unless it is NULL - if (log_options_.log_file_stream) { + if (log_options_.log_stream) { if (prefix) - fprintf(log_options_.log_file_stream, "%-9s", - HighsLogTypeTag[(int)type]); - vfprintf(log_options_.log_file_stream, format, argptr); - if (flush_streams) fflush(log_options_.log_file_stream); + fprintf(log_options_.log_stream, "%-9s", HighsLogTypeTag[(int)type]); + vfprintf(log_options_.log_stream, format, argptr); + if (flush_streams) fflush(log_options_.log_stream); va_start(argptr, format); } // Write to stdout unless log file stream is stdout - if (*log_options_.log_to_console && - log_options_.log_file_stream != stdout) { + if (*log_options_.log_to_console && log_options_.log_stream != stdout) { if (prefix) fprintf(stdout, "%-9s", HighsLogTypeTag[(int)type]); vfprintf(stdout, format, argptr); if (flush_streams) fflush(stdout); @@ -139,7 +135,7 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, // Output was truncated: for now just ensure string is null-terminated msgbuffer[sizeof(msgbuffer) - 1] = '\0'; } - log_options_.log_callback(type, msgbuffer, log_options_.log_callback_data); + log_options_.log_user_callback(type, msgbuffer, nullptr); } va_end(argptr); } @@ -147,7 +143,7 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...) { if (!*log_options_.output_flag || - (log_options_.log_file_stream == NULL && !*log_options_.log_to_console) || + (log_options_.log_stream == NULL && !*log_options_.log_to_console) || !*log_options_.log_dev_level) return; // Always report HighsLogType::kInfo, HighsLogType::kWarning or @@ -167,17 +163,16 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, va_list argptr; va_start(argptr, format); const bool flush_streams = true; - if (!log_options_.log_callback) { + if (!log_options_.log_user_callback) { // Write to log file stream unless it is NULL - if (log_options_.log_file_stream) { + if (log_options_.log_stream) { // Write to log file stream - vfprintf(log_options_.log_file_stream, format, argptr); - if (flush_streams) fflush(log_options_.log_file_stream); + vfprintf(log_options_.log_stream, format, argptr); + if (flush_streams) fflush(log_options_.log_stream); va_start(argptr, format); } // Write to stdout unless log file stream is stdout - if (*log_options_.log_to_console && - log_options_.log_file_stream != stdout) { + if (*log_options_.log_to_console && log_options_.log_stream != stdout) { vfprintf(stdout, format, argptr); if (flush_streams) fflush(stdout); } @@ -189,7 +184,7 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, // Output was truncated: for now just ensure string is null-terminated msgbuffer[sizeof(msgbuffer) - 1] = '\0'; } - log_options_.log_callback(type, msgbuffer, log_options_.log_callback_data); + log_options_.log_user_callback(type, msgbuffer, nullptr); } va_end(argptr); } @@ -209,10 +204,10 @@ void highsOpenLogFile(HighsOptions& options, const std::string log_file) { void highsReportLogOptions(const HighsLogOptions& log_options_) { printf("\nHighs log options\n"); - if (log_options_.log_file_stream == NULL) { - printf(" log_file_stream = NULL\n"); + if (log_options_.log_stream == NULL) { + printf(" log_stream = NULL\n"); } else { - printf(" log_file_stream = Not NULL\n"); + printf(" log_stream = Not NULL\n"); } printf(" output_flag = %s\n", highsBoolToString(*log_options_.output_flag).c_str()); @@ -237,6 +232,25 @@ std::string highsFormatToString(const char* format, ...) { return std::string(msgbuffer); } -const std::string highsBoolToString(const bool b) { - return b ? "true" : "false"; +const std::string highsBoolToString(const bool b, const HighsInt field_width) { + const HighsInt abs_field_width = std::abs(field_width); + if (abs_field_width <= 1) return b ? "T" : "F"; + if (abs_field_width <= 2) return b ? "true" : "false"; + if (field_width < 0) return b ? "true " : "false"; + return b ? " true" : "false"; +} + +const std::string highsInsertMdEscapes(const std::string from_string) { + std::string to_string = ""; + const char* underscore = "_"; + const char* backslash = "\\"; + HighsInt from_string_length = from_string.length(); + for (HighsInt p = 0; p < from_string_length; p++) { + const char string_ch = from_string[p]; + if (string_ch == *underscore) { + to_string += backslash; + } + to_string += from_string[p]; + } + return to_string; } diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index 1786a81e55..5c59ef8083 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/HighsIO.h * @brief IO methods for HiGHS - currently just print/log messages @@ -25,6 +23,8 @@ class HighsOptions; const HighsInt kIoBufferSize = 1024; // 65536; +enum class HighsFileType { kNone = 0, kOther, kMps, kLp, kMd, kHtml }; + /** * @brief IO methods for HiGHS - currently just print/log messages */ @@ -42,12 +42,12 @@ enum LogDevLevel { }; struct HighsLogOptions { - FILE* log_file_stream; + FILE* log_stream; bool* output_flag; bool* log_to_console; HighsInt* log_dev_level; - void (*log_callback)(HighsLogType, const char*, void*) = nullptr; - void* log_callback_data = nullptr; + void (*log_highs_callback)(HighsLogType, const char*, void*) = nullptr; + void (*log_user_callback)(HighsLogType, const char*, void*) = nullptr; }; /** @@ -88,6 +88,9 @@ void highsReportLogOptions(const HighsLogOptions& log_options_); std::string highsFormatToString(const char* format, ...); -const std::string highsBoolToString(const bool b); +const std::string highsBoolToString(const bool b, + const HighsInt field_width = 2); + +const std::string highsInsertMdEscapes(const std::string from_string); #endif diff --git a/src/io/LoadOptions.cpp b/src/io/LoadOptions.cpp index 95e12297e4..521adda8a5 100644 --- a/src/io/LoadOptions.cpp +++ b/src/io/LoadOptions.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "io/LoadOptions.h" @@ -18,9 +16,10 @@ // For extended options to be parsed from a file. Assuming options file is // specified. -bool loadOptionsFromFile(const HighsLogOptions& report_log_options, - HighsOptions& options, const std::string filename) { - if (filename.size() == 0) return false; +HighsLoadOptionsStatus loadOptionsFromFile( + const HighsLogOptions& report_log_options, HighsOptions& options, + const std::string filename) { + if (filename.size() == 0) return HighsLoadOptionsStatus::kEmpty; string line, option, value; HighsInt line_count = 0; @@ -40,7 +39,7 @@ bool loadOptionsFromFile(const HighsLogOptions& report_log_options, highsLogUser(report_log_options, HighsLogType::kError, "Error on line %" HIGHSINT_FORMAT " of options file.\n", line_count); - return false; + return HighsLoadOptionsStatus::kError; } option = line.substr(0, equals); value = line.substr(equals + 1, line.size() - equals); @@ -48,13 +47,13 @@ bool loadOptionsFromFile(const HighsLogOptions& report_log_options, trim(value, non_chars); if (setLocalOptionValue(report_log_options, option, options.log_options, options.records, value) != OptionStatus::kOk) - return false; + return HighsLoadOptionsStatus::kError; } } else { highsLogUser(report_log_options, HighsLogType::kError, "Options file not found.\n"); - return false; + return HighsLoadOptionsStatus::kError; } - return true; + return HighsLoadOptionsStatus::kOk; } diff --git a/src/io/LoadOptions.h b/src/io/LoadOptions.h index 1c7e2c5158..b6854c418a 100644 --- a/src/io/LoadOptions.h +++ b/src/io/LoadOptions.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/LoadOptions.h * @brief @@ -19,8 +17,11 @@ #include "lp_data/HighsOptions.h" +enum class HighsLoadOptionsStatus { kError = -1, kOk = 0, kEmpty = 1 }; + // For extended options to be parsed from filename -bool loadOptionsFromFile(const HighsLogOptions& report_log_options, - HighsOptions& options, const std::string filename); +HighsLoadOptionsStatus loadOptionsFromFile( + const HighsLogOptions& report_log_options, HighsOptions& options, + const std::string filename); #endif diff --git a/src/ipm/IpxSolution.h b/src/ipm/IpxSolution.h index 614b5cfbba..d5579bf7f0 100644 --- a/src/ipm/IpxSolution.h +++ b/src/ipm/IpxSolution.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file ipm/IpxSolution.h * @brief diff --git a/src/ipm/IpxWrapper.cpp b/src/ipm/IpxWrapper.cpp index b67aa7cbc9..0db77c8c01 100644 --- a/src/ipm/IpxWrapper.cpp +++ b/src/ipm/IpxWrapper.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file ipm/IpxWrapper.cpp * @brief @@ -92,6 +90,25 @@ HighsStatus solveLpIpx(const HighsOptions& options, } // Just test feasibility and optimality tolerances for now // ToDo Set more parameters + // + // Translate dualization option + // + // parameters.dualize = -2 => Possibly dualize - Filippo style + // parameters.dualize = -1 => Possibly dualize - Lukas style + // parameters.dualize = 0 => No dualization + // parameters.dualize = 1 => Perform dualization + if (options.ipx_dualize_strategy == kIpxDualizeStrategyOn) { + parameters.dualize = 1; + } else if (options.ipx_dualize_strategy == kIpxDualizeStrategyOff) { + parameters.dualize = 0; + } else if (options.ipx_dualize_strategy == kIpxDualizeStrategyLukas) { + parameters.dualize = -1; + } else if (options.ipx_dualize_strategy == kIpxDualizeStrategyFilippo) { + parameters.dualize = -2; + } else { + assert(111==222); + } + parameters.ipm_feasibility_tol = min(options.primal_feasibility_tolerance, options.dual_feasibility_tolerance); @@ -132,8 +149,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, num_row, num_col, Ap[num_col]); ipx::Int load_status = - lps.LoadModel(num_col, &objective[0], &col_lb[0], &col_ub[0], num_row, - &Ap[0], &Ai[0], &Av[0], &rhs[0], &constraint_type[0]); + lps.LoadModel(num_col, objective.data(), col_lb.data(), col_ub.data(), num_row, + Ap.data(), Ai.data(), Av.data(), rhs.data(), constraint_type.data()); if (load_status) { model_status = HighsModelStatus::kSolveError; @@ -305,10 +322,9 @@ HighsStatus solveLpIpx(const HighsOptions& options, ipx_solution.ipx_row_dual.resize(num_row); ipx_solution.ipx_row_status.resize(num_row); ipx_solution.ipx_col_status.resize(num_col); - ipx::Int errflag = lps.GetBasicSolution( - &ipx_solution.ipx_col_value[0], &ipx_solution.ipx_row_value[0], - &ipx_solution.ipx_row_dual[0], &ipx_solution.ipx_col_dual[0], - &ipx_solution.ipx_row_status[0], &ipx_solution.ipx_col_status[0]); + ipx::Int errflag = lps.GetBasicSolution(ipx_solution.ipx_col_value.data(), ipx_solution.ipx_row_value.data(), + ipx_solution.ipx_row_dual.data(), ipx_solution.ipx_col_dual.data(), + ipx_solution.ipx_row_status.data(), ipx_solution.ipx_col_status.data()); if (errflag != 0) { highsLogUser(options.log_options, HighsLogType::kError, "IPX crossover getting basic solution: flag = %d\n", @@ -817,8 +833,8 @@ void getHighsNonVertexSolution(const HighsOptions& options, std::vector slack(num_row); std::vector y(num_row); - lps.GetInteriorSolution(&x[0], &xl[0], &xu[0], &slack[0], &y[0], &zl[0], - &zu[0]); + lps.GetInteriorSolution(x.data(), xl.data(), xu.data(), slack.data(), y.data(), zl.data(), + zu.data()); ipxSolutionToHighsSolution(options, lp, rhs, constraint_type, num_col, num_row, x, slack, y, zl, zu, diff --git a/src/ipm/IpxWrapper.h b/src/ipm/IpxWrapper.h index c2e8cab6b2..1cf1289780 100644 --- a/src/ipm/IpxWrapper.h +++ b/src/ipm/IpxWrapper.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file ipm/IpxWrapper.h * @brief diff --git a/src/ipm/ipx/model.cc b/src/ipm/ipx/model.cc index 9705046145..8cfa3eec6c 100644 --- a/src/ipm/ipx/model.cc +++ b/src/ipm/ipx/model.cc @@ -28,8 +28,18 @@ Int Model::Load(const Control& control, Int num_constr, Int num_var, // Make an automatic decision for dualization if not specified by user. Int dualize = control.dualize(); - if (dualize < 0) - dualize = num_constr > 2*num_var; + // dualize = -2 => Possibly dualize - Filippo style + // dualize = -1 => Possibly dualize - Lukas style + // dualize = 0 => No dualization + // dualize = 1 => Perform dualization + assert(dualize == -1); + const bool dualize_lukas = num_constr > 2*num_var; + const bool dualize_filippo = filippoDualizationTest(); + if (dualize == -1) { + dualize = dualize_lukas; + } else if (dualize == -2) { + dualize = dualize_filippo; + } if (dualize) LoadDual(); else @@ -51,6 +61,10 @@ Int Model::Load(const Control& control, Int num_constr, Int num_var, return 0; } +bool Model::filippoDualizationTest() const { + return false; +} + void Model::GetInfo(Info *info) const { info->num_var = num_var_; info->num_constr = num_constr_; diff --git a/src/ipm/ipx/model.h b/src/ipm/ipx/model.h index 52c4e86d0e..dcc10baa2a 100644 --- a/src/ipm/ipx/model.h +++ b/src/ipm/ipx/model.h @@ -60,7 +60,8 @@ class Model { const Int* Ap, const Int* Ai, const double* Ax, const double* rhs, const char* constr_type, const double* obj, const double* lbuser, const double* ubuser); - + // Performs Flippo's test for deciding dualization + bool filippoDualizationTest() const; // Writes statistics of input data and preprocessing to @info. void GetInfo(Info* info) const; diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index d59f6f5470..32f567f568 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HConst.h * @brief Constants for HiGHS @@ -23,8 +21,9 @@ #include "util/HighsInt.h" const std::string kHighsCopyrightStatement = - "Copyright (c) 2022 HiGHS under MIT licence terms"; + "Copyright (c) 2023 HiGHS under MIT licence terms"; +const size_t kHighsSize_tInf = std::numeric_limits::max(); const HighsInt kHighsIInf = std::numeric_limits::max(); const double kHighsInf = std::numeric_limits::infinity(); const double kHighsTiny = 1e-14; @@ -33,9 +32,11 @@ const double kHighsZero = 1e-50; const std::string kHighsOffString = "off"; const std::string kHighsChooseString = "choose"; const std::string kHighsOnString = "on"; +const HighsInt kHighsMaxStringLength = 512; const HighsInt kSimplexConcurrencyLimit = 8; const double kRunningAverageMultiplier = 0.05; +const bool kAllowDeveloperAssert = false; const bool kExtendInvertWhenAddingRows = false; enum SimplexScaleStrategy { @@ -102,6 +103,16 @@ enum OptionOffChooseOn { kHighsOptionOn }; +enum IpxDualizeStrategy { + kIpxDualizeStrategyOff = kHighsOptionOff, + kIpxDualizeStrategyChoose = kHighsOptionChoose, + kIpxDualizeStrategyOn = kHighsOptionOn, + kIpxDualizeStrategyLukas, + kIpxDualizeStrategyFilippo, + kIpxDualizeStrategyMin = kIpxDualizeStrategyOff, + kIpxDualizeStrategyMax = kIpxDualizeStrategyFilippo, +}; + /** SCIP/HiGHS Objective sense */ enum class ObjSense { kMinimize = 1, kMaximize = -1 }; @@ -155,8 +166,16 @@ enum class HighsPresolveStatus { kReduced, kReducedToEmpty, kTimeout, - kNullError, - kOptionsError, + kNullError, // V2.0: Delete since it's not used! + kOptionsError, // V2.0: Delete since it's not used! + kNotSet, +}; + +enum class HighsPostsolveStatus { // V2.0: Delete if not used! + kNotPresolved = -1, + kNoPrimalSolutionError, + kSolutionRecovered, + kBasisError }; enum class HighsModelStatus { @@ -164,11 +183,11 @@ enum class HighsModelStatus { // values is unchanged, since enums are not preserved in some // interfaces kNotset = 0, - kLoadError, + kLoadError, // V2.0: Delete since it's not used! kModelError, - kPresolveError, + kPresolveError, // V2.0: Delete since it's not used! kSolveError, - kPostsolveError, + kPostsolveError, // V2.0: Delete if not used! Add to documentation if used kModelEmpty, kOptimal, kInfeasible, @@ -192,7 +211,7 @@ enum class HighsBasisStatus : uint8_t { 0, // (slack) variable is at its lower bound [including fixed variables] kBasic, // (slack) variable is basic kUpper, // (slack) variable is at its upper bound - kZero, // free variable is non-basic and set to zero + kZero, // free variable is nonbasic and set to zero kNonbasic // nonbasic with no specific bound information - useful for users // and postsolve }; @@ -239,6 +258,12 @@ const HighsInt kHighsIllegalErrorIndex = -1; // Maximum upper bound on semi-variables const double kMaxSemiVariableUpper = 1e5; +// Limit on primal values being realistic +const double kExcessivePrimalValue = 1e25; + +// Hash marker for duplicates +const HighsInt kHashIsDuplicate = -1; + // Tolerance values for highsDoubleToString const double kModelValueToStringTolerance = 1e-15; const double kRangingValueToStringTolerance = 1e-13; diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 9f988b0f7e..0aaec2fcb3 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HStruct.h * @brief Structs for HiGHS @@ -16,6 +14,7 @@ #ifndef LP_DATA_HSTRUCT_H_ #define LP_DATA_HSTRUCT_H_ +#include #include #include "lp_data/HConst.h" @@ -38,6 +37,12 @@ struct HighsSolution { void clear(); }; +struct HighsObjectiveSolution { + double objective; + std::vector col_value; + void clear(); +}; + struct RefactorInfo { bool use = false; std::vector pivot_row; @@ -78,14 +83,27 @@ struct HighsScale { }; struct HighsLpMods { - std::vector save_semi_variable_lower_bound_index; - std::vector save_semi_variable_lower_bound_value; - std::vector save_semi_variable_upper_bound_index; - std::vector save_semi_variable_upper_bound_value; + std::vector save_non_semi_variable_index; + std::vector save_inconsistent_semi_variable_index; + std::vector save_inconsistent_semi_variable_lower_bound_value; + std::vector save_inconsistent_semi_variable_upper_bound_value; + std::vector save_inconsistent_semi_variable_type; + + std::vector save_relaxed_semi_variable_lower_bound_index; + std::vector save_relaxed_semi_variable_lower_bound_value; + std::vector save_tightened_semi_variable_upper_bound_index; + std::vector save_tightened_semi_variable_upper_bound_value; void clear(); bool isClear(); }; +struct HighsNameHash { + std::unordered_map name2index; + void form(const std::vector& name); + bool hasDuplicate(const std::vector& name); + void clear(); +}; + struct HighsPresolveRuleLog { HighsInt call; HighsInt col_removed; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index ba5389232e..3acf20f5a2 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/Highs.cpp * @brief @@ -36,18 +34,17 @@ #include "util/HighsMatrixPic.h" #include "util/HighsSort.h" -std::string highsVersion() { - std::stringstream ss; - ss << "v" << HIGHS_VERSION_MAJOR << "." << HIGHS_VERSION_MINOR << "." - << HIGHS_VERSION_PATCH; - return ss.str(); +#define STRINGFY(s) STRINGFY0(s) +#define STRINGFY0(s) #s +const char* highsVersion() { + return STRINGFY(HIGHS_VERSION_MAJOR) "." STRINGFY( + HIGHS_VERSION_MINOR) "." STRINGFY(HIGHS_VERSION_PATCH); } - HighsInt highsVersionMajor() { return HIGHS_VERSION_MAJOR; } HighsInt highsVersionMinor() { return HIGHS_VERSION_MINOR; } HighsInt highsVersionPatch() { return HIGHS_VERSION_PATCH; } -std::string highsGithash() { return HIGHS_GITHASH; } -std::string highsCompilationDate() { return HIGHS_COMPILATION_DATE; } +const char* highsGithash() { return HIGHS_GITHASH; } +const char* highsCompilationDate() { return HIGHS_COMPILATION_DATE; } Highs::Highs() {} @@ -116,8 +113,13 @@ HighsStatus Highs::readOptions(const std::string& filename) { return HighsStatus::kWarning; } HighsLogOptions report_log_options = options_.log_options; - if (!loadOptionsFromFile(report_log_options, options_, filename)) - return HighsStatus::kError; + switch (loadOptionsFromFile(report_log_options, options_, filename)) { + case HighsLoadOptionsStatus::kError: + case HighsLoadOptionsStatus::kEmpty: + return HighsStatus::kError; + default: + break; + } return HighsStatus::kOk; } @@ -128,46 +130,6 @@ HighsStatus Highs::passOptions(const HighsOptions& options) { return HighsStatus::kError; } -HighsStatus Highs::getOptionValue(const std::string& option, - bool& value) const { - if (getLocalOptionValue(options_.log_options, option, options_.records, - value) == OptionStatus::kOk) - return HighsStatus::kOk; - return HighsStatus::kError; -} - -HighsStatus Highs::getOptionValue(const std::string& option, - HighsInt& value) const { - if (getLocalOptionValue(options_.log_options, option, options_.records, - value) == OptionStatus::kOk) - return HighsStatus::kOk; - return HighsStatus::kError; -} - -HighsStatus Highs::getOptionValue(const std::string& option, - double& value) const { - if (getLocalOptionValue(options_.log_options, option, options_.records, - value) == OptionStatus::kOk) - return HighsStatus::kOk; - return HighsStatus::kError; -} - -HighsStatus Highs::getOptionValue(const std::string& option, - std::string& value) const { - if (getLocalOptionValue(options_.log_options, option, options_.records, - value) == OptionStatus::kOk) - return HighsStatus::kOk; - return HighsStatus::kError; -} - -HighsStatus Highs::getOptionType(const std::string& option, - HighsOptionType& type) const { - if (getLocalOptionType(options_.log_options, option, options_.records, - type) == OptionStatus::kOk) - return HighsStatus::kOk; - return HighsStatus::kError; -} - HighsStatus Highs::resetOptions() { resetLocalOptions(options_.records); return HighsStatus::kOk; @@ -177,27 +139,88 @@ HighsStatus Highs::writeOptions(const std::string& filename, const bool report_only_deviations) const { HighsStatus return_status = HighsStatus::kOk; FILE* file; - bool html; + HighsFileType file_type; return_status = interpretCallStatus( - options_.log_options, openWriteFile(filename, "writeOptions", file, html), - return_status, "openWriteFile"); + options_.log_options, + openWriteFile(filename, "writeOptions", file, file_type), return_status, + "openWriteFile"); if (return_status == HighsStatus::kError) return return_status; // 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, html), - return_status, "writeOptionsToFile"); + return_status = + interpretCallStatus(options_.log_options, + writeOptionsToFile(file, options_.records, + report_only_deviations, file_type), + return_status, "writeOptionsToFile"); if (file != stdout) fclose(file); return return_status; } +// HighsStatus Highs::getOptionType(const char* option, HighsOptionType* type) +// const { return getOptionType(option, type);} + +HighsStatus Highs::getOptionName(const HighsInt index, + std::string* name) const { + if (index < 0 || index >= HighsInt(this->options_.records.size())) + return HighsStatus::kError; + *name = this->options_.records[index]->name; + return HighsStatus::kOk; +} + +HighsStatus Highs::getOptionType(const std::string& option, + HighsOptionType* type) const { + if (getLocalOptionType(options_.log_options, option, options_.records, + type) == OptionStatus::kOk) + return HighsStatus::kOk; + return HighsStatus::kError; +} + +HighsStatus Highs::getBoolOptionValues(const std::string& option, + bool* current_value, + bool* default_value) const { + if (getLocalOptionValues(options_.log_options, option, options_.records, + current_value, default_value) != OptionStatus::kOk) + return HighsStatus::kError; + return HighsStatus::kOk; +} + +HighsStatus Highs::getIntOptionValues(const std::string& option, + HighsInt* current_value, + HighsInt* min_value, HighsInt* max_value, + HighsInt* default_value) const { + if (getLocalOptionValues(options_.log_options, option, options_.records, + current_value, min_value, max_value, + default_value) != OptionStatus::kOk) + return HighsStatus::kError; + return HighsStatus::kOk; +} + +HighsStatus Highs::getDoubleOptionValues(const std::string& option, + double* current_value, + double* min_value, double* max_value, + double* default_value) const { + if (getLocalOptionValues(options_.log_options, option, options_.records, + current_value, min_value, max_value, + default_value) != OptionStatus::kOk) + return HighsStatus::kError; + return HighsStatus::kOk; +} + +HighsStatus Highs::getStringOptionValues(const std::string& option, + std::string* current_value, + std::string* default_value) const { + if (getLocalOptionValues(options_.log_options, option, options_.records, + current_value, default_value) != OptionStatus::kOk) + return HighsStatus::kError; + return HighsStatus::kOk; +} + HighsStatus Highs::getInfoValue(const std::string& info, HighsInt& value) const { - InfoStatus status = - getLocalInfoValue(options_, info, info_.valid, info_.records, value); + InfoStatus status = getLocalInfoValue(options_.log_options, info, info_.valid, + info_.records, value); if (status == InfoStatus::kOk) { return HighsStatus::kOk; } else if (status == InfoStatus::kUnavailable) { @@ -209,8 +232,8 @@ HighsStatus Highs::getInfoValue(const std::string& info, #ifndef HIGHSINT64 HighsStatus Highs::getInfoValue(const std::string& info, int64_t& value) const { - InfoStatus status = - getLocalInfoValue(options_, info, info_.valid, info_.records, value); + InfoStatus status = getLocalInfoValue(options_.log_options, info, info_.valid, + info_.records, value); if (status == InfoStatus::kOk) { return HighsStatus::kOk; } else if (status == InfoStatus::kUnavailable) { @@ -221,9 +244,17 @@ HighsStatus Highs::getInfoValue(const std::string& info, int64_t& value) const { } #endif +HighsStatus Highs::getInfoType(const std::string& info, + HighsInfoType& type) const { + if (getLocalInfoType(options_.log_options, info, info_.records, type) == + InfoStatus::kOk) + return HighsStatus::kOk; + return HighsStatus::kError; +} + HighsStatus Highs::getInfoValue(const std::string& info, double& value) const { - InfoStatus status = - getLocalInfoValue(options_, info, info_.valid, info_.records, value); + InfoStatus status = getLocalInfoValue(options_.log_options, info, info_.valid, + info_.records, value); if (status == InfoStatus::kOk) { return HighsStatus::kOk; } else if (status == InfoStatus::kUnavailable) { @@ -236,10 +267,11 @@ HighsStatus Highs::getInfoValue(const std::string& info, double& value) const { HighsStatus Highs::writeInfo(const std::string& filename) const { HighsStatus return_status = HighsStatus::kOk; FILE* file; - bool html; - return_status = interpretCallStatus( - options_.log_options, openWriteFile(filename, "writeInfo", file, html), - return_status, "openWriteFile"); + HighsFileType file_type; + return_status = + interpretCallStatus(options_.log_options, + openWriteFile(filename, "writeInfo", file, file_type), + return_status, "openWriteFile"); if (return_status == HighsStatus::kError) return return_status; // Report to user that options are being written to a file if (filename != "") @@ -247,8 +279,8 @@ HighsStatus Highs::writeInfo(const std::string& filename) const { "Writing the info values to %s\n", filename.c_str()); return_status = interpretCallStatus( options_.log_options, - writeInfoToFile(file, info_.valid, info_.records, html), return_status, - "writeInfoToFile"); + writeInfoToFile(file, info_.valid, info_.records, file_type), + return_status, "writeInfoToFile"); if (file != stdout) fclose(file); return return_status; } @@ -260,6 +292,9 @@ HighsStatus Highs::passModel(HighsModel model) { // This is the "master" Highs::passModel, in that all the others // eventually call it this->logHeader(); + // Possibly analyse the LP data + if (kHighsAnalysisLevelModelData & options_.highs_analysis_level) + analyseLp(options_.log_options, model.lp_); HighsStatus return_status = HighsStatus::kOk; // Clear the incumbent model and any associated data clearModel(); @@ -503,6 +538,46 @@ HighsStatus Highs::passHessian(const HighsInt dim, const HighsInt num_nz, return passHessian(hessian); } +HighsStatus Highs::passColName(const HighsInt col, const std::string& name) { + const HighsInt num_col = this->model_.lp_.num_col_; + if (col < 0 || col >= num_col) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Index %d for column name %s is outside the range [0, num_col = %d)\n", + int(col), name.c_str(), int(num_col)); + return HighsStatus::kError; + } + if (int(name.length()) <= 0) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Cannot define empty column names\n"); + return HighsStatus::kError; + } + this->model_.lp_.col_names_.resize(num_col); + this->model_.lp_.col_names_[col] = name; + this->model_.lp_.col_hash_.clear(); + return HighsStatus::kOk; +} + +HighsStatus Highs::passRowName(const HighsInt row, const std::string& name) { + const HighsInt num_row = this->model_.lp_.num_row_; + if (row < 0 || row >= num_row) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Index %d for row name %s is outside the range [0, num_row = %d)\n", + int(row), name.c_str(), int(num_row)); + return HighsStatus::kError; + } + if (int(name.length()) <= 0) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Cannot define empty column names\n"); + return HighsStatus::kError; + } + this->model_.lp_.row_names_.resize(num_row); + this->model_.lp_.row_names_[row] = name; + this->model_.lp_.row_hash_.clear(); + return HighsStatus::kOk; +} + HighsStatus Highs::readModel(const std::string& filename) { this->logHeader(); HighsStatus return_status = HighsStatus::kOk; @@ -573,6 +648,17 @@ HighsStatus Highs::writeModel(const std::string& filename) { // Ensure that the LP is column-wise model_.lp_.ensureColwise(); + // Check for repeated column or row names that would corrupt the file + if (model_.lp_.col_hash_.hasDuplicate(model_.lp_.col_names_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Model has repeated column names\n"); + return returnFromHighs(HighsStatus::kError); + } + if (model_.lp_.row_hash_.hasDuplicate(model_.lp_.row_names_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Model has repeated row names\n"); + return returnFromHighs(HighsStatus::kError); + } if (filename == "") { // Empty file name: report model on logging stream reportModel(); @@ -601,8 +687,8 @@ HighsStatus Highs::writeBasis(const std::string& filename) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; FILE* file; - bool html; - call_status = openWriteFile(filename, "writebasis", file, html); + HighsFileType file_type; + call_status = openWriteFile(filename, "writebasis", file, file_type); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "openWriteFile"); if (return_status == HighsStatus::kError) return return_status; @@ -689,9 +775,9 @@ HighsStatus Highs::presolve() { presolved_model_.lp_.setMatrixDimensions(); } - highsLogUser( - options_.log_options, HighsLogType::kInfo, "Presolve status: %s\n", - presolve_.presolveStatusToString(model_presolve_status_).c_str()); + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Presolve status: %s\n", + presolveStatusToString(model_presolve_status_).c_str()); return returnFromHighs(return_status); } @@ -827,15 +913,19 @@ HighsStatus Highs::run() { highsLogDev(options_.log_options, HighsLogType::kVerbose, "Solving model: %s\n", model_.lp_.model_name_.c_str()); - // Check validity of any integrality, keeping a record of any upper - // bound modifications for semi-variables - call_status = assessIntegrality(model_.lp_, options_); - if (call_status == HighsStatus::kError) { - setHighsModelStatusAndClearSolutionAndBasis(HighsModelStatus::kSolveError); - return returnFromRun(HighsStatus::kError); + if (!options_.solve_relaxation) { + // Not solving the relaxation, so check validity of any + // integrality, keeping a record of any bound and type + // modifications for semi-variables + call_status = assessIntegrality(model_.lp_, options_); + if (call_status == HighsStatus::kError) { + setHighsModelStatusAndClearSolutionAndBasis( + HighsModelStatus::kSolveError); + return returnFromRun(HighsStatus::kError); + } } - - if (!options_.solver.compare(kHighsChooseString)) { + const bool use_simplex_or_ipm = options_.solver.compare(kHighsChooseString); + if (!use_simplex_or_ipm) { // Leaving HiGHS to choose method according to model class if (model_.isQp()) { if (model_.isMip()) { @@ -870,10 +960,15 @@ HighsStatus Highs::run() { // If model is MIP, must be solving the relaxation or not leaving // HiGHS to choose method according to model class if (model_.isMip()) { - assert(options_.solve_relaxation || - options_.solver.compare(kHighsChooseString)); + assert(options_.solve_relaxation || use_simplex_or_ipm); // Relax any semi-variables relaxSemiVariables(model_.lp_); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Solving LP relaxation since%s%s%s\n", + options_.solve_relaxation ? " solve_relaxation is true" : "", + options_.solve_relaxation && use_simplex_or_ipm ? " and" : "", + use_simplex_or_ipm ? (" solver = " + options_.solver).c_str() : ""); } // Solve the model as an LP HighsLp& incumbent_lp = model_.lp_; @@ -1057,8 +1152,26 @@ HighsStatus Highs::run() { case HighsPresolveStatus::kReduced: { HighsLp& reduced_lp = presolve_.getReducedProblem(); reduced_lp.setMatrixDimensions(); - // Validate the reduced LP - assert(assessLp(reduced_lp, options_) == HighsStatus::kOk); + if (kAllowDeveloperAssert) { + // Validate the reduced LP + // + // Although preseolve can yield small values in the matrix, + // they are only stripped out (by assessLp) in debug. This + // suggests that they are no real danger to the simplex + // solver. The only danger is pivoting on them, but that + // implies that values of roughly that size have been chosen + // in the ratio test. Even with the filter, values of 1e-9 + // could be in the matrix, and these would be bad + // pivots. Hence, since the small values may play a + // meaningful role in postsolve, then it's better to keep + // them. + // + // ToDo. Analyse the extent of small value creation. See #1187 + assert(assessLp(reduced_lp, options_) == HighsStatus::kOk); + } else { + reduced_lp.a_matrix_.assessSmallValues(options_.log_options, + options_.small_matrix_value); + } call_status = cleanBounds(options_, reduced_lp); // Ignore any warning from clean bounds since the original LP // is still solved after presolve @@ -1450,17 +1563,8 @@ HighsStatus Highs::getPrimalRay(bool& has_primal_ray, return getPrimalRayInterface(has_primal_ray, primal_ray_value); } -HighsStatus Highs::getRanging() { - // Create a HighsLpSolverObject of references to data in the Highs - // class, and the scaled/unscaled model status - HighsLpSolverObject solver_object(model_.lp_, basis_, solution_, info_, - ekk_instance_, options_, timer_); - solver_object.model_status_ = model_status_; - return getRangingData(this->ranging_, solver_object); -} - HighsStatus Highs::getRanging(HighsRanging& ranging) { - HighsStatus return_status = getRanging(); + HighsStatus return_status = getRangingInterface(); ranging = this->ranging_; return return_status; } @@ -1640,8 +1744,8 @@ HighsStatus Highs::getReducedRow(const HighsInt row, double* row_vector, rhs[row] = 1; basis_inverse_row.resize(num_row, 0); // Form B^{-T}e_{row} - basisSolveInterface(rhs, &basis_inverse_row[0], NULL, NULL, true); - basis_inverse_row_vector = &basis_inverse_row[0]; + basisSolveInterface(rhs, basis_inverse_row.data(), NULL, NULL, true); + basis_inverse_row_vector = basis_inverse_row.data(); } bool return_indices = row_num_nz != NULL; if (return_indices) *row_num_nz = 0; @@ -1740,11 +1844,11 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { return returnFromHighs(return_status); } -HighsStatus Highs::setLogCallback(void (*log_callback)(HighsLogType, - const char*, void*), - void* log_callback_data) { - options_.log_options.log_callback = log_callback; - options_.log_options.log_callback_data = log_callback_data; +HighsStatus Highs::setLogCallback(void (*log_user_callback)(HighsLogType, + const char*, void*), + void* deprecated // V2.0 remove +) { + options_.log_options.log_user_callback = log_user_callback; return HighsStatus::kOk; } @@ -1915,7 +2019,7 @@ HighsStatus Highs::addVars(const HighsInt num_new_var, const double* lower, if (num_new_var <= 0) returnFromHighs(return_status); std::vector cost; cost.assign(num_new_var, 0); - return addCols(num_new_var, &cost[0], lower, upper, 0, nullptr, nullptr, + return addCols(num_new_var, cost.data(), lower, upper, 0, nullptr, nullptr, nullptr); } @@ -1998,13 +2102,14 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt num_set_entries, std::vector local_integrality{integrality, integrality + num_set_entries}; std::vector local_set{set, set + num_set_entries}; - sortSetData(num_set_entries, local_set, integrality, &local_integrality[0]); + sortSetData(num_set_entries, local_set, integrality, + local_integrality.data()); HighsIndexCollection index_collection; const bool create_ok = create(index_collection, num_set_entries, - &local_set[0], model_.lp_.num_col_); + local_set.data(), model_.lp_.num_col_); assert(create_ok); HighsStatus call_status = - changeIntegralityInterface(index_collection, &local_integrality[0]); + changeIntegralityInterface(index_collection, local_integrality.data()); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeIntegrality"); @@ -2059,14 +2164,14 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, // Ensure that the set and data are in ascending order std::vector local_cost{cost, cost + num_set_entries}; std::vector local_set{set, set + num_set_entries}; - sortSetData(num_set_entries, local_set, cost, NULL, NULL, &local_cost[0], + sortSetData(num_set_entries, local_set, cost, NULL, NULL, local_cost.data(), NULL, NULL); HighsIndexCollection index_collection; const bool create_ok = create(index_collection, num_set_entries, - &local_set[0], model_.lp_.num_col_); + local_set.data(), model_.lp_.num_col_); assert(create_ok); HighsStatus call_status = - changeCostsInterface(index_collection, &local_cost[0]); + changeCostsInterface(index_collection, local_cost.data()); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeCosts"); @@ -2130,14 +2235,14 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, std::vector local_lower{lower, lower + num_set_entries}; std::vector local_upper{upper, upper + num_set_entries}; std::vector local_set{set, set + num_set_entries}; - sortSetData(num_set_entries, local_set, lower, upper, NULL, &local_lower[0], - &local_upper[0], NULL); + sortSetData(num_set_entries, local_set, lower, upper, NULL, + local_lower.data(), local_upper.data(), NULL); HighsIndexCollection index_collection; const bool create_ok = create(index_collection, num_set_entries, - &local_set[0], model_.lp_.num_col_); + local_set.data(), model_.lp_.num_col_); assert(create_ok); HighsStatus call_status = changeColBoundsInterface( - index_collection, &local_lower[0], &local_upper[0]); + index_collection, local_lower.data(), local_upper.data()); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeColBounds"); @@ -2203,14 +2308,14 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, std::vector local_lower{lower, lower + num_set_entries}; std::vector local_upper{upper, upper + num_set_entries}; std::vector local_set{set, set + num_set_entries}; - sortSetData(num_set_entries, local_set, lower, upper, NULL, &local_lower[0], - &local_upper[0], NULL); + sortSetData(num_set_entries, local_set, lower, upper, NULL, + local_lower.data(), local_upper.data(), NULL); HighsIndexCollection index_collection; const bool create_ok = create(index_collection, num_set_entries, - &local_set[0], model_.lp_.num_row_); + local_set.data(), model_.lp_.num_row_); assert(create_ok); HighsStatus call_status = changeRowBoundsInterface( - index_collection, &local_lower[0], &local_upper[0]); + index_collection, local_lower.data(), local_upper.data()); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeRowBounds"); @@ -2313,6 +2418,67 @@ HighsStatus Highs::getCols(const HighsInt* mask, HighsInt& num_col, return returnFromHighs(HighsStatus::kOk); } +HighsStatus Highs::getColName(const HighsInt col, std::string& name) const { + const HighsInt num_col = this->model_.lp_.num_col_; + if (col < 0 || col >= num_col) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Index %d for column name is outside the range [0, num_col = %d)\n", + int(col), int(num_col)); + return HighsStatus::kError; + } + const HighsInt num_col_name = this->model_.lp_.col_names_.size(); + if (col >= num_col_name) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Index %d for column name is outside the range [0, " + "num_col_name = %d)\n", + int(col), int(num_col_name)); + return HighsStatus::kError; + } + name = this->model_.lp_.col_names_[col]; + return HighsStatus::kOk; +} + +HighsStatus Highs::getColByName(const std::string& name, HighsInt& col) { + HighsLp& lp = model_.lp_; + if (!lp.col_names_.size()) return HighsStatus::kError; + if (!lp.col_hash_.name2index.size()) lp.col_hash_.form(lp.col_names_); + auto search = lp.col_hash_.name2index.find(name); + if (search == lp.col_hash_.name2index.end()) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Highs::getColByName: name %s is not found\n", name.c_str()); + return HighsStatus::kError; + } + if (search->second == kHashIsDuplicate) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Highs::getColByName: name %s is duplicated\n", name.c_str()); + return HighsStatus::kError; + } + col = search->second; + assert(lp.col_names_[col] == name); + return HighsStatus::kOk; +} + +HighsStatus Highs::getColIntegrality(const HighsInt col, + HighsVarType& integrality) const { + const HighsInt num_col = this->model_.lp_.num_col_; + if (col < 0 || col >= num_col) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Index %d for column integrality is outside the range [0, " + "num_col = %d)\n", + int(col), int(num_col)); + return HighsStatus::kError; + } + if (col < int(this->model_.lp_.integrality_.size())) { + integrality = this->model_.lp_.integrality_[col]; + return HighsStatus::kOk; + } else { + highsLogUser(options_.log_options, HighsLogType::kError, + "Model integrality does not exist for index %d\n", int(col)); + return HighsStatus::kError; + } +} + HighsStatus Highs::getRows(const HighsInt from_row, const HighsInt to_row, HighsInt& num_row, double* lower, double* upper, HighsInt& num_nz, HighsInt* start, HighsInt* index, @@ -2354,6 +2520,47 @@ HighsStatus Highs::getRows(const HighsInt* mask, HighsInt& num_row, return returnFromHighs(HighsStatus::kOk); } +HighsStatus Highs::getRowName(const HighsInt row, std::string& name) const { + const HighsInt num_row = this->model_.lp_.num_row_; + if (row < 0 || row >= num_row) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Index %d for row name is outside the range [0, num_row = %d)\n", + int(row), int(num_row)); + return HighsStatus::kError; + } + const HighsInt num_row_name = this->model_.lp_.row_names_.size(); + if (row >= num_row_name) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Index %d for row name is outside the range [0, num_row_name = %d)\n", + int(row), int(num_row_name)); + return HighsStatus::kError; + } + name = this->model_.lp_.row_names_[row]; + return HighsStatus::kOk; +} + +HighsStatus Highs::getRowByName(const std::string& name, HighsInt& row) { + HighsLp& lp = model_.lp_; + if (!lp.row_names_.size()) return HighsStatus::kError; + if (!lp.row_hash_.name2index.size()) lp.row_hash_.form(lp.row_names_); + auto search = lp.row_hash_.name2index.find(name); + if (search == lp.row_hash_.name2index.end()) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Highs::getRowByName: name %s is not found\n", name.c_str()); + return HighsStatus::kError; + } + if (search->second == kHashIsDuplicate) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Highs::getRowByName: name %s is duplicated\n", name.c_str()); + return HighsStatus::kError; + } + row = search->second; + assert(lp.row_names_[row] == name); + return HighsStatus::kOk; +} + HighsStatus Highs::getCoeff(const HighsInt row, const HighsInt col, double& value) { if (row < 0 || row >= model_.lp_.num_row_) { @@ -2480,10 +2687,9 @@ HighsStatus Highs::postsolve(const HighsSolution& solution, model_presolve_status_ == HighsPresolveStatus::kReducedToEmpty || model_presolve_status_ == HighsPresolveStatus::kTimeout; if (!can_run_postsolve) { - highsLogUser( - options_.log_options, HighsLogType::kWarning, - "Cannot run postsolve with presolve status: %s\n", - presolve_.presolveStatusToString(model_presolve_status_).c_str()); + highsLogUser(options_.log_options, HighsLogType::kWarning, + "Cannot run postsolve with presolve status: %s\n", + presolveStatusToString(model_presolve_status_).c_str()); return HighsStatus::kWarning; } HighsStatus return_status = callRunPostsolve(solution, basis); @@ -2495,8 +2701,8 @@ HighsStatus Highs::writeSolution(const std::string& filename, HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; FILE* file; - bool html; - call_status = openWriteFile(filename, "writeSolution", file, html); + HighsFileType file_type; + call_status = openWriteFile(filename, "writeSolution", file, file_type); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "openWriteFile"); if (return_status == HighsStatus::kError) return return_status; @@ -2519,8 +2725,9 @@ HighsStatus Highs::writeSolution(const std::string& filename, return_status = HighsStatus::kError; return returnFromWriteSolution(file, return_status); } - return_status = interpretCallStatus( - options_.log_options, this->getRanging(), return_status, "getRanging"); + return_status = + interpretCallStatus(options_.log_options, this->getRangingInterface(), + return_status, "getRangingInterface"); if (return_status == HighsStatus::kError) returnFromWriteSolution(file, return_status); fprintf(file, "\n# Ranging\n"); @@ -2542,6 +2749,33 @@ HighsStatus Highs::assessPrimalSolution(bool& valid, bool& integral, integral, feasible); } +std::string Highs::presolveStatusToString( + const HighsPresolveStatus presolve_status) const { + switch (presolve_status) { + case HighsPresolveStatus::kNotPresolved: + return "Not presolved"; + case HighsPresolveStatus::kNotReduced: + return "Not reduced"; + case HighsPresolveStatus::kInfeasible: + return "Infeasible"; + case HighsPresolveStatus::kUnboundedOrInfeasible: + return "Unbounded or infeasible"; + case HighsPresolveStatus::kReduced: + return "Reduced"; + case HighsPresolveStatus::kReducedToEmpty: + return "Reduced to empty"; + case HighsPresolveStatus::kTimeout: + return "Timeout"; + case HighsPresolveStatus::kNullError: + return "Null error"; + case HighsPresolveStatus::kOptionsError: + return "Options error"; + default: + assert(1 == 0); + return "Unrecognised presolve status"; + } +} + std::string Highs::modelStatusToString( const HighsModelStatus model_status) const { return utilModelStatusToString(model_status); @@ -2619,28 +2853,46 @@ HighsPresolveStatus Highs::runPresolve(const bool force_presolve) { } // Presolve. - presolve_.init(original_lp, timer_); - presolve_.options_ = &options_; - if (options_.time_limit > 0 && options_.time_limit < kHighsInf) { - double current = timer_.readRunHighsClock(); - double time_init = current - start_presolve; - double left = presolve_.options_->time_limit - time_init; - if (left <= 0) { - highsLogDev(options_.log_options, HighsLogType::kError, - "Time limit reached while copying matrix into presolve.\n"); - return HighsPresolveStatus::kTimeout; + HighsPresolveStatus presolve_return_status = + HighsPresolveStatus::kNotPresolved; + if (model_.isMip()) { + // Use presolve for MIP + // + // Presolved model is extracted now since it's part of solver, + // which is lost on return + HighsMipSolver solver(options_, original_lp, solution_); + solver.runPresolve(); + presolve_return_status = solver.getPresolveStatus(); + // Assign values to data members of presolve_ + presolve_.data_.reduced_lp_ = solver.getPresolvedModel(); + // presolved_model_.lp_ = solver.getPresolvedModel(); + presolve_.presolve_status_ = presolve_return_status; + // presolve_.data_.presolve_log_ = + } else { + // Use presolve for LP + presolve_.init(original_lp, timer_); + presolve_.options_ = &options_; + if (options_.time_limit > 0 && options_.time_limit < kHighsInf) { + double current = timer_.readRunHighsClock(); + double time_init = current - start_presolve; + double left = presolve_.options_->time_limit - time_init; + if (left <= 0) { + highsLogDev(options_.log_options, HighsLogType::kError, + "Time limit reached while copying matrix into presolve.\n"); + return HighsPresolveStatus::kTimeout; + } + highsLogDev(options_.log_options, HighsLogType::kVerbose, + "Time limit set: copying matrix took %.2g, presolve " + "time left: %.2g\n", + time_init, left); } - highsLogDev(options_.log_options, HighsLogType::kVerbose, - "Time limit set: copying matrix took %.2g, presolve " - "time left: %.2g\n", - time_init, left); - } - HighsPresolveStatus presolve_return_status = presolve_.run(); + presolve_return_status = presolve_.run(); + } highsLogDev(options_.log_options, HighsLogType::kVerbose, "presolve_.run() returns status: %s\n", - presolve_.presolveStatusToString(presolve_return_status).c_str()); + presolveStatusToString(presolve_return_status).c_str()); // Update reduction counts. assert(presolve_return_status == presolve_.presolve_status_); @@ -2689,7 +2941,7 @@ HighsPostsolveStatus Highs::runPostsolve() { calculateRowValuesQuad(model_.lp_, presolve_.data_.recovered_solution_); if (have_dual_solution && model_.lp_.sense_ == ObjSense::kMaximize) - presolve_.negateReducedLpColDuals(true); + presolve_.negateReducedLpColDuals(); // Ensure that the postsolve status is used to set // presolve_.postsolve_status_, as well as being returned @@ -2801,8 +3053,6 @@ HighsStatus Highs::callSolveLp(HighsLp& lp, const string message) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; - // Create a HighsLpSolverObject of references to data in the Highs - // class, and the scaled/unscaled model status HighsLpSolverObject solver_object(lp, basis_, solution_, info_, ekk_instance_, options_, timer_); @@ -2879,18 +3129,19 @@ HighsStatus Highs::callSolveQp() { int rep = rt.statistics.iteration.size() - 1; highsLogUser(options_.log_options, HighsLogType::kInfo, - "%" HIGHSINT_FORMAT ", %lf, %" HIGHSINT_FORMAT - ", %lf, %lf, %" HIGHSINT_FORMAT ", %lf, %lf\n", - rt.statistics.iteration[rep], rt.statistics.objval[rep], - rt.statistics.nullspacedimension[rep], rt.statistics.time[rep], - rt.statistics.sum_primal_infeasibilities[rep], - rt.statistics.num_primal_infeasibilities[rep], - rt.statistics.density_nullspace[rep], - rt.statistics.density_factor[rep]); + "%" HIGHSINT_FORMAT ", %lf, %lf, %" HIGHSINT_FORMAT "\n", + rt.statistics.iteration[rep], rt.statistics.time[rep], + rt.statistics.objval[rep], + rt.statistics.nullspacedimension[rep]); }); runtime.settings.timelimit = options_.time_limit; runtime.settings.iterationlimit = std::numeric_limits::max(); + + // print header for QP solver output + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Iteration, Runtime, ObjVal, NullspaceDim\n"); + Quass qpsolver(runtime); qpsolver.solve(); @@ -2899,17 +3150,18 @@ HighsStatus Highs::callSolveQp() { return_status = interpretCallStatus(options_.log_options, call_status, return_status, "QpSolver"); if (return_status == HighsStatus::kError) return return_status; - model_status_ = runtime.status == ProblemStatus::OPTIMAL + model_status_ = runtime.status == QpModelStatus::OPTIMAL ? HighsModelStatus::kOptimal - : runtime.status == ProblemStatus::UNBOUNDED + : runtime.status == QpModelStatus::UNBOUNDED ? HighsModelStatus::kUnbounded - : runtime.status == ProblemStatus::INFEASIBLE + : runtime.status == QpModelStatus::INFEASIBLE ? HighsModelStatus::kInfeasible - : runtime.status == ProblemStatus::ITERATIONLIMIT + : runtime.status == QpModelStatus::ITERATIONLIMIT ? HighsModelStatus::kIterationLimit - : runtime.status == ProblemStatus::TIMELIMIT + : runtime.status == QpModelStatus::TIMELIMIT ? HighsModelStatus::kTimeLimit : HighsModelStatus::kNotset; + // extract variable values solution_.col_value.resize(lp.num_col_); solution_.col_dual.resize(lp.num_col_); const double objective_multiplier = lp.sense_ == ObjSense::kMinimize ? 1 : -1; @@ -2918,6 +3170,7 @@ HighsStatus Highs::callSolveQp() { solution_.col_dual[iCol] = objective_multiplier * runtime.dualvar.value[iCol]; } + // extract constraint activity solution_.row_value.resize(lp.num_row_); solution_.row_dual.resize(lp.num_row_); // Negate the vector and Hessian @@ -2928,6 +3181,35 @@ HighsStatus Highs::callSolveQp() { } solution_.value_valid = true; solution_.dual_valid = true; + + // extract basis status + basis_.col_status.resize(lp.num_col_); + basis_.row_status.resize(lp.num_row_); + + for (HighsInt i = 0; i < lp.num_col_; i++) { + if (runtime.status_var[i] == BasisStatus::ActiveAtLower) { + basis_.col_status[i] = HighsBasisStatus::kLower; + } else if (runtime.status_var[i] == BasisStatus::ActiveAtUpper) { + basis_.col_status[i] = HighsBasisStatus::kUpper; + } else if (runtime.status_var[i] == BasisStatus::InactiveInBasis) { + basis_.col_status[i] = HighsBasisStatus::kNonbasic; + } else { + basis_.col_status[i] = HighsBasisStatus::kBasic; + } + } + + for (HighsInt i = 0; i < lp.num_row_; i++) { + if (runtime.status_con[i] == BasisStatus::ActiveAtLower) { + basis_.row_status[i] = HighsBasisStatus::kLower; + } else if (runtime.status_con[i] == BasisStatus::ActiveAtUpper) { + basis_.row_status[i] = HighsBasisStatus::kUpper; + } else if (runtime.status_con[i] == BasisStatus::InactiveInBasis) { + basis_.row_status[i] = HighsBasisStatus::kNonbasic; + } else { + basis_.row_status[i] = HighsBasisStatus::kBasic; + } + } + // Get the objective and any KKT failures info_.objective_function_value = model_.objectiveValue(solution_.col_value); getKktFailures(options_, model_, solution_, basis_, info_); @@ -2991,6 +3273,7 @@ HighsStatus Highs::callSolveMip() { // solution from the MIP solver solution_.col_value.resize(model_.lp_.num_col_); solution_.col_value = solver.solution_; + saved_objective_and_solution_ = solver.saved_objective_and_solution_; model_.lp_.a_matrix_.productQuad(solution_.row_value, solution_.col_value); solution_.value_valid = true; } else { @@ -3021,6 +3304,12 @@ HighsStatus Highs::callSolveMip() { info_.mip_node_count = solver.node_count_; info_.mip_dual_bound = solver.dual_bound_; info_.mip_gap = solver.gap_; + // Get the number of LP iterations, avoiding overflow if the int64_t + // value is too large + int64_t mip_total_lp_iterations = solver.total_lp_iterations_; + info_.simplex_iteration_count = mip_total_lp_iterations > kHighsIInf + ? -1 + : HighsInt(mip_total_lp_iterations); info_.valid = true; if (model_status_ == HighsModelStatus::kOptimal) checkOptimality("MIP", return_status); @@ -3148,8 +3437,8 @@ void Highs::reportModel() { if (model_.hessian_.dim_) { const HighsInt dim = model_.hessian_.dim_; reportHessian(options_.log_options, dim, model_.hessian_.start_[dim], - &model_.hessian_.start_[0], &model_.hessian_.index_[0], - &model_.hessian_.value_[0]); + model_.hessian_.start_.data(), model_.hessian_.index_.data(), + model_.hessian_.value_.data()); } } @@ -3201,8 +3490,8 @@ void Highs::setBasisValidity() { HighsStatus Highs::openWriteFile(const string filename, const string method_name, FILE*& file, - bool& html) const { - html = false; + HighsFileType& file_type) const { + file_type = HighsFileType::kNone; if (filename == "") { // Empty file name: use stdout file = stdout; @@ -3215,7 +3504,17 @@ HighsStatus Highs::openWriteFile(const string filename, return HighsStatus::kError; } const char* dot = strrchr(filename.c_str(), '.'); - if (dot && dot != filename) html = strcmp(dot + 1, "html") == 0; + if (dot && dot != filename) { + if (strcmp(dot + 1, "mps") == 0) { + file_type = HighsFileType::kMps; + } else if (strcmp(dot + 1, "lp") == 0) { + file_type = HighsFileType::kLp; + } else if (strcmp(dot + 1, "md") == 0) { + file_type = HighsFileType::kMd; + } else if (strcmp(dot + 1, "html") == 0) { + file_type = HighsFileType::kHtml; + } + } } return HighsStatus::kOk; } @@ -3378,8 +3677,8 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status) { this->model_.lp_.unapplyMods(); // Unless solved as a MIP, report on the solution - const bool solved_as_mip = - !options_.solver.compare(kHighsChooseString) && model_.isMip(); + const bool solved_as_mip = !options_.solver.compare(kHighsChooseString) && + model_.isMip() && !options_.solve_relaxation; if (!solved_as_mip) reportSolvedLpQpStats(); return returnFromHighs(return_status); diff --git a/src/lp_data/HighsAnalysis.h b/src/lp_data/HighsAnalysis.h index 18c84ff094..d2e3b916d9 100644 --- a/src/lp_data/HighsAnalysis.h +++ b/src/lp_data/HighsAnalysis.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsAnalysis.h * @brief diff --git a/src/lp_data/HighsDebug.cpp b/src/lp_data/HighsDebug.cpp index 8fe53b3b1b..58fc9f1b7e 100644 --- a/src/lp_data/HighsDebug.cpp +++ b/src/lp_data/HighsDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsDebug.cpp * @brief diff --git a/src/lp_data/HighsDebug.h b/src/lp_data/HighsDebug.h index b83ed4cdc8..ad00cac7df 100644 --- a/src/lp_data/HighsDebug.h +++ b/src/lp_data/HighsDebug.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsDebug.h * @brief diff --git a/src/lp_data/HighsDeprecated.cpp b/src/lp_data/HighsDeprecated.cpp index 269a97062c..83b17d4178 100644 --- a/src/lp_data/HighsDeprecated.cpp +++ b/src/lp_data/HighsDeprecated.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsDeprecated.cpp * @brief @@ -151,8 +149,8 @@ HighsStatus Highs::writeSolution(const std::string& filename, HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; FILE* file; - bool html; - call_status = openWriteFile(filename, "writeSolution", file, html); + HighsFileType file_type; + call_status = openWriteFile(filename, "writeSolution", file, file_type); return_status = interpretCallStatus(call_status, return_status, "openWriteFile"); if (return_status == HighsStatus::kError) return return_status; diff --git a/src/lp_data/HighsInfo.cpp b/src/lp_data/HighsInfo.cpp index 055a9c510b..9fd56976db 100644 --- a/src/lp_data/HighsInfo.cpp +++ b/src/lp_data/HighsInfo.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsInfo.cpp * @brief @@ -51,18 +49,19 @@ static std::string infoEntryTypeToString(const HighsInfoType type) { } } -InfoStatus getInfoIndex(const HighsOptions& options, const std::string& name, +InfoStatus getInfoIndex(const HighsLogOptions& report_log_options, + const std::string& name, const std::vector& info_records, HighsInt& index) { HighsInt num_info = info_records.size(); for (index = 0; index < num_info; index++) if (info_records[index]->name == name) return InfoStatus::kOk; - highsLogUser(options.log_options, HighsLogType::kError, + highsLogUser(report_log_options, HighsLogType::kError, "getInfoIndex: Info \"%s\" is unknown\n", name.c_str()); return InfoStatus::kUnknownInfo; } -InfoStatus checkInfo(const HighsOptions& options, +InfoStatus checkInfo(const HighsLogOptions& report_log_options, const std::vector& info_records) { bool error_found = false; HighsInt num_info = info_records.size(); @@ -74,7 +73,7 @@ InfoStatus checkInfo(const HighsOptions& options, if (check_index == index) continue; std::string check_name = info_records[check_index]->name; if (check_name == name) { - highsLogUser(options.log_options, HighsLogType::kError, + highsLogUser(report_log_options, HighsLogType::kError, "checkInfo: Info %" HIGHSINT_FORMAT " (\"%s\") has the same name as info %" HIGHSINT_FORMAT " \"%s\"\n", @@ -93,7 +92,7 @@ InfoStatus checkInfo(const HighsOptions& options, ((InfoRecordInt64*)info_records[check_index])[0]; if (check_info.type == HighsInfoType::kInt64) { if (check_info.value == value_pointer) { - highsLogUser(options.log_options, HighsLogType::kError, + highsLogUser(report_log_options, HighsLogType::kError, "checkInfo: Info %" HIGHSINT_FORMAT " (\"%s\") has the same value " "pointer as info %" HIGHSINT_FORMAT " (\"%s\")\n", @@ -114,7 +113,7 @@ InfoStatus checkInfo(const HighsOptions& options, ((InfoRecordInt*)info_records[check_index])[0]; if (check_info.type == HighsInfoType::kInt) { if (check_info.value == value_pointer) { - highsLogUser(options.log_options, HighsLogType::kError, + highsLogUser(report_log_options, HighsLogType::kError, "checkInfo: Info %" HIGHSINT_FORMAT " (\"%s\") has the same value " "pointer as info %" HIGHSINT_FORMAT " (\"%s\")\n", @@ -135,7 +134,7 @@ InfoStatus checkInfo(const HighsOptions& options, ((InfoRecordDouble*)info_records[check_index])[0]; if (check_info.type == HighsInfoType::kDouble) { if (check_info.value == value_pointer) { - highsLogUser(options.log_options, HighsLogType::kError, + highsLogUser(report_log_options, HighsLogType::kError, "checkInfo: Info %" HIGHSINT_FORMAT " (\"%s\") has the same value " "pointer as info %" HIGHSINT_FORMAT " (\"%s\")\n", @@ -148,24 +147,25 @@ InfoStatus checkInfo(const HighsOptions& options, } } if (error_found) return InfoStatus::kIllegalValue; - highsLogUser(options.log_options, HighsLogType::kInfo, + highsLogUser(report_log_options, HighsLogType::kInfo, "checkInfo: Info are OK\n"); return InfoStatus::kOk; } #ifndef HIGHSINT64 -InfoStatus getLocalInfoValue(const HighsOptions& options, +InfoStatus getLocalInfoValue(const HighsLogOptions& report_log_options, const std::string& name, const bool valid, const std::vector& info_records, int64_t& value) { HighsInt index; - InfoStatus status = getInfoIndex(options, name, info_records, index); + InfoStatus status = + getInfoIndex(report_log_options, name, info_records, index); if (status != InfoStatus::kOk) return status; if (!valid) return InfoStatus::kUnavailable; HighsInfoType type = info_records[index]->type; if (type != HighsInfoType::kInt64) { highsLogUser( - options.log_options, HighsLogType::kError, + report_log_options, HighsLogType::kError, "getInfoValue: Info \"%s\" requires value of type %s, not int64_t\n", name.c_str(), infoEntryTypeToString(type).c_str()); return InfoStatus::kIllegalValue; @@ -176,12 +176,13 @@ InfoStatus getLocalInfoValue(const HighsOptions& options, } #endif -InfoStatus getLocalInfoValue(const HighsOptions& options, +InfoStatus getLocalInfoValue(const HighsLogOptions& report_log_options, const std::string& name, const bool valid, const std::vector& info_records, HighsInt& value) { HighsInt index; - InfoStatus status = getInfoIndex(options, name, info_records, index); + InfoStatus status = + getInfoIndex(report_log_options, name, info_records, index); if (status != InfoStatus::kOk) return status; if (!valid) return InfoStatus::kUnavailable; HighsInfoType type = info_records[index]->type; @@ -197,7 +198,7 @@ InfoStatus getLocalInfoValue(const HighsOptions& options, illegal_type += " or int64_t"; #endif highsLogUser( - options.log_options, HighsLogType::kError, + report_log_options, HighsLogType::kError, "getInfoValue: Info \"%s\" requires value of type %s, not %s\n", name.c_str(), infoEntryTypeToString(type).c_str(), illegal_type.c_str()); @@ -214,18 +215,19 @@ InfoStatus getLocalInfoValue(const HighsOptions& options, return InfoStatus::kOk; } -InfoStatus getLocalInfoValue(const HighsOptions& options, +InfoStatus getLocalInfoValue(const HighsLogOptions& report_log_options, const std::string& name, const bool valid, const std::vector& info_records, double& value) { HighsInt index; - InfoStatus status = getInfoIndex(options, name, info_records, index); + InfoStatus status = + getInfoIndex(report_log_options, name, info_records, index); if (status != InfoStatus::kOk) return status; if (!valid) return InfoStatus::kUnavailable; HighsInfoType type = info_records[index]->type; if (type != HighsInfoType::kDouble) { highsLogUser( - options.log_options, HighsLogType::kError, + report_log_options, HighsLogType::kError, "getInfoValue: Info \"%s\" requires value of type %s, not double\n", name.c_str(), infoEntryTypeToString(type).c_str()); return InfoStatus::kIllegalValue; @@ -235,11 +237,26 @@ InfoStatus getLocalInfoValue(const HighsOptions& options, return InfoStatus::kOk; } +InfoStatus getLocalInfoType(const HighsLogOptions& report_log_options, + const std::string& name, + const std::vector& info_records, + HighsInfoType& type) { + HighsInt index; + InfoStatus status = + getInfoIndex(report_log_options, name, info_records, index); + if (status != InfoStatus::kOk) return status; + type = info_records[index]->type; + return InfoStatus::kOk; +} + HighsStatus writeInfoToFile(FILE* file, const bool valid, const std::vector& info_records, - const bool html) { - if (!html && !valid) return HighsStatus::kWarning; - if (html) { + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; + const bool documentation_file = html_file || md_file; + if (!documentation_file && !valid) return HighsStatus::kWarning; + if (html_file) { fprintf(file, "\n\n\n\n"); fprintf(file, " HiGHS Info\n"); fprintf(file, " \n"); @@ -253,8 +270,8 @@ HighsStatus writeInfoToFile(FILE* file, const bool valid, fprintf(file, "

HiGHS Info

\n\n"); fprintf(file, "
    \n"); } - if (html || valid) reportInfo(file, info_records, html); - if (html) { + if (documentation_file || valid) reportInfo(file, info_records, file_type); + if (html_file) { fprintf(file, "
\n"); fprintf(file, "\n\n\n"); } @@ -262,70 +279,80 @@ HighsStatus writeInfoToFile(FILE* file, const bool valid, } void reportInfo(FILE* file, const std::vector& info_records, - const bool html) { + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; HighsInt num_info = info_records.size(); for (HighsInt index = 0; index < num_info; index++) { HighsInfoType type = info_records[index]->type; // Skip the advanced info when creating HTML - if (html && info_records[index]->advanced) continue; + if (html_file && info_records[index]->advanced) continue; if (type == HighsInfoType::kInt64) { - reportInfo(file, ((InfoRecordInt64*)info_records[index])[0], html); + reportInfo(file, ((InfoRecordInt64*)info_records[index])[0], file_type); } else if (type == HighsInfoType::kInt) { - reportInfo(file, ((InfoRecordInt*)info_records[index])[0], html); + reportInfo(file, ((InfoRecordInt*)info_records[index])[0], file_type); } else { - reportInfo(file, ((InfoRecordDouble*)info_records[index])[0], html); + reportInfo(file, ((InfoRecordDouble*)info_records[index])[0], file_type); } } } -void reportInfo(FILE* file, const InfoRecordInt64& info, const bool html) { - if (html) { +void reportInfo(FILE* file, const InfoRecordInt64& info, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; + if (html_file) { fprintf(file, - "
  • %s
    \n", - info.name.c_str()); - fprintf(file, "%s
    \n", info.description.c_str()); - fprintf(file, "type: HighsInt, advanced: %s\n", - highsBoolToString(info.advanced).c_str()); - fprintf(file, "
  • \n"); + "
  • %s
    \n%s
    \ntype: " + "int64_t
  • \n", + info.name.c_str(), info.description.c_str()); + } else if (md_file) { + fprintf(file, "## %s\n- %s\n- Type: long integer\n\n", + highsInsertMdEscapes(info.name).c_str(), + highsInsertMdEscapes(info.description).c_str()); } else { - fprintf(file, "\n# %s\n", info.description.c_str()); - fprintf(file, "# [type: HighsInt, advanced: %s]\n", - highsBoolToString(info.advanced).c_str()); - fprintf(file, "%s = %" PRId64 "\n", info.name.c_str(), *info.value); + fprintf(file, "\n# %s\n# [type: int64_t]\n%s = %" PRId64 "\n", + info.description.c_str(), info.name.c_str(), *info.value); } } -void reportInfo(FILE* file, const InfoRecordInt& info, const bool html) { - if (html) { +void reportInfo(FILE* file, const InfoRecordInt& info, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; + if (html_file) { fprintf(file, - "
  • %s
    \n", - info.name.c_str()); - fprintf(file, "%s
    \n", info.description.c_str()); - fprintf(file, "type: HighsInt, advanced: %s\n", - highsBoolToString(info.advanced).c_str()); - fprintf(file, "
  • \n"); + "
  • %s
    \n%s
    \ntype: " + "HighsInt
  • \n", + info.name.c_str(), info.description.c_str()); + } else if (md_file) { + fprintf(file, "## %s\n- %s\n- Type: integer\n\n", + highsInsertMdEscapes(info.name).c_str(), + highsInsertMdEscapes(info.description).c_str()); } else { - fprintf(file, "\n# %s\n", info.description.c_str()); - fprintf(file, "# [type: HighsInt, advanced: %s]\n", - highsBoolToString(info.advanced).c_str()); - fprintf(file, "%s = %" HIGHSINT_FORMAT "\n", info.name.c_str(), - *info.value); + fprintf(file, "\n# %s\n# [type: HighsInt]\n%s = %" HIGHSINT_FORMAT "\n", + info.description.c_str(), info.name.c_str(), *info.value); } } -void reportInfo(FILE* file, const InfoRecordDouble& info, const bool html) { - if (html) { +void reportInfo(FILE* file, const InfoRecordDouble& info, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; + if (html_file) { fprintf(file, - "
  • %s
    \n", - info.name.c_str()); - fprintf(file, "%s
    \n", info.description.c_str()); - fprintf(file, "type: double, advanced: %s\n", - highsBoolToString(info.advanced).c_str()); - fprintf(file, "
  • \n"); + "
  • %s
    \n%s
    \ntype: " + "double\n
  • \n", + info.name.c_str(), info.description.c_str()); + } else if (md_file) { + fprintf(file, "## %s\n- %s\n- Type: double\n\n", + highsInsertMdEscapes(info.name).c_str(), + highsInsertMdEscapes(info.description).c_str()); } else { - fprintf(file, "\n# %s\n", info.description.c_str()); - fprintf(file, "# [type: double, advanced: %s]\n", - highsBoolToString(info.advanced).c_str()); - fprintf(file, "%s = %g\n", info.name.c_str(), *info.value); + fprintf(file, "\n# %s\n# [type: double]\n%s = %g\n", + info.description.c_str(), info.name.c_str(), *info.value); } } diff --git a/src/lp_data/HighsInfo.h b/src/lp_data/HighsInfo.h index 91e2c6c080..abd99f408c 100644 --- a/src/lp_data/HighsInfo.h +++ b/src/lp_data/HighsInfo.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsInfo.h * @brief @@ -89,38 +87,45 @@ class InfoRecordDouble : public InfoRecord { virtual ~InfoRecordDouble() {} }; -InfoStatus getInfoIndex(const HighsOptions& options, const std::string& name, +InfoStatus getInfoIndex(const HighsLogOptions& report_log_options, + const std::string& name, const std::vector& info_records, HighsInt& index); -InfoStatus checkInfo(const HighsOptions& options, +InfoStatus checkInfo(const HighsLogOptions& report_log_options, const std::vector& info_records); InfoStatus checkInfo(const InfoRecordInt& info); InfoStatus checkInfo(const InfoRecordDouble& info); -InfoStatus getLocalInfoValue(const HighsOptions& options, +InfoStatus getLocalInfoValue(const HighsLogOptions& report_log_options, const std::string& name, const bool valid, const std::vector& info_records, int64_t& value); -InfoStatus getLocalInfoValue(const HighsOptions& options, +InfoStatus getLocalInfoValue(const HighsLogOptions& report_log_options, const std::string& name, const bool valid, const std::vector& info_records, HighsInt& value); -InfoStatus getLocalInfoValue(const HighsOptions& options, +InfoStatus getLocalInfoValue(const HighsLogOptions& report_log_options, const std::string& name, const bool valid, const std::vector& info_records, double& value); -HighsStatus writeInfoToFile(FILE* file, const bool valid, +InfoStatus getLocalInfoType(const HighsLogOptions& report_log_options, + const std::string& name, const std::vector& info_records, - const bool html = false); + HighsInfoType& type); + +HighsStatus writeInfoToFile( + FILE* file, const bool valid, const std::vector& info_records, + const HighsFileType file_type = HighsFileType::kOther); void reportInfo(FILE* file, const std::vector& info_records, - const bool html = false); + const HighsFileType file_type = HighsFileType::kOther); void reportInfo(FILE* file, const InfoRecordInt64& info, - const bool html = false); -void reportInfo(FILE* file, const InfoRecordInt& info, const bool html = false); + const HighsFileType file_type = HighsFileType::kOther); +void reportInfo(FILE* file, const InfoRecordInt& info, + const HighsFileType file_type = HighsFileType::kOther); void reportInfo(FILE* file, const InfoRecordDouble& info, - const bool html = false); + const HighsFileType file_type = HighsFileType::kOther); // For now, but later change so HiGHS properties are string based so that new // info (for debug and testing too) can be added easily. The info below @@ -132,8 +137,8 @@ struct HighsInfoStruct { int64_t mip_node_count; HighsInt simplex_iteration_count; HighsInt ipm_iteration_count; - HighsInt qp_iteration_count; HighsInt crossover_iteration_count; + HighsInt qp_iteration_count; HighsInt primal_solution_status; HighsInt dual_solution_status; HighsInt basis_validity; @@ -194,8 +199,7 @@ class HighsInfo : public HighsInfoStruct { InfoRecordInt64* record_int64; InfoRecordInt* record_int; InfoRecordDouble* record_double; - bool advanced; - advanced = false; + const bool advanced = false; // Not used record_int = new InfoRecordInt("simplex_iteration_count", "Iteration count for simplex solver", @@ -207,11 +211,6 @@ class HighsInfo : public HighsInfoStruct { &ipm_iteration_count, 0); records.push_back(record_int); - record_int = - new InfoRecordInt("qp_iteration_count", "Iteration count for QP solver", - advanced, &qp_iteration_count, 0); - records.push_back(record_int); - record_int = new InfoRecordInt("crossover_iteration_count", "Iteration count for crossover", advanced, &crossover_iteration_count, 0); diff --git a/src/lp_data/HighsInfoDebug.cpp b/src/lp_data/HighsInfoDebug.cpp index 4829c4fa4f..3ca68d83f7 100644 --- a/src/lp_data/HighsInfoDebug.cpp +++ b/src/lp_data/HighsInfoDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsInfoDebug.cpp * @brief diff --git a/src/lp_data/HighsInfoDebug.h b/src/lp_data/HighsInfoDebug.h index 3821618a0a..6f3730d784 100644 --- a/src/lp_data/HighsInfoDebug.h +++ b/src/lp_data/HighsInfoDebug.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsInfoDebug.h * @brief diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 41e369b8a4..af9456e378 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsInterface.cpp * @brief @@ -170,6 +168,10 @@ HighsStatus Highs::addColsInterface( } // Update the basis correponding to new nonbasic columns if (valid_basis) appendNonbasicColsToBasisInterface(ext_num_new_col); + + // Possibly add column names + lp.addColNames("", ext_num_new_col); + // Increase the number of columns in the LP lp.num_col_ += ext_num_new_col; assert(lpDimensionsOk("addCols", lp, options.log_options)); @@ -289,6 +291,9 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, // Update the basis correponding to new basic rows if (valid_basis) appendBasicRowsToBasisInterface(ext_num_new_row); + // Possibly add row names + lp.addRowNames("", ext_num_new_row); + // Increase the number of rows in the LP lp.num_row_ += ext_num_new_row; assert(lpDimensionsOk("addRows", lp, options.log_options)); @@ -297,6 +302,7 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, invalidateModelStatusSolutionAndInfo(); // Determine any implications for simplex data ekk_instance_.addRows(lp, local_ar_matrix); + return return_status; } @@ -343,6 +349,7 @@ void Highs::deleteColsInterface(HighsIndexCollection& index_collection) { assert(new_col == lp.num_col_); } assert(lpDimensionsOk("deleteCols", lp, options_.log_options)); + lp.col_hash_.name2index.clear(); } void Highs::deleteRowsInterface(HighsIndexCollection& index_collection) { @@ -384,6 +391,7 @@ void Highs::deleteRowsInterface(HighsIndexCollection& index_collection) { assert(new_row == lp.num_row_); } assert(lpDimensionsOk("deleteRows", lp, options_.log_options)); + lp.row_hash_.name2index.clear(); } void Highs::getColsInterface(const HighsIndexCollection& index_collection, @@ -512,15 +520,13 @@ void Highs::getRowsInterface(const HighsIndexCollection& index_collection, if (row_upper != NULL) row_upper[new_iRow] = lp.row_upper_[iRow]; } } - // bail out if no matrix is to be extracted 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; - // Someone might just want the values, but to get them makes use of - // the starts so tough! - if (!extract_start) return; - // Allocate an array of lengths for the row-wise matrix to be extracted + // 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 @@ -532,6 +538,14 @@ void Highs::getRowsInterface(const HighsIndexCollection& index_collection, 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] = @@ -648,8 +662,8 @@ HighsStatus Highs::changeColBoundsInterface( // set and data are in ascending order if (index_collection.is_set_) sortSetData(index_collection.set_num_entries_, index_collection.set_, - col_lower, col_upper, NULL, &local_colLower[0], - &local_colUpper[0], NULL); + col_lower, col_upper, NULL, local_colLower.data(), + local_colUpper.data(), NULL); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus( options_.log_options, @@ -693,7 +707,8 @@ HighsStatus Highs::changeRowBoundsInterface( // set and data are in ascending order if (index_collection.is_set_) sortSetData(index_collection.set_num_entries_, index_collection.set_, lower, - upper, NULL, &local_rowLower[0], &local_rowUpper[0], NULL); + upper, NULL, local_rowLower.data(), local_rowUpper.data(), + NULL); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus( options_.log_options, @@ -730,10 +745,22 @@ void Highs::changeCoefficientInterface(const HighsInt ext_row, zero_new_value); // Deduce the consequences of a changed element // - // ToDo: Can do something more intelligent if element is in nonbasic column. - // Otherwise, treat it as if it's a new row + // ToDo: Can do something more intelligent if element is in nonbasic + // column + // + const bool basic_column = + this->basis_.col_status[ext_col] == HighsBasisStatus::kBasic; + // + // For now, treat it as if it's a new row invalidateModelStatusSolutionAndInfo(); + if (basic_column) { + // Basis is retained, but is has to be viewed as alien, since the + // basis matrix has changed + this->basis_.was_alien = true; + this->basis_.alien = true; + } + // Determine any implications for simplex data ekk_instance_.updateStatus(LpAction::kNewRows); } @@ -1091,8 +1118,6 @@ HighsStatus Highs::getBasicVariablesInterface(HighsInt* basic_variables) { if (!ekk_status.has_invert) { // The LP has no invert to use, so have to set one up, but only // for the current basis, so return_value is the rank deficiency. - // - // Create a HighsLpSolverObject HighsLpSolverObject solver_object(lp, basis_, solution_, info_, ekk_instance_, options_, timer_); const bool only_from_known_basis = true; @@ -1410,7 +1435,7 @@ HighsStatus Highs::getPrimalRayInterface(bool& has_primal_ray, rhs[col - num_col] = primal_ray_sign; } HighsInt* column_num_nz = 0; - basisSolveInterface(rhs, &column[0], column_num_nz, NULL, false); + basisSolveInterface(rhs, column.data(), column_num_nz, NULL, false); // Now zero primal_ray_value and scatter the column according to // the basic variables. for (HighsInt iCol = 0; iCol < num_col; iCol++) primal_ray_value[iCol] = 0; @@ -1423,6 +1448,13 @@ HighsStatus Highs::getPrimalRayInterface(bool& has_primal_ray, return return_status; } +HighsStatus Highs::getRangingInterface() { + HighsLpSolverObject solver_object(model_.lp_, basis_, solution_, info_, + ekk_instance_, options_, timer_); + solver_object.model_status_ = model_status_; + return getRangingData(this->ranging_, solver_object); +} + bool Highs::aFormatOk(const HighsInt num_nz, const HighsInt format) { if (!num_nz) return true; const bool ok_format = format == (HighsInt)MatrixFormat::kColwise || diff --git a/src/lp_data/HighsLp.cpp b/src/lp_data/HighsLp.cpp index e67497e01f..0146b51c0f 100644 --- a/src/lp_data/HighsLp.cpp +++ b/src/lp_data/HighsLp.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsLp.cpp * @brief @@ -41,8 +39,14 @@ bool HighsLp::hasSemiVariables() const { return false; } -bool HighsLp::operator==(const HighsLp& lp) { +bool HighsLp::operator==(const HighsLp& lp) const { bool equal = equalButForNames(lp); + equal = equalNames(lp) && equal; + return equal; +} + +bool HighsLp::equalNames(const HighsLp& lp) const { + bool equal = true; equal = this->objective_name_ == lp.objective_name_ && equal; equal = this->row_names_ == lp.row_names_ && equal; equal = this->col_names_ == lp.col_names_ && equal; @@ -82,6 +86,15 @@ double HighsLp::objectiveValue(const std::vector& solution) const { return objective_function_value; } +HighsCDouble HighsLp::objectiveCDoubleValue( + const std::vector& solution) const { + assert((int)solution.size() >= this->num_col_); + HighsCDouble objective_function_value = this->offset_; + for (HighsInt iCol = 0; iCol < this->num_col_; iCol++) + objective_function_value += this->col_cost_[iCol] * solution[iCol]; + return objective_function_value; +} + void HighsLp::setMatrixDimensions() { this->a_matrix_.num_col_ = this->num_col_; this->a_matrix_.num_row_ = this->num_row_; @@ -130,11 +143,16 @@ void HighsLp::clear() { this->model_name_ = ""; this->objective_name_ = ""; + this->new_col_name_ix_ = 0; + this->new_row_name_ix_ = 0; this->col_names_.clear(); this->row_names_.clear(); this->integrality_.clear(); + this->col_hash_.clear(); + this->row_hash_.clear(); + this->clearScale(); this->is_scaled_ = false; this->is_moved_ = false; @@ -215,53 +233,214 @@ void HighsLp::moveBackLpAndUnapplyScaling(HighsLp& lp) { assert(this->is_moved_ == false); } -void HighsLp::unapplyMods() { - // Restore any modified lower bounds - std::vector& lower_bound_index = - this->mods_.save_semi_variable_lower_bound_index; - std::vector& lower_bound_value = - this->mods_.save_semi_variable_lower_bound_value; - const HighsInt num_lower_bound = lower_bound_index.size(); +void HighsLp::addColNames(const std::string name, const HighsInt num_new_col) { + // Don't add names if there are no columns, or if the names are + // already incomplete + if (this->num_col_ == 0) return; + HighsInt col_names_size = this->col_names_.size(); + if (col_names_size < this->num_col_) return; + if (!this->col_hash_.name2index.size()) + this->col_hash_.form(this->col_names_); + // Handle the addition of user-defined names later + assert(name == ""); + for (HighsInt iCol = this->num_col_; iCol < this->num_col_ + num_new_col; + iCol++) { + const std::string col_name = + "col_ekk_" + std::to_string(this->new_col_name_ix_++); + bool added = false; + auto search = this->col_hash_.name2index.find(col_name); + if (search == this->col_hash_.name2index.end()) { + // Name not found in hash + if (col_names_size == this->num_col_) { + // No space (or name) for this col name + this->col_names_.push_back(col_name); + added = true; + } else if (col_names_size > iCol) { + // Space for this col name. Only add if name is blank + if (this->col_names_[iCol] == "") { + this->col_names_[iCol] = col_name; + added = true; + } + } + } + if (added) { + const bool duplicate = + !this->col_hash_.name2index.emplace(col_name, iCol).second; + assert(!duplicate); + assert(this->col_names_[iCol] == col_name); + assert(this->col_hash_.name2index.find(col_name)->second == iCol); + } else { + // Duplicate name or other failure + this->col_hash_.name2index.clear(); + return; + } + } +} - // Ensure that if there are no indices of modified lower bounds, - // then there are no modified lower bound values - if (!num_lower_bound) assert(!lower_bound_value.size()); +void HighsLp::addRowNames(const std::string name, const HighsInt num_new_row) { + // Don't add names if there are no rows, or if the names are already + // incomplete + if (this->num_row_ == 0) return; + HighsInt row_names_size = this->row_names_.size(); + if (row_names_size < this->num_row_) return; + if (!this->row_hash_.name2index.size()) + this->row_hash_.form(this->row_names_); + // Handle the addition of user-defined names later + assert(name == ""); + for (HighsInt iRow = this->num_row_; iRow < this->num_row_ + num_new_row; + iRow++) { + const std::string row_name = + "row_ekk_" + std::to_string(this->new_row_name_ix_++); + bool added = false; + auto search = this->row_hash_.name2index.find(row_name); + if (search == this->row_hash_.name2index.end()) { + // Name not found in hash + if (row_names_size == this->num_row_) { + // No space (or name) for this row name + this->row_names_.push_back(row_name); + added = true; + } else if (row_names_size > iRow) { + // Space for this row name. Only add if name is blank + if (this->row_names_[iRow] == "") { + this->row_names_[iRow] = row_name; + added = true; + } + } + } + if (added) { + const bool duplicate = + !this->row_hash_.name2index.emplace(row_name, iRow).second; + assert(!duplicate); + assert(this->row_names_[iRow] == row_name); + assert(this->row_hash_.name2index.find(row_name)->second == iRow); + } else { + // Duplicate name or other failure + this->row_hash_.name2index.clear(); + return; + } + } +} +void HighsLp::unapplyMods() { + // Restore any non-semi types + const HighsInt num_non_semi = this->mods_.save_non_semi_variable_index.size(); + for (HighsInt k = 0; k < num_non_semi; k++) { + HighsInt iCol = this->mods_.save_non_semi_variable_index[k]; + assert(this->integrality_[iCol] == HighsVarType::kContinuous || + this->integrality_[iCol] == HighsVarType::kInteger); + if (this->integrality_[iCol] == HighsVarType::kContinuous) { + this->integrality_[iCol] = HighsVarType::kSemiContinuous; + } else { + this->integrality_[iCol] = HighsVarType::kSemiInteger; + } + } + // Restore any inconsistent semi variables + const HighsInt num_inconsistent_semi = + this->mods_.save_inconsistent_semi_variable_index.size(); + if (!num_inconsistent_semi) { + assert( + !this->mods_.save_inconsistent_semi_variable_lower_bound_value.size()); + assert( + !this->mods_.save_inconsistent_semi_variable_upper_bound_value.size()); + assert(!this->mods_.save_inconsistent_semi_variable_type.size()); + } + for (HighsInt k = 0; k < num_inconsistent_semi; k++) { + HighsInt iCol = this->mods_.save_inconsistent_semi_variable_index[k]; + this->col_lower_[iCol] = + this->mods_.save_inconsistent_semi_variable_lower_bound_value[k]; + this->col_upper_[iCol] = + this->mods_.save_inconsistent_semi_variable_upper_bound_value[k]; + this->integrality_[iCol] = + this->mods_.save_inconsistent_semi_variable_type[k]; + } + // Restore any relaxed lower bounds + std::vector& relaxed_semi_variable_lower_index = + this->mods_.save_relaxed_semi_variable_lower_bound_index; + std::vector& relaxed_semi_variable_lower_value = + this->mods_.save_relaxed_semi_variable_lower_bound_value; + const HighsInt num_lower_bound = relaxed_semi_variable_lower_index.size(); + if (!num_lower_bound) { + assert(!relaxed_semi_variable_lower_value.size()); + } for (HighsInt k = 0; k < num_lower_bound; k++) { - HighsInt iCol = lower_bound_index[k]; - this->col_lower_[iCol] = lower_bound_value[k]; + HighsInt iCol = relaxed_semi_variable_lower_index[k]; + assert(this->integrality_[iCol] == HighsVarType::kSemiContinuous || + this->integrality_[iCol] == HighsVarType::kSemiInteger); + this->col_lower_[iCol] = relaxed_semi_variable_lower_value[k]; + } + // Restore any tightened upper bounds + std::vector& tightened_semi_variable_upper_bound_index = + this->mods_.save_tightened_semi_variable_upper_bound_index; + std::vector& tightened_semi_variable_upper_bound_value = + this->mods_.save_tightened_semi_variable_upper_bound_value; + const HighsInt num_upper_bound = + tightened_semi_variable_upper_bound_index.size(); + if (!num_upper_bound) { + assert(!tightened_semi_variable_upper_bound_value.size()); } - - // Restore any modified upper bounds - std::vector& upper_bound_index = - this->mods_.save_semi_variable_upper_bound_index; - std::vector& upper_bound_value = - this->mods_.save_semi_variable_upper_bound_value; - const HighsInt num_upper_bound = upper_bound_index.size(); - - // Ensure that if there are no indices of modified upper bounds, - // then there are no modified upper bound values - if (!num_upper_bound) assert(!upper_bound_value.size()); - for (HighsInt k = 0; k < num_upper_bound; k++) { - HighsInt iCol = upper_bound_index[k]; - this->col_upper_[iCol] = upper_bound_value[k]; + HighsInt iCol = tightened_semi_variable_upper_bound_index[k]; + assert(this->integrality_[iCol] == HighsVarType::kSemiContinuous || + this->integrality_[iCol] == HighsVarType::kSemiInteger); + this->col_upper_[iCol] = tightened_semi_variable_upper_bound_value[k]; } this->mods_.clear(); } void HighsLpMods::clear() { - this->save_semi_variable_lower_bound_index.clear(); - this->save_semi_variable_lower_bound_value.clear(); - this->save_semi_variable_upper_bound_index.clear(); - this->save_semi_variable_upper_bound_value.clear(); + this->save_non_semi_variable_index.clear(); + this->save_inconsistent_semi_variable_index.clear(); + this->save_inconsistent_semi_variable_lower_bound_value.clear(); + this->save_inconsistent_semi_variable_upper_bound_value.clear(); + this->save_inconsistent_semi_variable_type.clear(); + this->save_relaxed_semi_variable_lower_bound_index.clear(); + this->save_relaxed_semi_variable_lower_bound_value.clear(); + this->save_tightened_semi_variable_upper_bound_index.clear(); + this->save_tightened_semi_variable_upper_bound_value.clear(); } bool HighsLpMods::isClear() { - if (this->save_semi_variable_lower_bound_index.size()) return false; - if (this->save_semi_variable_lower_bound_value.size()) return false; - if (this->save_semi_variable_upper_bound_index.size()) return false; - if (this->save_semi_variable_upper_bound_value.size()) return false; + if (this->save_non_semi_variable_index.size()) return false; + if (this->save_inconsistent_semi_variable_index.size()) return false; + if (this->save_inconsistent_semi_variable_lower_bound_value.size()) + return false; + if (this->save_inconsistent_semi_variable_upper_bound_value.size()) + return false; + if (this->save_inconsistent_semi_variable_type.size()) return false; + if (this->save_relaxed_semi_variable_lower_bound_value.size()) return false; + if (this->save_relaxed_semi_variable_lower_bound_value.size()) return false; + if (this->save_tightened_semi_variable_upper_bound_index.size()) return false; + if (this->save_tightened_semi_variable_upper_bound_value.size()) return false; return true; } + +void HighsNameHash::form(const std::vector& name) { + HighsInt num_name = name.size(); + this->clear(); + for (HighsInt index = 0; index < num_name; index++) { + const bool duplicate = !this->name2index.emplace(name[index], index).second; + if (duplicate) { + // Find the original and mark it as duplicate + auto search = this->name2index.find(name[index]); + assert(search != this->name2index.end()); + assert(int(search->second) < int(this->name2index.size())); + this->name2index.erase(search); + this->name2index.insert({name[index], kHashIsDuplicate}); + } + } +} + +bool HighsNameHash::hasDuplicate(const std::vector& name) { + HighsInt num_name = name.size(); + this->clear(); + bool has_duplicate = false; + for (HighsInt index = 0; index < num_name; index++) { + has_duplicate = !this->name2index.emplace(name[index], index).second; + if (has_duplicate) break; + } + this->clear(); + return has_duplicate; +} + +void HighsNameHash::clear() { this->name2index.clear(); } diff --git a/src/lp_data/HighsLp.h b/src/lp_data/HighsLp.h index 7129c80c81..29dcc93157 100644 --- a/src/lp_data/HighsLp.h +++ b/src/lp_data/HighsLp.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsLp.h * @brief @@ -17,7 +15,6 @@ #define LP_DATA_HIGHS_LP_H_ #include -#include #include "lp_data/HStruct.h" #include "util/HighsSparseMatrix.h" @@ -43,22 +40,29 @@ class HighsLp { std::string model_name_; std::string objective_name_; + HighsInt new_col_name_ix_ = 0; + HighsInt new_row_name_ix_ = 0; std::vector col_names_; std::vector row_names_; std::vector integrality_; + HighsNameHash col_hash_; + HighsNameHash row_hash_; + HighsScale scale_; bool is_scaled_; bool is_moved_; HighsInt cost_row_location_; HighsLpMods mods_; - bool operator==(const HighsLp& lp); + bool operator==(const HighsLp& lp) const; bool equalButForNames(const HighsLp& lp) const; + bool equalNames(const HighsLp& lp) const; bool isMip() const; bool hasSemiVariables() const; double objectiveValue(const std::vector& solution) const; + HighsCDouble objectiveCDoubleValue(const std::vector& solution) const; void setMatrixDimensions(); void setFormat(const MatrixFormat format); void ensureColwise() { this->a_matrix_.ensureColwise(); }; @@ -70,6 +74,8 @@ class HighsLp { void unapplyScale(); void moveBackLpAndUnapplyScaling(HighsLp& lp); void exactResize(); + void addColNames(const std::string name, const HighsInt num_new_col = 1); + void addRowNames(const std::string name, const HighsInt num_new_row = 1); void unapplyMods(); void clear(); }; diff --git a/src/lp_data/HighsLpSolverObject.h b/src/lp_data/HighsLpSolverObject.h index 8900e56b2b..9b76bde11f 100644 --- a/src/lp_data/HighsLpSolverObject.h +++ b/src/lp_data/HighsLpSolverObject.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HighsLpSolverObject.h * @brief Collection of class instances required to solve an LP diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index d8662fccd0..ebe87ab32a 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsUtils.cpp * @brief Class-independent utilities for HiGHS @@ -66,7 +64,8 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) { if (return_status == HighsStatus::kError) return return_status; // Assess the LP column bounds call_status = assessBounds(options, "Col", 0, index_collection, lp.col_lower_, - lp.col_upper_, options.infinite_bound); + lp.col_upper_, options.infinite_bound, + lp.integrality_.data()); return_status = interpretCallStatus(options.log_options, call_status, return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; @@ -350,7 +349,8 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, const HighsInt ml_ix_os, const HighsIndexCollection& index_collection, vector& lower, vector& upper, - const double infinite_bound) { + const double infinite_bound, + const HighsVarType* integrality) { HighsStatus return_status = HighsStatus::kOk; assert(ok(index_collection)); HighsInt from_k; @@ -421,6 +421,12 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, } // Check that the lower bound does not exceed the upper bound bool legalLowerUpperBound = lower[usr_ix] <= upper[usr_ix]; + if (integrality) { + // Legal for semi-variables to have inconsistent bounds + if (integrality[usr_ix] == HighsVarType::kSemiContinuous || + integrality[usr_ix] == HighsVarType::kSemiInteger) + legalLowerUpperBound = true; + } if (!legalLowerUpperBound) { // Leave inconsistent bounds to be used to deduce infeasibility highsLogUser(options.log_options, HighsLogType::kWarning, @@ -478,28 +484,47 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { HighsInt num_illegal_lower = 0; HighsInt num_illegal_upper = 0; HighsInt num_modified_upper = 0; + HighsInt num_inconsistent_semi = 0; HighsInt num_non_semi = 0; HighsInt num_non_continuous_variables = 0; const double kLowerBoundMu = 10.0; - std::vector& upper_bound_index = - lp.mods_.save_semi_variable_upper_bound_index; - std::vector& upper_bound_value = - lp.mods_.save_semi_variable_upper_bound_value; - + std::vector& inconsistent_semi_variable_index = + lp.mods_.save_inconsistent_semi_variable_index; + std::vector& inconsistent_semi_variable_lower_bound_value = + lp.mods_.save_inconsistent_semi_variable_lower_bound_value; + std::vector& inconsistent_semi_variable_upper_bound_value = + lp.mods_.save_inconsistent_semi_variable_upper_bound_value; + std::vector& inconsistent_semi_variable_type = + lp.mods_.save_inconsistent_semi_variable_type; + + std::vector& tightened_semi_variable_upper_bound_index = + lp.mods_.save_tightened_semi_variable_upper_bound_index; + std::vector& tightened_semi_variable_upper_bound_value = + lp.mods_.save_tightened_semi_variable_upper_bound_value; + + assert(int(lp.mods_.save_inconsistent_semi_variable_index.size()) == 0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || lp.integrality_[iCol] == HighsVarType::kSemiInteger) { - // Semi-variables with zero lower bound aren't semi + if (lp.col_lower_[iCol] > lp.col_upper_[iCol]) { + // Semi-variables with inconsistent bounds become continous + // and fixed at zero + num_inconsistent_semi++; + inconsistent_semi_variable_index.push_back(iCol); + inconsistent_semi_variable_lower_bound_value.push_back( + lp.col_lower_[iCol]); + inconsistent_semi_variable_upper_bound_value.push_back( + lp.col_upper_[iCol]); + inconsistent_semi_variable_type.push_back(lp.integrality_[iCol]); + continue; + } + // Semi-variables with zero lower bound are not semi if (lp.col_lower_[iCol] == 0) { num_non_semi++; - if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous) { - // Semi-continuous become continuous - lp.integrality_[iCol] = HighsVarType::kContinuous; - } else { - // Semi-integer become integer - lp.integrality_[iCol] = HighsVarType::kInteger; + lp.mods_.save_non_semi_variable_index.push_back(iCol); + // Semi-integer become integer so still have a non-continuous variable + if (lp.integrality_[iCol] == HighsVarType::kSemiInteger) num_non_continuous_variables++; - } continue; } if (lp.col_lower_[iCol] < 0) { @@ -512,9 +537,10 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { if (kLowerBoundMu * lp.col_lower_[iCol] > kMaxSemiVariableUpper) { num_illegal_upper++; } else { - // Record the upper bound change - upper_bound_index.push_back(iCol); - upper_bound_value.push_back(kMaxSemiVariableUpper); + // Record the upper bound change to be made later + tightened_semi_variable_upper_bound_index.push_back(iCol); + tightened_semi_variable_upper_bound_value.push_back( + kMaxSemiVariableUpper); num_modified_upper++; } } @@ -523,6 +549,15 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { num_non_continuous_variables++; } } + if (num_inconsistent_semi) { + highsLogUser( + options.log_options, HighsLogType::kWarning, + "%" HIGHSINT_FORMAT + " semi-continuous/integer variable(s) have inconsistent bounds " + "so are fixed at zero\n", + num_inconsistent_semi); + return_status = HighsStatus::kWarning; + } if (num_non_semi) { highsLogUser(options.log_options, HighsLogType::kWarning, "%" HIGHSINT_FORMAT @@ -547,21 +582,55 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { kMaxSemiVariableUpper, kLowerBoundMu); return_status = HighsStatus::kWarning; if (has_illegal_bounds) { - // Don't apply upper bound modifications if there are illegal bounds + // Don't apply upper bound tightenings if there are illegal bounds assert(num_illegal_lower || num_illegal_upper); - upper_bound_index.clear(); - upper_bound_value.clear(); + tightened_semi_variable_upper_bound_index.clear(); + tightened_semi_variable_upper_bound_value.clear(); } else { - // Apply the upper bound modifications, saving the over-written + // Apply the upper bound tightenings, saving the over-written // values for (HighsInt k = 0; k < num_modified_upper; k++) { - const double use_upper_bound = upper_bound_value[k]; - const HighsInt iCol = upper_bound_index[k]; - upper_bound_value[k] = lp.col_upper_[iCol]; + const double use_upper_bound = + tightened_semi_variable_upper_bound_value[k]; + const HighsInt iCol = tightened_semi_variable_upper_bound_index[k]; + tightened_semi_variable_upper_bound_value[k] = lp.col_upper_[iCol]; lp.col_upper_[iCol] = use_upper_bound; } } } + if (num_inconsistent_semi) { + if (has_illegal_bounds) { + // Don't apply bound changes if there are illegal bounds + inconsistent_semi_variable_index.clear(); + inconsistent_semi_variable_lower_bound_value.clear(); + inconsistent_semi_variable_upper_bound_value.clear(); + inconsistent_semi_variable_type.clear(); + } else { + for (HighsInt k = 0; k < num_inconsistent_semi; k++) { + const HighsInt iCol = inconsistent_semi_variable_index[k]; + lp.col_lower_[iCol] = 0; + lp.col_upper_[iCol] = 0; + lp.integrality_[iCol] = HighsVarType::kContinuous; + } + } + } + if (num_non_semi) { + if (has_illegal_bounds) { + // Don't apply type changes if there are illegal bounds + lp.mods_.save_non_semi_variable_index.clear(); + } else { + for (HighsInt k = 0; k < num_non_semi; k++) { + const HighsInt iCol = lp.mods_.save_non_semi_variable_index[k]; + if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous) { + // Semi-continuous become continuous + lp.integrality_[iCol] = HighsVarType::kContinuous; + } else { + // Semi-integer become integer + lp.integrality_[iCol] = HighsVarType::kInteger; + } + } + } + } if (num_illegal_lower) { highsLogUser( options.log_options, HighsLogType::kError, @@ -589,16 +658,16 @@ void relaxSemiVariables(HighsLp& lp) { if (!lp.integrality_.size()) return; assert((HighsInt)lp.integrality_.size() == lp.num_col_); HighsInt num_modified_lower = 0; - std::vector& lower_bound_index = - lp.mods_.save_semi_variable_lower_bound_index; - std::vector& lower_bound_value = - lp.mods_.save_semi_variable_lower_bound_value; - assert(lower_bound_index.size() == 0); + std::vector& relaxed_semi_variable_lower_index = + lp.mods_.save_relaxed_semi_variable_lower_bound_index; + std::vector& relaxed_semi_variable_lower_value = + lp.mods_.save_relaxed_semi_variable_lower_bound_value; + assert(relaxed_semi_variable_lower_index.size() == 0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || lp.integrality_[iCol] == HighsVarType::kSemiInteger) { - lower_bound_index.push_back(iCol); - lower_bound_value.push_back(lp.col_lower_[iCol]); + relaxed_semi_variable_lower_index.push_back(iCol); + relaxed_semi_variable_lower_value.push_back(lp.col_lower_[iCol]); lp.col_lower_[iCol] = 0; } } @@ -606,14 +675,17 @@ void relaxSemiVariables(HighsLp& lp) { bool activeModifiedUpperBounds(const HighsOptions& options, const HighsLp& lp, const std::vector col_value) { - const std::vector& upper_bound_index = - lp.mods_.save_semi_variable_upper_bound_index; - const HighsInt num_modified_upper = upper_bound_index.size(); + const std::vector& tightened_semi_variable_upper_bound_index = + lp.mods_.save_tightened_semi_variable_upper_bound_index; + const HighsInt num_modified_upper = + tightened_semi_variable_upper_bound_index.size(); HighsInt num_active_modified_upper = 0; double min_semi_variable_margin = kHighsInf; for (HighsInt k = 0; k < num_modified_upper; k++) { - const double value = col_value[upper_bound_index[k]]; - const double upper = lp.col_upper_[upper_bound_index[k]]; + const double value = + col_value[tightened_semi_variable_upper_bound_index[k]]; + const double upper = + lp.col_upper_[tightened_semi_variable_upper_bound_index[k]]; double semi_variable_margin = upper - value; if (value > upper - options.primal_feasibility_tolerance) { min_semi_variable_margin = 0; @@ -1299,11 +1371,11 @@ HighsStatus applyScalingToLpRow(HighsLp& lp, const HighsInt row, lp.a_matrix_.scaleRow(row, rowScale); if (rowScale > 0) { - lp.row_lower_[row] /= rowScale; - lp.row_upper_[row] /= rowScale; + lp.row_lower_[row] *= rowScale; + lp.row_upper_[row] *= rowScale; } else { - const double new_upper = lp.row_lower_[row] / rowScale; - lp.row_lower_[row] = lp.row_upper_[row] / rowScale; + const double new_upper = lp.row_lower_[row] * rowScale; + lp.row_lower_[row] = lp.row_upper_[row] * rowScale; lp.row_upper_[row] = new_upper; } return HighsStatus::kOk; @@ -1874,12 +1946,12 @@ void reportLpColMatrix(const HighsLogOptions& log_options, const HighsLp& lp) { // With postitive number of rows, can assume that there are index and value // vectors to pass reportMatrix(log_options, "Column", lp.num_col_, - lp.a_matrix_.start_[lp.num_col_], &lp.a_matrix_.start_[0], - &lp.a_matrix_.index_[0], &lp.a_matrix_.value_[0]); + lp.a_matrix_.start_[lp.num_col_], lp.a_matrix_.start_.data(), + lp.a_matrix_.index_.data(), lp.a_matrix_.value_.data()); } else { // With no rows, can's assume that there are index and value vectors to pass reportMatrix(log_options, "Column", lp.num_col_, - lp.a_matrix_.start_[lp.num_col_], &lp.a_matrix_.start_[0], + lp.a_matrix_.start_[lp.num_col_], lp.a_matrix_.start_.data(), NULL, NULL); } } @@ -1906,6 +1978,7 @@ void reportMatrix(const HighsLogOptions& log_options, const std::string message, } void analyseLp(const HighsLogOptions& log_options, const HighsLp& lp) { + /* vector min_colBound; vector min_rowBound; vector colRange; @@ -1922,7 +1995,7 @@ void analyseLp(const HighsLogOptions& log_options, const HighsLp& lp) { colRange[col] = lp.col_upper_[col] - lp.col_lower_[col]; for (HighsInt row = 0; row < lp.num_row_; row++) rowRange[row] = lp.row_upper_[row] - lp.row_lower_[row]; - + */ std::string message; if (lp.is_scaled_) { message = "Scaled"; @@ -1944,18 +2017,20 @@ void analyseLp(const HighsLogOptions& log_options, const HighsLp& lp) { lp.col_lower_, true, lp.model_name_); analyseVectorValues(&log_options, "Column upper bounds", lp.num_col_, lp.col_upper_, true, lp.model_name_); - analyseVectorValues(&log_options, "Column min abs bound", lp.num_col_, - min_colBound, true, lp.model_name_); - analyseVectorValues(&log_options, "Column range", lp.num_col_, colRange, true, - lp.model_name_); + // analyseVectorValues(&log_options, "Column min abs bound", lp.num_col_, + // min_colBound, true, lp.model_name_); + // analyseVectorValues(&log_options, "Column range", lp.num_col_, colRange, + // true, + // lp.model_name_); analyseVectorValues(&log_options, "Row lower bounds", lp.num_row_, lp.row_lower_, true, lp.model_name_); analyseVectorValues(&log_options, "Row upper bounds", lp.num_row_, lp.row_upper_, true, lp.model_name_); - analyseVectorValues(&log_options, "Row min abs bound", lp.num_row_, - min_rowBound, true, lp.model_name_); - analyseVectorValues(&log_options, "Row range", lp.num_row_, rowRange, true, - lp.model_name_); + // analyseVectorValues(&log_options, "Row min abs bound", lp.num_row_, + // min_rowBound, true, lp.model_name_); + // analyseVectorValues(&log_options, "Row range", lp.num_row_, rowRange, + // true, + // lp.model_name_); analyseVectorValues(&log_options, "Matrix sparsity", lp.a_matrix_.start_[lp.num_col_], lp.a_matrix_.value_, true, lp.model_name_); @@ -2080,17 +2155,27 @@ HighsStatus readSolutionFile(const std::string filename, read_solution, read_basis, in_file); } assert(keyword == "Rows"); - if (num_row != lp_num_row) { - highsLogUser(log_options, HighsLogType::kError, - "readSolutionFile: Solution file is for %" HIGHSINT_FORMAT - " rows, not %" HIGHSINT_FORMAT "\n", - num_row, lp_num_row); - return readSolutionFileErrorReturn(in_file); - } + // OK to read from a file with different number of rows, since the + // primal solution is all that's important. For example, see #1284, + // where the user is solving a sequence of MIPs with the same number + // of variables, but incresing numbers of constraints, and wants to + // used the solution from one MIP as the starting solution for the + // next. + const bool num_row_ok = num_row == lp_num_row; for (HighsInt iRow = 0; iRow < num_row; iRow++) { if (!readSolutionFileIdDoubleLineOk(value, in_file)) return readSolutionFileErrorReturn(in_file); - read_solution.row_value[iRow] = value; + if (num_row_ok) read_solution.row_value[iRow] = value; + } + if (!num_row_ok) { + highsLogUser(log_options, HighsLogType::kWarning, + "readSolutionFile: Solution file is for %" HIGHSINT_FORMAT + " rows, not %" HIGHSINT_FORMAT ": row values ignored\n", + num_row, lp_num_row); + // Calculate the row values + if (calculateRowValues(lp, read_solution.col_value, + read_solution.row_value) != HighsStatus::kOk) + return readSolutionFileErrorReturn(in_file); } // OK to have no EOL if (!readSolutionFileIgnoreLineOk(in_file)) @@ -2462,11 +2547,11 @@ HighsStatus readBasisStream(const HighsLogOptions& log_options, } HighsStatus calculateColDuals(const HighsLp& lp, HighsSolution& solution) { - // assert(solution.row_dual.size() > 0); - if (int(solution.row_dual.size()) < lp.num_row_) return HighsStatus::kError; + const bool correct_size = int(solution.row_dual.size()) == lp.num_row_; const bool is_colwise = lp.a_matrix_.isColwise(); - assert(is_colwise); - if (!is_colwise) return HighsStatus::kError; + const bool data_error = !correct_size || !is_colwise; + assert(!data_error); + if (data_error) return HighsStatus::kError; solution.col_dual.assign(lp.num_col_, 0); @@ -2488,11 +2573,11 @@ HighsStatus calculateColDuals(const HighsLp& lp, HighsSolution& solution) { HighsStatus calculateRowValues(const HighsLp& lp, const std::vector& col_value, std::vector& row_value) { - // assert(col_value.size() > 0); - if (int(col_value.size()) < lp.num_col_) return HighsStatus::kError; + const bool correct_size = int(col_value.size()) == lp.num_col_; const bool is_colwise = lp.a_matrix_.isColwise(); - assert(is_colwise); - if (!is_colwise) return HighsStatus::kError; + const bool data_error = !correct_size || !is_colwise; + assert(!data_error); + if (data_error) return HighsStatus::kError; row_value.clear(); row_value.assign(lp.num_row_, 0); @@ -2515,12 +2600,13 @@ HighsStatus calculateRowValues(const HighsLp& lp, HighsSolution& solution) { return calculateRowValues(lp, solution.col_value, solution.row_value); } -HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution) { - // assert(solution.col_value.size() > 0); - if (int(solution.col_value.size()) != lp.num_col_) return HighsStatus::kError; +HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution, + const HighsInt report_row) { + const bool correct_size = int(solution.col_value.size()) == lp.num_col_; const bool is_colwise = lp.a_matrix_.isColwise(); - assert(is_colwise); - if (!is_colwise) return HighsStatus::kError; + const bool data_error = !correct_size || !is_colwise; + assert(!data_error); + if (data_error) return HighsStatus::kError; std::vector row_value; row_value.assign(lp.num_row_, HighsCDouble{0.0}); @@ -2533,8 +2619,14 @@ HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution) { const HighsInt row = lp.a_matrix_.index_[i]; assert(row >= 0); assert(row < lp.num_row_); - row_value[row] += solution.col_value[col] * lp.a_matrix_.value_[i]; + if (row == report_row) { + printf( + "calculateRowValuesQuad: Row %d becomes %g due to contribution of " + ".col_value[%d] = %g\n", + int(row), double(row_value[row]), int(col), + solution.col_value[col]); + } } } @@ -2548,8 +2640,16 @@ HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution) { bool isBoundInfeasible(const HighsLogOptions& log_options, const HighsLp& lp) { HighsInt num_bound_infeasible = 0; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + const bool has_integrality = lp.integrality_.size() > 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (has_integrality) { + // Semi-variables can have inconsistent bounds + if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || + lp.integrality_[iCol] == HighsVarType::kSemiInteger) + continue; + } if (lp.col_upper_[iCol] < lp.col_lower_[iCol]) num_bound_infeasible++; + } for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) if (lp.row_upper_[iRow] < lp.row_lower_[iRow]) num_bound_infeasible++; if (num_bound_infeasible > 0) diff --git a/src/lp_data/HighsLpUtils.h b/src/lp_data/HighsLpUtils.h index 46d87b1217..fa41cdb24d 100644 --- a/src/lp_data/HighsLpUtils.h +++ b/src/lp_data/HighsLpUtils.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsLpUtils.h * @brief Class-independent utilities for HiGHS @@ -53,7 +51,8 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, const HighsInt ml_ix_os, const HighsIndexCollection& index_collection, vector& lower, vector& upper, - const double infinite_bound); + const double infinite_bound, + const HighsVarType* integrality = nullptr); HighsStatus cleanBounds(const HighsOptions& options, HighsLp& lp); @@ -226,7 +225,8 @@ HighsStatus calculateRowValues(const HighsLp& lp, const std::vector& col_value, std::vector& row_value); HighsStatus calculateRowValues(const HighsLp& lp, HighsSolution& solution); -HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution); +HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution, + const HighsInt report_row = -1); HighsStatus calculateColDuals(const HighsLp& lp, HighsSolution& solution); bool isBoundInfeasible(const HighsLogOptions& log_options, const HighsLp& lp); diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 0ef18a038e..7b2b1dbb34 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsUtils.cpp * @brief Class-independent utilities for HiGHS @@ -18,6 +16,7 @@ #include #include +#include #include #include @@ -190,9 +189,59 @@ void writeModelBoundSolution( } } -void writeModelSolution(FILE* file, const HighsLp& lp, +void writeModelObjective(FILE* file, const HighsModel& model, + const std::vector& primal_solution) { + HighsCDouble objective_value = + model.lp_.objectiveCDoubleValue(primal_solution); + objective_value += model.hessian_.objectiveCDoubleValue(primal_solution); + writeObjectiveValue(file, (double)objective_value); +} + +void writeLpObjective(FILE* file, const HighsLp& lp, + const std::vector& primal_solution) { + HighsCDouble objective_value = lp.objectiveCDoubleValue(primal_solution); + writeObjectiveValue(file, (double)objective_value); +} + +void writeObjectiveValue(FILE* file, const double objective_value) { + std::array objStr = highsDoubleToString( + objective_value, kHighsSolutionValueToStringTolerance); + fprintf(file, "Objective %s\n", objStr.data()); +} + +void writePrimalSolution(FILE* file, const HighsLp& lp, + const std::vector& primal_solution, + const bool sparse) { + std::stringstream ss; + HighsInt num_nonzero_primal_value = 0; + const bool have_col_names = lp.col_names_.size() > 0; + if (sparse) { + // Determine the number of nonzero primal solution values + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + if (primal_solution[iCol]) num_nonzero_primal_value++; + } + // Indicate the number of column values to be written out, depending + // on whether format is sparse: either lp.num_col_ if not sparse, or + // the negation of the number of nonzero values, if sparse + fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", + sparse ? -num_nonzero_primal_value : lp.num_col_); + for (HighsInt ix = 0; ix < lp.num_col_; ix++) { + if (sparse && !primal_solution[ix]) continue; + std::array valStr = highsDoubleToString( + primal_solution[ix], kHighsSolutionValueToStringTolerance); + // Create a column name + ss.str(std::string()); + ss << "C" << ix; + const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); + fprintf(file, "%-s %s", name.c_str(), valStr.data()); + if (sparse) fprintf(file, " %d", int(ix)); + fprintf(file, "\n"); + } +} +void writeModelSolution(FILE* file, const HighsModel& model, const HighsSolution& solution, const HighsInfo& info, const bool sparse) { + const HighsLp& lp = model.lp_; const bool have_col_names = lp.col_names_.size() > 0; const bool have_row_names = lp.row_names_.size() > 0; const bool have_primal = solution.value_valid; @@ -210,12 +259,6 @@ void writeModelSolution(FILE* file, const HighsLp& lp, assert((int)solution.row_dual.size() >= lp.num_row_); assert(info.dual_solution_status != kSolutionStatusNone); } - HighsInt num_nonzero_primal_value = 0; - if (sparse && have_primal) { - // Determine the number of nonzero primal solution values - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - if (solution.col_value[iCol]) num_nonzero_primal_value++; - } fprintf(file, "\n# Primal solution values\n"); if (!have_primal || info.primal_solution_status == kSolutionStatusNone) { fprintf(file, "None\n"); @@ -226,30 +269,8 @@ void writeModelSolution(FILE* file, const HighsLp& lp, assert(info.primal_solution_status == kSolutionStatusInfeasible); fprintf(file, "Infeasible\n"); } - HighsCDouble objective_function_value = lp.offset_; - for (HighsInt i = 0; i < lp.num_col_; ++i) - objective_function_value += lp.col_cost_[i] * solution.col_value[i]; - std::array objStr = highsDoubleToString( - (double)objective_function_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "Objective %s\n", objStr.data()); - // Indicate the number of column values to be written out, - // depending on whether format is sparse: either lp.num_col_ if - // not sparse, or the negation of the number of nonzero values, if - // sparse - fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", - sparse ? -num_nonzero_primal_value : lp.num_col_); - for (HighsInt ix = 0; ix < lp.num_col_; ix++) { - if (sparse && !solution.col_value[ix]) continue; - std::array valStr = highsDoubleToString( - solution.col_value[ix], kHighsSolutionValueToStringTolerance); - // Create a column name - ss.str(std::string()); - ss << "C" << ix; - const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); - fprintf(file, "%-s %s", name.c_str(), valStr.data()); - if (sparse) fprintf(file, " %d", int(ix)); - fprintf(file, "\n"); - } + writeModelObjective(file, model, solution.col_value); + writePrimalSolution(file, model.lp_, solution.col_value, sparse); if (sparse) return; fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { @@ -377,12 +398,10 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, if (style == kSolutionStyleOldRaw) { writeOldRawSolution(file, lp, basis, solution); } else if (style == kSolutionStylePretty) { - const HighsVarType* integrality_ptr = - lp.integrality_.size() > 0 ? &lp.integrality_[0] : NULL; - writeModelBoundSolution(file, true, lp.num_col_, lp.col_lower_, - lp.col_upper_, lp.col_names_, have_primal, - solution.col_value, have_dual, solution.col_dual, - have_basis, basis.col_status, integrality_ptr); + writeModelBoundSolution( + file, true, lp.num_col_, lp.col_lower_, lp.col_upper_, lp.col_names_, + have_primal, solution.col_value, have_dual, solution.col_dual, + have_basis, basis.col_status, lp.integrality_.data()); writeModelBoundSolution(file, false, lp.num_row_, lp.row_lower_, lp.row_upper_, lp.row_names_, have_primal, solution.row_value, have_dual, solution.row_dual, @@ -404,7 +423,7 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, assert(style == kSolutionStyleRaw || sparse); fprintf(file, "Model status\n"); fprintf(file, "%s\n", utilModelStatusToString(model_status).c_str()); - writeModelSolution(file, lp, solution, info, sparse); + writeModelSolution(file, model, solution, info, sparse); } } @@ -442,7 +461,6 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const bool have_value = solution.value_valid; const bool have_dual = solution.dual_valid; const bool have_basis = basis.valid; - if (!have_value) return; const double kGlpsolHighQuality = 1e-9; const double kGlpsolMediumQuality = 1e-6; const double kGlpsolLowQuality = 1e-3; @@ -457,6 +475,9 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (lp.col_cost_[iCol]) num_nz++; const bool empty_cost_row = num_nz == lp.a_matrix_.numNz(); const bool has_objective = !empty_cost_row || model.hessian_.dim_; + // Writes the solution using the GLPK raw style (defined in + // api/wrsol.c) or pretty style (defined in api/prsol.c) + // // When writing out the row information (and hence the number of // rows and nonzeros), the case of the cost row is tricky // (particularly if it's empty) if HiGHS is to be able to reproduce @@ -581,38 +602,52 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, (int)num_binary); fprintf(file, "\n"); fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), "Non-zeros:", (int)num_nz); - std::string model_status_text = ""; - std::string model_status_char = ""; // Just for raw MIP solution + // Use model_status to define the GLPK model_status_text and + // solution_status_char, where the former is used to specify the + // model status. GLPK uses a single character to specify the + // solution status, and for LPs this is deduced from the primal and + // dual solution status. However, for MIPs, it is defined according + // to the model status, so only set solution_status_char for MIPs + std::string model_status_text = "???"; + std::string solution_status_char = "?"; switch (model_status) { case HighsModelStatus::kOptimal: - if (is_mip) model_status_text = "INTEGER "; - model_status_text += "OPTIMAL"; - model_status_char = "o"; + if (is_mip) { + model_status_text = "INTEGER OPTIMAL"; + solution_status_char = "o"; + } else { + model_status_text = "OPTIMAL"; + } break; case HighsModelStatus::kInfeasible: if (is_mip) { - model_status_text = "INTEGER INFEASIBLE"; + model_status_text = "INTEGER EMPTY"; + solution_status_char = "n"; } else { model_status_text = "INFEASIBLE (FINAL)"; } - model_status_char = "n"; - break; - case HighsModelStatus::kUnboundedOrInfeasible: - model_status_text += "INFEASIBLE (INTERMEDIATE)"; - model_status_char = "?"; // Glpk has no equivalent break; case HighsModelStatus::kUnbounded: - if (is_mip) model_status_text = "INTEGER "; - model_status_text += "UNBOUNDED"; - model_status_char = "u"; // Glpk has no equivalent + // No apparent case in wrmip.c + model_status_text = "UNBOUNDED"; + if (is_mip) solution_status_char = "u"; break; default: - model_status_text = "????"; - model_status_char = "?"; + if (info.primal_solution_status == kSolutionStatusFeasible) { + if (is_mip) { + model_status_text = "INTEGER NON-OPTIMAL"; + solution_status_char = "f"; + } else { + model_status_text = "FEASIBLE"; + } + } else { + model_status_text = "UNDEFINED"; + if (is_mip) solution_status_char = "u"; + } break; } - assert(model_status_text != ""); - assert(model_status_char != ""); + assert(model_status_text != "???"); + if (is_mip) assert(solution_status_char != "?"); fprintf(file, "%s%-12s%s\n", line_prefix.c_str(), "Status:", model_status_text.c_str()); // If info is not valid, then cannot write more @@ -642,7 +677,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, fprintf(file, "s %s %d %d ", is_mip ? "mip" : "bas", (int)glpsol_num_row, (int)num_col); if (is_mip) { - fprintf(file, "%s", model_status_char.c_str()); + fprintf(file, "%s", solution_status_char.c_str()); } else { if (info.primal_solution_status == kSolutionStatusNone) { fprintf(file, "u"); @@ -669,6 +704,9 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, highsDoubleToString(double_value, kHighsSolutionValueToStringTolerance); fprintf(file, " %s\n", double_string.data()); } + // GLPK puts out i 1 b 0 0 etc if there's no primal point, but + // that's meaningless at best, so HiGHS returns in that case + if (!have_value) return; if (!raw) { fprintf(file, " No. Row name %s Activity Lower bound " @@ -697,7 +735,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, fprintf(file, "i %d ", (int)row_id); if (is_mip) { // Complete the line if for a MIP - double double_value = solution.row_value[iRow]; + double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); fprintf(file, "%s\n", double_string.data()); @@ -715,7 +753,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } const double lower = lp.row_lower_[iRow]; const double upper = lp.row_upper_[iRow]; - const double value = solution.row_value[iRow]; + const double value = have_value ? solution.row_value[iRow] : 0; const double dual = have_dual ? solution.row_dual[iRow] : 0; std::string status_text = " "; std::string status_char = ""; @@ -746,7 +784,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } if (raw) { fprintf(file, "%s ", status_char.c_str()); - double double_value = solution.row_value[iRow]; + double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); fprintf(file, "%s ", double_string.data()); @@ -812,7 +850,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (raw) { fprintf(file, "%s%d ", line_prefix.c_str(), (int)(iCol + 1)); if (is_mip) { - double double_value = solution.col_value[iCol]; + double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); fprintf(file, "%s\n", double_string.data()); @@ -830,7 +868,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } const double lower = lp.col_lower_[iCol]; const double upper = lp.col_upper_[iCol]; - const double value = solution.col_value[iCol]; + const double value = have_value ? solution.col_value[iCol] : 0; const double dual = have_dual ? solution.col_dual[iCol] : 0; std::string status_text = " "; std::string status_char = ""; @@ -864,7 +902,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } if (raw) { fprintf(file, "%s ", status_char.c_str()); - double double_value = solution.col_value[iCol]; + double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); fprintf(file, "%s ", double_string.data()); @@ -1358,3 +1396,32 @@ std::string findModelObjectiveName(const HighsLp* lp, assert(objective_name != ""); return objective_name; } + +/* +void print_map(std::string comment, const std::map& m) +{ + std::cout << comment; + + for (const auto& n : m) + std::cout << n.first << " = " << n.second << "; "; + std::cout << '\n'; +} +*/ + +/* +bool repeatedNames(const std::vector name) { + const HighsInt num_name = name.size(); + // With no names, cannot have any repeated + if (num_name == 0) return false; + std::map name_map; + for (HighsInt ix = 0; ix < num_name; ix++) { + auto search = name_map.find(name[ix]); + if (search != name_map.end()) return true; + // printf("Search for %s yields %d\n", name[ix].c_str(), + // int(search->second)); + name_map.insert({name[ix], ix}); + // print_map("Map\n", name_map); + } + return false; +} +*/ diff --git a/src/lp_data/HighsModelUtils.h b/src/lp_data/HighsModelUtils.h index 0a56c4a1ed..bb1c71f85d 100644 --- a/src/lp_data/HighsModelUtils.h +++ b/src/lp_data/HighsModelUtils.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsModelUtils.h * @brief Class-independent utilities for HiGHS @@ -16,10 +14,10 @@ #ifndef LP_DATA_HIGHSMODELUTILS_H_ #define LP_DATA_HIGHSMODELUTILS_H_ -// #include "Highs.h" -// #include "lp_data/HighsStatus.h" #include "lp_data/HighsInfo.h" #include "model/HighsModel.h" +// #include "Highs.h" +// #include "lp_data/HighsStatus.h" // #include "lp_data/HStruct.h" // #include "lp_data/HighsInfo.h" // #include "lp_data/HighsLp.h" @@ -40,9 +38,22 @@ void writeModelBoundSolution( const std::vector& dual, const bool have_basis, const std::vector& status, const HighsVarType* integrality = NULL); -void writeModelSolution(FILE* file, const HighsLp& lp, + +void writeModelObjective(FILE* file, const HighsModel& model, + const std::vector& primal_solution); + +void writeLpObjective(FILE* file, const HighsLp& lp, + const std::vector& primal_solution); + +void writeObjectiveValue(FILE* file, const double objective_value); + +void writePrimalSolution(FILE* file, const HighsLp& lp, + const std::vector& primal_solution, + const bool sparse = false); + +void writeModelSolution(FILE* file, const HighsModel& model, const HighsSolution& solution, const HighsInfo& info, - const bool sparse); + const bool sparse = false); HighsInt maxNameLength(const HighsInt num_name, const std::vector& names); @@ -93,4 +104,6 @@ std::string typeToString(const HighsVarType type); std::string findModelObjectiveName(const HighsLp* lp, const HighsHessian* hessian = nullptr); +// bool repeatedNames(const std::vector name); + #endif diff --git a/src/lp_data/HighsOptions.cpp b/src/lp_data/HighsOptions.cpp index 903a7c710c..163b1c1d53 100644 --- a/src/lp_data/HighsOptions.cpp +++ b/src/lp_data/HighsOptions.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsOptions.cpp * @brief @@ -17,6 +15,9 @@ #include #include +#include + +#include "util/stringutil.h" // void setLogOptions(); @@ -27,17 +28,18 @@ void highsOpenLogFile(HighsLogOptions& log_options, OptionStatus status = getOptionIndex(log_options, "log_file", option_records, index); assert(status == OptionStatus::kOk); - if (log_options.log_file_stream != NULL) { + if (log_options.log_stream != NULL) { // Current log file stream is not null, so flush and close it - fflush(log_options.log_file_stream); - fclose(log_options.log_file_stream); + fflush(log_options.log_stream); + fclose(log_options.log_stream); } if (log_file.compare("")) { - // New log file name is not empty, so open it - log_options.log_file_stream = fopen(log_file.c_str(), "w"); + // New log file name is not empty, so open it, appending if + // possible + log_options.log_stream = fopen(log_file.c_str(), "a"); } else { // New log file name is empty, so set the stream to null - log_options.log_file_stream = NULL; + log_options.log_stream = NULL; } OptionRecordString& option = *(OptionRecordString*)option_records[index]; option.assignvalue(log_file); @@ -56,14 +58,25 @@ static std::string optionEntryTypeToString(const HighsOptionType type) { } bool commandLineOffChooseOnOk(const HighsLogOptions& report_log_options, - const string& value) { + const string& name, const string& value) { if (value == kHighsOffString || value == kHighsChooseString || value == kHighsOnString) return true; + highsLogUser( + report_log_options, HighsLogType::kWarning, + "Value \"%s\" for %s option is not one of \"%s\", \"%s\" or \"%s\"\n", + value.c_str(), name.c_str(), kHighsOffString.c_str(), + kHighsChooseString.c_str(), kHighsOnString.c_str()); + return false; +} + +bool commandLineOffOnOk(const HighsLogOptions& report_log_options, + const string& name, const string& value) { + if (value == kHighsOffString || value == kHighsOnString) return true; highsLogUser(report_log_options, HighsLogType::kWarning, - "Value \"%s\" is not one of \"%s\", \"%s\" or \"%s\"\n", - value.c_str(), kHighsOffString.c_str(), - kHighsChooseString.c_str(), kHighsOnString.c_str()); + "Value \"%s\" for %s option is not one of \"%s\" or \"%s\"\n", + value.c_str(), name.c_str(), kHighsOffString.c_str(), + kHighsOnString.c_str()); return false; } @@ -72,10 +85,11 @@ bool commandLineSolverOk(const HighsLogOptions& report_log_options, if (value == kSimplexString || value == kHighsChooseString || value == kIpmString) return true; - highsLogUser(report_log_options, HighsLogType::kWarning, - "Value \"%s\" is not one of \"%s\", \"%s\" or \"%s\"\n", - value.c_str(), kSimplexString.c_str(), - kHighsChooseString.c_str(), kIpmString.c_str()); + highsLogUser( + report_log_options, HighsLogType::kWarning, + "Value \"%s\" for solver option is not one of \"%s\", \"%s\" or \"%s\"\n", + value.c_str(), kSimplexString.c_str(), kHighsChooseString.c_str(), + kIpmString.c_str()); return false; } @@ -336,13 +350,20 @@ OptionStatus checkOptionValue(const HighsLogOptions& report_log_options, // Setting a string option. For some options only particular values // are permitted, so check them if (option.name == kPresolveString) { - if (!commandLineOffChooseOnOk(report_log_options, value) && value != "mip") + if (!commandLineOffChooseOnOk(report_log_options, option.name, value) && + value != "mip") return OptionStatus::kIllegalValue; } else if (option.name == kSolverString) { if (!commandLineSolverOk(report_log_options, value)) return OptionStatus::kIllegalValue; } else if (option.name == kParallelString) { - if (!commandLineOffChooseOnOk(report_log_options, value)) + if (!commandLineOffChooseOnOk(report_log_options, option.name, value)) + return OptionStatus::kIllegalValue; + } else if (option.name == kRunCrossoverString) { + if (!commandLineOffChooseOnOk(report_log_options, option.name, value)) + return OptionStatus::kIllegalValue; + } else if (option.name == kRangingString) { + if (!commandLineOffOnOk(report_log_options, option.name, value)) return OptionStatus::kIllegalValue; } return OptionStatus::kOk; @@ -426,7 +447,10 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, const std::string& name, HighsLogOptions& log_options, std::vector& option_records, - const std::string value) { + const std::string value_passed) { + // Trim any leading and trailing spaces + std::string value_trim = value_passed; + trim(value_trim, " "); HighsInt index; OptionStatus status = getOptionIndex(report_log_options, name, option_records, index); @@ -434,22 +458,28 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, HighsOptionType type = option_records[index]->type; if (type == HighsOptionType::kBool) { bool value_bool; - bool return_status = boolFromString(value, value_bool); + bool return_status = boolFromString(value_trim, value_bool); if (!return_status) { highsLogUser( report_log_options, HighsLogType::kError, "setLocalOptionValue: Value \"%s\" cannot be interpreted as a bool\n", - value.c_str()); + value_trim.c_str()); return OptionStatus::kIllegalValue; } return setLocalOptionValue(((OptionRecordBool*)option_records[index])[0], value_bool); } else if (type == HighsOptionType::kInt) { + // Check that the string only contains legitimate characters + HighsInt illegal = value_trim.find_first_not_of("+-0123456789eE"); + if (int(illegal) >= 0) return OptionStatus::kIllegalValue; + // Check that the string contains a numerical character + HighsInt found_digit = value_trim.find_first_of("0123456789"); HighsInt value_int; int scanned_num_char; - const char* value_char = value.c_str(); + const char* value_char = value_trim.c_str(); sscanf(value_char, "%" HIGHSINT_FORMAT "%n", &value_int, &scanned_num_char); const int value_num_char = strlen(value_char); + // Check that all characters in value_char are scanned by the format const bool converted_ok = scanned_num_char == value_num_char; if (!converted_ok) { highsLogDev(report_log_options, HighsLogType::kError, @@ -458,15 +488,21 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, " " "by scanning %" HIGHSINT_FORMAT " of %" HIGHSINT_FORMAT " characters\n", - value.c_str(), value_int, scanned_num_char, value_num_char); + value_trim.c_str(), value_int, scanned_num_char, + value_num_char); return OptionStatus::kIllegalValue; } return setLocalOptionValue(report_log_options, ((OptionRecordInt*)option_records[index])[0], value_int); } else if (type == HighsOptionType::kDouble) { - HighsInt value_int = atoi(value.c_str()); - double value_double = atof(value.c_str()); + // Check that the string only contains legitimate characters + HighsInt illegal = value_trim.find_first_not_of("+-.0123456789eE"); + if (int(illegal) >= 0) return OptionStatus::kIllegalValue; + // Check that the string contains a numerical character + HighsInt found_digit = value_trim.find_first_of("0123456789"); + HighsInt value_int = atoi(value_trim.c_str()); + double value_double = atof(value_trim.c_str()); double value_int_double = value_int; if (value_double == value_int_double) { highsLogDev(report_log_options, HighsLogType::kInfo, @@ -474,19 +510,20 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, "%" HIGHSINT_FORMAT " " "so is %g as double, and %g via atof\n", - value.c_str(), value_int, value_int_double, value_double); + value_trim.c_str(), value_int, value_int_double, + value_double); } return setLocalOptionValue(report_log_options, ((OptionRecordDouble*)option_records[index])[0], - atof(value.c_str())); + value_double); } else { // Setting a string option value if (!name.compare(kLogFileString)) { OptionRecordString& option = *(OptionRecordString*)option_records[index]; std::string original_log_file = *(option.value); - if (value.compare(original_log_file)) { + if (value_passed.compare(original_log_file)) { // Changing the name of the log file - highsOpenLogFile(log_options, option_records, value); + highsOpenLogFile(log_options, option_records, value_passed); } } if (!name.compare(kModelFileString)) { @@ -498,7 +535,7 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, } else { return setLocalOptionValue( report_log_options, ((OptionRecordString*)option_records[index])[0], - value); + value_passed); } } } @@ -557,8 +594,8 @@ OptionStatus passLocalOptions(const HighsLogOptions& report_log_options, // std::string empty_file = ""; // std::string from_log_file = from_options.log_file; // std::string original_to_log_file = to_options.log_file; - // FILE* original_to_log_file_stream = - // to_options.log_options.log_file_stream; + // FILE* original_to_log_stream = + // to_options.log_options.log_stream; HighsInt num_options = to_options.records.size(); // Check all the option values before setting any of them - in case // to_options are the main Highs options. Checks are only needed for @@ -622,7 +659,7 @@ OptionStatus passLocalOptions(const HighsLogOptions& report_log_options, /* if (from_log_file.compare(original_to_log_file)) { // The log file name has changed - if (from_options.log_options.log_file_stream && + if (from_options.log_options.log_stream && !original_to_log_file.compare(empty_file)) { // The stream corresponding to from_log_file is non-null and the // original log file name was empty, so to_options inherits the @@ -632,9 +669,9 @@ OptionStatus passLocalOptions(const HighsLogOptions& report_log_options, // This ensures that the stream to Highs.log opened in // RunHighs.cpp is retained unless the log file name is changed. assert(from_log_file.compare(empty_file)); - assert(!original_to_log_file_stream); - to_options.log_options.log_file_stream = - from_options.log_options.log_file_stream; + assert(!original_to_log_stream); + to_options.log_options.log_stream = + from_options.log_options.log_stream; } else { highsOpenLogFile(to_options, to_options.log_file); } @@ -643,94 +680,109 @@ OptionStatus passLocalOptions(const HighsLogOptions& report_log_options, return OptionStatus::kOk; } -OptionStatus getLocalOptionValue( - const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, bool& value) { +OptionStatus getLocalOptionValues( + const HighsLogOptions& report_log_options, const std::string& option, + const std::vector& option_records, bool* current_value, + bool* default_value) { HighsInt index; OptionStatus status = - getOptionIndex(report_log_options, name, option_records, index); + getOptionIndex(report_log_options, option, option_records, index); if (status != OptionStatus::kOk) return status; HighsOptionType type = option_records[index]->type; if (type != HighsOptionType::kBool) { highsLogUser(report_log_options, HighsLogType::kError, "getLocalOptionValue: Option \"%s\" requires value of type " "%s, not bool\n", - name.c_str(), optionEntryTypeToString(type).c_str()); + option.c_str(), optionEntryTypeToString(type).c_str()); return OptionStatus::kIllegalValue; } - OptionRecordBool option = ((OptionRecordBool*)option_records[index])[0]; - value = *option.value; + OptionRecordBool& option_record = + ((OptionRecordBool*)option_records[index])[0]; + if (current_value) *current_value = *(option_record.value); + if (default_value) *default_value = option_record.default_value; return OptionStatus::kOk; } -OptionStatus getLocalOptionValue( - const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, HighsInt& value) { +OptionStatus getLocalOptionValues( + const HighsLogOptions& report_log_options, const std::string& option, + const std::vector& option_records, HighsInt* current_value, + HighsInt* min_value, HighsInt* max_value, HighsInt* default_value) { HighsInt index; OptionStatus status = - getOptionIndex(report_log_options, name, option_records, index); + getOptionIndex(report_log_options, option, option_records, index); if (status != OptionStatus::kOk) return status; HighsOptionType type = option_records[index]->type; if (type != HighsOptionType::kInt) { highsLogUser(report_log_options, HighsLogType::kError, "getLocalOptionValue: Option \"%s\" requires value of type " "%s, not HighsInt\n", - name.c_str(), optionEntryTypeToString(type).c_str()); + option.c_str(), optionEntryTypeToString(type).c_str()); return OptionStatus::kIllegalValue; } - OptionRecordInt option = ((OptionRecordInt*)option_records[index])[0]; - value = *option.value; + OptionRecordInt& option_record = ((OptionRecordInt*)option_records[index])[0]; + if (current_value) *current_value = *(option_record.value); + if (min_value) *min_value = option_record.lower_bound; + if (max_value) *max_value = option_record.upper_bound; + if (default_value) *default_value = option_record.default_value; return OptionStatus::kOk; } -OptionStatus getLocalOptionValue( - const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, double& value) { +OptionStatus getLocalOptionValues( + const HighsLogOptions& report_log_options, const std::string& option, + const std::vector& option_records, double* current_value, + double* min_value, double* max_value, double* default_value) { HighsInt index; OptionStatus status = - getOptionIndex(report_log_options, name, option_records, index); + getOptionIndex(report_log_options, option, option_records, index); if (status != OptionStatus::kOk) return status; HighsOptionType type = option_records[index]->type; if (type != HighsOptionType::kDouble) { highsLogUser(report_log_options, HighsLogType::kError, "getLocalOptionValue: Option \"%s\" requires value of type " "%s, not double\n", - name.c_str(), optionEntryTypeToString(type).c_str()); + option.c_str(), optionEntryTypeToString(type).c_str()); return OptionStatus::kIllegalValue; } - OptionRecordDouble option = ((OptionRecordDouble*)option_records[index])[0]; - value = *option.value; + OptionRecordDouble& option_record = + ((OptionRecordDouble*)option_records[index])[0]; + if (current_value) *current_value = *(option_record.value); + if (min_value) *min_value = option_record.lower_bound; + if (max_value) *max_value = option_record.upper_bound; + if (default_value) *default_value = option_record.default_value; return OptionStatus::kOk; } -OptionStatus getLocalOptionValue( - const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, std::string& value) { +OptionStatus getLocalOptionValues( + const HighsLogOptions& report_log_options, const std::string& option, + const std::vector& option_records, + std::string* current_value, std::string* default_value) { HighsInt index; OptionStatus status = - getOptionIndex(report_log_options, name, option_records, index); + getOptionIndex(report_log_options, option, option_records, index); if (status != OptionStatus::kOk) return status; HighsOptionType type = option_records[index]->type; if (type != HighsOptionType::kString) { highsLogUser(report_log_options, HighsLogType::kError, "getLocalOptionValue: Option \"%s\" requires value of type " "%s, not string\n", - name.c_str(), optionEntryTypeToString(type).c_str()); + option.c_str(), optionEntryTypeToString(type).c_str()); return OptionStatus::kIllegalValue; } - OptionRecordString option = ((OptionRecordString*)option_records[index])[0]; - value = *option.value; + OptionRecordString& option_record = + ((OptionRecordString*)option_records[index])[0]; + if (current_value) *current_value = *(option_record.value); + if (default_value) *default_value = option_record.default_value; return OptionStatus::kOk; } OptionStatus getLocalOptionType( - const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, HighsOptionType& type) { + const HighsLogOptions& report_log_options, const std::string& option, + const std::vector& option_records, HighsOptionType* type) { HighsInt index; OptionStatus status = - getOptionIndex(report_log_options, name, option_records, index); + getOptionIndex(report_log_options, option, option_records, index); if (status != OptionStatus::kOk) return status; - type = option_records[index]->type; + if (type) *type = option_records[index]->type; return OptionStatus::kOk; } @@ -759,8 +811,10 @@ void resetLocalOptions(std::vector& option_records) { HighsStatus writeOptionsToFile(FILE* file, const std::vector& option_records, const bool report_only_deviations, - const bool html) { - if (html) { + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; + if (html_file) { fprintf(file, "\n\n\n\n"); fprintf(file, " HiGHS Options\n"); fprintf(file, " \n"); @@ -774,8 +828,8 @@ HighsStatus writeOptionsToFile(FILE* file, fprintf(file, "

    HiGHS Options

    \n\n"); fprintf(file, "
      \n"); } - reportOptions(file, option_records, report_only_deviations, html); - if (html) { + reportOptions(file, option_records, report_only_deviations, file_type); + if (html_file) { fprintf(file, "
    \n"); fprintf(file, "\n\n\n"); } @@ -783,33 +837,41 @@ HighsStatus writeOptionsToFile(FILE* file, } void reportOptions(FILE* file, const std::vector& option_records, - const bool report_only_deviations, const bool html) { + const bool report_only_deviations, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; HighsInt num_options = option_records.size(); for (HighsInt index = 0; index < num_options; index++) { HighsOptionType type = option_records[index]->type; - // fprintf(file, "\n# Option %1" HIGHSINT_FORMAT "\n", index); - // Skip the advanced options when creating HTML - if (html && option_records[index]->advanced) continue; + // Only report non-advanced options + if (option_records[index]->advanced) { // && (html_file || md_file)) { + // Possibly the advanced options when creating HTML or Md file + if (!kAdvancedInDocumentation) continue; + } if (type == HighsOptionType::kBool) { reportOption(file, ((OptionRecordBool*)option_records[index])[0], - report_only_deviations, html); + report_only_deviations, file_type); } else if (type == HighsOptionType::kInt) { reportOption(file, ((OptionRecordInt*)option_records[index])[0], - report_only_deviations, html); + report_only_deviations, file_type); } else if (type == HighsOptionType::kDouble) { reportOption(file, ((OptionRecordDouble*)option_records[index])[0], - report_only_deviations, html); + report_only_deviations, file_type); } else { reportOption(file, ((OptionRecordString*)option_records[index])[0], - report_only_deviations, html); + report_only_deviations, file_type); } } } void reportOption(FILE* file, const OptionRecordBool& option, - const bool report_only_deviations, const bool html) { + const bool report_only_deviations, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; if (!report_only_deviations || option.default_value != *option.value) { - if (html) { + if (html_file) { fprintf(file, "
  • %s
    \n", option.name.c_str()); @@ -819,6 +881,11 @@ void reportOption(FILE* file, const OptionRecordBool& option, highsBoolToString(option.advanced).c_str(), highsBoolToString(option.default_value).c_str()); fprintf(file, "
  • \n"); + } else if (md_file) { + fprintf(file, "## %s\n- %s\n- Type: boolean\n- Default: \"%s\"\n\n", + highsInsertMdEscapes(option.name).c_str(), + highsInsertMdEscapes(option.description).c_str(), + highsBoolToString(option.default_value).c_str()); } else { fprintf(file, "\n# %s\n", option.description.c_str()); fprintf( @@ -833,23 +900,33 @@ void reportOption(FILE* file, const OptionRecordBool& option, } void reportOption(FILE* file, const OptionRecordInt& option, - const bool report_only_deviations, const bool html) { + const bool report_only_deviations, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; if (!report_only_deviations || option.default_value != *option.value) { - if (html) { + if (html_file) { fprintf(file, "
  • %s
    \n", option.name.c_str()); fprintf(file, "%s
    \n", option.description.c_str()); fprintf(file, - "type: HighsInt, advanced: %s, range: {%" HIGHSINT_FORMAT + "type: integer, advanced: %s, range: {%" HIGHSINT_FORMAT ", %" HIGHSINT_FORMAT "}, default: %" HIGHSINT_FORMAT "\n", highsBoolToString(option.advanced).c_str(), option.lower_bound, option.upper_bound, option.default_value); fprintf(file, "
  • \n"); + } else if (md_file) { + 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); } else { fprintf(file, "\n# %s\n", option.description.c_str()); fprintf(file, - "# [type: HighsInt, advanced: %s, range: {%" HIGHSINT_FORMAT + "# [type: integer, advanced: %s, range: {%" HIGHSINT_FORMAT ", %" HIGHSINT_FORMAT "}, default: %" HIGHSINT_FORMAT "]\n", highsBoolToString(option.advanced).c_str(), option.lower_bound, option.upper_bound, option.default_value); @@ -860,9 +937,12 @@ void reportOption(FILE* file, const OptionRecordInt& option, } void reportOption(FILE* file, const OptionRecordDouble& option, - const bool report_only_deviations, const bool html) { + const bool report_only_deviations, + const HighsFileType file_type) { + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; if (!report_only_deviations || option.default_value != *option.value) { - if (html) { + if (html_file) { fprintf(file, "
  • %s
    \n", option.name.c_str()); @@ -872,6 +952,13 @@ void reportOption(FILE* file, const OptionRecordDouble& option, highsBoolToString(option.advanced).c_str(), option.lower_bound, option.upper_bound, option.default_value); fprintf(file, "
  • \n"); + } else if (md_file) { + fprintf( + file, + "## %s\n- %s\n- Type: double\n- Range: [%g, %g]\n- Default: %g\n\n", + highsInsertMdEscapes(option.name).c_str(), + highsInsertMdEscapes(option.description).c_str(), option.lower_bound, + option.upper_bound, option.default_value); } else { fprintf(file, "\n# %s\n", option.description.c_str()); fprintf(file, @@ -884,11 +971,17 @@ void reportOption(FILE* file, const OptionRecordDouble& option, } void reportOption(FILE* file, const OptionRecordString& option, - const bool report_only_deviations, const bool html) { + const bool report_only_deviations, + const HighsFileType file_type) { // Don't report for the options file if writing to an options file + const bool html_file = file_type == HighsFileType::kHtml; + const bool md_file = file_type == HighsFileType::kMd; + // Don't report options that can only be passed via the command line if (option.name == kOptionsFileString) return; + // ToDo: are there others? + if (!report_only_deviations || option.default_value != *option.value) { - if (html) { + if (html_file) { fprintf(file, "
  • %s
    \n", option.name.c_str()); @@ -897,6 +990,11 @@ void reportOption(FILE* file, const OptionRecordString& option, highsBoolToString(option.advanced).c_str(), option.default_value.c_str()); fprintf(file, "
  • \n"); + } else if (md_file) { + fprintf(file, "## %s\n- %s\n- Type: string\n- Default: \"%s\"\n\n", + highsInsertMdEscapes(option.name).c_str(), + highsInsertMdEscapes(option.description).c_str(), + option.default_value.c_str()); } else { fprintf(file, "\n# %s\n", option.description.c_str()); fprintf(file, "# [type: string, advanced: %s, default: \"%s\"]\n", diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 0354dd20fa..06ba620a3f 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsOptions.h * @brief @@ -29,6 +27,8 @@ using std::string; enum class OptionStatus { kOk = 0, kUnknownOption, kIllegalValue }; +const bool kAdvancedInDocumentation = false; + class OptionRecord { public: HighsOptionType type; @@ -133,7 +133,9 @@ void highsOpenLogFile(HighsLogOptions& log_options, const std::string log_file); bool commandLineOffChooseOnOk(const HighsLogOptions& report_log_options, - const string& value); + const string& name, const string& value); +bool commandLineOffOnOk(const HighsLogOptions& report_log_options, + const string& name, const string& value); bool commandLineSolverOk(const HighsLogOptions& report_log_options, const string& value); @@ -207,40 +209,51 @@ OptionStatus passLocalOptions(const HighsLogOptions& report_log_options, const HighsOptions& from_options, HighsOptions& to_options); -OptionStatus getLocalOptionValue( +OptionStatus getLocalOptionValues( const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, bool& value); -OptionStatus getLocalOptionValue( + const std::vector& option_records, bool* current_value, + bool* default_value = nullptr); +OptionStatus getLocalOptionValues( const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, HighsInt& value); -OptionStatus getLocalOptionValue( + const std::vector& option_records, HighsInt* current_value, + HighsInt* min_value = nullptr, HighsInt* max_value = nullptr, + HighsInt* default_value = nullptr); +OptionStatus getLocalOptionValues( const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, double& value); -OptionStatus getLocalOptionValue( + const std::vector& option_records, double* current_value, + double* min_value = nullptr, double* max_value = nullptr, + double* default_value = nullptr); +OptionStatus getLocalOptionValues( const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, std::string& value); + const std::vector& option_records, + std::string* current_value, std::string* default_value = nullptr); OptionStatus getLocalOptionType( const HighsLogOptions& report_log_options, const std::string& name, - const std::vector& option_records, HighsOptionType& type); + const std::vector& option_records, + HighsOptionType* type = nullptr); void resetLocalOptions(std::vector& option_records); -HighsStatus writeOptionsToFile(FILE* file, - const std::vector& option_records, - const bool report_only_deviations = false, - const bool html = false); +HighsStatus writeOptionsToFile( + FILE* file, const std::vector& option_records, + const bool report_only_deviations = false, + const HighsFileType file_type = HighsFileType::kOther); void reportOptions(FILE* file, const std::vector& option_records, const bool report_only_deviations = true, - const bool html = false); + const HighsFileType file_type = HighsFileType::kOther); void reportOption(FILE* file, const OptionRecordBool& option, - const bool report_only_deviations, const bool html); + const bool report_only_deviations, + const HighsFileType file_type); void reportOption(FILE* file, const OptionRecordInt& option, - const bool report_only_deviations, const bool html); + const bool report_only_deviations, + const HighsFileType file_type); void reportOption(FILE* file, const OptionRecordDouble& option, - const bool report_only_deviations, const bool html); + const bool report_only_deviations, + const HighsFileType file_type); void reportOption(FILE* file, const OptionRecordString& option, - const bool report_only_deviations, const bool html); + const bool report_only_deviations, + const HighsFileType file_type); const string kSimplexString = "simplex"; const string kIpmString = "ipm"; @@ -326,12 +339,14 @@ struct HighsOptionsStruct { HighsInt cost_scale_factor; HighsInt allowed_matrix_scale_factor; HighsInt allowed_cost_scale_factor; - HighsInt simplex_dualise_strategy; + HighsInt ipx_dualize_strategy; + HighsInt simplex_dualize_strategy; HighsInt simplex_permute_strategy; HighsInt max_dual_simplex_cleanup_level; HighsInt max_dual_simplex_phase1_cleanup_level; HighsInt simplex_price_strategy; HighsInt simplex_unscaled_solution_strategy; + HighsInt presolve_reduction_limit; HighsInt presolve_substitution_maxfillin; HighsInt presolve_rule_off; bool presolve_rule_logging; @@ -381,6 +396,9 @@ struct HighsOptionsStruct { #ifdef HIGHS_DEBUGSOL std::string mip_debug_solution_file; #endif + bool mip_improving_solution_save; + bool mip_improving_solution_report_sparse; + std::string mip_improving_solution_file; // Logging callback identifiers HighsLogOptions log_options; @@ -408,7 +426,7 @@ class HighsOptions : public HighsOptionsStruct { HighsOptions(HighsOptions&& options) { records = std::move(options.records); HighsOptionsStruct::operator=(std::move(options)); - this->log_options.log_file_stream = options.log_options.log_file_stream; + this->log_options.log_stream = options.log_options.log_stream; setLogOptions(); } @@ -416,7 +434,7 @@ class HighsOptions : public HighsOptionsStruct { if (&other != this) { if ((HighsInt)records.size() == 0) initRecords(); HighsOptionsStruct::operator=(other); - this->log_options.log_file_stream = other.log_options.log_file_stream; + this->log_options.log_stream = other.log_options.log_stream; setLogOptions(); } return *this; @@ -426,7 +444,7 @@ class HighsOptions : public HighsOptionsStruct { if (&other != this) { if ((HighsInt)records.size() == 0) initRecords(); HighsOptionsStruct::operator=(other); - this->log_options.log_file_stream = other.log_options.log_file_stream; + this->log_options.log_stream = other.log_options.log_stream; setLogOptions(); } return *this; @@ -442,8 +460,8 @@ class HighsOptions : public HighsOptionsStruct { OptionRecordInt* record_int; OptionRecordDouble* record_double; OptionRecordString* record_string; - bool advanced; - advanced = false; + bool advanced = false; + const bool now_advanced = true; // Options read from the command line record_string = new OptionRecordString( kPresolveString, "Presolve option: \"off\", \"choose\" or \"on\"", @@ -530,28 +548,28 @@ class HighsOptions : public HighsOptionsStruct { records.push_back(record_double); record_double = new OptionRecordDouble( - "objective_target", "Objective target for termination", advanced, + "objective_target", "Objective target for termination", now_advanced, &objective_target, -kHighsInf, -kHighsInf, kHighsInf); records.push_back(record_double); record_int = - new OptionRecordInt(kRandomSeedString, "random seed used in HiGHS", + new OptionRecordInt(kRandomSeedString, "Random seed used in HiGHS", advanced, &random_seed, 0, 0, kHighsIInf); records.push_back(record_int); record_int = new OptionRecordInt( - "threads", "number of threads used by HiGHS (0: automatic)", advanced, + "threads", "Number of threads used by HiGHS (0: automatic)", advanced, &threads, 0, 0, kHighsIInf); records.push_back(record_int); - record_int = - new OptionRecordInt("highs_debug_level", "Debugging level in HiGHS", - advanced, &highs_debug_level, kHighsDebugLevelMin, - kHighsDebugLevelMin, kHighsDebugLevelMax); + record_int = new OptionRecordInt("highs_debug_level", + "Debugging level in HiGHS", now_advanced, + &highs_debug_level, kHighsDebugLevelMin, + kHighsDebugLevelMin, kHighsDebugLevelMax); records.push_back(record_int); record_int = new OptionRecordInt( - "highs_analysis_level", "Analysis level in HiGHS", advanced, + "highs_analysis_level", "Analysis level in HiGHS", now_advanced, &highs_analysis_level, kHighsAnalysisLevelMin, kHighsAnalysisLevelMin, kHighsAnalysisLevelMax); records.push_back(record_int); @@ -574,7 +592,7 @@ class HighsOptions : public HighsOptionsStruct { record_int = new OptionRecordInt( "simplex_crash_strategy", - "Strategy for simplex crash: off / LTSSF / Bixby (0/1/2)", advanced, + "Strategy for simplex crash: off / LTSSF / Bixby (0/1/2)", now_advanced, &simplex_crash_strategy, kSimplexCrashStrategyMin, kSimplexCrashStrategyOff, kSimplexCrashStrategyMax); records.push_back(record_int); @@ -600,7 +618,9 @@ class HighsOptions : public HighsOptionsStruct { records.push_back(record_int); record_int = new OptionRecordInt( - "simplex_iteration_limit", "Iteration limit for simplex solver", + "simplex_iteration_limit", + "Iteration limit for simplex solver when solving LPs, but not " + "subproblems in the MIP solver", advanced, &simplex_iteration_limit, 0, kHighsIInf, kHighsIInf); records.push_back(record_int); @@ -612,7 +632,7 @@ class HighsOptions : public HighsOptionsStruct { record_int = new OptionRecordInt( "simplex_min_concurrency", - "Minimum level of concurrency in parallel simplex", advanced, + "Minimum level of concurrency in parallel simplex", now_advanced, &simplex_min_concurrency, 1, 1, kSimplexConcurrencyLimit); records.push_back(record_int); @@ -669,42 +689,43 @@ class HighsOptions : public HighsOptionsStruct { kHighsIInf); records.push_back(record_int); - record_bool = - new OptionRecordBool("icrash", "Run iCrash", advanced, &icrash, false); + record_bool = new OptionRecordBool("icrash", "Run iCrash", now_advanced, + &icrash, false); records.push_back(record_bool); record_bool = - new OptionRecordBool("icrash_dualize", "Dualise strategy for iCrash", - advanced, &icrash_dualize, false); + new OptionRecordBool("icrash_dualize", "Dualize strategy for iCrash", + now_advanced, &icrash_dualize, false); records.push_back(record_bool); record_string = new OptionRecordString("icrash_strategy", "Strategy for iCrash", - advanced, &icrash_strategy, "ICA"); + now_advanced, &icrash_strategy, "ICA"); records.push_back(record_string); record_double = new OptionRecordDouble( - "icrash_starting_weight", "iCrash starting weight", advanced, + "icrash_starting_weight", "iCrash starting weight", now_advanced, &icrash_starting_weight, 1e-10, 1e-3, 1e50); records.push_back(record_double); - record_int = new OptionRecordInt("icrash_iterations", "iCrash iterations", - advanced, &icrash_iterations, 0, 30, 200); + record_int = + new OptionRecordInt("icrash_iterations", "iCrash iterations", + now_advanced, &icrash_iterations, 0, 30, 200); records.push_back(record_int); record_int = new OptionRecordInt( "icrash_approx_iter", "iCrash approximate minimization iterations", - advanced, &icrash_approx_iter, 0, 50, 100); + now_advanced, &icrash_approx_iter, 0, 50, 100); records.push_back(record_int); record_bool = new OptionRecordBool("icrash_exact", "Exact subproblem solution for iCrash", - advanced, &icrash_exact, false); + now_advanced, &icrash_exact, false); records.push_back(record_bool); - record_bool = new OptionRecordBool("icrash_breakpoints", - "Exact subproblem solution for iCrash", - advanced, &icrash_breakpoints, false); + record_bool = new OptionRecordBool( + "icrash_breakpoints", "Exact subproblem solution for iCrash", + now_advanced, &icrash_breakpoints, false); records.push_back(record_bool); record_string = new OptionRecordString( @@ -717,9 +738,9 @@ class HighsOptions : public HighsOptionsStruct { advanced, &write_model_to_file, false); records.push_back(record_bool); - record_bool = new OptionRecordBool("mip_detect_symmetry", - "Whether symmetry should be detected", - advanced, &mip_detect_symmetry, true); + record_bool = new OptionRecordBool( + "mip_detect_symmetry", "Whether MIP symmetry should be detected", + advanced, &mip_detect_symmetry, true); records.push_back(record_bool); record_int = new OptionRecordInt("mip_max_nodes", @@ -740,6 +761,24 @@ class HighsOptions : public HighsOptionsStruct { records.push_back(record_string); #endif + record_bool = + new OptionRecordBool("mip_improving_solution_save", + "Whether improving MIP solutions should be saved", + advanced, &mip_improving_solution_save, false); + records.push_back(record_bool); + + record_bool = new OptionRecordBool( + "mip_improving_solution_report_sparse", + "Whether improving MIP solutions should be reported in sparse format", + advanced, &mip_improving_solution_report_sparse, false); + records.push_back(record_bool); + + record_string = new OptionRecordString( + "mip_improving_solution_file", + "File for reporting improving MIP solutions: not reported if \"\"", + advanced, &mip_improving_solution_file, kHighsFilenameDefault); + records.push_back(record_string); + record_int = new OptionRecordInt( "mip_max_leaves", "MIP solver max number of leave nodes", advanced, &mip_max_leaves, 0, kHighsIInf, kHighsIInf); @@ -747,41 +786,43 @@ class HighsOptions : public HighsOptionsStruct { record_int = new OptionRecordInt( "mip_max_improving_sols", - "limit on the number of improving solutions found to stop the MIP " + "Limit on the number of improving solutions found to stop the MIP " "solver prematurely", advanced, &mip_max_improving_sols, 1, kHighsIInf, kHighsIInf); records.push_back(record_int); - record_int = new OptionRecordInt("mip_lp_age_limit", - "maximal age of dynamic LP rows before " - "they are removed from the LP relaxation", - advanced, &mip_lp_age_limit, 0, 10, - std::numeric_limits::max()); + record_int = new OptionRecordInt( + "mip_lp_age_limit", + "Maximal age of dynamic LP rows before " + "they are removed from the LP relaxation in the MIP solver", + advanced, &mip_lp_age_limit, 0, 10, + std::numeric_limits::max()); records.push_back(record_int); record_int = new OptionRecordInt( "mip_pool_age_limit", - "maximal age of rows in the cutpool before they are deleted", advanced, - &mip_pool_age_limit, 0, 30, 1000); + "Maximal age of rows in the MIP solver cutpool before they are deleted", + advanced, &mip_pool_age_limit, 0, 30, 1000); records.push_back(record_int); - record_int = new OptionRecordInt("mip_pool_soft_limit", - "soft limit on the number of rows in the " - "cutpool for dynamic age adjustment", - advanced, &mip_pool_soft_limit, 1, 10000, - kHighsIInf); + record_int = new OptionRecordInt( + "mip_pool_soft_limit", + "Soft limit on the number of rows in the " + "MIP solver cutpool for dynamic age adjustment", + advanced, &mip_pool_soft_limit, 1, 10000, kHighsIInf); records.push_back(record_int); - record_int = new OptionRecordInt("mip_pscost_minreliable", - "minimal number of observations before " - "pseudo costs are considered reliable", - advanced, &mip_pscost_minreliable, 0, 8, - kHighsIInf); + record_int = new OptionRecordInt( + "mip_pscost_minreliable", + "Minimal number of observations before " + "MIP solver pseudo costs are considered reliable", + advanced, &mip_pscost_minreliable, 0, 8, kHighsIInf); records.push_back(record_int); record_int = new OptionRecordInt( "mip_min_cliquetable_entries_for_parallelism", - "minimal number of entries in the cliquetable before neighborhood " + "Minimal number of entries in the MIP solver cliquetable before " + "neighbourhood " "queries of the conflict graph use parallel processing", advanced, &mip_min_cliquetable_entries_for_parallelism, 0, 100000, kHighsIInf); @@ -789,7 +830,7 @@ class HighsOptions : public HighsOptionsStruct { record_int = new OptionRecordInt("mip_report_level", "MIP solver reporting level", - advanced, &mip_report_level, 0, 1, 2); + now_advanced, &mip_report_level, 0, 1, 2); records.push_back(record_int); record_double = new OptionRecordDouble( @@ -798,20 +839,20 @@ class HighsOptions : public HighsOptionsStruct { records.push_back(record_double); record_double = new OptionRecordDouble( - "mip_heuristic_effort", "effort spent for MIP heuristics", advanced, + "mip_heuristic_effort", "Effort spent for MIP heuristics", advanced, &mip_heuristic_effort, 0.0, 0.05, 1.0); records.push_back(record_double); record_double = new OptionRecordDouble( "mip_rel_gap", - "tolerance on relative gap, |ub-lb|/|ub|, to determine whether " + "Tolerance on relative gap, |ub-lb|/|ub|, to determine whether " "optimality has been reached for a MIP instance", advanced, &mip_rel_gap, 0.0, 1e-4, kHighsInf); records.push_back(record_double); record_double = new OptionRecordDouble( "mip_abs_gap", - "tolerance on absolute gap of MIP, |ub-lb|, to determine whether " + "Tolerance on absolute gap of MIP, |ub-lb|, to determine whether " "optimality has been reached for a MIP instance", advanced, &mip_abs_gap, 0.0, 1e-6, kHighsInf); records.push_back(record_double); @@ -821,6 +862,9 @@ class HighsOptions : public HighsOptionsStruct { &ipm_iteration_limit, 0, kHighsIInf, kHighsIInf); records.push_back(record_int); + // Fix the number of user settable options + num_user_settable_options_ = records.size(); + // Advanced options advanced = true; @@ -888,8 +932,14 @@ class HighsOptions : public HighsOptionsStruct { records.push_back(record_int); record_int = new OptionRecordInt( - "simplex_dualise_strategy", "Strategy for dualising before simplex", - advanced, &simplex_dualise_strategy, kHighsOptionOff, kHighsOptionOff, + "ipx_dualize_strategy", "Strategy for dualizing before IPX", advanced, + &ipx_dualize_strategy, kIpxDualizeStrategyMin, kIpxDualizeStrategyLukas, + kIpxDualizeStrategyMax); + records.push_back(record_int); + + record_int = new OptionRecordInt( + "simplex_dualize_strategy", "Strategy for dualizing before simplex", + advanced, &simplex_dualize_strategy, kHighsOptionOff, kHighsOptionOff, kHighsOptionOn); records.push_back(record_int); @@ -991,6 +1041,12 @@ class HighsOptions : public HighsOptionsStruct { kMaxPivotThreshold); records.push_back(record_double); + record_int = new OptionRecordInt( + "presolve_reduction_limit", + "Limit on number of presolve reductions -1 => no limit", advanced, + &presolve_reduction_limit, -1, -1, kHighsIInf); + records.push_back(record_int); + record_int = new OptionRecordInt( "presolve_rule_off", "Bit mask of presolve rules that are not allowed", advanced, &presolve_rule_off, 0, 0, kHighsIInf); @@ -998,7 +1054,7 @@ class HighsOptions : public HighsOptionsStruct { record_bool = new OptionRecordBool( "presolve_rule_logging", "Log effectiveness of presolve rules for LP", - advanced, &presolve_rule_logging, true); + advanced, &presolve_rule_logging, false); records.push_back(record_bool); record_int = new OptionRecordInt( @@ -1042,13 +1098,14 @@ class HighsOptions : public HighsOptionsStruct { &less_infeasible_DSE_choose_row, true); records.push_back(record_bool); - log_options.log_file_stream = + // Set up the log_options aliases + log_options.log_stream = log_file.empty() ? NULL : fopen(log_file.c_str(), "w"); log_options.output_flag = &output_flag; log_options.log_to_console = &log_to_console; log_options.log_dev_level = &log_dev_level; - log_options.log_callback = nullptr; - log_options.log_callback_data = nullptr; + log_options.log_highs_callback = nullptr; + log_options.log_user_callback = nullptr; } void deleteRecords() { @@ -1057,6 +1114,7 @@ class HighsOptions : public HighsOptionsStruct { public: std::vector records; + HighsInt num_user_settable_options_; void setLogOptions(); }; diff --git a/src/lp_data/HighsRanging.cpp b/src/lp_data/HighsRanging.cpp index 2c101bf0b0..3c0999952b 100644 --- a/src/lp_data/HighsRanging.cpp +++ b/src/lp_data/HighsRanging.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsRanging.cpp * @brief Compute LP ranging data for HiGHS @@ -297,7 +295,7 @@ HighsStatus getRangingData(HighsRanging& ranging, vector c_up_l(numTotal), c_dn_l(numTotal); // - // Ranging 2.1. non-basic cost ranging + // Ranging 2.1. nonbasic cost ranging // // const HighsInt check_col = 2951; for (HighsInt j = 0; j < numCol; j++) { @@ -390,7 +388,7 @@ HighsStatus getRangingData(HighsRanging& ranging, vector b_up_l(numTotal), b_dn_l(numTotal); // - // Ranging 3.1. non-basic bounds ranging + // Ranging 3.1. nonbasic bounds ranging // for (HighsInt j = 0; j < numTotal; j++) { if (Nflag_[j]) { diff --git a/src/lp_data/HighsRanging.h b/src/lp_data/HighsRanging.h index 03079499fe..3e073dd349 100644 --- a/src/lp_data/HighsRanging.h +++ b/src/lp_data/HighsRanging.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsRanging.h * @brief diff --git a/src/lp_data/HighsRuntimeOptions.h b/src/lp_data/HighsRuntimeOptions.h index d3ed9786ea..018950be9b 100644 --- a/src/lp_data/HighsRuntimeOptions.h +++ b/src/lp_data/HighsRuntimeOptions.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef LP_DATA_HIGHSRUNTIMEOPTIONS_H_ @@ -143,7 +141,15 @@ bool loadOptions(const HighsLogOptions& report_log_options, int argc, std::cout << "Multiple options files not implemented.\n"; return false; } - if (!loadOptionsFromFile(report_log_options, options, v[0])) return false; + switch (loadOptionsFromFile(report_log_options, options, v[0])) { + case HighsLoadOptionsStatus::kError: + return false; + case HighsLoadOptionsStatus::kEmpty: + writeOptionsToFile(stdout, options.records); + return false; + default: + break; + } } // Handle command line option specifications diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index a777b4e494..11812ff14c 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsSolution.cpp * @brief Class-independent utilities for HiGHS @@ -730,6 +728,7 @@ HighsStatus ipxSolutionToHighsSolution( // of machine precision const double primal_margin = 0; // primal_feasibility_tolerance; const double dual_margin = 0; // dual_feasibility_tolerance; + double max_abs_primal_value = 0; for (HighsInt var = 0; var < lp.num_col_ + lp.num_row_; var++) { if (var == lp.num_col_) { col_primal_truncation_norm = primal_truncation_norm; @@ -782,28 +781,31 @@ HighsStatus ipxSolutionToHighsSolution( } // Continue if no dual infeasibility if (dual_infeasibility <= dual_feasibility_tolerance) continue; + if (residual < dual_infeasibility && !force_dual_feasibility) { - // Residual is less than dual infeasibility, or not forcing - // dual feasibility, so truncate value - if (at_lower) { - assert(10 == 50); - } else if (at_upper) { - assert(11 == 50); - } else { - // Off bound - if (lower <= -kHighsInf) { - if (upper >= kHighsInf) { - // Free shouldn't be possible, as residual would be inf - assert(12 == 50); - } else { - // Upper bounded, so assume dual is negative - if (dual > 0) assert(13 == 50); + /* + // Residual is less than dual infeasibility, or not forcing + // dual feasibility, so truncate value + if (at_lower) { + assert(10 == 50); + } else if (at_upper) { + assert(11 == 50); + } else { + // Off bound + if (lower <= -kHighsInf) { + if (upper >= kHighsInf) { + // Free shouldn't be possible, as residual would be inf + assert(12 == 50); + } else { + // Upper bounded, so assume dual is negative + if (dual > 0) assert(13 == 50); + } + } else if (upper >= kHighsInf) { + // Lower bounded, so assume dual is positive + if (dual < 0) assert(14 == 50); } - } else if (upper >= kHighsInf) { - // Lower bounded, so assume dual is positive - if (dual < 0) assert(14 == 50); } - } + */ num_primal_truncations++; if (dual > 0) { // Put closest to lower @@ -853,6 +855,8 @@ HighsStatus ipxSolutionToHighsSolution( upper, residual, new_value, primal_truncation, dual, new_dual, dual_truncation); */ + max_abs_primal_value = + std::max(std::abs(new_value), max_abs_primal_value); if (is_col) { highs_solution.col_value[col] = new_value; highs_solution.col_dual[col] = new_dual; @@ -911,8 +915,14 @@ HighsStatus ipxSolutionToHighsSolution( "ipxSolutionToHighsSolution: Final norm of dual residual " "values is %10.4g\n", dual_residual_norm); + if (max_abs_primal_value > kExcessivePrimalValue) { + // highsLogUser(options.log_options, HighsLogType::kInfo, + printf( + "ipxSolutionToHighsSolution: " + "Excessive corrected |primal value| is %10.4g\n", + max_abs_primal_value); + } } - assert(ipx_row == ipx_num_row); assert(ipx_slack == ipx_num_col); // Indicate that the primal and dual solution are known @@ -1174,6 +1184,14 @@ HighsStatus ipxBasicSolutionToHighsBasicSolution( return HighsStatus::kOk; } +HighsStatus formSimplexLpBasisAndFactorReturn( + const HighsStatus return_status, HighsLpSolverObject& solver_object) { + HighsLp& lp = solver_object.lp_; + HighsLp& ekk_lp = solver_object.ekk_instance_.lp_; + if (lp.is_moved_) lp.moveBackLpAndUnapplyScaling(ekk_lp); + return return_status; +} + HighsStatus formSimplexLpBasisAndFactor(HighsLpSolverObject& solver_object, const bool only_from_known_basis) { // Ideally, forms a SimplexBasis from the HighsBasis in the @@ -1217,17 +1235,19 @@ HighsStatus formSimplexLpBasisAndFactor(HighsLpSolverObject& solver_object, HighsStatus call_status = ekk_instance.setBasis(basis); return_status = interpretCallStatus(options.log_options, call_status, return_status, "setBasis"); - if (return_status == HighsStatus::kError) return return_status; + if (return_status == HighsStatus::kError) + return formSimplexLpBasisAndFactorReturn(return_status, solver_object); } // Now form the invert assert(ekk_status.has_basis); call_status = ekk_instance.initialiseSimplexLpBasisAndFactor(only_from_known_basis); - if (call_status != HighsStatus::kOk) return HighsStatus::kError; - // Once the invert is formed, move back the LP and remove any scaling. - lp.moveBackLpAndUnapplyScaling(ekk_lp); // If the current basis cannot be inverted, return an error - return HighsStatus::kOk; + if (call_status != HighsStatus::kOk) + return formSimplexLpBasisAndFactorReturn(HighsStatus::kError, + solver_object); + // Once the invert is formed, move back the LP and remove any scaling. + return formSimplexLpBasisAndFactorReturn(HighsStatus::kOk, solver_object); } void accommodateAlienBasis(HighsLpSolverObject& solver_object) { @@ -1250,7 +1270,7 @@ void accommodateAlienBasis(HighsLpSolverObject& solver_object) { } HighsInt num_basic_variables = basic_index.size(); HFactor factor; - factor.setupGeneral(&lp.a_matrix_, num_basic_variables, &basic_index[0], + factor.setupGeneral(&lp.a_matrix_, num_basic_variables, basic_index.data(), kDefaultPivotThreshold, kDefaultPivotTolerance, kHighsDebugLevelMin, &options.log_options); HighsInt rank_deficiency = factor.build(); @@ -1377,6 +1397,8 @@ void HighsSolution::clear() { this->row_dual.clear(); } +void HighsObjectiveSolution::clear() { this->col_value.clear(); } + void HighsBasis::invalidate() { this->valid = false; this->alien = true; diff --git a/src/lp_data/HighsSolution.h b/src/lp_data/HighsSolution.h index b2137449cf..05c84154da 100644 --- a/src/lp_data/HighsSolution.h +++ b/src/lp_data/HighsSolution.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsSolution.h * @brief Class-independent utilities for HiGHS @@ -117,6 +115,8 @@ HighsStatus ipxBasicSolutionToHighsBasicSolution( const IpxSolution& ipx_solution, HighsBasis& highs_basis, HighsSolution& highs_solution); +HighsStatus formSimplexLpBasisAndFactorReturn( + const HighsStatus return_status, HighsLpSolverObject& solver_object); HighsStatus formSimplexLpBasisAndFactor( HighsLpSolverObject& solver_object, const bool only_from_known_basis = false); diff --git a/src/lp_data/HighsSolutionDebug.cpp b/src/lp_data/HighsSolutionDebug.cpp index 71eeb13b56..75cc3d42d0 100644 --- a/src/lp_data/HighsSolutionDebug.cpp +++ b/src/lp_data/HighsSolutionDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsSolutionDebug.cpp * @brief diff --git a/src/lp_data/HighsSolutionDebug.h b/src/lp_data/HighsSolutionDebug.h index 80333ec2cc..1c73644830 100644 --- a/src/lp_data/HighsSolutionDebug.h +++ b/src/lp_data/HighsSolutionDebug.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsSolutionDebug.h * @brief diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index 64976d043d..d55b90ea1f 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsSolve.cpp * @brief Class-independent utilities for HiGHS diff --git a/src/lp_data/HighsSolve.h b/src/lp_data/HighsSolve.h index 2ac2e2e7d1..1bf38d672a 100644 --- a/src/lp_data/HighsSolve.h +++ b/src/lp_data/HighsSolve.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HighsSolve.h * @brief Class-independent utilities for HiGHS diff --git a/src/lp_data/HighsStatus.cpp b/src/lp_data/HighsStatus.cpp index 4f85e6474b..7bcff56001 100644 --- a/src/lp_data/HighsStatus.cpp +++ b/src/lp_data/HighsStatus.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "lp_data/HighsStatus.h" diff --git a/src/lp_data/HighsStatus.h b/src/lp_data/HighsStatus.h index c58309183a..3e6c91f516 100644 --- a/src/lp_data/HighsStatus.h +++ b/src/lp_data/HighsStatus.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef LP_DATA_HIGHS_STATUS_H_ #define LP_DATA_HIGHS_STATUS_H_ diff --git a/src/mip/HighsCliqueTable.cpp b/src/mip/HighsCliqueTable.cpp index dd36c38b26..34d391c203 100644 --- a/src/mip/HighsCliqueTable.cpp +++ b/src/mip/HighsCliqueTable.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsCliqueTable.h" @@ -213,11 +211,11 @@ void HighsCliqueTable::bronKerboschRecurse(BronKerboschData& data, std::vector PminusNu; PminusNu.reserve(Plen); - queryNeighborhood(data.neighborhoodInds, data.numNeighborhoodQueries, pivot, - data.P.data(), Plen); - data.neighborhoodInds.push_back(Plen); + queryNeighbourhood(data.neighbourhoodInds, data.numNeighbourhoodQueries, + pivot, data.P.data(), Plen); + data.neighbourhoodInds.push_back(Plen); HighsInt k = 0; - for (HighsInt i : data.neighborhoodInds) { + for (HighsInt i : data.neighbourhoodInds) { while (k < i) PminusNu.push_back(data.P[k++]); ++k; } @@ -231,12 +229,12 @@ void HighsCliqueTable::bronKerboschRecurse(BronKerboschData& data, localX.insert(localX.end(), X, X + Xlen); for (CliqueVar v : PminusNu) { - HighsInt newPlen = partitionNeighborhood(data.neighborhoodInds, - data.numNeighborhoodQueries, v, - data.P.data(), Plen); - HighsInt newXlen = partitionNeighborhood(data.neighborhoodInds, - data.numNeighborhoodQueries, v, - localX.data(), localX.size()); + HighsInt newPlen = partitionNeighbourhood(data.neighbourhoodInds, + data.numNeighbourhoodQueries, v, + data.P.data(), Plen); + HighsInt newXlen = partitionNeighbourhood(data.neighbourhoodInds, + data.numNeighbourhoodQueries, v, + localX.data(), localX.size()); // add v to R, update the weight, and do the recursive call data.R.push_back(v); @@ -439,71 +437,71 @@ void HighsCliqueTable::doAddClique(const CliqueVar* cliquevars, cliqueentries[cliques[cliqueid].start + 1]), cliqueid); } -struct ThreadNeighborhoodQueryData { +struct ThreadNeighbourhoodQueryData { int64_t numQueries; - std::vector neighborhoodInds; + std::vector neighbourhoodInds; }; -void HighsCliqueTable::queryNeighborhood( - std::vector& neighborhoodInds, int64_t& numQueries, CliqueVar v, +void HighsCliqueTable::queryNeighbourhood( + std::vector& neighbourhoodInds, int64_t& numQueries, CliqueVar v, CliqueVar* q, HighsInt N) const { - neighborhoodInds.clear(); + neighbourhoodInds.clear(); if (numCliques(v) == 0) return; if (numEntries - sizeTwoCliques.size() * 2 < minEntriesForParallelism) { for (HighsInt i = 0; i < N; ++i) { - if (haveCommonClique(numQueries, v, q[i])) neighborhoodInds.push_back(i); + if (haveCommonClique(numQueries, v, q[i])) neighbourhoodInds.push_back(i); } } else { - auto neighborhoodData = - makeHighsCombinable([N]() { - ThreadNeighborhoodQueryData d; - d.neighborhoodInds.reserve(N); + auto neighbourhoodData = + makeHighsCombinable([N]() { + ThreadNeighbourhoodQueryData d; + d.neighbourhoodInds.reserve(N); d.numQueries = 0; return d; }); highs::parallel::for_each( 0, N, - [this, &neighborhoodData, v, q](HighsInt start, HighsInt end) { - ThreadNeighborhoodQueryData& d = neighborhoodData.local(); + [this, &neighbourhoodData, v, q](HighsInt start, HighsInt end) { + ThreadNeighbourhoodQueryData& d = neighbourhoodData.local(); for (HighsInt i = start; i < end; ++i) { if (haveCommonClique(d.numQueries, v, q[i])) - d.neighborhoodInds.push_back(i); + d.neighbourhoodInds.push_back(i); } }, 10); - neighborhoodData.combine_each([&](ThreadNeighborhoodQueryData& d) { - neighborhoodInds.insert(neighborhoodInds.end(), - d.neighborhoodInds.begin(), - d.neighborhoodInds.end()); + neighbourhoodData.combine_each([&](ThreadNeighbourhoodQueryData& d) { + neighbourhoodInds.insert(neighbourhoodInds.end(), + d.neighbourhoodInds.begin(), + d.neighbourhoodInds.end()); numQueries += d.numQueries; }); - pdqsort(neighborhoodInds.begin(), neighborhoodInds.end()); + pdqsort(neighbourhoodInds.begin(), neighbourhoodInds.end()); } } -HighsInt HighsCliqueTable::partitionNeighborhood( - std::vector& neighborhoodInds, int64_t& numQueries, CliqueVar v, +HighsInt HighsCliqueTable::partitionNeighbourhood( + std::vector& neighbourhoodInds, int64_t& numQueries, CliqueVar v, CliqueVar* q, HighsInt N) const { - queryNeighborhood(neighborhoodInds, numQueries, v, q, N); + queryNeighbourhood(neighbourhoodInds, numQueries, v, q, N); - for (HighsInt i = 0; i < (HighsInt)neighborhoodInds.size(); ++i) - std::swap(q[i], q[neighborhoodInds[i]]); + for (HighsInt i = 0; i < (HighsInt)neighbourhoodInds.size(); ++i) + std::swap(q[i], q[neighbourhoodInds[i]]); - return neighborhoodInds.size(); + return neighbourhoodInds.size(); } -HighsInt HighsCliqueTable::shrinkToNeighborhood( - std::vector& neighborhoodInds, int64_t& numQueries, CliqueVar v, +HighsInt HighsCliqueTable::shrinkToNeighbourhood( + std::vector& neighbourhoodInds, int64_t& numQueries, CliqueVar v, CliqueVar* q, HighsInt N) { - queryNeighborhood(neighborhoodInds, numQueries, v, q, N); + queryNeighbourhood(neighbourhoodInds, numQueries, v, q, N); - for (HighsInt i = 0; i < (HighsInt)neighborhoodInds.size(); ++i) - q[i] = q[neighborhoodInds[i]]; + for (HighsInt i = 0; i < (HighsInt)neighbourhoodInds.size(); ++i) + q[i] = q[neighbourhoodInds[i]]; - return neighborhoodInds.size(); + return neighbourhoodInds.size(); } bool HighsCliqueTable::processNewEdge(HighsDomain& globaldom, CliqueVar v1, @@ -981,8 +979,8 @@ void HighsCliqueTable::cliquePartition(std::vector& clqVars, std::vector& partitionStart) { randgen.shuffle(clqVars.data(), clqVars.size()); - std::vector neighborhoodInds; - neighborhoodInds.reserve(clqVars.size()); + std::vector neighbourhoodInds; + neighbourhoodInds.reserve(clqVars.size()); HighsInt numClqVars = clqVars.size(); partitionStart.clear(); @@ -997,9 +995,9 @@ void HighsCliqueTable::cliquePartition(std::vector& clqVars, CliqueVar v = clqVars[i]; HighsInt extensionStart = i + 1; extensionEnd = - partitionNeighborhood(neighborhoodInds, numNeighborhoodQueries, v, - clqVars.data() + extensionStart, - extensionEnd - extensionStart) + + partitionNeighbourhood(neighbourhoodInds, numNeighbourhoodQueries, v, + clqVars.data() + extensionStart, + extensionEnd - extensionStart) + extensionStart; } @@ -1017,8 +1015,8 @@ void HighsCliqueTable::cliquePartition(const std::vector& objective, (2 * v2.val - 1) * objective[v2.col]; }); - std::vector neighborhoodInds; - neighborhoodInds.reserve(clqVars.size()); + std::vector neighbourhoodInds; + neighbourhoodInds.reserve(clqVars.size()); HighsInt numClqVars = clqVars.size(); partitionStart.clear(); @@ -1042,13 +1040,13 @@ void HighsCliqueTable::cliquePartition(const std::vector& objective, CliqueVar v = clqVars[i]; HighsInt extensionStart = i + 1; extensionEnd = - partitionNeighborhood(neighborhoodInds, numNeighborhoodQueries, v, - clqVars.data() + extensionStart, - extensionEnd - extensionStart) + + partitionNeighbourhood(neighbourhoodInds, numNeighbourhoodQueries, v, + clqVars.data() + extensionStart, + extensionEnd - extensionStart) + extensionStart; - if (!neighborhoodInds.empty()) + if (!neighbourhoodInds.empty()) lastSwappedIndex = - std::max(neighborhoodInds.back() + extensionStart, lastSwappedIndex); + std::max(neighbourhoodInds.back() + extensionStart, lastSwappedIndex); } partitionStart.push_back(numClqVars); @@ -1678,11 +1676,11 @@ void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, HighsCutPool& cutpool, double feastol) { BronKerboschData data(sol); data.feastol = feastol; - data.maxNeighborhoodQueries = 1000000 + - int64_t{100} * mipsolver.numNonzero() + - mipsolver.mipdata_->total_lp_iterations * 1000; - if (numNeighborhoodQueries > data.maxNeighborhoodQueries) return; - data.maxNeighborhoodQueries -= numNeighborhoodQueries; + data.maxNeighbourhoodQueries = 1000000 + + int64_t{100} * mipsolver.numNonzero() + + mipsolver.mipdata_->total_lp_iterations * 1000; + if (numNeighbourhoodQueries > data.maxNeighbourhoodQueries) return; + data.maxNeighbourhoodQueries -= numNeighbourhoodQueries; const HighsDomain& globaldom = mipsolver.mipdata_->domain; for (HighsInt i : mipsolver.mipdata_->integral_cols) { @@ -1728,9 +1726,9 @@ void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, #ifdef ADD_ZERO_WEIGHT_VARS HighsInt extensionend = (HighsInt)data.Z.size(); for (CliqueVar v : clique) { - extensionend = partitionNeighborhood(data.neighborhoodInds, - data.numNeighborhoodQueries, v, - data.Z.data(), extensionend); + extensionend = partitionNeighbourhood(data.neighbourhoodInds, + data.numNeighbourhoodQueries, v, + data.Z.data(), extensionend); if (extensionend == 0) break; } @@ -1740,9 +1738,9 @@ void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, for (HighsInt i = 0; i < extensionend; ++i) { HighsInt k = i + 1; extensionend = - k + partitionNeighborhood(data.neighborhoodInds, - data.numNeighborhoodQueries, data.Z[i], - data.Z.data() + k, extensionend - k); + k + partitionNeighbourhood(data.neighbourhoodInds, + data.numNeighbourhoodQueries, data.Z[i], + data.Z.data() + k, extensionend - k); } clique.insert(clique.end(), data.Z.begin(), @@ -1770,7 +1768,7 @@ void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, false, false); } - numNeighborhoodQueries += data.numNeighborhoodQueries; + numNeighbourhoodQueries += data.numNeighbourhoodQueries; if (runcliquesubsumption) { if (cliquehits.size() < cliques.size()) cliquehits.resize(cliques.size()); @@ -1928,8 +1926,8 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, CliqueVar extensionstart; HighsInt numcliques = kHighsIInf; iscandidate.resize(invertedHashList.size()); - std::vector neighborhoodInds; - neighborhoodInds.reserve(invertedHashList.size()); + std::vector neighbourhoodInds; + neighbourhoodInds.reserve(invertedHashList.size()); HighsInt initialCliqueSize = clique.size(); for (HighsInt i = 0; i != initialCliqueSize; ++i) { @@ -1982,9 +1980,9 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, HighsInt newSize = initialCliqueSize + - shrinkToNeighborhood(neighborhoodInds, numNeighborhoodQueries, - clique[i], clique.data() + initialCliqueSize, - clique.size() - initialCliqueSize); + shrinkToNeighbourhood(neighbourhoodInds, numNeighbourhoodQueries, + clique[i], clique.data() + initialCliqueSize, + clique.size() - initialCliqueSize); clique.erase(clique.begin() + newSize, clique.end()); } @@ -1997,8 +1995,8 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, CliqueVar extvar = clique[i]; i += 1; - HighsInt newSize = i + shrinkToNeighborhood( - neighborhoodInds, numNeighborhoodQueries, + HighsInt newSize = i + shrinkToNeighbourhood( + neighbourhoodInds, numNeighbourhoodQueries, extvar, clique.data() + i, clique.size() - i); clique.erase(clique.begin() + newSize, clique.end()); } @@ -2028,8 +2026,8 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { std::vector extensionvars; iscandidate.resize(invertedHashList.size()); - std::vector neighborhoodInds; - neighborhoodInds.reserve(invertedHashList.size()); + std::vector neighbourhoodInds; + neighbourhoodInds.reserve(invertedHashList.size()); if (cliquehits.size() < cliques.size()) cliquehits.resize(cliques.size()); @@ -2090,8 +2088,8 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { for (HighsInt i = 0; i != numclqvars && !extensionvars.empty(); ++i) { if (clqvars[i] == extensionstart) continue; - HighsInt newSize = shrinkToNeighborhood( - neighborhoodInds, numNeighborhoodQueries, clqvars[i], + HighsInt newSize = shrinkToNeighbourhood( + neighbourhoodInds, numNeighbourhoodQueries, clqvars[i], extensionvars.data(), extensionvars.size()); extensionvars.erase(extensionvars.begin() + newSize, extensionvars.end()); } @@ -2105,9 +2103,9 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { i += 1; HighsInt newSize = - i + shrinkToNeighborhood(neighborhoodInds, numNeighborhoodQueries, - extvar, extensionvars.data() + i, - extensionvars.size() - i); + i + shrinkToNeighbourhood( + neighbourhoodInds, numNeighbourhoodQueries, extvar, + extensionvars.data() + i, extensionvars.size() - i); extensionvars.erase(extensionvars.begin() + newSize, extensionvars.end()); } diff --git a/src/mip/HighsCliqueTable.h b/src/mip/HighsCliqueTable.h index 10825aa970..2f062306bb 100644 --- a/src/mip/HighsCliqueTable.h +++ b/src/mip/HighsCliqueTable.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_CLIQUE_TABLE_H_ #define HIGHS_CLIQUE_TABLE_H_ @@ -109,7 +107,7 @@ class HighsCliqueTable { CliqueVar v2) const; HighsInt findCommonCliqueId(CliqueVar v1, CliqueVar v2) { - return findCommonCliqueId(numNeighborhoodQueries, v1, v2); + return findCommonCliqueId(numNeighbourhoodQueries, v1, v2); } HighsInt runCliqueSubsumption(const HighsDomain& globaldom, @@ -120,7 +118,7 @@ class HighsCliqueTable { std::vector R; std::vector Z; std::vector> cliques; - std::vector neighborhoodInds; + std::vector neighbourhoodInds; double wR = 0.0; double minW = 1.05; @@ -128,12 +126,12 @@ class HighsCliqueTable { HighsInt ncalls = 0; HighsInt maxcalls = 10000; HighsInt maxcliques = 100; - int64_t maxNeighborhoodQueries = std::numeric_limits::max(); - int64_t numNeighborhoodQueries = 0; + int64_t maxNeighbourhoodQueries = std::numeric_limits::max(); + int64_t numNeighbourhoodQueries = 0; bool stop() const { return maxcalls == ncalls || int(cliques.size()) == maxcliques || - numNeighborhoodQueries > maxNeighborhoodQueries; + numNeighbourhoodQueries > maxNeighbourhoodQueries; } BronKerboschData(const std::vector& sol) : sol(sol) {} @@ -152,12 +150,12 @@ class HighsCliqueTable { void propagateAndCleanup(HighsDomain& globaldom); - void queryNeighborhood(std::vector& neighborhoodInds, - int64_t& numNeighborhoodqueries, CliqueVar v, - CliqueVar* q, HighsInt N) const; + void queryNeighbourhood(std::vector& neighbourhoodInds, + int64_t& numNeighbourhoodqueries, CliqueVar v, + CliqueVar* q, HighsInt N) const; public: - int64_t numNeighborhoodQueries; + int64_t numNeighbourhoodQueries; HighsCliqueTable(HighsInt ncols) { invertedHashList.resize(2 * ncols); @@ -166,7 +164,7 @@ class HighsCliqueTable { colsubstituted.resize(ncols); colDeleted.resize(ncols, false); nfixings = 0; - numNeighborhoodQueries = 0; + numNeighbourhoodQueries = 0; numEntries = 0; maxEntries = kHighsIInf; minEntriesForParallelism = kHighsIInf; @@ -179,13 +177,13 @@ class HighsCliqueTable { HighsInt getNumEntries() const { return numEntries; } - HighsInt partitionNeighborhood(std::vector& neighborhoodInds, - int64_t& numNeighborhoodqueries, CliqueVar v, - CliqueVar* q, HighsInt N) const; + HighsInt partitionNeighbourhood(std::vector& neighbourhoodInds, + int64_t& numNeighbourhoodqueries, CliqueVar v, + CliqueVar* q, HighsInt N) const; - HighsInt shrinkToNeighborhood(std::vector& neighborhoodInds, - int64_t& numNeighborhoodqueries, CliqueVar v, - CliqueVar* q, HighsInt N); + HighsInt shrinkToNeighbourhood(std::vector& neighbourhoodInds, + int64_t& numNeighbourhoodqueries, CliqueVar v, + CliqueVar* q, HighsInt N); bool processNewEdge(HighsDomain& globaldom, CliqueVar v1, CliqueVar v2); diff --git a/src/mip/HighsConflictPool.cpp b/src/mip/HighsConflictPool.cpp index ef4c1fa036..3c7f7cde3d 100644 --- a/src/mip/HighsConflictPool.cpp +++ b/src/mip/HighsConflictPool.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsConflictPool.h" diff --git a/src/mip/HighsConflictPool.h b/src/mip/HighsConflictPool.h index 8556538340..09be5db530 100644 --- a/src/mip/HighsConflictPool.h +++ b/src/mip/HighsConflictPool.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_CONFLICTPOOL_H_ #define HIGHS_CONFLICTPOOL_H_ diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 44185f4409..ce6e475e5d 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsCutGeneration.h" diff --git a/src/mip/HighsCutGeneration.h b/src/mip/HighsCutGeneration.h index 70166f4de3..1e722cb7c4 100644 --- a/src/mip/HighsCutGeneration.h +++ b/src/mip/HighsCutGeneration.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsCutGeneration.h * @brief Class that generates cuts from single row relaxations diff --git a/src/mip/HighsCutPool.cpp b/src/mip/HighsCutPool.cpp index 7f52cde9c7..0f4d9e9289 100644 --- a/src/mip/HighsCutPool.cpp +++ b/src/mip/HighsCutPool.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsCutPool.h" diff --git a/src/mip/HighsCutPool.h b/src/mip/HighsCutPool.h index 811aff1239..7e63677e0b 100644 --- a/src/mip/HighsCutPool.h +++ b/src/mip/HighsCutPool.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_CUTPOOL_H_ #define HIGHS_CUTPOOL_H_ diff --git a/src/mip/HighsDebugSol.cpp b/src/mip/HighsDebugSol.cpp index dab4dc1624..88352dcb82 100644 --- a/src/mip/HighsDebugSol.cpp +++ b/src/mip/HighsDebugSol.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsDebugSol.h" diff --git a/src/mip/HighsDebugSol.h b/src/mip/HighsDebugSol.h index 891d367452..65a8a9f85a 100644 --- a/src/mip/HighsDebugSol.h +++ b/src/mip/HighsDebugSol.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsDebugSol.h * @brief Debug solution for MIP solver diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index 2ae36b9af6..214ceda6ba 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsDomain.h" @@ -1108,7 +1106,7 @@ void HighsDomain::ObjectivePropagation::propagate() { debugCheckObjectiveLower(); const double upperLimit = domain->mipsolver->mipdata_->upper_limit; - if (objectiveLower > upperLimit) { + if (numInfObjLower == 0 && objectiveLower > upperLimit) { domain->infeasible_ = true; domain->infeasible_pos = domain->domchgstack_.size(); domain->infeasible_reason = Reason::objective(); diff --git a/src/mip/HighsDomain.h b/src/mip/HighsDomain.h index 0da39ce427..70008b5f19 100644 --- a/src/mip/HighsDomain.h +++ b/src/mip/HighsDomain.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_DOMAIN_H_ #define HIGHS_DOMAIN_H_ @@ -480,7 +478,9 @@ class HighsDomain { } void fixCol(HighsInt col, double val, Reason reason = Reason::unspecified()) { - assert(infeasible_ == 0); + if (kAllowDeveloperAssert) { + assert(infeasible_ == 0); + } if (col_lower_[col] < val) { changeBound({val, col, HighsBoundType::kLower}, reason); if (infeasible_ == 0) propagate(); diff --git a/src/mip/HighsDomainChange.h b/src/mip/HighsDomainChange.h index 3b756dcd7e..1bec80f396 100644 --- a/src/mip/HighsDomainChange.h +++ b/src/mip/HighsDomainChange.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_DOMAIN_CHANGE_H_ diff --git a/src/mip/HighsDynamicRowMatrix.cpp b/src/mip/HighsDynamicRowMatrix.cpp index 48273d50d0..18e2645425 100644 --- a/src/mip/HighsDynamicRowMatrix.cpp +++ b/src/mip/HighsDynamicRowMatrix.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsDynamicRowMatrix.h" diff --git a/src/mip/HighsDynamicRowMatrix.h b/src/mip/HighsDynamicRowMatrix.h index 8f7f237050..244febf25d 100644 --- a/src/mip/HighsDynamicRowMatrix.h +++ b/src/mip/HighsDynamicRowMatrix.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_DYNAMIC_ROW_MATRIX_H_ #define HIGHS_DYNAMIC_ROW_MATRIX_H_ diff --git a/src/mip/HighsGFkSolve.cpp b/src/mip/HighsGFkSolve.cpp index 29a8f1fbc1..241c7368e7 100644 --- a/src/mip/HighsGFkSolve.cpp +++ b/src/mip/HighsGFkSolve.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsGFkSolve.h" diff --git a/src/mip/HighsGFkSolve.h b/src/mip/HighsGFkSolve.h index d3a3f2203d..d9b1bb8d19 100644 --- a/src/mip/HighsGFkSolve.h +++ b/src/mip/HighsGFkSolve.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsGFkLU.h * @brief linear system solve in GF(k) for mod-k cut separation diff --git a/src/mip/HighsImplications.cpp b/src/mip/HighsImplications.cpp index 16cf002eb9..e353dcf1e6 100644 --- a/src/mip/HighsImplications.cpp +++ b/src/mip/HighsImplications.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsImplications.h" @@ -521,7 +519,8 @@ void HighsImplications::separateImpliedBounds( // first do probing on all candidates that have not been probed yet if (!mipsolver.mipdata_->cliquetable.isFull()) { - auto oldNumQueries = mipsolver.mipdata_->cliquetable.numNeighborhoodQueries; + auto oldNumQueries = + mipsolver.mipdata_->cliquetable.numNeighbourhoodQueries; HighsInt oldNumEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); for (std::pair fracint : @@ -558,7 +557,7 @@ void HighsImplications::separateImpliedBounds( // printf("nextCleanupCall: %d\n", nextCleanupCall); } - mipsolver.mipdata_->cliquetable.numNeighborhoodQueries = oldNumQueries; + mipsolver.mipdata_->cliquetable.numNeighbourhoodQueries = oldNumQueries; } for (std::pair fracint : diff --git a/src/mip/HighsImplications.h b/src/mip/HighsImplications.h index 812ef7a82f..69328a055b 100644 --- a/src/mip/HighsImplications.h +++ b/src/mip/HighsImplications.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_IMPLICATIONS_H_ #define HIGHS_IMPLICATIONS_H_ diff --git a/src/mip/HighsLpAggregator.cpp b/src/mip/HighsLpAggregator.cpp index 00a2a728db..59e0cda0e0 100644 --- a/src/mip/HighsLpAggregator.cpp +++ b/src/mip/HighsLpAggregator.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsLpAggregator.h" diff --git a/src/mip/HighsLpAggregator.h b/src/mip/HighsLpAggregator.h index 2d6168611d..811d5a8e71 100644 --- a/src/mip/HighsLpAggregator.h +++ b/src/mip/HighsLpAggregator.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsLpAggregator.h * @brief Class to aggregate rows of the LP diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 327704cb77..7f3d683fad 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsLpRelaxation.h" @@ -1010,9 +1008,9 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { if (resolve_on_error) { // still an error: now try to solve with presolve from scratch lpsolver.setOptionValue("simplex_strategy", kSimplexStrategyDual); - lpsolver.setOptionValue("presolve", "on"); + lpsolver.setOptionValue("presolve", kHighsOnString); auto retval = run(false); - lpsolver.setOptionValue("presolve", "off"); + lpsolver.setOptionValue("presolve", kHighsOffString); return retval; } diff --git a/src/mip/HighsLpRelaxation.h b/src/mip/HighsLpRelaxation.h index cda1a09ce1..4377367e47 100644 --- a/src/mip/HighsLpRelaxation.h +++ b/src/mip/HighsLpRelaxation.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_LP_RELAXATION_H_ #define HIGHS_LP_RELAXATION_H_ diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index f25328f7f7..0548d9c63a 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsMipSolver.h" @@ -106,6 +104,10 @@ void HighsMipSolver::run() { modelstatus_ = HighsModelStatus::kNotset; // std::cout << options_mip_->presolve << std::endl; timer_.start(timer_.solve_clock); + improving_solution_file_ = nullptr; + if (!submip && options_mip_->mip_improving_solution_file != "") + improving_solution_file_ = + fopen(options_mip_->mip_improving_solution_file.c_str(), "w"); mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); mipdata_->init(); @@ -503,6 +505,7 @@ void HighsMipSolver::cleanupSolve() { dual_bound_ += model_->offset_; primal_bound_ = mipdata_->upper_bound + model_->offset_; node_count_ = mipdata_->num_nodes; + total_lp_iterations_ = mipdata_->total_lp_iterations; dual_bound_ = std::min(dual_bound_, primal_bound_); // adjust objective sense in case of maximization problem @@ -608,3 +611,17 @@ void HighsMipSolver::cleanupSolve() { assert(modelstatus_ != HighsModelStatus::kNotset); } + +void HighsMipSolver::runPresolve() { + mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); + mipdata_->init(); + mipdata_->runPresolve(); +} + +const HighsLp& HighsMipSolver::getPresolvedModel() const { + return mipdata_->presolvedModel; +} + +HighsPresolveStatus HighsMipSolver::getPresolveStatus() const { + return mipdata_->presolve_status; +} diff --git a/src/mip/HighsMipSolver.h b/src/mip/HighsMipSolver.h index b922228b5d..0c6a901357 100644 --- a/src/mip/HighsMipSolver.h +++ b/src/mip/HighsMipSolver.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef MIP_HIGHS_MIP_SOLVER_H_ #define MIP_HIGHS_MIP_SOLVER_H_ @@ -37,6 +35,10 @@ class HighsMipSolver { double primal_bound_; double gap_; int64_t node_count_; + int64_t total_lp_iterations_; + + FILE* improving_solution_file_; + std::vector saved_objective_and_solution_; bool submip; const HighsBasis* rootbasis; @@ -86,6 +88,10 @@ class HighsMipSolver { mutable HighsTimer timer_; void cleanupSolve(); + + void runPresolve(); + const HighsLp& getPresolvedModel() const; + HighsPresolveStatus getPresolveStatus() const; }; #endif diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 06d061d1c3..edafc240f9 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -2,19 +2,18 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsMipSolverData.h" #include -#include "lp_data/HighsLpUtils.h" +// #include "lp_data/HighsLpUtils.h" +#include "lp_data/HighsModelUtils.h" #include "mip/HighsPseudocost.h" #include "mip/HighsRedcostFixing.h" #include "parallel/HighsParallel.h" @@ -403,6 +402,7 @@ void HighsMipSolverData::runPresolve() { presolve::HPresolve presolve; presolve.setInput(mipsolver); mipsolver.modelstatus_ = presolve.run(postSolveStack); + presolve_status = presolve.getPresolveStatus(); mipsolver.timer_.stop(mipsolver.timer_.presolve_clock); #ifdef HIGHS_DEBUGSOL @@ -445,6 +445,7 @@ void HighsMipSolverData::runSetup() { if (feasible && solobj < upper_bound) { upper_bound = solobj; double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); + if (!mipsolver.submip) saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { upper_limit = new_upper_limit; optimality_limit = @@ -662,11 +663,16 @@ double HighsMipSolverData::transformNewIncumbent( const std::vector& sol) { HighsSolution solution; solution.col_value = sol; - calculateRowValuesQuad(*mipsolver.orig_model_, solution); solution.value_valid = true; - + // Perform primal postsolve to get the original column values postSolveStack.undoPrimal(*mipsolver.options_mip_, solution); - calculateRowValuesQuad(*mipsolver.orig_model_, solution); + // Determine the row values, as they aren't computed in primal + // postsolve + HighsInt first_check_row = + -1; // mipsolver.mipdata_->presolve.debugGetCheckRow(); + HighsStatus return_status = + calculateRowValuesQuad(*mipsolver.orig_model_, solution, first_check_row); + if (kAllowDeveloperAssert) assert(return_status == HighsStatus::kOk); bool allow_try_again = true; try_again: @@ -676,16 +682,30 @@ double HighsMipSolverData::transformNewIncumbent( double integrality_violation_ = 0; HighsCDouble obj = mipsolver.orig_model_->offset_; - assert((HighsInt)solution.col_value.size() == - mipsolver.orig_model_->num_col_); + if (kAllowDeveloperAssert) + assert((HighsInt)solution.col_value.size() == + mipsolver.orig_model_->num_col_); + HighsInt check_col = -1; + HighsInt check_int = -1; + HighsInt check_row = -1; + const bool debug_report = false; for (HighsInt i = 0; i != mipsolver.orig_model_->num_col_; ++i) { const double value = solution.col_value[i]; obj += mipsolver.orig_model_->col_cost_[i] * value; if (mipsolver.orig_model_->integrality_[i] == HighsVarType::kInteger) { double intval = std::floor(value + 0.5); + double integrality_infeasibility = std::fabs(intval - value); + if (integrality_infeasibility > + mipsolver.options_mip_->mip_feasibility_tolerance) { + if (debug_report) + printf("Col %d[%s] value %g has integrality infeasibility %g\n", + int(i), mipsolver.orig_model_->col_names_[i].c_str(), value, + integrality_infeasibility); + check_int = i; + } integrality_violation_ = - std::max(std::fabs(intval - value), integrality_violation_); + std::max(integrality_infeasibility, integrality_violation_); } const double lower = mipsolver.orig_model_->col_lower_[i]; @@ -698,7 +718,14 @@ double HighsMipSolverData::transformNewIncumbent( primal_infeasibility = value - upper; } else continue; - + if (primal_infeasibility > + mipsolver.options_mip_->primal_feasibility_tolerance) { + if (debug_report) + printf("Col %d[%s] [%g, %g, %g] has infeasibility %g\n", int(i), + mipsolver.orig_model_->col_names_[i].c_str(), lower, value, + upper, primal_infeasibility); + check_col = i; + } bound_violation_ = std::max(bound_violation_, primal_infeasibility); } @@ -714,7 +741,14 @@ double HighsMipSolverData::transformNewIncumbent( primal_infeasibility = value - upper; } else continue; - + if (primal_infeasibility > + mipsolver.options_mip_->primal_feasibility_tolerance) { + if (debug_report) + printf("Row %d[%s] [%g, %g, %g] has infeasibility %g\n", int(i), + mipsolver.orig_model_->row_names_[i].c_str(), lower, value, + upper, primal_infeasibility); + check_row = i; + } row_violation_ = std::max(row_violation_, primal_infeasibility); } @@ -747,7 +781,7 @@ double HighsMipSolverData::transformNewIncumbent( tmpSolver.passModel(std::move(fixedModel)); tmpSolver.run(); - if (tmpSolver.getInfo().primal_solution_status == 2) { + if (tmpSolver.getInfo().primal_solution_status == kSolutionStatusFeasible) { solution = tmpSolver.getSolution(); allow_try_again = false; goto try_again; @@ -773,12 +807,51 @@ double HighsMipSolverData::transformNewIncumbent( mipsolver.options_mip_->mip_feasibility_tolerance && mipsolver.row_violation_ <= mipsolver.options_mip_->mip_feasibility_tolerance; - highsLogUser( - mipsolver.options_mip_->log_options, HighsLogType::kWarning, - "Untransformed solution with objective %g is violated by %.12g for the " - "original model\n", - double(obj), - std::max({bound_violation_, integrality_violation_, row_violation_})); + // check_col = 37;//mipsolver.mipdata_->presolve.debugGetCheckCol(); + // check_row = 37;//mipsolver.mipdata_->presolve.debugGetCheckRow(); + std::string check_col_data = ""; + if (check_col >= 0) { + check_col_data = " (col " + std::to_string(check_col); + if (mipsolver.orig_model_->col_names_.size()) + check_col_data += + "[" + mipsolver.orig_model_->col_names_[check_col] + "]"; + check_col_data += ")"; + } + std::string check_int_data = ""; + if (check_int >= 0) { + check_int_data = " (col " + std::to_string(check_int); + if (mipsolver.orig_model_->col_names_.size()) + check_int_data += + "[" + mipsolver.orig_model_->col_names_[check_int] + "]"; + check_int_data += ")"; + } + std::string check_row_data = ""; + if (check_row >= 0) { + check_row_data = " (row " + std::to_string(check_row); + if (mipsolver.orig_model_->row_names_.size()) + check_row_data += + "[" + mipsolver.orig_model_->row_names_[check_row] + "]"; + check_row_data += ")"; + } + highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kWarning, + // printf( + "Solution with objective %g has untransformed violations: " + "bound = %.4g%s; integrality = %.4g%s; row = %.4g%s\n", + double(obj), bound_violation_, check_col_data.c_str(), + integrality_violation_, check_int_data.c_str(), row_violation_, + check_row_data.c_str()); + + const bool debug_repeat = false; // true;// + if (debug_repeat) { + HighsSolution check_solution; + check_solution.col_value = sol; + check_solution.value_valid = true; + postSolveStack.undoPrimal(*mipsolver.options_mip_, check_solution, + check_col); + fflush(stdout); + if (kAllowDeveloperAssert) assert(111 == 999); + } + if (!currentFeasible) { // if the current incumbent is non existent or also not feasible we still // store the new one @@ -936,6 +1009,7 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, incumbent = sol; double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); + if (!mipsolver.submip) saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { ++numImprovingSols; upper_limit = new_upper_limit; @@ -1585,6 +1659,9 @@ void HighsMipSolverData::evaluateRootNode() { } bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { + // ToDo Add user termination callback here - + // if (!mipsolver.submip) Callbackfor termination + const HighsOptions& options = *mipsolver.options_mip_; if (options.mip_max_nodes != kHighsIInf && @@ -1662,3 +1739,28 @@ void HighsMipSolverData::setupDomainPropagation() { domain = HighsDomain(mipsolver); domain.computeRowActivities(); } + +void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { + const bool non_improving = new_upper_limit >= upper_limit; + /* + printf( + "MIP %4simproving solution: numImprovingSols = %4d; Limits (%11.4g, " + "%11.4g)\n", + non_improving ? "non-" : "", int(numImprovingSols), new_upper_limit, + upper_limit); + */ + if (non_improving) return; + if (mipsolver.options_mip_->mip_improving_solution_save) { + HighsObjectiveSolution record; + record.objective = mipsolver.solution_objective_; + record.col_value = mipsolver.solution_; + mipsolver.saved_objective_and_solution_.push_back(record); + } + FILE* file = mipsolver.improving_solution_file_; + if (file) { + writeLpObjective(file, *(mipsolver.orig_model_), mipsolver.solution_); + writePrimalSolution( + file, *(mipsolver.orig_model_), mipsolver.solution_, + mipsolver.options_mip_->mip_improving_solution_report_sparse); + } +} diff --git a/src/mip/HighsMipSolverData.h b/src/mip/HighsMipSolverData.h index 56c5511614..c49d0abbfa 100644 --- a/src/mip/HighsMipSolverData.h +++ b/src/mip/HighsMipSolverData.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_MIP_SOLVER_DATA_H_ @@ -48,6 +46,7 @@ struct HighsMipSolverData { HighsRedcostFixing redcostfixing; HighsObjectiveFunction objectiveFunction; presolve::HighsPostsolveStack postSolveStack; + HighsPresolveStatus presolve_status; HighsLp presolvedModel; bool cliquesExtracted; bool rowMatrixSet; @@ -159,6 +158,7 @@ struct HighsMipSolverData { void checkObjIntegrality(); void runPresolve(); void setupDomainPropagation(); + void saveReportMipSolution(const double new_upper_limit); void runSetup(); double transformNewIncumbent(const std::vector& sol); double percentageInactiveIntegers() const; diff --git a/src/mip/HighsModkSeparator.cpp b/src/mip/HighsModkSeparator.cpp index 24f310e2ba..390cda504b 100644 --- a/src/mip/HighsModkSeparator.cpp +++ b/src/mip/HighsModkSeparator.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsModKSeparator.cpp */ diff --git a/src/mip/HighsModkSeparator.h b/src/mip/HighsModkSeparator.h index d802f4fd71..b899d4a3f6 100644 --- a/src/mip/HighsModkSeparator.h +++ b/src/mip/HighsModkSeparator.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsModkSeparator.h * @brief Class for separating maximally violated mod-k MIR cuts. diff --git a/src/mip/HighsNodeQueue.cpp b/src/mip/HighsNodeQueue.cpp index 5591d30f86..fa762b8031 100644 --- a/src/mip/HighsNodeQueue.cpp +++ b/src/mip/HighsNodeQueue.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsNodeQueue.h" @@ -421,4 +419,28 @@ HighsInt HighsNodeQueue::getBestBoundDomchgStackSize() const { return std::min(HighsInt(nodes[suboptimalMin].domchgstack.size()), domchgStackSize); -} \ No newline at end of file +} + +void HighsNodeQueue::clear() { + const bool original = false; + HighsNodeQueue nodequeue; + nodequeue.setNumCol(numCol); + if (original) { + *this = std::move(nodequeue); + } else { + (*this).nodes = std::move(nodequeue.nodes); + (*this).colLowerNodesPtr = std::move(nodequeue.colLowerNodesPtr); + (*this).colUpperNodesPtr = std::move(nodequeue.colUpperNodesPtr); + (*this).freeslots = std::move(nodequeue.freeslots); + (*this).allocatorState = std::move(nodequeue.allocatorState); + (*this).lowerRoot = nodequeue.lowerRoot; + (*this).lowerMin = nodequeue.lowerMin; + (*this).hybridEstimRoot = nodequeue.hybridEstimRoot; + (*this).hybridEstimMin = nodequeue.hybridEstimMin; + (*this).suboptimalRoot = nodequeue.suboptimalRoot; + (*this).suboptimalMin = nodequeue.suboptimalMin; + (*this).numSuboptimal = nodequeue.numSuboptimal; + (*this).optimality_limit = nodequeue.optimality_limit; + (*this).numCol = nodequeue.numCol; + } +} diff --git a/src/mip/HighsNodeQueue.h b/src/mip/HighsNodeQueue.h index bdf1db7363..3417310d85 100644 --- a/src/mip/HighsNodeQueue.h +++ b/src/mip/HighsNodeQueue.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_NODE_QUEUE_H_ @@ -288,11 +286,7 @@ class HighsNodeQueue { HighsInt getBestBoundDomchgStackSize() const; - void clear() { - HighsNodeQueue nodequeue; - nodequeue.setNumCol(numCol); - *this = std::move(nodequeue); - } + void clear(); int64_t numNodes() const { return nodes.size() - freeslots.size(); } diff --git a/src/mip/HighsObjectiveFunction.cpp b/src/mip/HighsObjectiveFunction.cpp index 04f53b1521..3e396a7033 100644 --- a/src/mip/HighsObjectiveFunction.cpp +++ b/src/mip/HighsObjectiveFunction.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsObjectiveFunction.h" diff --git a/src/mip/HighsObjectiveFunction.h b/src/mip/HighsObjectiveFunction.h index 6037db6f05..78bcaf0757 100644 --- a/src/mip/HighsObjectiveFunction.h +++ b/src/mip/HighsObjectiveFunction.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_OBJECTIVE_FUNCTION_H_ #define HIGHS_OBJECTIVE_FUNCTION_H_ diff --git a/src/mip/HighsPathSeparator.cpp b/src/mip/HighsPathSeparator.cpp index 0ee92b63e6..2923fa2d70 100644 --- a/src/mip/HighsPathSeparator.cpp +++ b/src/mip/HighsPathSeparator.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsPathSeparator.cpp */ diff --git a/src/mip/HighsPathSeparator.h b/src/mip/HighsPathSeparator.h index d5b8e960e3..5e54c47d35 100644 --- a/src/mip/HighsPathSeparator.h +++ b/src/mip/HighsPathSeparator.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsPathSeparator.h * @brief Class for separating cuts from heuristically aggregating rows from the diff --git a/src/mip/HighsPrimalHeuristics.cpp b/src/mip/HighsPrimalHeuristics.cpp index aa007015b2..05d1739d8b 100644 --- a/src/mip/HighsPrimalHeuristics.cpp +++ b/src/mip/HighsPrimalHeuristics.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsPrimalHeuristics.h" @@ -26,6 +24,16 @@ #include "util/HighsHash.h" #include "util/HighsIntegers.h" +// GCC floating point errors are well-known for 32-bit architectures; +// see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323. +// An easy workaround is to add the "volatile" keyword to avoid +// problematic GCC optimizations that impact precision. +#ifdef __i386__ +#define FP_32BIT_VOLATILE volatile +#else +#define FP_32BIT_VOLATILE +#endif + HighsPrimalHeuristics::HighsPrimalHeuristics(HighsMipSolver& mipsolver) : mipsolver(mipsolver), lp_iterations(0), @@ -40,24 +48,24 @@ void HighsPrimalHeuristics::setupIntCols() { intcols = mipsolver.mipdata_->integer_cols; pdqsort(intcols.begin(), intcols.end(), [&](HighsInt c1, HighsInt c2) { - double lockScore1 = + const FP_32BIT_VOLATILE double lockScore1 = (mipsolver.mipdata_->feastol + mipsolver.mipdata_->uplocks[c1]) * (mipsolver.mipdata_->feastol + mipsolver.mipdata_->downlocks[c1]); - double lockScore2 = + const FP_32BIT_VOLATILE double lockScore2 = (mipsolver.mipdata_->feastol + mipsolver.mipdata_->uplocks[c2]) * (mipsolver.mipdata_->feastol + mipsolver.mipdata_->downlocks[c2]); if (lockScore1 > lockScore2) return true; if (lockScore2 > lockScore1) return false; - double cliqueScore1 = + const FP_32BIT_VOLATILE double cliqueScore1 = (mipsolver.mipdata_->feastol + mipsolver.mipdata_->cliquetable.getNumImplications(c1, 1)) * (mipsolver.mipdata_->feastol + mipsolver.mipdata_->cliquetable.getNumImplications(c1, 0)); - double cliqueScore2 = + const FP_32BIT_VOLATILE double cliqueScore2 = (mipsolver.mipdata_->feastol + mipsolver.mipdata_->cliquetable.getNumImplications(c2, 1)) * (mipsolver.mipdata_->feastol + @@ -86,6 +94,16 @@ bool HighsPrimalHeuristics::solveSubMip( // set limits submipoptions.mip_max_leaves = maxleaves; submipoptions.output_flag = false; + + const bool allow_submip_log = true; + if (allow_submip_log && lp.num_col_ == -54 && lp.num_row_ == -172) { + submipoptions.output_flag = true; + printf( + "HighsPrimalHeuristics::solveSubMip (%d, %d) with output_flag = %s\n", + int(lp.num_col_), int(lp.num_row_), + highsBoolToString(submipoptions.output_flag).c_str()); + } + submipoptions.mip_max_nodes = maxnodes; submipoptions.mip_max_stall_nodes = stallnodes; submipoptions.mip_pscost_minreliable = 0; @@ -179,7 +197,7 @@ double HighsPrimalHeuristics::determineTargetFixingRate() { return fixingRate; } -class HeuristicNeighborhood { +class HeuristicNeighbourhood { HighsDomain& localdom; HighsInt numFixed; HighsHashTable fixedCols; @@ -188,7 +206,7 @@ class HeuristicNeighborhood { HighsInt numTotal; public: - HeuristicNeighborhood(HighsMipSolver& mipsolver, HighsDomain& localdom) + HeuristicNeighbourhood(HighsMipSolver& mipsolver, HighsDomain& localdom) : localdom(localdom), numFixed(0), startCheckedChanges(localdom.getDomainChangeStack().size()), @@ -228,7 +246,7 @@ void HighsPrimalHeuristics::rootReducedCost() { auto localdom = mipsolver.mipdata_->domain; - HeuristicNeighborhood neighborhood(mipsolver, localdom); + HeuristicNeighbourhood neighbourhood(mipsolver, localdom); double currCutoff = kHighsInf; double lower_bound; @@ -251,19 +269,19 @@ void HighsPrimalHeuristics::rootReducedCost() { std::max(mipsolver.mipdata_->lower_bound, currCutoff); localdom.backtrack(); if (localdom.getBranchDepth() == 0) break; - neighborhood.backtracked(); + neighbourhood.backtracked(); continue; } break; } - double fixingRate = neighborhood.getFixingRate(); + double fixingRate = neighbourhood.getFixingRate(); if (fixingRate >= 0.5) break; // double gap = (currCutoff - mipsolver.mipdata_->lower_bound) / // std::max(std::abs(mipsolver.mipdata_->lower_bound), 1.0); // if (gap < 0.001) break; } - double fixingRate = neighborhood.getFixingRate(); + double fixingRate = neighbourhood.getFixingRate(); if (fixingRate < 0.3) return; solveSubMip(*mipsolver.model_, mipsolver.mipdata_->firstrootbasis, fixingRate, @@ -307,10 +325,10 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // heurlp.getLpSolver().getOptions().simplex_iteration_limit); HighsInt targetdepth = 1; HighsInt nbacktracks = -1; - HeuristicNeighborhood neighborhood(mipsolver, localdom); + HeuristicNeighbourhood neighbourhood(mipsolver, localdom); retry: ++nbacktracks; - neighborhood.backtracked(); + neighbourhood.backtracked(); // printf("current depth : %" HIGHSINT_FORMAT // " target depth : %" HIGHSINT_FORMAT "\n", // heur.getCurrentDepth(), targetdepth); @@ -335,11 +353,11 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { } if (!heur.backtrack()) break; - neighborhood.backtracked(); + neighbourhood.backtracked(); continue; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); // printf("after evaluating node current fixingrate is %g\n", fixingrate); if (fixingrate >= maxfixingrate) break; if (stop) break; @@ -347,7 +365,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { HighsInt numBranched = 0; double stopFixingRate = std::min( - 1.0 - (1.0 - neighborhood.getFixingRate()) * 0.9, maxfixingrate); + 1.0 - (1.0 - neighbourhood.getFixingRate()) * 0.9, maxfixingrate); const auto& relaxationsol = heurlp.getSolution().col_value; for (HighsInt i : intcols) { if (localdom.col_lower_[i] == localdom.col_upper_[i]) continue; @@ -377,7 +395,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { } } - if (neighborhood.getFixingRate() >= stopFixingRate) break; + if (neighbourhood.getFixingRate() >= stopFixingRate) break; } if (numBranched == 0) { @@ -437,7 +455,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { break; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); } if (localdom.col_upper_[fracint.first] > fixval) { @@ -449,7 +467,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { break; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); } if (fixingrate >= maxfixingrate) break; @@ -473,7 +491,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // determine the fixing rate to decide if the problem is restricted enough to // be considered for solving a submip - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); // printf("fixing rate is %g\n", fixingrate); if (fixingrate < 0.1 || (mipsolver.submip && mipsolver.mipdata_->numImprovingSols != 0)) { @@ -551,10 +569,10 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { bool stop = false; HighsInt nbacktracks = -1; HighsInt targetdepth = 1; - HeuristicNeighborhood neighborhood(mipsolver, localdom); + HeuristicNeighbourhood neighbourhood(mipsolver, localdom); retry: ++nbacktracks; - neighborhood.backtracked(); + neighbourhood.backtracked(); // printf("current depth : %" HIGHSINT_FORMAT " target depth : %" // HIGHSINT_FORMAT "\n", heur.getCurrentDepth(), // targetdepth); @@ -578,11 +596,11 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { } if (!heur.backtrack()) break; - neighborhood.backtracked(); + neighbourhood.backtracked(); continue; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); if (stop) break; if (fixingrate >= maxfixingrate) break; @@ -592,7 +610,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // partition the fractional variables to consider which ones should we fix // in this dive first if there is an incumbent, we dive towards the RINS - // neighborhood + // neighbourhood fixcandend = std::partition( heurlp.getFractionalIntegers().begin(), heurlp.getFractionalIntegers().end(), @@ -607,7 +625,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { auto getFixVal = [&](HighsInt col, double fracval) { double fixval; if (fixtolpsol) { - // RINS neighborhood (with extension) + // RINS neighbourhood (with extension) fixval = std::floor(relaxationsol[col] + 0.5); } else { // reinforce direction of this solution away from root @@ -632,12 +650,12 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { return fixval; }; - // no candidates left to fix for getting to the neighborhood, therefore we + // no candidates left to fix for getting to the neighbourhood, therefore we // switch to a different diving strategy until the minimal fixing rate is // reached HighsInt numBranched = 0; if (heurlp.getFractionalIntegers().begin() == fixcandend) { - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); double stopFixingRate = std::min(maxfixingrate, 1.0 - (1.0 - fixingrate) * 0.9); const auto& currlpsol = heurlp.getSolution().col_value; @@ -657,7 +675,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { break; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); } if (localdom.col_upper_[i] > fixval) { ++numBranched; @@ -668,7 +686,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { break; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); } if (fixingrate >= stopFixingRate) break; @@ -724,7 +742,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { break; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); } if (localdom.col_upper_[fracint->first] > fixval) { @@ -735,7 +753,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { break; } - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); } if (fixingrate >= maxfixingrate) break; @@ -762,7 +780,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // to be considered for solving a submip // printf("fixing rate is %g\n", fixingrate); - fixingrate = neighborhood.getFixingRate(); + fixingrate = neighbourhood.getFixingRate(); if (fixingrate < 0.1 || (mipsolver.submip && mipsolver.mipdata_->numImprovingSols != 0)) { // heur.childselrule = ChildSelectionRule::kBestCost; diff --git a/src/mip/HighsPrimalHeuristics.h b/src/mip/HighsPrimalHeuristics.h index 15c4202ec8..31792c5de6 100644 --- a/src/mip/HighsPrimalHeuristics.h +++ b/src/mip/HighsPrimalHeuristics.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_PRIMAL_HEURISTICS_H_ #define HIGHS_PRIMAL_HEURISTICS_H_ diff --git a/src/mip/HighsPseudocost.cpp b/src/mip/HighsPseudocost.cpp index d00d3e75f3..b88cf2321c 100644 --- a/src/mip/HighsPseudocost.cpp +++ b/src/mip/HighsPseudocost.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsPseudocost.h" diff --git a/src/mip/HighsPseudocost.h b/src/mip/HighsPseudocost.h index e380a45561..def030bf34 100644 --- a/src/mip/HighsPseudocost.h +++ b/src/mip/HighsPseudocost.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_PSEUDOCOST_H_ diff --git a/src/mip/HighsRedcostFixing.cpp b/src/mip/HighsRedcostFixing.cpp index 372724f527..80fe4adcbb 100644 --- a/src/mip/HighsRedcostFixing.cpp +++ b/src/mip/HighsRedcostFixing.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsRedcostFixing.h" diff --git a/src/mip/HighsRedcostFixing.h b/src/mip/HighsRedcostFixing.h index 5d48f5e3b5..3984721b49 100644 --- a/src/mip/HighsRedcostFixing.h +++ b/src/mip/HighsRedcostFixing.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsRedcostFixing.h * @brief reduced cost fixing using the current cutoff bound diff --git a/src/mip/HighsSearch.cpp b/src/mip/HighsSearch.cpp index bdd8bc7d90..f6fbf501e1 100644 --- a/src/mip/HighsSearch.cpp +++ b/src/mip/HighsSearch.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsSearch.h" @@ -274,8 +272,39 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, HighsInt col = fracints[k].first; double fracval = fracints[k].second; - assert(fracval > localdom.col_lower_[col] + mipsolver.mipdata_->feastol); - assert(fracval < localdom.col_upper_[col] - mipsolver.mipdata_->feastol); + const double lower_residual = + (fracval - localdom.col_lower_[col]) - mipsolver.mipdata_->feastol; + const bool lower_ok = lower_residual > 0; + if (!lower_ok) + highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kError, + "HighsSearch::selectBranchingCandidate Error fracval = %g " + "<= %g = %g + %g = " + "localdom.col_lower_[col] + mipsolver.mipdata_->feastol: " + "Residual %g\n", + fracval, + localdom.col_lower_[col] + mipsolver.mipdata_->feastol, + localdom.col_lower_[col], mipsolver.mipdata_->feastol, + lower_residual); + + const double upper_residual = + (localdom.col_upper_[col] - fracval) - mipsolver.mipdata_->feastol; + const bool upper_ok = upper_residual > 0; + if (!upper_ok) + highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kError, + "HighsSearch::selectBranchingCandidate Error fracval = %g " + ">= %g = %g - %g = " + "localdom.col_upper_[col] - mipsolver.mipdata_->feastol: " + "Residual %g\n", + fracval, + localdom.col_upper_[col] - mipsolver.mipdata_->feastol, + localdom.col_upper_[col], mipsolver.mipdata_->feastol, + upper_residual); + + assert(lower_residual > -1e-12 && upper_residual > -1e-12); + + // assert(fracval > localdom.col_lower_[col] + + // mipsolver.mipdata_->feastol); assert(fracval < + // localdom.col_upper_[col] - mipsolver.mipdata_->feastol); if (pseudocost.isReliable(col)) { upscore[k] = pseudocost.getPseudocostUp(col, fracval); diff --git a/src/mip/HighsSearch.h b/src/mip/HighsSearch.h index eab32f68bc..65e7287df2 100644 --- a/src/mip/HighsSearch.h +++ b/src/mip/HighsSearch.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_SEARCH_H_ #define HIGHS_SEARCH_H_ diff --git a/src/mip/HighsSeparation.cpp b/src/mip/HighsSeparation.cpp index 4237f3331e..29ba58c162 100644 --- a/src/mip/HighsSeparation.cpp +++ b/src/mip/HighsSeparation.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsSeparation.h" diff --git a/src/mip/HighsSeparation.h b/src/mip/HighsSeparation.h index 2095522eff..b7634d4633 100644 --- a/src/mip/HighsSeparation.h +++ b/src/mip/HighsSeparation.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_SEPARATION_H_ #define HIGHS_SEPARATION_H_ diff --git a/src/mip/HighsSeparator.cpp b/src/mip/HighsSeparator.cpp index b39e7077da..ab9a14d382 100644 --- a/src/mip/HighsSeparator.cpp +++ b/src/mip/HighsSeparator.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsSeparator.h" diff --git a/src/mip/HighsSeparator.h b/src/mip/HighsSeparator.h index c10f45ba80..1607f5de93 100644 --- a/src/mip/HighsSeparator.h +++ b/src/mip/HighsSeparator.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsSeparator.h * @brief Base class for separators diff --git a/src/mip/HighsTableauSeparator.cpp b/src/mip/HighsTableauSeparator.cpp index 543dc24be9..86d47dd77b 100644 --- a/src/mip/HighsTableauSeparator.cpp +++ b/src/mip/HighsTableauSeparator.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsTableauSeparator.cpp */ diff --git a/src/mip/HighsTableauSeparator.h b/src/mip/HighsTableauSeparator.h index 731f530f6d..7fed2d88ee 100644 --- a/src/mip/HighsTableauSeparator.h +++ b/src/mip/HighsTableauSeparator.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsTableauSeparator.h * @brief Class for separating cuts from the LP tableaux rows diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 49a2e119fc..3c95f4f44e 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsTransformedLp.h" diff --git a/src/mip/HighsTransformedLp.h b/src/mip/HighsTransformedLp.h index 99c5485d9f..90aa59da68 100644 --- a/src/mip/HighsTransformedLp.h +++ b/src/mip/HighsTransformedLp.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsTransformedLp.h * @brief LP transformations useful for cutting plane separation. This includes diff --git a/src/model/HighsHessian.cpp b/src/model/HighsHessian.cpp index 5b93e9cb76..5de783017d 100644 --- a/src/model/HighsHessian.cpp +++ b/src/model/HighsHessian.cpp @@ -72,7 +72,7 @@ void HighsHessian::print() const { col[this->index_[iEl]] = 0; } } -bool HighsHessian::operator==(const HighsHessian& hessian) { +bool HighsHessian::operator==(const HighsHessian& hessian) const { bool equal = true; equal = this->dim_ == hessian.dim_ && equal; equal = this->start_ == hessian.start_ && equal; @@ -108,3 +108,19 @@ double HighsHessian::objectiveValue(const std::vector& solution) const { } return objective_function_value; } + +HighsCDouble HighsHessian::objectiveCDoubleValue( + const std::vector& solution) const { + HighsCDouble objective_function_value = HighsCDouble(0); + for (HighsInt iCol = 0; iCol < this->dim_; iCol++) { + HighsInt iEl = this->start_[iCol]; + assert(this->index_[iEl] == iCol); + objective_function_value += + 0.5 * solution[iCol] * this->value_[iEl] * solution[iCol]; + for (HighsInt iEl = this->start_[iCol] + 1; iEl < this->start_[iCol + 1]; + iEl++) + objective_function_value += + solution[iCol] * this->value_[iEl] * solution[this->index_[iEl]]; + } + return objective_function_value; +} diff --git a/src/model/HighsHessian.h b/src/model/HighsHessian.h index 7327563971..3002f561ec 100644 --- a/src/model/HighsHessian.h +++ b/src/model/HighsHessian.h @@ -19,6 +19,7 @@ #include #include "lp_data/HConst.h" +#include "util/HighsCDouble.h" // class HighsHessian; @@ -30,10 +31,11 @@ class HighsHessian { std::vector start_; std::vector index_; std::vector value_; - bool operator==(const HighsHessian& hessian); + bool operator==(const HighsHessian& hessian) const; void product(const std::vector& solution, std::vector& product) const; double objectiveValue(const std::vector& solution) const; + HighsCDouble objectiveCDoubleValue(const std::vector& solution) const; void exactResize(); void clear(); bool formatOk() const { diff --git a/src/model/HighsModel.cpp b/src/model/HighsModel.cpp index d717954dcb..ccc1f159e4 100644 --- a/src/model/HighsModel.cpp +++ b/src/model/HighsModel.cpp @@ -17,6 +17,18 @@ #include +bool HighsModel::operator==(const HighsModel& model) const { + bool equal = equalButForNames(model); + equal = this->lp_.equalNames(model.lp_) && equal; + return equal; +} + +bool HighsModel::equalButForNames(const HighsModel& model) const { + bool equal = this->lp_.equalButForNames(model.lp_); + equal = this->hessian_ == model.hessian_ && equal; + return equal; +} + void HighsModel::clear() { this->lp_.clear(); this->hessian_.clear(); diff --git a/src/model/HighsModel.h b/src/model/HighsModel.h index 02f2faa5f1..4f089d2751 100644 --- a/src/model/HighsModel.h +++ b/src/model/HighsModel.h @@ -27,6 +27,8 @@ class HighsModel { public: HighsLp lp_; HighsHessian hessian_; + bool operator==(const HighsModel& model) const; + bool equalButForNames(const HighsModel& model) const; bool isQp() const { return this->hessian_.dim_; } bool isMip() const { return this->lp_.isMip(); } bool isEmpty() const { diff --git a/src/parallel/HighsSpinMutex.h b/src/parallel/HighsSpinMutex.h index 47c58e639a..cbd6a3572d 100644 --- a/src/parallel/HighsSpinMutex.h +++ b/src/parallel/HighsSpinMutex.h @@ -32,6 +32,7 @@ class HighsSpinMutex { #ifdef HIGHS_HAVE_MM_PAUSE _mm_pause(); #else + // ToDo: See if this is OK on Mac M1 std::this_thread::yield(); #endif } @@ -49,4 +50,4 @@ class HighsSpinMutex { void unlock() { flag.store(false, std::memory_order_release); } }; -#endif \ No newline at end of file +#endif diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 9d9dfbbe18..08aa0fef18 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "presolve/HPresolve.h" @@ -118,7 +116,13 @@ void HPresolve::setInput(HighsLp& model_, const HighsOptions& options_, changedColIndices.reserve(model->num_col_); numDeletedCols = 0; numDeletedRows = 0; - reductionLimit = std::numeric_limits::max(); + reductionLimit = options->presolve_reduction_limit < 0 + ? kHighsSize_tInf + : options->presolve_reduction_limit; + if (options->presolve != kHighsOffString && reductionLimit < kHighsSize_tInf) + highsLogUser(options->log_options, HighsLogType::kInfo, + "HPresolve::setInput reductionLimit = %d\n", + int(reductionLimit)); } // for MIP presolve @@ -657,26 +661,30 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { HighsInt oldNumCol = model->num_col_; model->num_col_ = 0; std::vector newColIndex(oldNumCol); + const bool have_col_names = model->col_names_.size() > 0; + assert(!have_col_names || HighsInt(model->col_names_.size()) == oldNumCol); for (HighsInt i = 0; i != oldNumCol; ++i) { if (colDeleted[i]) newColIndex[i] = -1; else { newColIndex[i] = model->num_col_++; - model->col_cost_[newColIndex[i]] = model->col_cost_[i]; - model->col_lower_[newColIndex[i]] = model->col_lower_[i]; - model->col_upper_[newColIndex[i]] = model->col_upper_[i]; - assert(!std::isnan(model->col_lower_[newColIndex[i]])); - assert(!std::isnan(model->col_upper_[newColIndex[i]])); - model->integrality_[newColIndex[i]] = model->integrality_[i]; - implColLower[newColIndex[i]] = implColLower[i]; - implColUpper[newColIndex[i]] = implColUpper[i]; - colLowerSource[newColIndex[i]] = colLowerSource[i]; - colUpperSource[newColIndex[i]] = colUpperSource[i]; - colhead[newColIndex[i]] = colhead[i]; - colsize[newColIndex[i]] = colsize[i]; - if ((HighsInt)model->col_names_.size() > 0) - model->col_names_[newColIndex[i]] = std::move(model->col_names_[i]); - changedColFlag[newColIndex[i]] = changedColFlag[i]; + if (newColIndex[i] < i) { + model->col_cost_[newColIndex[i]] = model->col_cost_[i]; + model->col_lower_[newColIndex[i]] = model->col_lower_[i]; + model->col_upper_[newColIndex[i]] = model->col_upper_[i]; + assert(!std::isnan(model->col_lower_[newColIndex[i]])); + assert(!std::isnan(model->col_upper_[newColIndex[i]])); + model->integrality_[newColIndex[i]] = model->integrality_[i]; + implColLower[newColIndex[i]] = implColLower[i]; + implColUpper[newColIndex[i]] = implColUpper[i]; + colLowerSource[newColIndex[i]] = colLowerSource[i]; + colUpperSource[newColIndex[i]] = colUpperSource[i]; + colhead[newColIndex[i]] = colhead[i]; + colsize[newColIndex[i]] = colsize[i]; + if (have_col_names) + model->col_names_[newColIndex[i]] = std::move(model->col_names_[i]); + changedColFlag[newColIndex[i]] = changedColFlag[i]; + } } } colDeleted.assign(model->num_col_, false); @@ -690,11 +698,12 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { colUpperSource.resize(model->num_col_); colhead.resize(model->num_col_); colsize.resize(model->num_col_); - if ((HighsInt)model->col_names_.size() > 0) - model->col_names_.resize(model->num_col_); + if (have_col_names) model->col_names_.resize(model->num_col_); changedColFlag.resize(model->num_col_); numDeletedCols = 0; HighsInt oldNumRow = model->num_row_; + const bool have_row_names = model->row_names_.size() > 0; + assert(!have_row_names || HighsInt(model->row_names_.size()) == oldNumRow); model->num_row_ = 0; std::vector newRowIndex(oldNumRow); for (HighsInt i = 0; i != oldNumRow; ++i) { @@ -702,26 +711,27 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { newRowIndex[i] = -1; else { newRowIndex[i] = model->num_row_++; - model->row_lower_[newRowIndex[i]] = model->row_lower_[i]; - model->row_upper_[newRowIndex[i]] = model->row_upper_[i]; - assert(!std::isnan(model->row_lower_[newRowIndex[i]])); - assert(!std::isnan(model->row_upper_[newRowIndex[i]])); - rowDualLower[newRowIndex[i]] = rowDualLower[i]; - rowDualUpper[newRowIndex[i]] = rowDualUpper[i]; - implRowDualLower[newRowIndex[i]] = implRowDualLower[i]; - implRowDualUpper[newRowIndex[i]] = implRowDualUpper[i]; - rowDualLowerSource[newRowIndex[i]] = rowDualLowerSource[i]; - rowDualUpperSource[newRowIndex[i]] = rowDualUpperSource[i]; - rowroot[newRowIndex[i]] = rowroot[i]; - rowsize[newRowIndex[i]] = rowsize[i]; - rowsizeInteger[newRowIndex[i]] = rowsizeInteger[i]; - rowsizeImplInt[newRowIndex[i]] = rowsizeImplInt[i]; - if ((HighsInt)model->row_names_.size() > 0) - model->row_names_[newRowIndex[i]] = std::move(model->row_names_[i]); - changedRowFlag[newRowIndex[i]] = changedRowFlag[i]; + if (newRowIndex[i] < i) { + model->row_lower_[newRowIndex[i]] = model->row_lower_[i]; + model->row_upper_[newRowIndex[i]] = model->row_upper_[i]; + assert(!std::isnan(model->row_lower_[newRowIndex[i]])); + assert(!std::isnan(model->row_upper_[newRowIndex[i]])); + rowDualLower[newRowIndex[i]] = rowDualLower[i]; + rowDualUpper[newRowIndex[i]] = rowDualUpper[i]; + implRowDualLower[newRowIndex[i]] = implRowDualLower[i]; + implRowDualUpper[newRowIndex[i]] = implRowDualUpper[i]; + rowDualLowerSource[newRowIndex[i]] = rowDualLowerSource[i]; + rowDualUpperSource[newRowIndex[i]] = rowDualUpperSource[i]; + rowroot[newRowIndex[i]] = rowroot[i]; + rowsize[newRowIndex[i]] = rowsize[i]; + rowsizeInteger[newRowIndex[i]] = rowsizeInteger[i]; + rowsizeImplInt[newRowIndex[i]] = rowsizeImplInt[i]; + if (have_row_names) + model->row_names_[newRowIndex[i]] = std::move(model->row_names_[i]); + changedRowFlag[newRowIndex[i]] = changedRowFlag[i]; + } } } - for (HighsInt i = 0; i != model->num_col_; ++i) { if (colLowerSource[i] != -1) colLowerSource[i] = newRowIndex[colLowerSource[i]]; @@ -749,8 +759,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { rowsize.resize(model->num_row_); rowsizeInteger.resize(model->num_row_); rowsizeImplInt.resize(model->num_row_); - if ((HighsInt)model->row_names_.size() > 0) - model->row_names_.resize(model->num_row_); + if (have_row_names) model->row_names_.resize(model->num_row_); changedRowFlag.resize(model->num_row_); numDeletedRows = 0; @@ -1336,7 +1345,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { implications.substitutions.size() + cliquetable.getSubstitutions().size(); int64_t splayContingent = - cliquetable.numNeighborhoodQueries + + cliquetable.numNeighbourhoodQueries + std::max(mipsolver->submip ? HighsInt{0} : HighsInt{100000}, 10 * numNonzeros()); HighsInt numFail = 0; @@ -1368,11 +1377,11 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { // if (numProbed % 10 == 0) // printf( // "numprobed=%d numDel=%d newcliques=%d " - // "numNeighborhoodQueries=%ld " + // "numNeighbourhoodQueries=%ld " // "splayContingent=%ld\n", // numProbed, numDel, cliquetable.numCliques() - numCliquesStart, - // cliquetable.numNeighborhoodQueries, splayContingent); - if (cliquetable.numNeighborhoodQueries > splayContingent) break; + // cliquetable.numNeighbourhoodQueries, splayContingent); + if (cliquetable.numNeighbourhoodQueries > splayContingent) break; if (probingContingent - numProbed < 0) break; @@ -1482,7 +1491,8 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { return checkLimits(postsolve_stack); } -void HPresolve::addToMatrix(HighsInt row, HighsInt col, double val) { +void HPresolve::addToMatrix(const HighsInt row, const HighsInt col, + const double val) { HighsInt pos = findNonzero(row, col); markChangedRow(row); @@ -2356,6 +2366,7 @@ HPresolve::Result HPresolve::doubletonEq(HighsPostsolveStack& postsolve_stack, assert(!rowDeleted[row]); assert(rowsize[row] == 2); assert(model->row_lower_[row] == model->row_upper_[row]); + // printf("doubleton equation: "); // debugPrintRow(row); HighsInt nzPos1 = rowroot[row]; @@ -2730,11 +2741,12 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, analysis_.allow_rule_[kPresolveRuleForcingCol]) { // todo: forcing column, since this implies colDual >= 0 and we // already checked that colDual <= 0 and since the cost are 0.0 - // all the rows are at a dual multiplier of zero and we can determine - // one nonbasic row in postsolve, and make the other rows and the column - // basic. The columns primal value is computed from the non-basic row - // which is chosen such that the values of all rows are primal feasible - // printf("removing forcing column of size %" HIGHSINT_FORMAT "\n", + // all the rows are at a dual multiplier of zero and we can + // determine one nonbasic row in postsolve, and make the other + // rows and the column basic. The columns primal value is + // computed from the nonbasic row which is chosen such that the + // values of all rows are primal feasible printf("removing + // forcing column of size %" HIGHSINT_FORMAT "\n", // colsize[col]); if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); postsolve_stack.forcingColumn(col, getColumnVector(col), @@ -2989,8 +3001,19 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, break; } } - - if (binCol != -1) { + // Reduction uses substitution involving range of all columns + // other than the binary. This is not well defined when any of + // the columns is not boxed, so look for non-boxed columns + // Exposed as #1280 + bool all_boxed_column = true; + for (const HighsSliceNonzero& nonz : getStoredRow()) { + if (model->col_lower_[nonz.index()] <= -kHighsInf || + model->col_upper_[nonz.index()] >= kHighsInf) { + all_boxed_column = false; + break; + } + } + if (binCol != -1 && all_boxed_column) { // found binary column for substituting all other columns // printf("simple probing case on row of size %" HIGHSINT_FORMAT "\n", // rowsize[row]); @@ -3188,15 +3211,26 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, primal_feastol); if (zLower == zUpper) { - // rounded bounds are equal, so fix x1 to the corresponding - // bound + // Rounded bounds are equal + // + // Adjust bounds if variable is fixed to a value in between + // its bounds double fixVal = zLower * d + b; + assert(fixVal > model->col_lower_[x1] - primal_feastol); + assert(fixVal < model->col_upper_[x1] + primal_feastol); + if (fixVal > model->col_lower_[x1]) + changeColLower(x1, fixVal); + if (fixVal < model->col_upper_[x1]) + changeColUpper(x1, fixVal); + // Fix variable if (std::abs(model->col_lower_[x1] - fixVal) <= - primal_feastol) + primal_feastol) { fixColToLower(postsolve_stack, x1); - else + } else { + assert(std::abs(model->col_upper_[x1] - fixVal) <= + primal_feastol); fixColToUpper(postsolve_stack, x1); - + } rowpositions.erase(rowpositions.begin() + x1Cand); } else { transformColumn(postsolve_stack, x1, d, b); @@ -3614,6 +3648,19 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, markRowDeleted(row); for (const HighsSliceNonzero& nonzero : rowVector) { if (nonzero.value() < 0) { + if (model->integrality_[nonzero.index()] != + HighsVarType::kContinuous) { + // If a non-continuous variable is fixed at a fractional + // value then the problem is infeasible + const double upper = model->col_upper_[nonzero.index()]; + const double fraction = upper - std::floor(upper); + assert(fraction >= 0); + const bool non_fractional = + fraction <= + mipsolver->options_mip_->mip_feasibility_tolerance; + // assert(non_fractional); + if (!non_fractional) return Result::kPrimalInfeasible; + } postsolve_stack.fixedColAtUpper(nonzero.index(), model->col_upper_[nonzero.index()], model->col_cost_[nonzero.index()], @@ -3625,6 +3672,19 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, removeFixedCol(nonzero.index()); } else { + if (model->integrality_[nonzero.index()] != + HighsVarType::kContinuous) { + // If a non-continuous variable is fixed at a fractional + // value then the problem is infeasible + const double lower = model->col_lower_[nonzero.index()]; + const double fraction = std::ceil(lower) - lower; + assert(fraction >= 0); + const bool non_fractional = + fraction <= + mipsolver->options_mip_->mip_feasibility_tolerance; + // assert(non_fractional); + if (!non_fractional) return Result::kPrimalInfeasible; + } postsolve_stack.fixedColAtLower(nonzero.index(), model->col_lower_[nonzero.index()], model->col_cost_[nonzero.index()], @@ -3975,7 +4035,7 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { analysis_.setup(this->model, this->options, this->numDeletedRows, this->numDeletedCols); - if (options->presolve != "off") { + if (options->presolve != kHighsOffString) { if (mipsolver) mipsolver->mipdata_->cliquetable.setPresolveFlag(true); if (!mipsolver || mipsolver->mipdata_->numRestarts == 0) highsLogUser(options->log_options, HighsLogType::kInfo, @@ -4021,7 +4081,7 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { // when presolving after a restart the clique table and implication // structure may contain substitutions which we apply directly before - // running the aggregator as they might loose validity otherwise + // running the aggregator as they might lose validity otherwise if (mipsolver != nullptr) { HPRESOLVE_CHECKED_CALL( applyConflictGraphSubstitutions(postsolve_stack)); @@ -4149,6 +4209,47 @@ HPresolve::Result HPresolve::checkLimits(HighsPostsolveStack& postsolve_stack) { // todo: check timelimit size_t numreductions = postsolve_stack.numReductions(); + bool debug_report = false; + HighsInt check_col = debugGetCheckCol(); + HighsInt check_row = debugGetCheckRow(); + bool col_bound_change = false; + bool row_bound_change = false; + if (check_col >= 0 || check_row >= 0) { + if (check_col >= 0) { + col_bound_change = + numreductions == 1 || + postsolve_stack.debug_prev_col_lower != + model->col_lower_[check_col] || + postsolve_stack.debug_prev_col_upper != model->col_upper_[check_col]; + postsolve_stack.debug_prev_col_lower = model->col_lower_[check_col]; + postsolve_stack.debug_prev_col_upper = model->col_upper_[check_col]; + } + if (check_row >= 0) { + row_bound_change = + numreductions == 1 || + postsolve_stack.debug_prev_row_lower != + model->row_lower_[check_row] || + postsolve_stack.debug_prev_row_upper != model->row_upper_[check_row]; + postsolve_stack.debug_prev_row_lower = model->row_lower_[check_row]; + postsolve_stack.debug_prev_row_upper = model->row_upper_[check_row]; + } + debug_report = numreductions > postsolve_stack.debug_prev_numreductions; + } + if (check_col >= 0 && col_bound_change && debug_report) { + printf("After reduction %4d: col = %4d[%s] has bounds [%11.4g, %11.4g]\n", + int(numreductions - 1), int(check_col), + model->col_names_[check_col].c_str(), model->col_lower_[check_col], + model->col_upper_[check_col]); + postsolve_stack.debug_prev_numreductions = numreductions; + } + if (check_row >= 0 && row_bound_change && debug_report) { + printf("After reduction %4d: row = %4d[%s] has bounds [%11.4g, %11.4g]\n", + int(numreductions - 1), int(check_row), + model->row_names_[check_row].c_str(), model->row_lower_[check_row], + model->row_upper_[check_row]); + postsolve_stack.debug_prev_numreductions = numreductions; + } + if (timer != nullptr && (numreductions & 1023u) == 0) { if (timer->readRunHighsClock() >= options->time_limit) return Result::kStopped; @@ -4174,17 +4275,31 @@ double HPresolve::problemSizeReduction() { } HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) { + presolve_status_ = HighsPresolveStatus::kNotSet; shrinkProblemEnabled = true; + postsolve_stack.debug_prev_numreductions = 0; + postsolve_stack.debug_prev_col_lower = 0; + postsolve_stack.debug_prev_col_upper = 0; + postsolve_stack.debug_prev_row_lower = 0; + postsolve_stack.debug_prev_row_upper = 0; switch (presolve(postsolve_stack)) { case Result::kStopped: case Result::kOk: break; case Result::kPrimalInfeasible: + presolve_status_ = HighsPresolveStatus::kInfeasible; return HighsModelStatus::kInfeasible; case Result::kDualInfeasible: + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; return HighsModelStatus::kUnboundedOrInfeasible; } + if (options->presolve != kHighsOffString && + reductionLimit < kHighsSize_tInf) { + highsLogUser(options->log_options, HighsLogType::kInfo, + "Presolve performed %d of %d permitted reductions\n", + int(postsolve_stack.numReductions()), int(reductionLimit)); + } shrinkProblem(postsolve_stack); if (mipsolver != nullptr) { @@ -4238,21 +4353,34 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) { model->a_matrix_.start_); if (model->num_col_ == 0) { + // Reduced to empty if (mipsolver) { - if (model->offset_ > mipsolver->mipdata_->upper_limit) + if (model->offset_ > mipsolver->mipdata_->upper_limit) { + presolve_status_ = HighsPresolveStatus::kInfeasible; return HighsModelStatus::kInfeasible; - + } mipsolver->mipdata_->lower_bound = 0; } else { assert(model->num_row_ == 0); - if (model->num_row_ != 0) return HighsModelStatus::kNotset; + if (model->num_row_ != 0) { + presolve_status_ = HighsPresolveStatus::kNotPresolved; + return HighsModelStatus::kNotset; + } } + presolve_status_ = HighsPresolveStatus::kReducedToEmpty; return HighsModelStatus::kOptimal; + } else if (postsolve_stack.numReductions() > 0) { + // Reductions performed + presolve_status_ = HighsPresolveStatus::kReduced; + } else { + // No reductions performed + presolve_status_ = HighsPresolveStatus::kNotReduced; } if (!mipsolver && options->use_implied_bounds_from_presolve) setRelaxedImpliedBounds(); + assert(presolve_status_ != HighsPresolveStatus::kNotSet); return HighsModelStatus::kNotset; } @@ -5282,6 +5410,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( std::unordered_multimap buckets; + const bool debug_report = false; for (HighsInt i = 0; i != model->num_col_; ++i) { if (colDeleted[i]) continue; if (colsize[i] == 0) { @@ -5428,13 +5557,15 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( // if the scale is larger than 1, duplicate column cannot compensate for // all values of scaled col due to integrality as the scaled column // moves on a grid of 1/scale. + // + // ToDo: Check whether this is too restrictive if (colScale != 1.0) checkDuplicateColImplBounds = false; } else if (model->integrality_[i] == HighsVarType::kInteger) { col = i; duplicateCol = parallelColCandidate; colScale = colMax[duplicateCol].first / colMax[col].first; - // as col is integral and dulicateCol is not col cannot compensate for + // as col is integral and duplicateCol is not col cannot compensate for // duplicate col checkColImplBounds = false; } else { @@ -5442,7 +5573,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( duplicateCol = i; colScale = colMax[duplicateCol].first / colMax[col].first; - // as col might be integral and dulicateCol is not integral. In that + // as col might be integral and duplicateCol is not integral. In that // case col cannot compensate for duplicate col checkColImplBounds = model->integrality_[parallelColCandidate] != HighsVarType::kInteger; @@ -5500,73 +5631,47 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( reductionCase = colScale > 0 ? kDominanceColToUpper : kDominanceColToLower; } - double mergeLower = 0; - double mergeUpper = 0; if (reductionCase == kMergeParallelCols) { - if (colScale > 0) { - mergeLower = model->col_lower_[col] + - colScale * model->col_lower_[duplicateCol]; - mergeUpper = model->col_upper_[col] + - colScale * model->col_upper_[duplicateCol]; - } else { - mergeLower = model->col_lower_[col] + - colScale * model->col_upper_[duplicateCol]; - mergeUpper = model->col_upper_[col] + - colScale * model->col_lower_[duplicateCol]; - } - if (model->integrality_[col] == HighsVarType::kInteger) { - // the only possible reduction if the column parallelism check + const bool x_int = model->integrality_[col] == HighsVarType::kInteger; + const bool y_int = + model->integrality_[duplicateCol] == HighsVarType::kInteger; + bool illegal_scale = true; + if (x_int) { + // The only possible reduction if the column parallelism check // succeeds is to merge the two columns into one. If one column is // integral this means we have restrictions on integers and need to // check additional conditions to allow the merging of two integer // columns, or a continuous column and an integer. if (model->integrality_[duplicateCol] != HighsVarType::kInteger) { + assert(!y_int); // only one column is integral which cannot be duplicateCol due to // the way we assign the columns above - if (std::abs(colScale * (model->col_upper_[duplicateCol] - + // + // Scale must not exceed 1/(y_u-y_l) in magnitude + illegal_scale = + std::abs(colScale * (model->col_upper_[duplicateCol] - model->col_lower_[duplicateCol])) < - 1.0 - primal_feastol) - continue; - } else if (colScale > 1.0) { - // round bounds to exact integer values to make sure they are not - // wrongly truncated in conversions happening below - mergeLower = std::round(mergeLower); - mergeUpper = std::round(mergeUpper); - - // this should not happen, since this would allow domination and - // would have been caught by the cases above - assert(mergeLower != -kHighsInf); - assert(mergeUpper != kHighsInf); - - HighsInt kMax = mergeUpper; - bool representable = true; - for (HighsInt k = mergeLower; k <= kMax; ++k) { - // we loop over the domain of the merged variable to check whether - // there exists a value for col and duplicateCol so that both are - // within their bounds. since the merged column y is defined as y - // = col + colScale * duplicateCol, we know that the value of col - // can be computed as col = y - colScale * duplicateCol. Hence we - // loop over the domain of col2 until we verify that a suitable - // value of column 1 exists to yield the desired value for y. - double mergeVal = mergeLower + k; - HighsInt k2Max = model->col_upper_[duplicateCol]; - assert(k2Max == model->col_upper_[duplicateCol]); - representable = false; - for (HighsInt k2 = model->col_lower_[duplicateCol]; k2 <= k2Max; - ++k2) { - double colVal = mergeVal - colScale * k2; - if (colVal >= model->col_lower_[col] - primal_feastol && - colVal <= model->col_upper_[col] + primal_feastol) { - representable = true; - break; - } - } - - if (!representable) break; - } - - if (!representable) continue; + 1.0 - primal_feastol; + if (!illegal_scale && debug_report) + printf( + "kMergeParallelCols: T-F is %s legal with scale %.4g and " + "duplicateCol = [%.4g, %.4g]\n", + illegal_scale ? "not" : " ", colScale, + model->col_lower_[duplicateCol], + model->col_upper_[duplicateCol]); + } else { + // Both columns integer + assert(x_int && y_int); + // Scale must be integer and not exceed (x_u-x_l)+1 in magnitude + const double scale_limit = model->col_upper_[col] - + model->col_lower_[col] + 1 + + primal_feastol; + illegal_scale = std::fabs(colScale) > scale_limit; } + if (illegal_scale) continue; + } else { + // Neither column integer: no problem with + assert(!x_int && !y_int); } } @@ -5623,12 +5728,29 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( fixColToUpper(postsolve_stack, col); break; case kMergeParallelCols: - postsolve_stack.duplicateColumn( + const bool ok_merge = postsolve_stack.duplicateColumn( colScale, model->col_lower_[col], model->col_upper_[col], model->col_lower_[duplicateCol], model->col_upper_[duplicateCol], col, duplicateCol, model->integrality_[col] == HighsVarType::kInteger, - model->integrality_[duplicateCol] == HighsVarType::kInteger); + model->integrality_[duplicateCol] == HighsVarType::kInteger, + options->mip_feasibility_tolerance); + if (!ok_merge && debug_report) { + printf( + "HPresolve::detectParallelRowsAndCols Illegal merge " + "prevented\n"); + break; + } + // When merging a continuous variable into an integer + // variable, the integer will become continuous - since any + // value in its range can be mapped back to an integer and a + // continuous variable. Hence the number of integer + // variables in the rows corresponding to the former integer + // variable reduces. + // + // With the opposite - merging an integer variable into a + // continuous variable - the retained variable is + // continuous, so no action is required HighsInt rowsizeIntReduction = 0; if (model->integrality_[duplicateCol] != HighsVarType::kInteger && model->integrality_[col] == HighsVarType::kInteger) { @@ -5647,7 +5769,14 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( // infinite bounds we need to make sure the counters for the number of // infinite bounds that contribute to the implied row bounds are // updated correctly and that all finite contributions are removed. + + double mergeLower = 0; + double mergeUpper = 0; if (colScale > 0) { + mergeLower = model->col_lower_[col] + + colScale * model->col_lower_[duplicateCol]; + mergeUpper = model->col_upper_[col] + + colScale * model->col_upper_[duplicateCol]; if (mergeUpper == kHighsInf && model->col_upper_[col] != kHighsInf) model->col_upper_[duplicateCol] = model->col_upper_[col] / colScale; @@ -5664,6 +5793,10 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( else model->col_lower_[duplicateCol] = 0; } else { + mergeLower = model->col_lower_[col] + + colScale * model->col_upper_[duplicateCol]; + mergeUpper = model->col_upper_[col] + + colScale * model->col_lower_[duplicateCol]; if (mergeUpper == kHighsInf && model->col_upper_[col] != kHighsInf) model->col_lower_[duplicateCol] = model->col_upper_[col] / colScale; @@ -5694,8 +5827,10 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt colrow = Arow[coliter]; // if an an integer column was merged into a continuous one make // sure to update the integral rowsize - if (rowsizeIntReduction) + if (rowsizeIntReduction) { + assert(rowsizeIntReduction == 1); rowsizeInteger[colrow] -= rowsizeIntReduction; + } coliter = Anext[coliter]; unlink(colpos); @@ -6083,7 +6218,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { Highs highs; highs.passModel(model); highs.passOptions(options); - highs.setOptionValue("presolve", "off"); + highs.setOptionValue("presolve", kHighsOffString); highs.run(); if (highs.getModelStatus() != HighsModelStatus::kOptimal) return; reducedsol = highs.getSolution(); @@ -6472,4 +6607,36 @@ HPresolve::Result HPresolve::sparsify(HighsPostsolveStack& postsolve_stack) { return Result::kOk; } +HighsInt HPresolve::debugGetCheckCol() const { + const std::string check_col_name = ""; // c37"; + HighsInt check_col = -1; + if (check_col_name == "") return check_col; + if (model->col_names_.size()) { + if (HighsInt(model->col_hash_.name2index.size()) != model->num_col_) + model->col_hash_.form(model->col_names_); + auto search = model->col_hash_.name2index.find(check_col_name); + if (search != model->col_hash_.name2index.end()) { + check_col = search->second; + assert(model->col_names_[check_col] == check_col_name); + } + } + return check_col; +} + +HighsInt HPresolve::debugGetCheckRow() const { + const std::string check_row_name = ""; //"row_ekk_119"; + HighsInt check_row = -1; + if (check_row_name == "") return check_row; + if (model->row_names_.size()) { + if (HighsInt(model->row_hash_.name2index.size()) != model->num_row_) + model->row_hash_.form(model->row_names_); + auto search = model->row_hash_.name2index.find(check_row_name); + if (search != model->row_hash_.name2index.end()) { + check_row = search->second; + assert(model->row_names_[check_row] == check_row_name); + } + } + return check_row; +} + } // namespace presolve diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 7017c1fa21..cd5d36bc98 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file presolve/HPresolve.h * @brief @@ -137,7 +135,7 @@ class HPresolve { kDualInfeasible, kStopped, }; - + HighsPresolveStatus presolve_status_; HPresolveAnalysis analysis_; // private functions for different shared functionality and matrix @@ -275,7 +273,7 @@ class HPresolve { void shrinkProblem(HighsPostsolveStack& postsolve_stack); - void addToMatrix(HighsInt row, HighsInt col, double val); + void addToMatrix(const HighsInt row, const HighsInt col, const double val); Result runProbing(HighsPostsolveStack& postsolve_stack); @@ -341,6 +339,11 @@ class HPresolve { return analysis_.presolve_log_; } + HighsPresolveStatus getPresolveStatus() const { return presolve_status_; } + + HighsInt debugGetCheckCol() const; + HighsInt debugGetCheckRow() const; + // Not currently called static void debug(const HighsLp& lp, const HighsOptions& options); }; diff --git a/src/presolve/HPresolveAnalysis.cpp b/src/presolve/HPresolveAnalysis.cpp index 8c9a87461b..044b9b2405 100644 --- a/src/presolve/HPresolveAnalysis.cpp +++ b/src/presolve/HPresolveAnalysis.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "lp_data/HighsModelUtils.h" #include "presolve/HPresolve.h" diff --git a/src/presolve/HPresolveAnalysis.h b/src/presolve/HPresolveAnalysis.h index 6d0f6f48fd..af9e4abeb7 100644 --- a/src/presolve/HPresolveAnalysis.h +++ b/src/presolve/HPresolveAnalysis.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file presolve/HPresolveAnalysis.h * @brief diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 7e4f8641cc..0196514612 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "presolve/HighsPostsolveStack.h" @@ -89,6 +87,7 @@ void HighsPostsolveStack::FreeColSubstitution::undo( } assert(colCoef != 0); + // Row values aren't fully postsolved, so why do this? solution.row_value[row] = double(rowValue + colCoef * solution.col_value[col]); solution.col_value[col] = double((rhs - rowValue) / colCoef); @@ -250,9 +249,13 @@ void HighsPostsolveStack::ForcingColumn::undo( HighsBasisStatus nonbasicRowStatus = HighsBasisStatus::kNonbasic; double colValFromNonbasicRow = colBound; + HighsInt debug_num_use_row_value = 0; + const bool debug_report = false; if (atInfiniteUpper) { // choose largest value as then all rows are feasible for (const auto& colVal : colValues) { + // Row values aren't fully postsolved, so how can this work? + debug_num_use_row_value++; double colValFromRow = solution.row_value[colVal.index] / colVal.value; if (colValFromRow > colValFromNonbasicRow) { nonbasicRow = colVal.index; @@ -264,6 +267,8 @@ void HighsPostsolveStack::ForcingColumn::undo( } else { // choose smallest value, as then all rows are feasible for (const auto& colVal : colValues) { + // Row values aren't fully postsolved, so how can this work? + debug_num_use_row_value++; double colValFromRow = solution.row_value[colVal.index] / colVal.value; if (colValFromRow < colValFromNonbasicRow) { nonbasicRow = colVal.index; @@ -273,6 +278,13 @@ void HighsPostsolveStack::ForcingColumn::undo( } } } + if (debug_num_use_row_value && debug_report) { + printf( + "HighsPostsolveStack::ForcingColumn::undo Using %d unknown row " + "activit%s\n", + int(debug_num_use_row_value), + debug_num_use_row_value > 1 ? "ies" : "y"); + } solution.col_value[col] = colValFromNonbasicRow; @@ -299,6 +311,7 @@ void HighsPostsolveStack::ForcingColumnRemovedRow::undo( for (const auto& rowVal : rowValues) val -= rowVal.value * solution.col_value[rowVal.index]; + // Row values aren't fully postsolved, so why do this? solution.row_value[row] = double(val); if (solution.dual_valid) solution.row_dual[row] = 0.0; @@ -555,6 +568,31 @@ void HighsPostsolveStack::DuplicateRow::undo(const HighsOptions& options, void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, HighsSolution& solution, HighsBasis& basis) const { + const bool debug_report = false; + const double mergeVal = solution.col_value[col]; + + auto okResidual = [&](const double x, const double y) { + const double check_mergeVal = x + colScale * y; + const double residual = std::fabs(check_mergeVal - mergeVal); + const bool ok_residual = residual <= options.primal_feasibility_tolerance; + if (!ok_residual && debug_report) { + printf( + "HighsPostsolveStack::DuplicateColumn::undo %g + %g.%g = %g != %g: " + "residual = %g\n", + x, colScale, y, check_mergeVal, mergeVal, residual); + } + return ok_residual; + }; + + auto isAtBound = [&](const double value, const double bound) { + if (value < bound - options.primal_feasibility_tolerance) return false; + if (value <= bound + options.primal_feasibility_tolerance) return true; + return false; + }; + + // const bool ok_merge = okMerge(options.mip_feasibility_tolerance); + // assert(ok_merge); + // // the column dual of the duplicate column is easily computed by scaling // since col * colScale yields the coefficient values and cost of the // duplicate column. @@ -562,9 +600,25 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, solution.col_dual[duplicateCol] = solution.col_dual[col] * colScale; if (basis.valid) { - // do postsolve using basis status if a basis is available: - // if the merged column is nonbasic, we can just set both columns - // to the corresponding basis status and value + // do postsolve using basis status if a basis is available: if the + // merged column is nonbasic, we can just set both columns to + // appropriate nonbasic status and value + // + // Undoing z = x + a.y + // + // Since x became z, its basis status is unchanged + // + // For a > 0, z\in [x_l + a.y_l, x_u + a.y_u] + // + // If z is nonbasic at its lower (upper) bound, set y to be + // nonbasic at its lower (upper) bound + // + // For a < 0, z\in [x_l + a.y_u, x_u + a.y_l] + // + // If z is nonbasic at lower (upper) bound, set y to be nonbasic + // at its upper (lower) bounds + // + // Check for perturbations switch (basis.col_status[col]) { case HighsBasisStatus::kLower: { solution.col_value[col] = colLower; @@ -576,6 +630,8 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, solution.col_value[duplicateCol] = duplicateColUpper; } // nothing else to do + assert(okResidual(solution.col_value[col], + solution.col_value[duplicateCol])); return; } case HighsBasisStatus::kUpper: { @@ -588,6 +644,8 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, solution.col_value[duplicateCol] = duplicateColLower; } // nothing else to do + assert(okResidual(solution.col_value[col], + solution.col_value[duplicateCol])); return; } case HighsBasisStatus::kZero: { @@ -595,38 +653,39 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, basis.col_status[duplicateCol] = HighsBasisStatus::kZero; solution.col_value[duplicateCol] = 0.0; // nothing else to do + assert(okResidual(solution.col_value[col], + solution.col_value[duplicateCol])); return; } case HighsBasisStatus::kBasic: case HighsBasisStatus::kNonbasic:; } - + // Nonbasic cases should have been considered; basic case + // considered later assert(basis.col_status[col] == HighsBasisStatus::kBasic); } // either no basis for postsolve, or column status is basic. One of // the two columns must become nonbasic. In case of integrality it is - // simpler to choose col, since it has a coefficient of +1 in the equation y - // = col + colScale * duplicateCol where the merged column is y and is + // simpler to choose col, since it has a coefficient of +1 in the equation z + // = col + colScale * duplicateCol where the merged column is z and is // currently using the index of col. The duplicateCol can have a positive or // negative coefficient. So for postsolve, we first start out with col // sitting at the lower bound and compute the corresponding value for the - // duplicate column as (y - colLower)/colScale. Then the following things + // duplicate column as (z - colLower)/colScale. Then the following things // might happen: // - case 1: the value computed for duplicateCol is within the bounds // - case 1.1: duplicateCol is continuous -> accept value, make col nonbasic // at lower and duplicateCol basic // - case 1.2: duplicateCol is integer -> accept value if integer feasible, // otherwise round down and compute value of col as - // col = y - colScale * duplicateCol + // col = z - colScale * duplicateCol // - case 2: the value for duplicateCol violates the column bounds: make it // sit at the bound that is violated - // and compute the value of col as col = y - colScale * + // and compute the value of col as col = z - colScale * // duplicateCol for basis postsolve col is basic and duplicateCol // nonbasic at lower/upper depending on which bound is violated. - double mergeVal = solution.col_value[col]; - if (colLower != -kHighsInf) solution.col_value[col] = colLower; else @@ -636,6 +695,10 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, bool recomputeCol = false; + // Set any basis status for duplicateCol to kNonbasic to check that + // it is set + if (basis.valid) basis.col_status[duplicateCol] = HighsBasisStatus::kNonbasic; + if (solution.col_value[duplicateCol] > duplicateColUpper) { solution.col_value[duplicateCol] = duplicateColUpper; recomputeCol = true; @@ -645,6 +708,8 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, recomputeCol = true; if (basis.valid) basis.col_status[duplicateCol] = HighsBasisStatus::kLower; } else if (duplicateColIntegral) { + // Doesn't set basis.col_status[duplicateCol], so assume no basis + assert(!basis.valid); double roundVal = std::round(solution.col_value[duplicateCol]); if (std::abs(roundVal - solution.col_value[duplicateCol]) > options.mip_feasibility_tolerance) { @@ -660,6 +725,9 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, if (!duplicateColIntegral && colIntegral) { // if column is integral and duplicateCol is not we need to make sure // we split the values into an integral one for col + // + // Doesn't set basis.col_status[duplicateCol], so assume no basis + assert(!basis.valid); solution.col_value[col] = std::ceil(solution.col_value[col] - options.mip_feasibility_tolerance); solution.col_value[duplicateCol] = @@ -667,12 +735,589 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, } } else { // setting col to its lower bound yielded a feasible value for - // duplicateCol + // duplicateCol - not necessarily! if (basis.valid) { + // This makes duplicateCol basic basis.col_status[duplicateCol] = basis.col_status[col]; basis.col_status[col] = HighsBasisStatus::kLower; + assert(basis.col_status[duplicateCol] == HighsBasisStatus::kBasic); } } + // Check that any basis status for duplicateCol has been set + if (basis.valid) + assert(basis.col_status[duplicateCol] != HighsBasisStatus::kNonbasic); + + bool illegal_duplicateCol_lower = + solution.col_value[duplicateCol] < + duplicateColLower - options.mip_feasibility_tolerance; + bool illegal_duplicateCol_upper = + solution.col_value[duplicateCol] > + duplicateColUpper + options.mip_feasibility_tolerance; + bool illegal_col_lower = + solution.col_value[col] < colLower - options.mip_feasibility_tolerance; + bool illegal_col_upper = + solution.col_value[col] > colUpper + options.mip_feasibility_tolerance; + bool illegal_residual = + !okResidual(solution.col_value[col], solution.col_value[duplicateCol]); + bool error = illegal_duplicateCol_lower || illegal_duplicateCol_upper || + illegal_col_lower || illegal_col_upper || illegal_residual; + if (error) { + if (debug_report) + printf( + "DuplicateColumn::undo error: col = %d(%g), duplicateCol = %d(%g)\n" + "%g\n%g\n%g %g %d\n%g %g %d\n", + int(col), solution.col_value[col], int(duplicateCol), + solution.col_value[duplicateCol], mergeVal, colScale, colLower, + colUpper, colIntegral, duplicateColLower, duplicateColUpper, + duplicateColIntegral); + // Fix error due to undo + undoFix(options, solution); + illegal_duplicateCol_lower = + solution.col_value[duplicateCol] < + duplicateColLower - options.mip_feasibility_tolerance; + illegal_duplicateCol_upper = + solution.col_value[duplicateCol] > + duplicateColUpper + options.mip_feasibility_tolerance; + illegal_col_lower = + solution.col_value[col] < colLower - options.mip_feasibility_tolerance; + illegal_col_upper = + solution.col_value[col] > colUpper + options.mip_feasibility_tolerance; + illegal_residual = + !okResidual(solution.col_value[col], solution.col_value[duplicateCol]); + } else { + return; + } + const bool allow_assert = false; + if (allow_assert) { + assert(!illegal_duplicateCol_lower); + assert(!illegal_duplicateCol_upper); + assert(!illegal_col_lower); + assert(!illegal_col_upper); + assert(!illegal_residual); + } + // Following undoFix, set any basis status, ideally keeping col basic + if (basis.valid) { + bool duplicateCol_basic = false; + if (duplicateColLower <= -kHighsInf && duplicateColUpper >= kHighsInf) { + // duplicateCol is free, so may be zero + if (solution.col_value[duplicateCol] == 0) { + basis.col_status[col] = HighsBasisStatus::kBasic; + basis.col_status[duplicateCol] = HighsBasisStatus::kZero; + } else { + duplicateCol_basic = true; + } + } else if (isAtBound(solution.col_value[duplicateCol], duplicateColLower)) { + basis.col_status[col] = HighsBasisStatus::kBasic; + basis.col_status[duplicateCol] = HighsBasisStatus::kLower; + } else if (isAtBound(solution.col_value[duplicateCol], duplicateColUpper)) { + basis.col_status[col] = HighsBasisStatus::kBasic; + basis.col_status[duplicateCol] = HighsBasisStatus::kUpper; + } else { + // duplicateCol is not free or at a bound, so must be basic + duplicateCol_basic = true; + } + if (duplicateCol_basic) { + // duplicateCol must be basic + basis.col_status[duplicateCol] = HighsBasisStatus::kBasic; + // Hopefully col can be nonbasic + if (isAtBound(solution.col_value[col], colLower)) { + basis.col_status[col] = HighsBasisStatus::kLower; + } else if (isAtBound(solution.col_value[col], colUpper)) { + basis.col_status[col] = HighsBasisStatus::kUpper; + } else { + basis.col_status[col] = HighsBasisStatus::kNonbasic; + if (debug_report) + printf( + "When demerging, neither col nor duplicateCol can be nonbasic\n"); + if (kAllowDeveloperAssert) assert(666 == 999); + } + } + } +} + +bool HighsPostsolveStack::DuplicateColumn::okMerge( + const double tolerance) const { + // When merging x and y to x+a.y, not all values of a are permitted, + // since it must be possible to map back onto feasible values of x + // and y. + // + // Assume WLOG that a > 0, x\in[x_l, x_u], y\in[y_l, y_u] + // + // Let z = x + a.y + // + // Range for z is [x_l+a.y_l, x_u+a.y_u] + // + // * If x and y are both integer: + // + // z will be integer and x+a.y must generate all integer values in + // [x_l+a.y_l, x_u+a.y_u]. Hence a must be an integer. If a >= + // (x_u-x_l)+2 then, since [a.y_l, a.y_u] contains integer multiples + // of a, some of the intervening integers don't correspond to a + // value of x. Hence a must be an integer and a <= (x_u-x_l)+1 + // + // For example, if x and y are binary, then x+a.y is [0, 1, a, + // 1+a]. For this to be a continuous sequernce of integers, we must + // have a <= 2. + // + // * If x is integer and y is continuous: + // + // z will be continuous and x+a.y must generate all values in + // [x_l+a.y_l, x_u+a.y_u]. Since [x_l, x_u] are integers, [a.y_l, + // a.y_u] = a[y_l, y_u] must be of length at least 1. Hence a must + // be at least 1/(y_u-y_l) in magnitude. + // + // * If x is continuous and y is integer: + // + // z will be continuous and x+a.y must generate all values in + // [x_l+a.y_l, x_u+a.y_u]. Since [a.y_l, a.y_u] contains integer + // multiples of a, the gaps between them must not exceed the length + // of [x_l, x_u]. Hence a must be at most x_u-x_l in + // magnitude. + // + // Observe that this is equivalent to requiring 1/a to be at least + // 1/(x_u-x_l) in magnitude, the symmetric result corresponding to + // the merge (1/a)x+y. + // + // * If x and y are both continuous + // + // z will be continuous and x+a.y naturally generates all values in + // [x_l+a.y_l, x_u+a.y_u]. + + const double scale = colScale; + const bool x_int = colIntegral; + const bool y_int = duplicateColIntegral; + const double x_lo = x_int ? std::ceil(colLower) : colLower; + const double x_up = x_int ? std::floor(colUpper) : colUpper; + const double y_lo = y_int ? std::ceil(duplicateColLower) : duplicateColLower; + const double y_up = y_int ? std::floor(duplicateColUpper) : duplicateColUpper; + const double x_len = x_up - x_lo; + const double y_len = y_up - y_lo; + std::string newline = "\n"; + bool ok_merge = true; + const bool debug_report = false; + if (scale == 0) { + if (debug_report) + printf("%sDuplicateColumn::checkMerge: Scale cannot be zero\n", + newline.c_str()); + newline = ""; + ok_merge = false; + } + const double abs_scale = std::fabs(scale); + if (x_int) { + if (y_int) { + // Scale must be integer and not exceed (x_u-x_l)+1 in magnitude + double int_scale = std::floor(scale + 0.5); + bool scale_is_int = std::fabs(int_scale - scale) <= tolerance; + if (!scale_is_int) { + if (debug_report) + printf( + "%sDuplicateColumn::checkMerge: scale must be integer, but is " + "%g\n", + newline.c_str(), scale); + newline = ""; + ok_merge = false; + } + double scale_limit = x_len + 1 + tolerance; + if (abs_scale > scale_limit) { + if (debug_report) + printf( + "%sDuplicateColumn::checkMerge: scale = %g, but |scale| cannot " + "exceed %g since x is [%g, %g]\n", + newline.c_str(), scale, scale_limit, x_lo, x_up); + newline = ""; + ok_merge = false; + } + } else { // y is continuous + if (debug_report) + printf("DuplicateColumn::checkMerge: x-integer; y-continuous\n"); + // Scale must be at least 1/(y_u-y_l) in magnitude + if (y_len == 0) { + if (debug_report) + printf( + "%sDuplicateColumn::checkMerge: scale = %g is too small in " + "magnitude, as y is [%g, %g]\n", + newline.c_str(), scale, y_lo, y_up); + newline = ""; + ok_merge = false; + } else { + double scale_limit = 1 / y_len; + if (abs_scale < scale_limit) { + if (debug_report) + printf( + "%sDuplicateColumn::checkMerge: scale = %g, but |scale| must " + "be " + "at least %g since y is [%g, %g]\n", + newline.c_str(), scale, scale_limit, y_lo, y_up); + newline = ""; + ok_merge = false; + } + } + } + } else { + if (y_int) { + if (debug_report) + printf("DuplicateColumn::checkMerge: x-continuous; y-integer\n"); + // Scale must be at most (x_u-x_l) in magnitude + double scale_limit = x_len; + if (abs_scale > scale_limit) { + if (debug_report) + printf( + "%sDuplicateColumn::checkMerge: scale = %g, but |scale| must be " + "at " + "most %g since x is [%g, %g]\n", + newline.c_str(), scale, scale_limit, x_lo, x_up); + newline = ""; + ok_merge = false; + } + } else { + // x and y are continuous + // if (debug_report) printf("DuplicateColumn::checkMerge: + // x-continuous ; + // y-continuous\n"); + } + } + return ok_merge; +} + +void HighsPostsolveStack::DuplicateColumn::undoFix( + const HighsOptions& options, HighsSolution& solution) const { + const double mip_feasibility_tolerance = options.mip_feasibility_tolerance; + const double primal_feasibility_tolerance = + options.primal_feasibility_tolerance; + std::vector& col_value = solution.col_value; + const bool allow_assert = false; + const bool debug_report = false; + //============================================================================================= + + auto isInteger = [&](const double v) { + double int_v = std::floor(v + 0.5); + return std::fabs(int_v - v) <= mip_feasibility_tolerance; + }; + + auto isFeasible = [&](const double l, const double v, const double u) { + if (v < l - primal_feasibility_tolerance) return false; + if (v > u + primal_feasibility_tolerance) return false; + return true; + }; + const double merge_value = col_value[col]; + const double value_max = 1000; + const double eps = 1e-8; + const double scale = colScale; + const bool x_int = colIntegral; + const bool y_int = duplicateColIntegral; + const int x_ix = col; + const int y_ix = duplicateCol; + const double x_lo = x_int ? std::ceil(colLower) : colLower; + const double x_up = x_int ? std::floor(colUpper) : colUpper; + const double y_lo = y_int ? std::ceil(duplicateColLower) : duplicateColLower; + const double y_up = y_int ? std::floor(duplicateColUpper) : duplicateColUpper; + if (kAllowDeveloperAssert) assert(scale); + double x_v = merge_value; + double y_v; + + // assert(x_int); + // assert(y_int); + // assert(scale < 0); + if (x_int) { + double x_0 = 0; + double x_d = 0; + double x_1 = 0; + double x_free = false; + if (x_lo <= -kHighsInf) { + if (x_up >= kHighsInf) { + // x is free + x_free = true; + x_0 = 0; + x_d = 1.0; + x_1 = value_max; + } else { + // x is (-int, u] + x_0 = x_up; + x_d = -1.0; + x_1 = -value_max; + } + } else { + if (x_up >= kHighsInf) { + // x is [l, inf) + x_0 = x_lo; + x_d = 1.0; + x_1 = value_max; + } else { + // x is [l, u] + x_0 = x_lo; + x_d = 1.0; + x_1 = x_up; + } + } + // x is integer, so look through its possible values to find a + // suitable y + if (x_free && debug_report) printf("DuplicateColumn::undo x is free\n"); + if (debug_report) + printf("DuplicateColumn::undo Using x (%g; %g; %g)\n", x_0, x_d, x_1); + bool found_y = false; + for (x_v = x_0;; x_v += x_d) { + // printf("x_v = %g\n", x_v); + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + if (isFeasible(y_lo, y_v, y_up)) { + found_y = !y_int || isInteger(y_v); + if (found_y) break; + } + if (x_d > 0 && x_v + x_d >= x_1 + eps) break; + if (x_d < 0 && x_v + x_d <= x_1 - eps) break; + } + if (allow_assert) assert(found_y); + } else if (y_int) { + double y_0 = 0; + double y_d = 0; + double y_1 = 0; + double y_free = false; + if (y_lo <= -kHighsInf) { + if (y_up >= kHighsInf) { + // y is free + y_free = true; + y_0 = 0; + y_d = 1.0; + y_1 = value_max; + } else { + // y is (-int, u] + y_0 = y_up; + y_d = -1.0; + y_1 = -value_max; + } + } else { + if (y_up >= kHighsInf) { + // y is [l, inf) + y_0 = y_lo; + y_d = 1.0; + y_1 = value_max; + } else { + // y is [l, u] + y_0 = y_lo; + y_d = 1.0; + y_1 = y_up; + } + } + // y is integer, so look through its possible values to find a + // suitable x + if (y_free && debug_report) printf("DuplicateColumn::undo y is free\n"); + if (debug_report) + printf("DuplicateColumn::undo Using y (%g; %g; %g)\n", y_0, y_d, y_1); + bool found_x = false; + for (y_v = y_0;; y_v += y_d) { + // printf("y_v = %g\n", y_v); + x_v = double((HighsCDouble(merge_value) - HighsCDouble(y_v) * scale)); + if (isFeasible(x_lo, x_v, x_up)) { + found_x = !x_int || isInteger(x_v); + if (found_x) break; + } + if (y_d > 0 && y_v + y_d >= y_1 + eps) break; + if (y_d < 0 && y_v + y_d <= y_1 - eps) break; + } + if (allow_assert) assert(found_x); + } else { + // x and y are both continuous + double v_m_a_ylo = 0; + double v_m_a_yup = 0; + if (y_lo <= -kHighsInf) { + v_m_a_ylo = scale > 0 ? kHighsInf : -kHighsInf; + } else { + v_m_a_ylo = + double((HighsCDouble(merge_value) - HighsCDouble(y_lo) * scale)); + } + if (y_up >= kHighsInf) { + v_m_a_yup = scale > 0 ? -kHighsInf : kHighsInf; + } else { + v_m_a_yup = + double((HighsCDouble(merge_value) - HighsCDouble(y_up) * scale)); + } + // Need to ensure that y puts x in [x_l, x_u] + if (scale > 0) { + if (debug_report) + printf("DuplicateColumn::undo [V-a(y_u), V-a(y_l)] == [%g, %g]\n", + v_m_a_yup, v_m_a_ylo); + // V-ay is in [V-a(y_u), V-a(y_l)] == [v_m_a_yup, v_m_a_ylo] + if (y_up < kHighsInf) { + // If v_m_a_yup is right of x_up+eps then [v_m_a_yup, v_m_a_ylo] is + // right of [x_lo-eps, x_up+eps] so there's no solution. [Could + // try v_m_a_ylo computed from y_lo-eps.] + if (kAllowDeveloperAssert) + assert(x_up + primal_feasibility_tolerance >= v_m_a_yup); + // This assignment is OK unless x_v < x_lo-eps + y_v = y_up; + x_v = v_m_a_yup; + if (x_v < x_lo - primal_feasibility_tolerance) { + // Try y_v corresponding to x_lo + x_v = x_lo; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + if (y_v < y_lo - primal_feasibility_tolerance) { + // Very tight: use x_v on its margin and hope! + x_v = x_lo - primal_feasibility_tolerance; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + } + } + } else if (y_lo > -kHighsInf) { + // If v_m_a_ylo is left of x_lo-eps then [v_m_a_yup, v_m_a_ylo] is + // left of [x_lo-eps, x_up+eps] so there's no solution. [Could + // try v_m_a_yup computed from y_up+eps.] + if (kAllowDeveloperAssert) + assert(x_lo - primal_feasibility_tolerance <= v_m_a_ylo); + // This assignment is OK unless x_v > x_up-eps + y_v = y_lo; + x_v = v_m_a_ylo; + if (x_v > x_up + primal_feasibility_tolerance) { + // Try y_v corresponding to x_up + // if (kAllowDeveloperAssert) assert(1==102); + x_v = x_up; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + if (y_v > y_up + primal_feasibility_tolerance) { + // Very tight: use x_v on its margin and hope! + if (debug_report) printf("DuplicateColumn::undoFix 2==102\n"); + if (kAllowDeveloperAssert) assert(2 == 102); + x_v = x_up + primal_feasibility_tolerance; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + } + } + } else { + // y is free, so use x_v = max(0, x_lo) + x_v = std::max(0.0, x_lo); + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + } + } else { // scale < 0 + if (debug_report) + printf("DuplicateColumn::undo [V-a(y_l), V-a(y_u)] == [%g, %g]\n", + v_m_a_ylo, v_m_a_yup); + // V-ay is in [V-a(y_l), V-a(y_u)] == [v_m_a_ylo, v_m_a_yup] + // + if (y_lo > -kHighsInf) { + // If v_m_a_ylo is right of x_up+eps then [v_m_a_ylo, v_m_a_yup] is + // right of [x_lo-eps, x_up+eps] so there's no solution. [Could + // try v_m_a_ylo computed from y_up+eps.] + if (kAllowDeveloperAssert) + assert(x_up + primal_feasibility_tolerance >= v_m_a_ylo); + // This assignment is OK unless x_v < x_lo-eps + y_v = y_lo; + x_v = v_m_a_ylo; + if (x_v < x_lo - primal_feasibility_tolerance) { + // Try y_v corresponding to x_lo + // if (kAllowDeveloperAssert) assert(11==101); + x_v = x_lo; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + if (y_v > y_up + primal_feasibility_tolerance) { + // Very tight: use x_v on its margin and hope! + if (debug_report) printf("DuplicateColumn::undoFix 12==101\n"); + if (kAllowDeveloperAssert) assert(12 == 101); + x_v = x_lo - primal_feasibility_tolerance; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + } + } + } else if (y_up < kHighsInf) { + // If v_m_a_yup is left of x_lo-eps then [v_m_a_ylo, v_m_a_yup] is + // left of [x_lo-eps, x_up+eps] so there's no solution. [Could + // try v_m_a_yup computed from y_lo-eps.] + if (kAllowDeveloperAssert) + assert(x_lo - primal_feasibility_tolerance <= v_m_a_yup); + // This assignment is OK unless x_v < x_lo-eps + y_v = y_up; + x_v = v_m_a_yup; + if (x_v > x_up + primal_feasibility_tolerance) { + // Try y_v corresponding to x_up + // if (kAllowDeveloperAssert) assert(11==102); + x_v = x_up; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + if (y_v < y_lo - primal_feasibility_tolerance) { + // Very tight: use x_v on its margin and hope! + if (debug_report) printf("DuplicateColumn::undoFix 12==102\n"); + if (kAllowDeveloperAssert) assert(12 == 102); + x_v = x_up + primal_feasibility_tolerance; + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + } + } + } else { + // y is free, so use x_v = max(0, x_lo) + x_v = std::max(0.0, x_lo); + y_v = double((HighsCDouble(merge_value) - x_v) / scale); + } + } + } + const double residual_tolerance = 1e-12; + double residual = + std::fabs(double(HighsCDouble(x_v) + HighsCDouble(y_v) * scale - + HighsCDouble(merge_value))); + const bool x_y_ok = + isFeasible(x_lo, x_v, x_up) && isFeasible(y_lo, y_v, y_up) && + (!x_int || isInteger(x_v)) && (!y_int || isInteger(y_v)) && + (std::fabs(x_v) < kHighsInf) && (std::fabs(y_v) < kHighsInf) && + (residual <= residual_tolerance); + + bool check; + check = isFeasible(x_lo, x_v, x_up); + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: isFeasible(x_lo, x_v, x_up) is " + "false\n"); + if (allow_assert) assert(check); + } + check = isFeasible(y_lo, y_v, y_up); + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: isFeasible(y_lo, y_v, y_up) is " + "false\n"); + if (allow_assert) assert(check); + } + check = !x_int || isInteger(x_v); + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: !x_int || isInteger(x_v) is false\n"); + if (allow_assert) assert(check); + } + check = !y_int || isInteger(y_v); + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: !y_int || isInteger(y_v) is false\n"); + if (allow_assert) assert(check); + } + check = std::fabs(x_v) < kHighsInf; + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: std::fabs(x_v) < kHighsInf is false\n"); + if (allow_assert) assert(check); + } + check = std::fabs(y_v) < kHighsInf; + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: std::fabs(y_v) < kHighsInf is false\n"); + if (allow_assert) assert(check); + } + check = residual <= residual_tolerance; + if (!check) { + if (debug_report) + printf( + "DuplicateColumn::undo error: residual <= residual_tolerance is " + "false\n"); + if (allow_assert) assert(check); + } + check = residual <= residual_tolerance; + if (debug_report) + printf("DuplicateColumn::undo%s x = %g; y = %g to give x + (%g)y = %g", + x_y_ok ? "" : " ERROR", x_v, y_v, scale, merge_value); + if (x_y_ok) { + if (debug_report) printf(": FIXED\n"); + } else if (check) { + if (debug_report) printf("\n"); + } else { + if (debug_report) printf(": residual = %g\n", residual); + } + //============================================================================================= + if (x_y_ok) { + col_value[x_ix] = x_v; + col_value[y_ix] = y_v; + } } void HighsPostsolveStack::DuplicateColumn::transformToPresolvedSpace( diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 3633056f88..f6c64f320d 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file HighsPostsolveStack.h * @brief Class to hold all information for postsolve and can transform back @@ -62,6 +60,12 @@ class HighsPostsolveStack { Nonzero() = default; }; + size_t debug_prev_numreductions = 0; + double debug_prev_col_lower = 0; + double debug_prev_col_upper = 0; + double debug_prev_row_lower = 0; + double debug_prev_row_upper = 0; + private: /// transform a column x by a linear mapping with a new column x'. /// I.e. substitute x = a * x' + b @@ -207,7 +211,8 @@ class HighsPostsolveStack { void undo(const HighsOptions& options, HighsSolution& solution, HighsBasis& basis) const; - + bool okMerge(const double tolerance) const; + void undoFix(const HighsOptions& options, HighsSolution& solution) const; void transformToPresolvedSpace(std::vector& primalSol) const; }; @@ -474,20 +479,32 @@ class HighsPostsolveStack { reductionAdded(ReductionType::kDuplicateRow); } - void duplicateColumn(double colScale, double colLower, double colUpper, + bool duplicateColumn(double colScale, double colLower, double colUpper, double duplicateColLower, double duplicateColUpper, HighsInt col, HighsInt duplicateCol, bool colIntegral, - bool duplicateColIntegral) { + bool duplicateColIntegral, + const double ok_merge_tolerance) { const HighsInt origCol = origColIndex[col]; const HighsInt origDuplicateCol = origColIndex[duplicateCol]; - reductionValues.push(DuplicateColumn{ - colScale, colLower, colUpper, duplicateColLower, duplicateColUpper, - origCol, origDuplicateCol, colIntegral, duplicateColIntegral}); + DuplicateColumn debug_values = { + colScale, colLower, colUpper, + duplicateColLower, duplicateColUpper, origCol, + origDuplicateCol, colIntegral, duplicateColIntegral}; + const bool ok_merge = debug_values.okMerge(ok_merge_tolerance); + const bool prevent_illegal_merge = true; + if (!ok_merge && prevent_illegal_merge) return false; + reductionValues.push(debug_values); + // reductionValues.push(DuplicateColumn{ + // colScale, colLower, colUpper, duplicateColLower, + // duplicateColUpper, origCol, origDuplicateCol, colIntegral, + // duplicateColIntegral}); + reductionAdded(ReductionType::kDuplicateColumn); // mark columns as not linearly transformable linearlyTransformable[origCol] = false; linearlyTransformable[origDuplicateCol] = false; + return true; } std::vector getReducedPrimalSolution( @@ -530,7 +547,7 @@ class HighsPostsolveStack { /// undo presolve steps for primal dual solution and basis void undo(const HighsOptions& options, HighsSolution& solution, - HighsBasis& basis) { + HighsBasis& basis, const HighsInt report_col = -1) { reductionValues.resetPosition(); // Verify that undo can be performed @@ -576,6 +593,10 @@ class HighsPostsolveStack { // now undo the changes for (HighsInt i = reductions.size() - 1; i >= 0; --i) { + if (report_col >= 0) + printf("Before reduction %2d (type %2d): col_value[%2d] = %g\n", + int(i), int(reductions[i].first), int(report_col), + solution.col_value[report_col]); switch (reductions[i].first) { case ReductionType::kLinearTransform: { LinearTransform reduction; @@ -663,20 +684,32 @@ class HighsPostsolveStack { DuplicateColumn reduction; reductionValues.pop(reduction); reduction.undo(options, solution, basis); + break; } + default: + printf("Reduction case %d not handled\n", int(reductions[i].first)); + if (kAllowDeveloperAssert) assert(1 == 0); } } + if (report_col >= 0) + printf("After last reduction: col_value[%2d] = %g\n", int(report_col), + solution.col_value[report_col]); } /// undo presolve steps for primal solution - void undoPrimal(const HighsOptions& options, HighsSolution& solution) { + void undoPrimal(const HighsOptions& options, HighsSolution& solution, + const HighsInt report_col = -1) { + // Call to reductionValues.resetPosition(); seems unnecessary as + // it's the first thing done in undo reductionValues.resetPosition(); HighsBasis basis; basis.valid = false; solution.dual_valid = false; - undo(options, solution, basis); + undo(options, solution, basis, report_col); } + /* + // Not used /// undo presolve steps for primal and dual solution void undoPrimalDual(const HighsOptions& options, HighsSolution& solution) { reductionValues.resetPosition(); @@ -686,7 +719,9 @@ class HighsPostsolveStack { assert(solution.dual_valid); undo(options, solution, basis); } + */ + // Only used for debugging void undoUntil(const HighsOptions& options, const std::vector& flagRow, const std::vector& flagCol, HighsSolution& solution, diff --git a/src/presolve/HighsSymmetry.cpp b/src/presolve/HighsSymmetry.cpp index 7e281959d7..33f60faf1e 100644 --- a/src/presolve/HighsSymmetry.cpp +++ b/src/presolve/HighsSymmetry.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file HighsSymmetry.cpp * @brief Facilities for symmetry detection @@ -1379,7 +1377,7 @@ bool HighsSymmetryDetection::compareCurrentGraph( for (HighsInt j = Gstart[i]; j != Gend[i]; ++j) if (!otherGraph.find(std::make_tuple(vertexToCell[Gedge[j].first], colCell, Gedge[j].second))) { - // return which cell does not match in its neighborhood as this should + // return which cell does not match in its neighbourhood as this should // have been detected with the hashing it can very rarely happen due to // a hash collision. In such a case we want to backtrack to the last // time where we targeted this particular cell. Otherwise we could spent @@ -1794,7 +1792,7 @@ void HighsSymmetryDetection::run(HighsSymmetries& symmetries) { } else { // This case can be caused by a hash collision which was now // detected in the graph comparison call. The graph comparison call - // will return the cell where the vertex neighborhood caused a + // will return the cell where the vertex neighbourhood caused a // mismatch on the edges. This would have been detected by // an exact partition refinement when we targeted that cell the last // time, so that is where we can backtrack to. diff --git a/src/presolve/HighsSymmetry.h b/src/presolve/HighsSymmetry.h index c707b8a075..40f6354a55 100644 --- a/src/presolve/HighsSymmetry.h +++ b/src/presolve/HighsSymmetry.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file HighsSymmetry.h * @brief Facilities for symmetry detection diff --git a/src/presolve/ICrash.cpp b/src/presolve/ICrash.cpp index 12a6fc3f2f..934bcb11a7 100644 --- a/src/presolve/ICrash.cpp +++ b/src/presolve/ICrash.cpp @@ -2,7 +2,8 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2021 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ @@ -14,6 +15,7 @@ #include "presolve/ICrash.h" #include +#include #include #include #include @@ -99,7 +101,7 @@ Quadratic parseOptions(const HighsLp& lp, const ICrashOptions options) { // if (status == HighsStatus::kOk) { // ilp = local_lp; // } else { - // printf("Cannot dualise equality problem\n"); + // printf("Cannot dualize equality problem\n"); // } } } else { @@ -120,7 +122,7 @@ Quadratic parseOptions(const HighsLp& lp, const ICrashOptions options) { // // if (status == HighsStatus::kOk) { // // ilp = local_lp; // // } else { - // // printf("Cannot dualise equality problem\n"); + // // printf("Cannot dualize equality problem\n"); // // } // } } diff --git a/src/presolve/ICrash.h b/src/presolve/ICrash.h index ab72565246..649383145a 100644 --- a/src/presolve/ICrash.h +++ b/src/presolve/ICrash.h @@ -2,7 +2,8 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2021 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ diff --git a/src/presolve/ICrashUtil.cpp b/src/presolve/ICrashUtil.cpp index f68053f596..8d0852215f 100644 --- a/src/presolve/ICrashUtil.cpp +++ b/src/presolve/ICrashUtil.cpp @@ -1,9 +1,9 @@ - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2021 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ diff --git a/src/presolve/ICrashUtil.h b/src/presolve/ICrashUtil.h index 6b05f097f5..67c52b5d51 100644 --- a/src/presolve/ICrashUtil.h +++ b/src/presolve/ICrashUtil.h @@ -1,9 +1,9 @@ - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2021 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ diff --git a/src/presolve/ICrashX.cpp b/src/presolve/ICrashX.cpp index f012d147d6..b75a9fd82c 100644 --- a/src/presolve/ICrashX.cpp +++ b/src/presolve/ICrashX.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "presolve/ICrashX.h" @@ -53,9 +51,9 @@ HighsStatus callCrossover(const HighsOptions& options, const HighsLp& lp, ipx::LpSolver lps; lps.SetParameters(parameters); - ipx::Int load_status = - lps.LoadModel(num_col, &objective[0], &col_lb[0], &col_ub[0], num_row, - &Ap[0], &Ai[0], &Av[0], &rhs[0], &constraint_type[0]); + ipx::Int load_status = lps.LoadModel( + num_col, objective.data(), col_lb.data(), col_ub.data(), num_row, + Ap.data(), Ai.data(), Av.data(), rhs.data(), constraint_type.data()); if (load_status != 0) { highsLogUser(log_options, HighsLogType::kError, "Error loading ipx model\n"); @@ -94,13 +92,13 @@ HighsStatus callCrossover(const HighsOptions& options, const HighsLp& lp, highsLogUser(log_options, HighsLogType::kInfo, "Calling IPX crossover with primal and dual values\n"); crossover_status = lps.CrossoverFromStartingPoint( - &x[0], &slack[0], &highs_solution.row_dual[0], - &highs_solution.col_dual[0]); + x.data(), slack.data(), highs_solution.row_dual.data(), + highs_solution.col_dual.data()); } else { highsLogUser(log_options, HighsLogType::kInfo, "Calling IPX crossover with only primal values\n"); - crossover_status = - lps.CrossoverFromStartingPoint(&x[0], &slack[0], nullptr, nullptr); + crossover_status = lps.CrossoverFromStartingPoint(x.data(), slack.data(), + nullptr, nullptr); } if (crossover_status != 0) { @@ -136,9 +134,9 @@ HighsStatus callCrossover(const HighsOptions& options, const HighsLp& lp, ipx_solution.ipx_row_status.resize(num_row); ipx_solution.ipx_col_status.resize(num_col); ipx::Int errflag = lps.GetBasicSolution( - &ipx_solution.ipx_col_value[0], &ipx_solution.ipx_row_value[0], - &ipx_solution.ipx_row_dual[0], &ipx_solution.ipx_col_dual[0], - &ipx_solution.ipx_row_status[0], &ipx_solution.ipx_col_status[0]); + ipx_solution.ipx_col_value.data(), ipx_solution.ipx_row_value.data(), + ipx_solution.ipx_row_dual.data(), ipx_solution.ipx_col_dual.data(), + ipx_solution.ipx_row_status.data(), ipx_solution.ipx_col_status.data()); if (errflag != 0) { highsLogUser(log_options, HighsLogType::kError, "IPX crossover getting basic solution: flag = %d\n", diff --git a/src/presolve/ICrashX.h b/src/presolve/ICrashX.h index de71f88fa0..e84c19d2b4 100644 --- a/src/presolve/ICrashX.h +++ b/src/presolve/ICrashX.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef PRESOLVE_ICRASHX_H_ #define PRESOLVE_ICRASHX_H_ diff --git a/src/presolve/PresolveComponent.cpp b/src/presolve/PresolveComponent.cpp index ffea0c9fbe..234996eb12 100644 --- a/src/presolve/PresolveComponent.cpp +++ b/src/presolve/PresolveComponent.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file PresolveComponent.cpp * @brief The HiGHS class @@ -26,107 +24,21 @@ HighsStatus PresolveComponent::init(const HighsLp& lp, HighsTimer& timer, return HighsStatus::kOk; } -HighsStatus PresolveComponent::setOptions(const HighsOptions& options) { - options_ = &options; - - return HighsStatus::kOk; -} - -std::string PresolveComponent::presolveStatusToString( - const HighsPresolveStatus presolve_status) { - switch (presolve_status) { - case HighsPresolveStatus::kNotPresolved: - return "Not presolved"; - case HighsPresolveStatus::kNotReduced: - return "Not reduced"; - case HighsPresolveStatus::kInfeasible: - return "Infeasible"; - case HighsPresolveStatus::kUnboundedOrInfeasible: - return "Unbounded or infeasible"; - case HighsPresolveStatus::kReduced: - return "Reduced"; - case HighsPresolveStatus::kReducedToEmpty: - return "Reduced to empty"; - case HighsPresolveStatus::kTimeout: - return "Timeout"; - case HighsPresolveStatus::kNullError: - return "Null error"; - case HighsPresolveStatus::kOptionsError: - return "Options error"; - default: - assert(1 == 0); - return "Unrecognised presolve status"; - } -} - -void PresolveComponent::negateReducedLpColDuals(bool reduced) { +void PresolveComponent::negateReducedLpColDuals() { for (HighsInt col = 0; col < data_.reduced_lp_.num_col_; col++) data_.recovered_solution_.col_dual[col] = -data_.recovered_solution_.col_dual[col]; return; } -void PresolveComponent::negateReducedLpCost() { return; } - HighsPresolveStatus PresolveComponent::run() { presolve::HPresolve presolve; presolve.setInput(data_.reduced_lp_, *options_, timer); HighsModelStatus status = presolve.run(data_.postSolveStack); data_.presolve_log_ = presolve.getPresolveLog(); - - // Ensure that the presolve status is used to set - // presolve_.presolve_status_, as well as being returned - HighsPresolveStatus presolve_status; - switch (status) { - case HighsModelStatus::kInfeasible: - presolve_status = HighsPresolveStatus::kInfeasible; - break; - case HighsModelStatus::kUnboundedOrInfeasible: - presolve_status = HighsPresolveStatus::kUnboundedOrInfeasible; - break; - case HighsModelStatus::kOptimal: - presolve_status = HighsPresolveStatus::kReducedToEmpty; - break; - default: - if (data_.postSolveStack.numReductions() == 0) - presolve_status = HighsPresolveStatus::kNotReduced; - else - presolve_status = HighsPresolveStatus::kReduced; - } - this->presolve_status_ = presolve_status; - return presolve_status; -} - -void PresolveComponent::clear() { - // has_run_ = false; - data_.clear(); -} -namespace presolve { - -bool checkOptions(const PresolveComponentOptions& options) { - // todo: check options in a smart way - if (options.dev) std::cout << "Checking presolve options... "; - - if (!(options.iteration_strategy == "smart" || - options.iteration_strategy == "off" || - options.iteration_strategy == "num_limit")) { - if (options.dev) - std::cout << "error: iteration strategy unknown: " - << options.iteration_strategy << "." << std::endl; - return false; - } - - if (options.iteration_strategy == "num_limit" && options.max_iterations < 0) { - if (options.dev) - std::cout << "warning: negative iteration limit: " - << options.max_iterations - << ". Presolve will be run with no limit on iterations." - << std::endl; - return false; - } - - return true; + presolve_status_ = presolve.getPresolveStatus(); + return presolve_status_; } -} // namespace presolve +void PresolveComponent::clear() { data_.clear(); } diff --git a/src/presolve/PresolveComponent.h b/src/presolve/PresolveComponent.h index 91670104d1..8c81de81b0 100644 --- a/src/presolve/PresolveComponent.h +++ b/src/presolve/PresolveComponent.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file PresolveComponent.h * @brief The HiGHS class @@ -24,8 +22,8 @@ #include #include -#include "HighsPostsolveStack.h" #include "lp_data/HighsLp.h" +#include "presolve/HighsPostsolveStack.h" #include "util/HighsComponent.h" #include "util/HighsTimer.h" @@ -35,13 +33,6 @@ // The structure of component is general, of the presolve component - presolve // specific. -enum class HighsPostsolveStatus { - kNotPresolved = -1, - kNoPrimalSolutionError, - kSolutionRecovered, - kBasisError -}; - struct PresolveComponentData : public HighsComponentData { HighsLp reduced_lp_; presolve::HighsPostsolveStack postSolveStack; @@ -70,29 +61,12 @@ struct PresolveComponentInfo : public HighsComponentInfo { HighsInt n_cols_removed = 0; HighsInt n_nnz_removed = 0; - double init_time = 0; double presolve_time = 0; - double solve_time = 0; double postsolve_time = 0; - double cleanup_time = 0; virtual ~PresolveComponentInfo() = default; }; -// HighsComponentOptions is a placeholder for options specific to this component -struct PresolveComponentOptions : public HighsComponentOptions { - bool is_valid = false; - // presolve options later when needed. - - std::string iteration_strategy = "smart"; - HighsInt max_iterations = 0; - - double time_limit = -1; - bool dev = false; - - virtual ~PresolveComponentOptions() = default; -}; - class PresolveComponent : public HighsComponent { public: void clear() override; @@ -104,13 +78,7 @@ class PresolveComponent : public HighsComponent { HighsLp& getReducedProblem() { return data_.reduced_lp_; } HighsPresolveLog& getPresolveLog() { return data_.presolve_log_; } - HighsStatus setOptions(const HighsOptions& options); - std::string presolveStatusToString(const HighsPresolveStatus presolve_status); - - void negateReducedLpColDuals(bool reduced); - void negateReducedLpCost(); - - // bool has_run_ = false; + void negateReducedLpColDuals(); PresolveComponentInfo info_; PresolveComponentData data_; @@ -122,10 +90,4 @@ class PresolveComponent : public HighsComponent { virtual ~PresolveComponent() = default; }; - -namespace presolve { - -bool checkOptions(const PresolveComponentOptions& options); -} - #endif diff --git a/src/qpsolver/basis.cpp b/src/qpsolver/basis.cpp index 973997966a..ec1cabc920 100644 --- a/src/qpsolver/basis.cpp +++ b/src/qpsolver/basis.cpp @@ -4,17 +4,23 @@ #include Basis::Basis(Runtime& rt, std::vector active, - std::vector lower, std::vector inactive) + std::vector status, std::vector inactive) : runtime(rt), buffer_column_aq(rt.instance.num_var), buffer_row_ep(rt.instance.num_var) { buffer_vec2hvec.setup(rt.instance.num_var); + + for (HighsInt i=0; i L_old = L; L.clear(); L.resize((new_k_max) * (new_k_max)); + const HighsInt l_size = L.size(); + // Driven by #958, changes made in following lines to avoid array + // bound error when new_k_max < current_k_max HighsInt min_k_max = min(new_k_max, current_k_max); for (HighsInt i = 0; i < min_k_max; i++) { - for (HighsInt j = 0; j < current_k_max; j++) { + for (HighsInt j = 0; j < min_k_max; j++) { + assert(i * (new_k_max) + j < l_size); L[i * (new_k_max) + j] = L_old[i * current_k_max + j]; } } @@ -99,7 +103,6 @@ class CholeskyFactor { double mu = gyp * yp; l.resparsify(); double lambda = mu - l.norm2(); - if (lambda > 0.0) { if (current_k_max <= current_k + 1) { resize(current_k_max * 2); @@ -112,6 +115,7 @@ class CholeskyFactor { current_k++; } else { + printf("lambda = %lf\n", lambda); return QpSolverStatus::NOTPOSITIVDEFINITE; // |LL' 0| diff --git a/src/qpsolver/feasibility_highs.hpp b/src/qpsolver/feasibility_highs.hpp index 22c1d71668..d6386b3987 100644 --- a/src/qpsolver/feasibility_highs.hpp +++ b/src/qpsolver/feasibility_highs.hpp @@ -70,7 +70,7 @@ static void computestartingpoint_highs(Runtime& runtime, CrashSolution& result) HighsStatus status = highs.run(); if (status != HighsStatus::kOk) { - runtime.status = ProblemStatus::ERROR; + runtime.status = QpModelStatus::ERROR; return; } @@ -78,7 +78,7 @@ static void computestartingpoint_highs(Runtime& runtime, CrashSolution& result) HighsModelStatus phase1stat = highs.getModelStatus(); if (phase1stat == HighsModelStatus::kInfeasible) { - runtime.status = ProblemStatus::INFEASIBLE; + runtime.status = QpModelStatus::INFEASIBLE; return; } diff --git a/src/qpsolver/feasibility_quass.hpp b/src/qpsolver/feasibility_quass.hpp index caea5b9d61..c401c4b6f6 100644 --- a/src/qpsolver/feasibility_quass.hpp +++ b/src/qpsolver/feasibility_quass.hpp @@ -57,7 +57,7 @@ void computestartingpoint_quass(Runtime& runtime, CrashSolution& result) { // HighsModelStatus phase1stat = highs.getModelStatus(); // if (phase1stat == HighsModelStatus::kInfeasible) { - // runtime.status = ProblemStatus::INFEASIBLE; + // runtime.status = QpModelStatus::INFEASIBLE; // return; // } diff --git a/src/qpsolver/qpconst.hpp b/src/qpsolver/qpconst.hpp index 6225037f20..0d8b75f60c 100644 --- a/src/qpsolver/qpconst.hpp +++ b/src/qpsolver/qpconst.hpp @@ -4,4 +4,22 @@ enum class QpSolverStatus { OK, NOTPOSITIVDEFINITE, DEGENERATE }; +enum class QpModelStatus { + INDETERMINED, + OPTIMAL, + UNBOUNDED, + INFEASIBLE, + ITERATIONLIMIT, + TIMELIMIT, + ERROR +}; + +enum class BasisStatus { + Inactive, + ActiveAtLower = 1, + ActiveAtUpper, + InactiveInBasis +}; + + #endif diff --git a/src/qpsolver/quass.cpp b/src/qpsolver/quass.cpp index b72d01c8ef..3e5a1a664e 100644 --- a/src/qpsolver/quass.cpp +++ b/src/qpsolver/quass.cpp @@ -29,7 +29,7 @@ void Quass::solve() { runtime.instance = runtime.perturbed; CrashSolution crash(runtime.instance.num_var, runtime.instance.num_con); computestartingpoint(runtime, crash); - if (runtime.status != ProblemStatus::INDETERMINED) { + if (runtime.status != QpModelStatus::INDETERMINED) { return; } Basis basis(runtime, crash.active, crash.rowstatus, crash.inactive); @@ -196,13 +196,16 @@ static std::unique_ptr getPricing(Runtime& runtime, Basis& basis, } static void regularize(Runtime& rt) { + if (!rt.settings.hessianregularization) { + return; + } // add small diagonal to hessian for (HighsInt i = 0; i < rt.instance.num_var; i++) { for (HighsInt index = rt.instance.Q.mat.start[i]; index < rt.instance.Q.mat.start[i + 1]; index++) { if (rt.instance.Q.mat.index[index] == i) { rt.instance.Q.mat.value[index] += - rt.settings.semidefiniteregularization; + rt.settings.hessianregularizationfactor; } } } @@ -243,13 +246,13 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { while (true) { // check iteration limit if (runtime.statistics.num_iterations >= runtime.settings.iterationlimit) { - runtime.status = ProblemStatus::ITERATIONLIMIT; - break; + runtime.status = QpModelStatus::ITERATIONLIMIT; + break; } // check time limit if (runtime.timer.readRunHighsClock() >= runtime.settings.timelimit) { - runtime.status = ProblemStatus::TIMELIMIT; + runtime.status = QpModelStatus::TIMELIMIT; break; } @@ -269,7 +272,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { if (atfsep) { HighsInt minidx = pricing->price(runtime.primal, gradient.getGradient()); if (minidx == -1) { - runtime.status = ProblemStatus::OPTIMAL; + runtime.status = QpModelStatus::OPTIMAL; break; } @@ -292,7 +295,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { if (!zero_curvature_direction) { status = factor.expand(buffer_yp, buffer_gyp, buffer_l, buffer_m); if (status != QpSolverStatus::OK) { - runtime.status = ProblemStatus::INDETERMINED; + runtime.status = QpModelStatus::INDETERMINED; return; } } @@ -304,7 +307,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { } if (p.norm2() < runtime.settings.pnorm_zero_threshold || - maxsteplength == 0.0) { + maxsteplength == 0.0 || fabs(gradient.getGradient().dot(p)) < runtime.settings.improvement_zero_threshold) { atfsep = true; } else { RatiotestResult stepres = ratiotest(runtime, p, rowmove, maxsteplength); @@ -314,7 +317,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { status = reduce(runtime, basis, stepres.limitingconstraint, buffer_d, maxabsd, constrainttodrop); if (status != QpSolverStatus::OK) { - runtime.status = ProblemStatus::INDETERMINED; + runtime.status = QpModelStatus::INDETERMINED; return; } if (!zero_curvature_direction) { @@ -331,7 +334,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { : BasisStatus::ActiveAtUpper, constrainttodrop, pricing.get()); if (status != QpSolverStatus::OK) { - runtime.status = ProblemStatus::INDETERMINED; + runtime.status = QpModelStatus::INDETERMINED; return; } if (basis.getnumactive() != runtime.instance.num_var) { @@ -341,7 +344,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { if (stepres.alpha == std::numeric_limits::infinity()) { // unbounded - runtime.status = ProblemStatus::UNBOUNDED; + runtime.status = QpModelStatus::UNBOUNDED; return; } atfsep = false; @@ -374,6 +377,15 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0) { } } + // extract basis status + for (HighsInt i=0; i status_var; + std::vector status_con; Runtime(Instance& inst, HighsTimer& ht) : instance(inst), @@ -40,7 +34,9 @@ struct Runtime { primal(Vector(instance.num_var)), rowactivity(Vector(instance.num_con)), dualvar(instance.num_var), - dualcon(instance.num_con) {} + dualcon(instance.num_con), + status_var(instance.num_var), + status_con(instance.num_con) {} }; #endif diff --git a/src/qpsolver/settings.hpp b/src/qpsolver/settings.hpp index efe0af1a9a..4b48530a3c 100644 --- a/src/qpsolver/settings.hpp +++ b/src/qpsolver/settings.hpp @@ -16,18 +16,20 @@ struct Settings { PricingStrategy pricing = PricingStrategy::Devex; - double pnorm_zero_threshold = 10E-12; - double d_zero_threshold = 10E-13; - double lambda_zero_threshold = 10E-10; + double pnorm_zero_threshold = 10E-12; // if ||p|| < this threshold, p is determined to not be an improving search direction + double improvement_zero_threshold = 10E-5; // if p^t gradient < this threshold, p is determined to not be an improving search direction + double d_zero_threshold = 10E-13; // minimal value for pivot, will declare degeneracy if no larger pivot is found + double lambda_zero_threshold = 10E-10; // used for pricing / optimality checking - double semidefiniteregularization = 1E-7; + bool hessianregularization = false; // if true, a small multiple of the identity matrix will be added to the Hessian + double hessianregularizationfactor = 1E-7; // multiple of identity matrix added to hessian in case of regularization Phase1Strategy phase1strategy = Phase1Strategy::HIGHS; bool phase1movefreevarsbasic = false; bool phase1boundfreevars = false; OutputLevel outputlevel = OutputLevel::LIGHT; - HighsInt reportingfequency = 100; + HighsInt reportingfequency = 1; HighsInt reinvertfrequency = 100; HighsInt gradientrecomputefrequency = 1; diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index bb275eaa92..fe3ac561ec 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef SIMPLEX_HAPP_H_ #define SIMPLEX_HAPP_H_ @@ -147,17 +145,17 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { return returnFromSolveLpSimplex(solver_object, call_status); } } else { - // Starting from a logical basis, so consider dualising and/or + // Starting from a logical basis, so consider dualizing and/or // permuting the LP - if (options.simplex_dualise_strategy == kHighsOptionChoose || - options.simplex_dualise_strategy == kHighsOptionOn) { - // Dualise unless we choose not to - bool dualise_lp = true; - if (options.simplex_dualise_strategy == kHighsOptionChoose) { + if (options.simplex_dualize_strategy == kHighsOptionChoose || + options.simplex_dualize_strategy == kHighsOptionOn) { + // Dualize unless we choose not to + bool dualize_lp = true; + if (options.simplex_dualize_strategy == kHighsOptionChoose) { if (incumbent_lp.num_row_ < 10 * incumbent_lp.num_col_) - dualise_lp = false; + dualize_lp = false; } - if (dualise_lp) ekk_instance.dualise(); + if (dualize_lp) ekk_instance.dualize(); } if (options.simplex_permute_strategy == kHighsOptionChoose || options.simplex_permute_strategy == kHighsOptionOn) { @@ -186,9 +184,9 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { return_status = ekk_instance.solve(); solved_unscaled_lp = true; ekk_instance.unpermute(); - ekk_instance.undualise(); + ekk_instance.undualize(); assert(!ekk_instance.status_.is_permuted && - !ekk_instance.status_.is_dualised); + !ekk_instance.status_.is_dualized); if (options.cost_scale_factor) { double cost_scale_factor = pow(2.0, -options.cost_scale_factor); highsLogDev(options.log_options, HighsLogType::kInfo, @@ -211,9 +209,9 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // return_status = ekk_instance.solve(); ekk_instance.unpermute(); - ekk_instance.undualise(); + ekk_instance.undualize(); assert(!ekk_instance.status_.is_permuted && - !ekk_instance.status_.is_dualised); + !ekk_instance.status_.is_dualized); // if (options.cost_scale_factor) { double cost_scale_factor = pow(2.0, -options.cost_scale_factor); diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index 8b26a61223..7ecb7fbef0 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkk.cpp * @brief @@ -37,7 +35,7 @@ void HEkk::clear() { // Clears Ekk entirely. Clears all associated pointers, data scalars // and vectors, and the status values. this->clearEkkLp(); - this->clearEkkDualise(); + this->clearEkkDualize(); this->clearEkkData(); this->clearEkkDualEdgeWeightData(); this->clearEkkPointers(); @@ -95,7 +93,7 @@ void HEkk::clearEkkLp() { lp_name_ = ""; } -void HEkk::clearEkkDualise() { +void HEkk::clearEkkDualize() { this->original_col_cost_.clear(); this->original_col_lower_.clear(); this->original_col_upper_.clear(); @@ -288,7 +286,7 @@ void HEkk::clearHotStart() { void HEkk::invalidate() { this->status_.initialised_for_new_lp = false; - assert(!this->status_.is_dualised); + assert(!this->status_.is_dualized); assert(!this->status_.is_permuted); this->status_.initialised_for_solve = false; this->invalidateBasisMatrix(); @@ -323,7 +321,7 @@ void HEkk::invalidateBasisArtifacts() { } void HEkk::updateStatus(LpAction action) { - assert(!this->status_.is_dualised); + assert(!this->status_.is_dualized); assert(!this->status_.is_permuted); switch (action) { case LpAction::kScale: @@ -412,7 +410,7 @@ void HEkk::setNlaPointersForTrans(const HighsLp& lp) { assert(status_.has_nla); assert(status_.has_basis); simplex_nla_.setLpAndScalePointers(&lp); - simplex_nla_.basic_index_ = &basis_.basicIndex_[0]; + simplex_nla_.basic_index_ = basis_.basicIndex_.data(); } void HEkk::setNlaRefactorInfo() { @@ -472,7 +470,7 @@ HighsSparseMatrix* HEkk::getScaledAMatrixPointer() { return local_scaled_a_matrix; } -HighsStatus HEkk::dualise() { +HighsStatus HEkk::dualize() { assert(lp_.a_matrix_.isColwise()); original_num_col_ = lp_.num_col_; original_num_row_ = lp_.num_row_; @@ -755,7 +753,7 @@ HighsStatus HEkk::dualise() { // Flip LP dimensions lp_.num_col_ = dual_num_col; lp_.num_row_ = dual_num_row; - status_.is_dualised = true; + status_.is_dualized = true; status_.has_basis = false; status_.has_ar_matrix = false; status_.has_nla = false; @@ -780,8 +778,8 @@ HighsStatus HEkk::dualise() { return HighsStatus::kOk; } -HighsStatus HEkk::undualise() { - if (!this->status_.is_dualised) return HighsStatus::kOk; +HighsStatus HEkk::undualize() { + if (!this->status_.is_dualized) return HighsStatus::kOk; HighsInt dual_num_col = lp_.num_col_; HighsInt primal_num_tot = original_num_col_ + original_num_row_; // These two aren't used (yet) @@ -991,13 +989,13 @@ HighsStatus HEkk::undualise() { HighsInt num_basic_variables = primal_basic_index.size(); bool num_basic_variables_ok = num_basic_variables == original_num_row_; if (!num_basic_variables_ok) - printf("HEkk::undualise: Have %d basic variables, not %d\n", + printf("HEkk::undualize: Have %d basic variables, not %d\n", (int)num_basic_variables, (int)original_num_row_); assert(num_basic_variables_ok); // Clear the data retained when solving dual LP - clearEkkDualise(); - status_.is_dualised = false; + clearEkkDualize(); + status_.is_dualized = false; // Now solve with this basis. Should just be a case of reinverting // and re-solving for optimal primal and dual values, but // numerically marginal LPs will need clean-up @@ -1207,8 +1205,12 @@ HighsStatus HEkk::setBasis(const HighsBasis& highs_basis) { // internal call, but it may be a basis that's set up internally // with errors :-) ... // - // The basis should be dual faeible unless it's alien - debug_dual_feasible = !highs_basis.was_alien; + if (kDebugMipNodeDualFeasible) { + // The basis should be dual feasible unless it was alien + debug_dual_feasible = !highs_basis.was_alien; + } else { + assert(!debug_dual_feasible); + } HighsOptions& options = *options_; if (debugHighsBasisConsistent(options, lp_, highs_basis) == HighsDebugStatus::kLogicalError) { @@ -1305,7 +1307,8 @@ void HEkk::addRows(const HighsLp& lp, // if (valid_simplex_lp) // assert(ekk_instance_.lp_.dimensionsOk("addRows - simplex")); if (kExtendInvertWhenAddingRows && this->status_.has_nla) { - this->simplex_nla_.addRows(&lp, &basis_.basicIndex_[0], &scaled_ar_matrix); + this->simplex_nla_.addRows(&lp, basis_.basicIndex_.data(), + &scaled_ar_matrix); setNlaPointersForTrans(lp); this->debugNlaCheckInvert("HEkk::addRows - on entry", kHighsDebugLevelExpensive + 1); @@ -1513,19 +1516,19 @@ HighsStatus HEkk::initialiseSimplexLpBasisAndFactor( // if (this->status_.has_nla) { assert(lpFactorRowCompatible()); - this->simplex_nla_.setPointers(&(this->lp_), local_scaled_a_matrix, - &this->basis_.basicIndex_[0], this->options_, - this->timer_, &(this->analysis_)); + this->simplex_nla_.setPointers( + &(this->lp_), local_scaled_a_matrix, this->basis_.basicIndex_.data(), + this->options_, this->timer_, &(this->analysis_)); } else { // todo @ Julian: this fails on glass4 assert(info_.factor_pivot_threshold >= options_->factor_pivot_threshold); - simplex_nla_.setup(&(this->lp_), //&lp_, - &this->basis_.basicIndex_[0], //&basis_.basicIndex_[0], - this->options_, // options_, - this->timer_, // timer_, - &(this->analysis_), //&analysis_, - local_scaled_a_matrix, - this->info_.factor_pivot_threshold); + simplex_nla_.setup( + &(this->lp_), //&lp_, + this->basis_.basicIndex_.data(), // basis_.basicIndex_.data(), + this->options_, // options_, + this->timer_, // timer_, + &(this->analysis_), //&analysis_, + local_scaled_a_matrix, this->info_.factor_pivot_threshold); status_.has_nla = true; } @@ -2120,8 +2123,8 @@ void HEkk::updateDualSteepestEdgeWeights( const HighsInt num_row = lp_.num_row_; const HighsInt column_count = column->count; - const HighsInt* variable_index = &column->index[0]; - const double* column_array = &column->array[0]; + const HighsInt* variable_index = column->index.data(); + const double* column_array = column->array.data(); const double col_aq_scale = simplex_nla_.variableScaleFactor(variable_in); const double col_ap_scale = simplex_nla_.basicColScaleFactor(row_out); @@ -2264,8 +2267,8 @@ void HEkk::updateDualDevexWeights(const HVector* column, const HighsInt num_row = lp_.num_row_; const HighsInt column_count = column->count; - const HighsInt* variable_index = &column->index[0]; - const double* column_array = &column->array[0]; + const HighsInt* variable_index = column->index.data(); + const double* column_array = column->array.data(); if ((HighsInt)dual_edge_weight_.size() < num_row) { printf( @@ -2296,8 +2299,9 @@ void HEkk::resetSyntheticClock() { void HEkk::initialisePartitionedRowwiseMatrix() { if (status_.has_ar_matrix) return; analysis_.simplexTimerStart(matrixSetupClock); - ar_matrix_.createRowwisePartitioned(lp_.a_matrix_, &basis_.nonbasicFlag_[0]); - assert(ar_matrix_.debugPartitionOk(&basis_.nonbasicFlag_[0])); + ar_matrix_.createRowwisePartitioned(lp_.a_matrix_, + basis_.nonbasicFlag_.data()); + assert(ar_matrix_.debugPartitionOk(basis_.nonbasicFlag_.data())); analysis_.simplexTimerStop(matrixSetupClock); status_.has_ar_matrix = true; } @@ -2892,7 +2896,7 @@ void HEkk::tableauRowPrice(const bool quad_precision, const HVector& row_ep, // Column-wise PRICE computes components corresponding to basic // variables, so zero these by exploiting the fact that, for basic // variables, nonbasicFlag[*]=0 - const int8_t* nonbasicFlag = &basis_.nonbasicFlag_[0]; + const int8_t* nonbasicFlag = basis_.nonbasicFlag_.data(); for (HighsInt iCol = 0; iCol < solver_num_col; iCol++) row_ap.array[iCol] *= nonbasicFlag[iCol]; } @@ -3212,7 +3216,7 @@ void HEkk::updateMatrix(const HighsInt variable_in, const HighsInt variable_out) { analysis_.simplexTimerStart(UpdateMatrixClock); ar_matrix_.update(variable_in, variable_out, lp_.a_matrix_); - // assert(ar_matrix_.debugPartitionOk(&basis_.nonbasicFlag_[0])); + // assert(ar_matrix_.debugPartitionOk(basis_.nonbasicFlag_.data())); analysis_.simplexTimerStop(UpdateMatrixClock); } @@ -3629,8 +3633,8 @@ double HEkk::computeBasisCondition() { HVector row_ep; row_ep.setup(solver_num_row); - const HighsInt* Astart = &lp_.a_matrix_.start_[0]; - const double* Avalue = &lp_.a_matrix_.value_[0]; + const HighsInt* Astart = lp_.a_matrix_.start_.data(); + const double* Avalue = lp_.a_matrix_.value_.data(); // Compute the Hager condition number estimate for the basis matrix const double expected_density = 1; bs_cond_x.resize(solver_num_row); @@ -3786,7 +3790,7 @@ HighsStatus HEkk::unfreezeBasis(const HighsInt frozen_basis_id) { // The pointers to simplex basis components have changed, so have to // tell simplex NLA to refresh the use of the pointer to the basic // indices - this->simplex_nla_.setBasicIndexPointers(&basis_.basicIndex_[0]); + this->simplex_nla_.setBasicIndexPointers(basis_.basicIndex_.data()); updateStatus(LpAction::kNewBounds); // Indicate whether there is a valid factorization after unfreezing this->status_.has_invert = will_have_invert; diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index 13eb51eeea..9a74e4776e 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkk.h * @brief Primal simplex solver for HiGHS @@ -33,7 +31,7 @@ class HEkk { void clear(); void clearEkkLp(); void clearEkkData(); - void clearEkkDualise(); + void clearEkkDualize(); void clearEkkDualEdgeWeightData(); void clearEkkPointers(); void clearEkkDataInfo(); @@ -63,8 +61,8 @@ class HEkk { HighsScale* getScalePointer(); void initialiseEkk(); - HighsStatus dualise(); - HighsStatus undualise(); + HighsStatus dualize(); + HighsStatus undualize(); HighsStatus permute(); HighsStatus unpermute(); HighsStatus solve(const bool force_phase2 = false); @@ -173,7 +171,7 @@ class HEkk { vector proof_index_; vector proof_value_; - // Data to be retained when dualising + // Data to be retained when dualizing HighsInt original_num_col_; HighsInt original_num_row_; HighsInt original_num_nz_; diff --git a/src/simplex/HEkkControl.cpp b/src/simplex/HEkkControl.cpp index 2da8143478..aa32e0b66f 100644 --- a/src/simplex/HEkkControl.cpp +++ b/src/simplex/HEkkControl.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkControl.cpp * @brief diff --git a/src/simplex/HEkkDebug.cpp b/src/simplex/HEkkDebug.cpp index 5b3025b8a9..deefab70dc 100644 --- a/src/simplex/HEkkDebug.cpp +++ b/src/simplex/HEkkDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HEkkDebug.cpp * @brief @@ -180,7 +178,7 @@ void HEkk::timeReporting(const HighsInt save_mod_recover) { bool output_flag = true; bool log_to_console = false; HighsInt log_dev_level = kHighsLogDevLevelVerbose; - log_options.log_file_stream = stdout; + log_options.log_stream = stdout; log_options.output_flag = &output_flag; log_options.log_to_console = &log_to_console; log_options.log_dev_level = &log_dev_level; diff --git a/src/simplex/HEkkDual.cpp b/src/simplex/HEkkDual.cpp index b2f71074d3..6412e403a7 100644 --- a/src/simplex/HEkkDual.cpp +++ b/src/simplex/HEkkDual.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDual.cpp * @brief @@ -74,6 +72,11 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { force_phase2 = pass_force_phase2 || info.max_dual_infeasibility * info.max_dual_infeasibility < ekk_instance_.options_->dual_feasibility_tolerance; + // Within the MIP solver, unless the basis supplied was alien, the + // simplex solver should be able to start from dual feasibility, so + // possibly debug this property. Note that debug_dual_feasible is + // set in HEkk::setBasis, and is false if kDebugMipNodeDualFeasible + // is false if (ekk_instance_.debug_dual_feasible && !dual_feasible_with_unperturbed_costs) { SimplexBasis& basis = ekk_instance_.basis_; @@ -410,13 +413,13 @@ void HEkkDual::initialiseInstance() { analysis = &ekk_instance_.analysis_; // Copy pointers - jMove = &ekk_instance_.basis_.nonbasicMove_[0]; - workDual = &ekk_instance_.info_.workDual_[0]; - workValue = &ekk_instance_.info_.workValue_[0]; - workRange = &ekk_instance_.info_.workRange_[0]; - baseLower = &ekk_instance_.info_.baseLower_[0]; - baseUpper = &ekk_instance_.info_.baseUpper_[0]; - baseValue = &ekk_instance_.info_.baseValue_[0]; + jMove = ekk_instance_.basis_.nonbasicMove_.data(); + workDual = ekk_instance_.info_.workDual_.data(); + workValue = ekk_instance_.info_.workValue_.data(); + workRange = ekk_instance_.info_.workRange_.data(); + baseLower = ekk_instance_.info_.baseLower_.data(); + baseUpper = ekk_instance_.info_.baseUpper_.data(); + baseValue = ekk_instance_.info_.baseValue_.data(); // Setup local vectors col_DSE.setup(solver_num_row); @@ -499,9 +502,9 @@ void HEkkDual::initSlice(const HighsInt initial_num_slice) { } // Alias to the matrix - const HighsInt* Astart = &a_matrix->start_[0]; - const HighsInt* Aindex = &a_matrix->index_[0]; - const double* Avalue = &a_matrix->value_[0]; + const HighsInt* Astart = a_matrix->start_.data(); + const HighsInt* Aindex = a_matrix->index_.data(); + const double* Avalue = a_matrix->value_.data(); const HighsInt AcountX = Astart[solver_num_col]; // Figure out partition weight @@ -721,9 +724,10 @@ void HEkkDual::solvePhase1() { // infeasibilities, it will set solve_phase = kSolvePhase2; assessPhase1Optimality(); } - } else if (rebuild_reason == kRebuildReasonChooseColumnFail) { - // chooseColumn has failed - // Behave as "Report strange issues" below + } else if (rebuild_reason == kRebuildReasonChooseColumnFail || + rebuild_reason == kRebuildReasonExcessivePrimalValue) { + // chooseColumn has failed or excessive primal values have been + // created Behave as "Report strange issues" below solve_phase = kSolvePhaseError; highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, "dual-phase-1-not-solved\n"); @@ -969,9 +973,10 @@ void HEkkDual::solvePhase2() { "problem-optimal\n"); model_status = HighsModelStatus::kOptimal; } - } else if (rebuild_reason == kRebuildReasonChooseColumnFail) { - // chooseColumn has failed - // Behave as "Report strange issues" below + } else if (rebuild_reason == kRebuildReasonChooseColumnFail || + rebuild_reason == kRebuildReasonExcessivePrimalValue) { + // chooseColumn has failed or excessive primal values have been + // created Behave as "Report strange issues" below solve_phase = kSolvePhaseError; highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, "dual-phase-2-not-solved\n"); @@ -1033,7 +1038,7 @@ void HEkkDual::rebuild() { assert(info.backtracking_); ekk_instance_.initialisePartitionedRowwiseMatrix(); assert(ekk_instance_.ar_matrix_.debugPartitionOk( - &ekk_instance_.basis_.nonbasicFlag_[0])); + ekk_instance_.basis_.nonbasicFlag_.data())); } // Record whether the update objective value should be tested. If // the objective value is known, then the updated objective value @@ -2128,7 +2133,11 @@ void HEkkDual::updatePrimal(HVector* DSE_Vector) { double l_out = baseLower[row_out]; double u_out = baseUpper[row_out]; theta_primal = (x_out - (delta_primal < 0 ? l_out : u_out)) / alpha_col; - dualRHS.updatePrimal(&col_aq, theta_primal); + const bool ok_update_primal = dualRHS.updatePrimal(&col_aq, theta_primal); + if (!ok_update_primal) { + rebuild_reason = kRebuildReasonExcessivePrimalValue; + return; + } ekk_instance_.updateBadBasisChange(col_aq, theta_primal); if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) { const double pivot_in_scaled_space = @@ -2141,7 +2150,7 @@ void HEkkDual::updatePrimal(HVector* DSE_Vector) { const double Kai = -2 / pivot_in_scaled_space; ekk_instance_.updateDualSteepestEdgeWeights(row_out, variable_in, &col_aq, new_pivotal_edge_weight, Kai, - &DSE_Vector->array[0]); + DSE_Vector->array.data()); edge_weight[row_out] = new_pivotal_edge_weight; } else if (edge_weight_mode == EdgeWeightMode::kDevex) { // Pivotal row is for the current basis: weights are required for diff --git a/src/simplex/HEkkDual.h b/src/simplex/HEkkDual.h index 62d8673dc1..584af97fa0 100644 --- a/src/simplex/HEkkDual.h +++ b/src/simplex/HEkkDual.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDual.h * @brief Dual simplex solver for HiGHS diff --git a/src/simplex/HEkkDualMulti.cpp b/src/simplex/HEkkDualMulti.cpp index 95029540f7..bad80254c6 100644 --- a/src/simplex/HEkkDualMulti.cpp +++ b/src/simplex/HEkkDualMulti.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDualMulti.cpp * @brief @@ -89,9 +87,9 @@ void HEkkDual::majorChooseRow() { // Call the hyper-graph method, but partSwitch=0 so just uses // choose_multi_global - dualRHS.chooseMultiHyperGraphAuto(&choiceIndex[0], &initialCount, + dualRHS.chooseMultiHyperGraphAuto(choiceIndex.data(), &initialCount, multi_num); - // dualRHS.chooseMultiGlobal(&choiceIndex[0], &initialCount, multi_num); + // dualRHS.chooseMultiGlobal(choiceIndex.data(), &initialCount, multi_num); if (initialCount == 0 && dualRHS.workCutoff == 0) { // OPTIMAL return; @@ -587,7 +585,7 @@ void HEkkDual::majorUpdateFtranPrepare() { // Update this buffer by previous Row_ep for (HighsInt jFn = iFn - 1; jFn >= 0; jFn--) { MFinish* jFinish = &multi_finish[jFn]; - double* jRow_epArray = &jFinish->row_ep->array[0]; + double* jRow_epArray = jFinish->row_ep->array.data(); double pivotX = 0; for (HighsInt k = 0; k < Vec->count; k++) { HighsInt iRow = Vec->index[k]; @@ -702,12 +700,12 @@ void HEkkDual::majorUpdateFtranFinal() { for (HighsInt iFn = 0; iFn < multi_nFinish; iFn++) { multi_finish[iFn].col_aq->count = -1; multi_finish[iFn].row_ep->count = -1; - double* myCol = &multi_finish[iFn].col_aq->array[0]; - double* myRow = &multi_finish[iFn].row_ep->array[0]; + double* myCol = multi_finish[iFn].col_aq->array.data(); + double* myRow = multi_finish[iFn].row_ep->array.data(); for (HighsInt jFn = 0; jFn < iFn; jFn++) { HighsInt pivotRow = multi_finish[jFn].row_out; const double pivotAlpha = multi_finish[jFn].alpha_row; - const double* pivotArray = &multi_finish[jFn].col_aq->array[0]; + const double* pivotArray = multi_finish[jFn].col_aq->array.data(); double pivotX1 = myCol[pivotRow]; double pivotX2 = myRow[pivotRow]; @@ -772,8 +770,8 @@ void HEkkDual::majorUpdatePrimal() { if (updatePrimal_inDense) { // Dense update of primal values, infeasibility list and // non-pivotal edge weights - const double* mixArray = &col_BFRT.array[0]; - double* local_work_infeasibility = &dualRHS.work_infeasibility[0]; + const double* mixArray = col_BFRT.array.data(); + double* local_work_infeasibility = dualRHS.work_infeasibility.data(); // #pragma omp parallel for schedule(static) highs::parallel::for_each( 0, solver_num_row, @@ -800,10 +798,10 @@ void HEkkDual::majorUpdatePrimal() { // multi_finish[iFn].EdWt has already been transformed to correspond to // the new basis const double new_pivotal_edge_weight = multi_finish[iFn].EdWt; - const double* colArray = &multi_finish[iFn].col_aq->array[0]; + const double* colArray = multi_finish[iFn].col_aq->array.data(); if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) { // Update steepest edge weights - const double* dseArray = &multi_finish[iFn].row_ep->array[0]; + const double* dseArray = multi_finish[iFn].row_ep->array.data(); const double Kai = -2 / multi_finish[iFn].alpha_row; // #pragma omp parallel for schedule(static) highs::parallel::for_each( @@ -850,7 +848,7 @@ void HEkkDual::majorUpdatePrimal() { double Kai = -2 / finish->alpha_row; ekk_instance_.updateDualSteepestEdgeWeights(row_out, variable_in, Col, new_pivotal_edge_weight, - Kai, &Row->array[0]); + Kai, Row->array.data()); } else if (edge_weight_mode == EdgeWeightMode::kDevex && !new_devex_framework) { // Update Devex weights @@ -876,12 +874,12 @@ void HEkkDual::majorUpdatePrimal() { for (HighsInt iFn = 0; iFn < multi_nFinish; iFn++) { const HighsInt iRow = multi_finish[iFn].row_out; const double new_pivotal_edge_weight = multi_finish[iFn].EdWt; - const double* colArray = &multi_finish[iFn].col_aq->array[0]; + const double* colArray = multi_finish[iFn].col_aq->array.data(); // The weight for this pivot is known, but weights for rows // pivotal earlier need to be updated if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) { // Steepest edge - const double* dseArray = &multi_finish[iFn].row_ep->array[0]; + const double* dseArray = multi_finish[iFn].row_ep->array.data(); double Kai = -2 / multi_finish[iFn].alpha_row; for (HighsInt jFn = 0; jFn < iFn; jFn++) { HighsInt jRow = multi_finish[jFn].row_out; diff --git a/src/simplex/HEkkDualRHS.cpp b/src/simplex/HEkkDualRHS.cpp index 075b4c73a0..1971cfe176 100644 --- a/src/simplex/HEkkDualRHS.cpp +++ b/src/simplex/HEkkDualRHS.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDualRHS.cpp * @brief @@ -312,19 +310,20 @@ void HEkkDualRHS::chooseMultiHyperGraphPart(HighsInt* chIndex, analysis->simplexTimerStop(ChuzrDualClock); } -void HEkkDualRHS::updatePrimal(HVector* column, double theta) { +bool HEkkDualRHS::updatePrimal(HVector* column, double theta) { analysis->simplexTimerStart(UpdatePrimalClock); const HighsInt numRow = ekk_instance_.lp_.num_row_; const HighsInt columnCount = column->count; - const HighsInt* variable_index = &column->index[0]; - const double* columnArray = &column->array[0]; + const HighsInt* variable_index = column->index.data(); + const double* columnArray = column->array.data(); - const double* baseLower = &ekk_instance_.info_.baseLower_[0]; - const double* baseUpper = &ekk_instance_.info_.baseUpper_[0]; + const double* baseLower = ekk_instance_.info_.baseLower_.data(); + const double* baseUpper = ekk_instance_.info_.baseUpper_.data(); const double Tp = ekk_instance_.options_->primal_feasibility_tolerance; - double* baseValue = &ekk_instance_.info_.baseValue_[0]; + double* baseValue = ekk_instance_.info_.baseValue_.data(); + HighsInt num_excessive_primal = 0; bool updatePrimal_inDense = columnCount < 0 || columnCount > 0.4 * numRow; const HighsInt to_entry = updatePrimal_inDense ? numRow : columnCount; @@ -346,8 +345,14 @@ void HEkkDualRHS::updatePrimal(HVector* column, double theta) { work_infeasibility[iRow] = primal_infeasibility * primal_infeasibility; else work_infeasibility[iRow] = fabs(primal_infeasibility); + if (baseValue[iRow] <= -kExcessivePrimalValue || + baseValue[iRow] >= kExcessivePrimalValue) + num_excessive_primal++; } analysis->simplexTimerStop(UpdatePrimalClock); + // Flag detection of excessive values in return + if (num_excessive_primal) return false; + return true; } void HEkkDualRHS::updatePivots(const HighsInt iRow, const double value) { @@ -374,7 +379,7 @@ void HEkkDualRHS::updatePivots(const HighsInt iRow, const double value) { void HEkkDualRHS::updateInfeasList(HVector* column) { const HighsInt columnCount = column->count; - const HighsInt* variable_index = &column->index[0]; + const HighsInt* variable_index = column->index.data(); // DENSE mode: disabled if (workCount < 0) return; @@ -411,9 +416,9 @@ void HEkkDualRHS::updateInfeasList(HVector* column) { void HEkkDualRHS::createArrayOfPrimalInfeasibilities() { HighsInt numRow = ekk_instance_.lp_.num_row_; - const double* baseValue = &ekk_instance_.info_.baseValue_[0]; - const double* baseLower = &ekk_instance_.info_.baseLower_[0]; - const double* baseUpper = &ekk_instance_.info_.baseUpper_[0]; + const double* baseValue = ekk_instance_.info_.baseValue_.data(); + const double* baseLower = ekk_instance_.info_.baseLower_.data(); + const double* baseUpper = ekk_instance_.info_.baseUpper_.data(); const double Tp = ekk_instance_.options_->primal_feasibility_tolerance; for (HighsInt i = 0; i < numRow; i++) { // @primal_infeasibility calculation @@ -435,10 +440,10 @@ void HEkkDualRHS::createArrayOfPrimalInfeasibilities() { void HEkkDualRHS::createInfeasList(double columnDensity) { HighsInt numRow = ekk_instance_.lp_.num_row_; - double* dwork = &ekk_instance_.scattered_dual_edge_weight_[0]; + double* dwork = ekk_instance_.scattered_dual_edge_weight_.data(); // 1. Build the full list - fill_n(&workMark[0], numRow, 0); + fill_n(workMark.data(), numRow, 0); workCount = 0; workCutoff = 0; for (HighsInt iRow = 0; iRow < numRow; iRow++) { @@ -465,7 +470,7 @@ void HEkkDualRHS::createInfeasList(double columnDensity) { workCutoff = min(maxMerit * 0.99999, cutMerit * 1.00001); // Create again - fill_n(&workMark[0], numRow, 0); + fill_n(workMark.data(), numRow, 0); workCount = 0; for (HighsInt iRow = 0; iRow < numRow; iRow++) { if (work_infeasibility[iRow] >= edge_weight[iRow] * workCutoff) { diff --git a/src/simplex/HEkkDualRHS.h b/src/simplex/HEkkDualRHS.h index b308ce3fdf..b7a3408ffe 100644 --- a/src/simplex/HEkkDualRHS.h +++ b/src/simplex/HEkkDualRHS.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDualRHS.h * @brief Dual simplex optimality test for HiGHS @@ -78,9 +76,10 @@ class HEkkDualRHS { ); /** - * @brief Update the primal values by adding a multiple of a given std::vector + * @brief Update the primal values by adding a multiple of a given + * std::vector, returning false if infinite values are created */ - void updatePrimal( + bool updatePrimal( HVector* column, //!< Column to add into primal values double theta //!< Multiple of column to add into primal values ); diff --git a/src/simplex/HEkkDualRow.cpp b/src/simplex/HEkkDualRow.cpp index e76ce76bde..a9d8e8babe 100644 --- a/src/simplex/HEkkDualRow.cpp +++ b/src/simplex/HEkkDualRow.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDualRow.cpp * @brief @@ -31,10 +29,10 @@ using std::set; void HEkkDualRow::setupSlice(HighsInt size) { workSize = size; - workMove = &ekk_instance_.basis_.nonbasicMove_[0]; - workDual = &ekk_instance_.info_.workDual_[0]; - workRange = &ekk_instance_.info_.workRange_[0]; - work_devex_index = &ekk_instance_.info_.devex_index_[0]; + workMove = ekk_instance_.basis_.nonbasicMove_.data(); + workDual = ekk_instance_.info_.workDual_.data(); + workRange = ekk_instance_.info_.workRange_.data(); + work_devex_index = ekk_instance_.info_.devex_index_.data(); // Allocate spaces packCount = 0; @@ -51,7 +49,7 @@ void HEkkDualRow::setup() { const HighsInt numTot = ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_; setupSlice(numTot); - workNumTotPermutation = &ekk_instance_.info_.numTotPermutation_[0]; + workNumTotPermutation = ekk_instance_.info_.numTotPermutation_.data(); // deleteFreelist() is being called in Phase 1 and Phase 2 since // it's in updatePivots(), but create_Freelist() is only called in @@ -72,8 +70,8 @@ void HEkkDualRow::chooseMakepack(const HVector* row, const HighsInt offset) { * Offset of numCol is used when packing row_ep */ const HighsInt rowCount = row->count; - const HighsInt* rowIndex = &row->index[0]; - const double* rowArray = &row->array[0]; + const HighsInt* rowIndex = row->index.data(); + const double* rowArray = row->array.data(); for (HighsInt i = 0; i < rowCount; i++) { const HighsInt index = rowIndex[i]; const double value = rowArray[index]; @@ -113,7 +111,7 @@ void HEkkDualRow::chooseJoinpack(const HEkkDualRow* otherRow) { * candidates in otherRow */ const HighsInt otherCount = otherRow->workCount; - const pair* otherData = &otherRow->workData[0]; + const pair* otherData = otherRow->workData.data(); copy(otherData, otherData + otherCount, &workData[workCount]); workCount = workCount + otherCount; workTheta = min(workTheta, otherRow->workTheta); @@ -465,7 +463,7 @@ bool HEkkDualRow::chooseFinalWorkGroupHeap() { heap_v[heap_num_en] = ratio; } } - maxheapsort(&heap_v[0], &heap_i[0], heap_num_en); + maxheapsort(heap_v.data(), heap_i.data(), heap_num_en); alt_workCount = 0; alt_workGroup.clear(); @@ -543,7 +541,7 @@ void HEkkDualRow::chooseFinalLargeAlpha( } void HEkkDualRow::updateFlip(HVector* bfrtColumn) { - double* workDual = &ekk_instance_.info_.workDual_[0]; + double* workDual = ekk_instance_.info_.workDual_.data(); double dual_objective_value_change = 0; bfrtColumn->clear(); for (HighsInt i = 0; i < workCount; i++) { @@ -561,7 +559,7 @@ void HEkkDualRow::updateFlip(HVector* bfrtColumn) { void HEkkDualRow::updateDual(double theta) { analysis->simplexTimerStart(UpdateDualClock); - double* workDual = &ekk_instance_.info_.workDual_[0]; + double* workDual = ekk_instance_.info_.workDual_.data(); double dual_objective_value_change = 0; for (HighsInt i = 0; i < packCount; i++) { workDual[packIndex[i]] -= theta * packValue[i]; @@ -600,14 +598,14 @@ void HEkkDualRow::createFreemove(HVector* row_ep) { HighsInt move_out = workDelta < 0 ? -1 : 1; set::iterator sit; for (sit = freeList.begin(); sit != freeList.end(); sit++) { - HighsInt iCol = *sit; - assert(iCol < ekk_instance_.lp_.num_col_); - double alpha = ekk_instance_.lp_.a_matrix_.computeDot(*row_ep, iCol); + HighsInt iVar = *sit; + assert(iVar < ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_); + double alpha = ekk_instance_.lp_.a_matrix_.computeDot(*row_ep, iVar); if (fabs(alpha) > Ta) { if (alpha * move_out > 0) - ekk_instance_.basis_.nonbasicMove_[iCol] = 1; + ekk_instance_.basis_.nonbasicMove_[iVar] = 1; else - ekk_instance_.basis_.nonbasicMove_[iCol] = -1; + ekk_instance_.basis_.nonbasicMove_[iVar] = -1; } } } @@ -616,16 +614,16 @@ void HEkkDualRow::deleteFreemove() { if (!freeList.empty()) { set::iterator sit; for (sit = freeList.begin(); sit != freeList.end(); sit++) { - HighsInt iCol = *sit; - assert(iCol < ekk_instance_.lp_.num_col_); - ekk_instance_.basis_.nonbasicMove_[iCol] = 0; + HighsInt iVar = *sit; + assert(iVar < ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_); + ekk_instance_.basis_.nonbasicMove_[iVar] = 0; } } } -void HEkkDualRow::deleteFreelist(HighsInt iColumn) { +void HEkkDualRow::deleteFreelist(HighsInt iVar) { if (!freeList.empty()) { - if (freeList.count(iColumn)) freeList.erase(iColumn); + if (freeList.count(iVar)) freeList.erase(iVar); } } diff --git a/src/simplex/HEkkDualRow.h b/src/simplex/HEkkDualRow.h index d577ce0b36..b4e740957d 100644 --- a/src/simplex/HEkkDualRow.h +++ b/src/simplex/HEkkDualRow.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkDualRow.h * @brief Dual simplex ratio test for HiGHS diff --git a/src/simplex/HEkkInterface.cpp b/src/simplex/HEkkInterface.cpp index 90ab3098ce..b501118e60 100644 --- a/src/simplex/HEkkInterface.cpp +++ b/src/simplex/HEkkInterface.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkInterface.cpp * @brief diff --git a/src/simplex/HEkkPrimal.cpp b/src/simplex/HEkkPrimal.cpp index 3c41c81068..fa02e369d9 100644 --- a/src/simplex/HEkkPrimal.cpp +++ b/src/simplex/HEkkPrimal.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkPrimal.cpp * @brief @@ -365,7 +363,7 @@ void HEkkPrimal::initialiseInstance() { num_free_col); nonbasic_free_col_set.setup( num_free_col, num_tot, ekk_instance_.options_->output_flag, - ekk_instance_.options_->log_options.log_file_stream, debug); + ekk_instance_.options_->log_options.log_stream, debug); } // Set up the hyper-sparse CHUZC data hyper_chuzc_candidate.resize(1 + max_num_hyper_chuzc_candidates); @@ -373,7 +371,7 @@ void HEkkPrimal::initialiseInstance() { hyper_chuzc_candidate_set.setup( max_num_hyper_chuzc_candidates, num_tot, ekk_instance_.options_->output_flag, - ekk_instance_.options_->log_options.log_file_stream, debug); + ekk_instance_.options_->log_options.log_stream, debug); } void HEkkPrimal::initialiseSolve() { @@ -735,7 +733,7 @@ void HEkkPrimal::rebuild() { assert(info.backtracking_); ekk_instance_.initialisePartitionedRowwiseMatrix(); assert(ekk_instance_.ar_matrix_.debugPartitionOk( - &ekk_instance_.basis_.nonbasicFlag_[0])); + ekk_instance_.basis_.nonbasicFlag_.data())); } if (info.backtracking_) { @@ -747,7 +745,9 @@ void HEkkPrimal::rebuild() { ekk_instance_.computePrimal(); if (solve_phase == kSolvePhase2) { bool correct_primal_ok = correctPrimal(); - assert(correct_primal_ok); + if (kAllowDeveloperAssert) { + assert(correct_primal_ok); + } } getBasicPrimalInfeasibility(); if (info.num_primal_infeasibilities > 0) { @@ -2102,7 +2102,9 @@ bool HEkkPrimal::correctPrimal(const bool initialise) { highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kError, "correctPrimal: Missed %d bound shifts\n", num_primal_correction_skipped); - assert(!num_primal_correction_skipped); + if (kAllowDeveloperAssert) { + assert(!num_primal_correction_skipped); + } return false; } if (max_primal_correction > 2 * max_max_primal_correction) { @@ -2471,7 +2473,7 @@ void HEkkPrimal::updateDualSteepestEdgeWeights() { const double Kai = -2 / pivot_in_scaled_space; ekk_instance_.updateDualSteepestEdgeWeights(row_out, variable_in, &col_aq, new_pivotal_edge_weight, Kai, - &col_steepest_edge.array[0]); + col_steepest_edge.array.data()); edge_weight[row_out] = new_pivotal_edge_weight; } @@ -2543,7 +2545,9 @@ void HEkkPrimal::updateVerify() { ekk_instance_.iteration_count_, alpha_col, alpha_row_source.c_str(), alpha_row, abs_alpha_diff, numericalTrouble); - assert(numericalTrouble < 1e-3); + if (kAllowDeveloperAssert) { + assert(numericalTrouble < 1e-3); + } // Reinvert if the relative difference is large enough, and updates have been // performed // diff --git a/src/simplex/HEkkPrimal.h b/src/simplex/HEkkPrimal.h index d501d96fec..4cb8d22ec0 100644 --- a/src/simplex/HEkkPrimal.h +++ b/src/simplex/HEkkPrimal.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HEkkPrimal.h * @brief Phase 2 primal simplex solver for HiGHS diff --git a/src/simplex/HSimplex.cpp b/src/simplex/HSimplex.cpp index 4fb326072b..c1c9bf887f 100644 --- a/src/simplex/HSimplex.cpp +++ b/src/simplex/HSimplex.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HSimplex.cpp * @brief diff --git a/src/simplex/HSimplex.h b/src/simplex/HSimplex.h index c307ff5454..f51136f622 100644 --- a/src/simplex/HSimplex.h +++ b/src/simplex/HSimplex.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HSimplex.h * @brief diff --git a/src/simplex/HSimplexDebug.cpp b/src/simplex/HSimplexDebug.cpp index 73fef8ec59..e47a8bd9b3 100644 --- a/src/simplex/HSimplexDebug.cpp +++ b/src/simplex/HSimplexDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HSimplexDebug.cpp * @brief diff --git a/src/simplex/HSimplexDebug.h b/src/simplex/HSimplexDebug.h index ccf61c89e4..64a9f9652f 100644 --- a/src/simplex/HSimplexDebug.h +++ b/src/simplex/HSimplexDebug.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HSimplexDebug.h * @brief diff --git a/src/simplex/HSimplexNla.cpp b/src/simplex/HSimplexNla.cpp index c687814f65..62a4ff67e3 100644 --- a/src/simplex/HSimplexNla.cpp +++ b/src/simplex/HSimplexNla.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HSimplexNla.cpp * @@ -38,10 +36,10 @@ void HSimplexNla::setup(const HighsLp* lp, HighsInt* basic_index, this->report_ = false; this->factor_.setupGeneral( this->lp_->num_col_, this->lp_->num_row_, this->lp_->num_row_, - &factor_a_matrix->start_[0], &factor_a_matrix->index_[0], - &factor_a_matrix->value_[0], this->basic_index_, factor_pivot_threshold, - this->options_->factor_pivot_tolerance, this->options_->highs_debug_level, - &(this->options_->log_options)); + factor_a_matrix->start_.data(), factor_a_matrix->index_.data(), + factor_a_matrix->value_.data(), this->basic_index_, + factor_pivot_threshold, this->options_->factor_pivot_tolerance, + this->options_->highs_debug_level, &(this->options_->log_options)); assert(debugCheckData("After HSimplexNla::setup") == HighsDebugStatus::kOk); } @@ -487,9 +485,9 @@ HighsDebugStatus HSimplexNla::debugCheckData(const std::string message) const { const double* factor_Avalue = factor_.getAvalue(); if (scale_ == NULL) { - if (factor_Astart != &(lp_->a_matrix_.start_[0])) error0_found = true; - if (factor_Aindex != &(lp_->a_matrix_.index_[0])) error1_found = true; - if (factor_Avalue != &(lp_->a_matrix_.value_[0])) error2_found = true; + if (factor_Astart != lp_->a_matrix_.start_.data()) error0_found = true; + if (factor_Aindex != lp_->a_matrix_.index_.data()) error1_found = true; + if (factor_Avalue != lp_->a_matrix_.value_.data()) error2_found = true; error_found = error0_found || error1_found || error2_found; if (error_found) { highsLogUser(options_->log_options, HighsLogType::kError, @@ -498,7 +496,7 @@ HighsDebugStatus HSimplexNla::debugCheckData(const std::string message) const { message.c_str(), scale_status.c_str()); if (error0_found) printf("a_matrix_.start_ pointer error: %p vs %p\n", - (void*)factor_Astart, (void*)&(lp_->a_matrix_.start_[0])); + (void*)factor_Astart, (void*)lp_->a_matrix_.start_.data()); if (error1_found) printf("a_matrix_.index pointer error\n"); if (error2_found) printf("a_matrix_.value pointer error\n"); assert(!error_found); diff --git a/src/simplex/HSimplexNla.h b/src/simplex/HSimplexNla.h index fd6877a1d2..4cbda269f5 100644 --- a/src/simplex/HSimplexNla.h +++ b/src/simplex/HSimplexNla.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HSimplexNla.h * diff --git a/src/simplex/HSimplexNlaDebug.cpp b/src/simplex/HSimplexNlaDebug.cpp index daafcfd984..80d57afd64 100644 --- a/src/simplex/HSimplexNlaDebug.cpp +++ b/src/simplex/HSimplexNlaDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HSimplexNlaDebug.cpp * diff --git a/src/simplex/HSimplexNlaFreeze.cpp b/src/simplex/HSimplexNlaFreeze.cpp index fb4332d489..712a00f6f0 100644 --- a/src/simplex/HSimplexNlaFreeze.cpp +++ b/src/simplex/HSimplexNlaFreeze.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HSimplexNlaFreeze.cpp * diff --git a/src/simplex/HSimplexNlaProductForm.cpp b/src/simplex/HSimplexNlaProductForm.cpp index 237dff089e..e261814c69 100644 --- a/src/simplex/HSimplexNlaProductForm.cpp +++ b/src/simplex/HSimplexNlaProductForm.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HSimplexNlaProductForm.cpp * diff --git a/src/simplex/HSimplexReport.cpp b/src/simplex/HSimplexReport.cpp index d914ac5a4d..ce900ce652 100644 --- a/src/simplex/HSimplexReport.cpp +++ b/src/simplex/HSimplexReport.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HSimplexDebug.cpp * @brief @@ -20,35 +18,30 @@ void reportSimplexPhaseIterations(const HighsLogOptions& log_options, const HighsInt iteration_count, - const HighsSimplexInfo& info, + HighsSimplexInfo& info, const bool initialise) { if (info.run_quiet) return; - static HighsInt iteration_count0 = 0; - static HighsInt dual_phase1_iteration_count0 = 0; - static HighsInt dual_phase2_iteration_count0 = 0; - static HighsInt primal_phase1_iteration_count0 = 0; - static HighsInt primal_phase2_iteration_count0 = 0; - static HighsInt primal_bound_swap0 = 0; if (initialise) { - iteration_count0 = iteration_count; - dual_phase1_iteration_count0 = info.dual_phase1_iteration_count; - dual_phase2_iteration_count0 = info.dual_phase2_iteration_count; - primal_phase1_iteration_count0 = info.primal_phase1_iteration_count; - primal_phase2_iteration_count0 = info.primal_phase2_iteration_count; - primal_bound_swap0 = info.primal_bound_swap; + info.iteration_count0 = iteration_count; + info.dual_phase1_iteration_count0 = info.dual_phase1_iteration_count; + info.dual_phase2_iteration_count0 = info.dual_phase2_iteration_count; + info.primal_phase1_iteration_count0 = info.primal_phase1_iteration_count; + info.primal_phase2_iteration_count0 = info.primal_phase2_iteration_count; + info.primal_bound_swap0 = info.primal_bound_swap; return; } - const HighsInt delta_iteration_count = iteration_count - iteration_count0; + const HighsInt delta_iteration_count = + iteration_count - info.iteration_count0; const HighsInt delta_dual_phase1_iteration_count = - info.dual_phase1_iteration_count - dual_phase1_iteration_count0; + info.dual_phase1_iteration_count - info.dual_phase1_iteration_count0; const HighsInt delta_dual_phase2_iteration_count = - info.dual_phase2_iteration_count - dual_phase2_iteration_count0; + info.dual_phase2_iteration_count - info.dual_phase2_iteration_count0; const HighsInt delta_primal_phase1_iteration_count = - info.primal_phase1_iteration_count - primal_phase1_iteration_count0; + info.primal_phase1_iteration_count - info.primal_phase1_iteration_count0; const HighsInt delta_primal_phase2_iteration_count = - info.primal_phase2_iteration_count - primal_phase2_iteration_count0; + info.primal_phase2_iteration_count - info.primal_phase2_iteration_count0; const HighsInt delta_primal_bound_swap = - info.primal_bound_swap - primal_bound_swap0; + info.primal_bound_swap - info.primal_bound_swap0; HighsInt check_delta_iteration_count = delta_dual_phase1_iteration_count + delta_dual_phase2_iteration_count + diff --git a/src/simplex/HSimplexReport.h b/src/simplex/HSimplexReport.h index a6d11e3ac9..fb86c1b393 100644 --- a/src/simplex/HSimplexReport.h +++ b/src/simplex/HSimplexReport.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/HSimplexReport.h * @brief @@ -21,6 +19,6 @@ void reportSimplexPhaseIterations(const HighsLogOptions& log_options, const HighsInt iteration_count, - const HighsSimplexInfo& info, + HighsSimplexInfo& info, const bool initialise = false); #endif // SIMPLEX_HSIMPLEXREPORT_H_ diff --git a/src/simplex/HighsSimplexAnalysis.cpp b/src/simplex/HighsSimplexAnalysis.cpp index 44592bee7b..bf72f26e89 100644 --- a/src/simplex/HighsSimplexAnalysis.cpp +++ b/src/simplex/HighsSimplexAnalysis.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HighsSimplexAnalysis.cpp * @brief @@ -303,7 +301,7 @@ void HighsSimplexAnalysis::setupFactorTime(const HighsOptions& options) { clock.timer_pointer_ = timer_; thread_factor_clocks.push_back(clock); } - pointer_serial_factor_clocks = &thread_factor_clocks[0]; + pointer_serial_factor_clocks = thread_factor_clocks.data(); FactorTimer factor_timer; for (HighsTimerClock& clock : thread_factor_clocks) factor_timer.initialiseFactorClocks(clock); diff --git a/src/simplex/HighsSimplexAnalysis.h b/src/simplex/HighsSimplexAnalysis.h index bcdf543b86..ef7482de12 100644 --- a/src/simplex/HighsSimplexAnalysis.h +++ b/src/simplex/HighsSimplexAnalysis.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HighsSimplexAnalysis.h * @brief Analyse simplex iterations, both for run-time control and data diff --git a/src/simplex/SimplexConst.h b/src/simplex/SimplexConst.h index a8fbb787ff..650203d059 100644 --- a/src/simplex/SimplexConst.h +++ b/src/simplex/SimplexConst.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/SimplexConst.h * @brief Constants for HiGHS simplex solvers @@ -121,6 +119,7 @@ enum RebuildReason { kRebuildReasonPrimalInfeasibleInPrimalSimplex, // 8 kRebuildReasonChooseColumnFail, // 9 kRebuildReasonForceRefactor, // 10 + kRebuildReasonExcessivePrimalValue, // 11 kRebuildReasonCount }; @@ -168,7 +167,10 @@ const double kMinDualSteepestEdgeWeight = 1e-4; const HighsInt kNoRowSought = -2; const HighsInt kNoRowChosen = -1; -const double minDualSteepestEdgeWeight = 1e-4; +// Switch to use code to check that, unless the basis supplied by the +// MIP solver was alien, the simplex solver starts from dual +// feasibility. +const bool kDebugMipNodeDualFeasible = false; enum class LpAction { kScale = 0, diff --git a/src/simplex/SimplexStruct.h b/src/simplex/SimplexStruct.h index 02575860ea..50f7b26d6e 100644 --- a/src/simplex/SimplexStruct.h +++ b/src/simplex/SimplexStruct.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file lp_data/SimplexStruct.h * @brief Structs for HiGHS simplex solvers @@ -44,7 +42,7 @@ struct SimplexBasis { struct HighsSimplexStatus { // Status of LP solved by the simplex method and its data bool initialised_for_new_lp = false; - bool is_dualised = false; + bool is_dualized = false; bool is_permuted = false; bool initialised_for_solve = false; bool has_basis = false; // The simplex LP has a valid simplex basis @@ -221,6 +219,14 @@ struct HighsSimplexInfo { HighsInt primal_phase2_iteration_count = 0; HighsInt primal_bound_swap = 0; + // Starting values for use in reportSimplexPhaseIterations + HighsInt iteration_count0 = 0; + HighsInt dual_phase1_iteration_count0 = 0; + HighsInt dual_phase2_iteration_count0 = 0; + HighsInt primal_phase1_iteration_count0 = 0; + HighsInt primal_phase2_iteration_count0 = 0; + HighsInt primal_bound_swap0 = 0; + HighsInt min_concurrency = 1; HighsInt num_concurrency = 1; HighsInt max_concurrency = kSimplexConcurrencyLimit; diff --git a/src/simplex/SimplexTimer.h b/src/simplex/SimplexTimer.h index 8c919493c2..4481b6b10a 100644 --- a/src/simplex/SimplexTimer.h +++ b/src/simplex/SimplexTimer.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/SimplexTimer.h * @brief Indices of simplex iClocks diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index 2a03c40022..6e4b65c983 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file * @brief diff --git a/src/test/DevKkt.h b/src/test/DevKkt.h index ba52565df0..ef5deb1bb2 100644 --- a/src/test/DevKkt.h +++ b/src/test/DevKkt.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file test/DevKkt.h * @brief diff --git a/src/test/KktCh2.cpp b/src/test/KktCh2.cpp index b45f9cc5dd..54626aafc5 100644 --- a/src/test/KktCh2.cpp +++ b/src/test/KktCh2.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file test/KktChStep.cpp * @brief diff --git a/src/test/KktCh2.h b/src/test/KktCh2.h index 184de65095..86635a1831 100644 --- a/src/test/KktCh2.h +++ b/src/test/KktCh2.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file test/KktChStep.h * @brief diff --git a/src/util/FactorTimer.h b/src/util/FactorTimer.h index 8037d8d76b..7dc46fafe1 100644 --- a/src/util/FactorTimer.h +++ b/src/util/FactorTimer.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/FactorTimer.h * @brief Indices of factor iClocks diff --git a/src/util/HFactor.cpp b/src/util/HFactor.cpp index 8a48c1baf7..e4b163845e 100644 --- a/src/util/HFactor.cpp +++ b/src/util/HFactor.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactor.cpp * @brief Types of solution classes @@ -69,14 +67,14 @@ static void solveHyper(const HighsInt h_size, const HighsInt* h_lookup, const HighsInt* h_end, const HighsInt* h_index, const double* h_value, HVector* rhs) { HighsInt rhs_count = rhs->count; - HighsInt* rhs_index = &rhs->index[0]; - double* rhs_array = &rhs->array[0]; + HighsInt* rhs_index = rhs->index.data(); + double* rhs_array = rhs->array.data(); // Take count // Build list - char* list_mark = &rhs->cwork[0]; - HighsInt* list_index = &rhs->iwork[0]; + char* list_mark = rhs->cwork.data(); + HighsInt* list_index = rhs->iwork.data(); HighsInt* list_stack = &rhs->iwork[h_size]; HighsInt list_count = 0; @@ -171,7 +169,7 @@ void HFactor::setup(const HighsSparseMatrix& a_matrix, // Nothing to do if basic index has no entries, and mustn't try to // pass the pointer to entry 0 of a vector of size 0. if (basic_index_size <= 0) return; - this->setupGeneral(&a_matrix, basic_index_size, &basic_index[0], + this->setupGeneral(&a_matrix, basic_index_size, basic_index.data(), pivot_threshold, pivot_tolerance, highs_debug_level, log_options); return; @@ -183,10 +181,11 @@ void HFactor::setupGeneral(const HighsSparseMatrix* a_matrix, const double pivot_tolerance, const HighsInt highs_debug_level, const HighsLogOptions* log_options) { - this->setupGeneral( - a_matrix->num_col_, a_matrix->num_row_, num_basic, &a_matrix->start_[0], - &a_matrix->index_[0], &a_matrix->value_[0], basic_index, pivot_threshold, - pivot_tolerance, highs_debug_level, log_options, true, kUpdateMethodFt); + this->setupGeneral(a_matrix->num_col_, a_matrix->num_row_, num_basic, + a_matrix->start_.data(), a_matrix->index_.data(), + a_matrix->value_.data(), basic_index, pivot_threshold, + pivot_tolerance, highs_debug_level, log_options, true, + kUpdateMethodFt); } void HFactor::setup( @@ -232,12 +231,12 @@ void HFactor::setupGeneral( log_data->output_flag = false; log_data->log_to_console = true; log_data->log_dev_level = 0; - log_options.log_file_stream = nullptr; + log_options.log_stream = nullptr; } else { log_data->output_flag = *(log_options_->output_flag); log_data->log_to_console = *(log_options_->log_to_console); log_data->log_dev_level = *(log_options_->log_dev_level); - log_options.log_file_stream = log_options_->log_file_stream; + log_options.log_stream = log_options_->log_stream; } use_original_HFactor_logic = use_original_HFactor_logic_; @@ -352,7 +351,8 @@ void HFactor::setupMatrix(const HighsInt* a_start_, const HighsInt* a_index_, } void HFactor::setupMatrix(const HighsSparseMatrix* a_matrix) { - setupMatrix(&a_matrix->start_[0], &a_matrix->index_[0], &a_matrix->value_[0]); + setupMatrix(a_matrix->start_.data(), a_matrix->index_.data(), + a_matrix->value_.data()); } HighsInt HFactor::build(HighsTimerClock* factor_timer_clock_pointer) { @@ -584,7 +584,7 @@ void HFactor::buildSimple() { const bool report_anything = report_unit || report_singletons || report_markowitz; HighsInt BcountX = 0; - fill_n(&mr_count_before[0], num_row, 0); + fill_n(mr_count_before.data(), num_row, 0); nwork = 0; if (report_anything) printf("\nFactor\n"); // Compile a vector iwork of the indices within basic_index of the @@ -1545,13 +1545,12 @@ void HFactor::ftranL(HVector& rhs, const double expected_density, if (sparse_solve) { factor_timer.start(FactorFtranLowerSps, factor_timer_clock_pointer); // Alias to RHS - HighsInt* rhs_index = &rhs.index[0]; - double* rhs_array = &rhs.array[0]; + HighsInt* rhs_index = rhs.index.data(); + double* rhs_array = rhs.array.data(); // Alias to factor L - const HighsInt* l_start = &this->l_start[0]; - const HighsInt* l_index = - this->l_index.size() > 0 ? &this->l_index[0] : NULL; - const double* l_value = this->l_value.size() > 0 ? &this->l_value[0] : NULL; + const HighsInt* l_start = this->l_start.data(); + const HighsInt* l_index = this->l_index.data(); + const double* l_value = this->l_value.data(); // Local accumulation of RHS count HighsInt rhs_count = 0; // Transform @@ -1573,11 +1572,10 @@ void HFactor::ftranL(HVector& rhs, const double expected_density, } else { // Hyper-sparse solve factor_timer.start(FactorFtranLowerHyper, factor_timer_clock_pointer); - const HighsInt* l_index = - this->l_index.size() > 0 ? &this->l_index[0] : NULL; - const double* l_value = this->l_value.size() > 0 ? &this->l_value[0] : NULL; - solveHyper(num_row, &l_pivot_lookup[0], &l_pivot_index[0], 0, &l_start[0], - &l_start[1], &l_index[0], &l_value[0], &rhs); + const HighsInt* l_index = this->l_index.data(); + const double* l_value = this->l_value.data(); + solveHyper(num_row, l_pivot_lookup.data(), l_pivot_index.data(), 0, + l_start.data(), &l_start[1], &l_index[0], &l_value[0], &rhs); factor_timer.stop(FactorFtranLowerHyper, factor_timer_clock_pointer); } factor_timer.stop(FactorFtranLower, factor_timer_clock_pointer); @@ -1595,14 +1593,12 @@ void HFactor::btranL(HVector& rhs, const double expected_density, if (sparse_solve) { factor_timer.start(FactorBtranLowerSps, factor_timer_clock_pointer); // Alias to RHS - HighsInt* rhs_index = &rhs.index[0]; - double* rhs_array = &rhs.array[0]; + HighsInt* rhs_index = rhs.index.data(); + double* rhs_array = rhs.array.data(); // Alias to factor L - const HighsInt* lr_start = &this->lr_start[0]; - const HighsInt* lr_index = - this->lr_index.size() > 0 ? &this->lr_index[0] : NULL; - const double* lr_value = - this->lr_value.size() > 0 ? &this->lr_value[0] : NULL; + const HighsInt* lr_start = this->lr_start.data(); + const HighsInt* lr_index = this->lr_index.data(); + const double* lr_value = this->lr_value.data(); // Local accumulation of RHS count HighsInt rhs_count = 0; // Transform @@ -1625,12 +1621,10 @@ void HFactor::btranL(HVector& rhs, const double expected_density, } else { // Hyper-sparse solve factor_timer.start(FactorBtranLowerHyper, factor_timer_clock_pointer); - const HighsInt* lr_index = - this->lr_index.size() > 0 ? &this->lr_index[0] : NULL; - const double* lr_value = - this->lr_value.size() > 0 ? &this->lr_value[0] : NULL; - solveHyper(num_row, &l_pivot_lookup[0], &l_pivot_index[0], 0, &lr_start[0], - &lr_start[1], &lr_index[0], &lr_value[0], &rhs); + const HighsInt* lr_index = this->lr_index.data(); + const double* lr_value = this->lr_value.data(); + solveHyper(num_row, l_pivot_lookup.data(), l_pivot_index.data(), 0, + lr_start.data(), &lr_start[1], &lr_index[0], &lr_value[0], &rhs); factor_timer.stop(FactorBtranLowerHyper, factor_timer_clock_pointer); } @@ -1685,14 +1679,13 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, // Alias to non constant double rhs_synthetic_tick = 0; // Alias to RHS - HighsInt* rhs_index = &rhs.index[0]; - double* rhs_array = &rhs.array[0]; + HighsInt* rhs_index = rhs.index.data(); + double* rhs_array = rhs.array.data(); // Alias to factor U - const HighsInt* u_start = &this->u_start[0]; - const HighsInt* u_end = &this->u_last_p[0]; - const HighsInt* u_index = - this->u_index.size() > 0 ? &this->u_index[0] : NULL; - const double* u_value = this->u_value.size() > 0 ? &this->u_value[0] : NULL; + const HighsInt* u_start = this->u_start.data(); + const HighsInt* u_end = this->u_last_p.data(); + const HighsInt* u_index = this->u_index.data(); + const double* u_value = this->u_value.data(); // Local accumulation of RHS count HighsInt rhs_count = 0; // Transform @@ -1744,12 +1737,11 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, else use_clock = FactorFtranUpperHyper0; factor_timer.start(use_clock, factor_timer_clock_pointer); - const HighsInt* u_index = - this->u_index.size() > 0 ? &this->u_index[0] : NULL; - const double* u_value = this->u_value.size() > 0 ? &this->u_value[0] : NULL; - solveHyper(num_row, &u_pivot_lookup[0], &u_pivot_index[0], - &u_pivot_value[0], &u_start[0], &u_last_p[0], &u_index[0], - &u_value[0], &rhs); + const HighsInt* u_index = this->u_index.data(); + const double* u_value = this->u_value.data(); + solveHyper(num_row, u_pivot_lookup.data(), u_pivot_index.data(), + u_pivot_value.data(), u_start.data(), u_last_p.data(), + &u_index[0], &u_value[0], &rhs); factor_timer.stop(use_clock, factor_timer_clock_pointer); } if (update_method == kUpdateMethodPf) { @@ -1785,13 +1777,13 @@ void HFactor::btranU(HVector& rhs, const double expected_density, // Alias to non constant double rhs_synthetic_tick = 0; // Alias to RHS - double* rhs_array = &rhs.array[0]; - HighsInt* rhs_index = &rhs.index[0]; + double* rhs_array = rhs.array.data(); + HighsInt* rhs_index = rhs.index.data(); // Alias to factor U - const HighsInt* ur_start = &this->ur_start[0]; - const HighsInt* ur_end = &this->ur_lastp[0]; - const HighsInt* ur_index = &this->ur_index[0]; - const double* ur_value = &this->ur_value[0]; + const HighsInt* ur_start = this->ur_start.data(); + const HighsInt* ur_end = this->ur_lastp.data(); + const HighsInt* ur_index = this->ur_index.data(); + const double* ur_value = this->ur_value.data(); // Local accumulation of RHS count HighsInt rhs_count = 0; // Transform @@ -1823,9 +1815,9 @@ void HFactor::btranU(HVector& rhs, const double expected_density, factor_timer.stop(FactorBtranUpperSps, factor_timer_clock_pointer); } else { factor_timer.start(FactorBtranUpperHyper, factor_timer_clock_pointer); - solveHyper(num_row, &u_pivot_lookup[0], &u_pivot_index[0], - &u_pivot_value[0], &ur_start[0], &ur_lastp[0], &ur_index[0], - &ur_value[0], &rhs); + solveHyper(num_row, u_pivot_lookup.data(), u_pivot_index.data(), + u_pivot_value.data(), &ur_start[0], ur_lastp.data(), + &ur_index[0], &ur_value[0], &rhs); factor_timer.stop(FactorBtranUpperHyper, factor_timer_clock_pointer); } @@ -1855,20 +1847,14 @@ void HFactor::ftranFT(HVector& vector) const { // Alias to non constant assert(vector.count >= 0); HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Alias to PF buffer const HighsInt pf_pivot_count = pf_pivot_index.size(); - HighsInt* pf_pivot_index = NULL; - if (this->pf_pivot_index.size() > 0) - pf_pivot_index = (HighsInt*)&this->pf_pivot_index[0]; - - const HighsInt* pf_start = - this->pf_start.size() > 0 ? &this->pf_start[0] : NULL; - const HighsInt* pf_index = - this->pf_index.size() > 0 ? &this->pf_index[0] : NULL; - const double* pf_value = - this->pf_value.size() > 0 ? &this->pf_value[0] : NULL; + const HighsInt* pf_pivot_index = this->pf_pivot_index.data(); + const HighsInt* pf_start = this->pf_start.data(); + const HighsInt* pf_index = this->pf_index.data(); + const double* pf_value = this->pf_value.data(); for (HighsInt i = 0; i < pf_pivot_count; i++) { HighsInt iRow = pf_pivot_index[i]; double value0 = rhs_array[iRow]; @@ -1895,18 +1881,14 @@ void HFactor::btranFT(HVector& vector) const { // Alias to non constant assert(vector.count >= 0); HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Alias to PF buffer const HighsInt pf_pivot_count = pf_pivot_index.size(); - const HighsInt* pf_pivot_index = - this->pf_pivot_index.size() > 0 ? &this->pf_pivot_index[0] : NULL; - const HighsInt* pf_start = - this->pf_start.size() > 0 ? &this->pf_start[0] : NULL; - const HighsInt* pf_index = - this->pf_index.size() > 0 ? &this->pf_index[0] : NULL; - const double* pf_value = - this->pf_value.size() > 0 ? &this->pf_value[0] : NULL; + const HighsInt* pf_pivot_index = this->pf_pivot_index.data(); + const HighsInt* pf_start = this->pf_start.data(); + const HighsInt* pf_index = this->pf_index.data(); + const double* pf_value = this->pf_value.data(); // Apply row ETA backward double rhs_synthetic_tick = 0; for (HighsInt i = pf_pivot_count - 1; i >= 0; i--) { @@ -1933,16 +1915,16 @@ void HFactor::btranFT(HVector& vector) const { void HFactor::ftranPF(HVector& vector) const { // Alias to PF buffer const HighsInt pf_pivot_count = pf_pivot_index.size(); - const HighsInt* pf_pivot_index = &this->pf_pivot_index[0]; - const double* pf_pivot_value = &this->pf_pivot_value[0]; - const HighsInt* pf_start = &this->pf_start[0]; - const HighsInt* pf_index = &this->pf_index[0]; - const double* pf_value = &this->pf_value[0]; + const HighsInt* pf_pivot_index = this->pf_pivot_index.data(); + const double* pf_pivot_value = this->pf_pivot_value.data(); + const HighsInt* pf_start = this->pf_start.data(); + const HighsInt* pf_index = this->pf_index.data(); + const double* pf_value = this->pf_value.data(); // Alias to non constant HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Forwardly for (HighsInt i = 0; i < pf_pivot_count; i++) { @@ -1968,16 +1950,16 @@ void HFactor::ftranPF(HVector& vector) const { void HFactor::btranPF(HVector& vector) const { // Alias to PF buffer const HighsInt pf_pivot_count = pf_pivot_index.size(); - const HighsInt* pf_pivot_index = &this->pf_pivot_index[0]; - const double* pf_pivot_value = &this->pf_pivot_value[0]; - const HighsInt* pf_start = &this->pf_start[0]; - const HighsInt* pf_index = &this->pf_index[0]; - const double* pf_value = &this->pf_value[0]; + const HighsInt* pf_pivot_index = this->pf_pivot_index.data(); + const double* pf_pivot_value = this->pf_pivot_value.data(); + const HighsInt* pf_start = this->pf_start.data(); + const HighsInt* pf_index = this->pf_index.data(); + const double* pf_value = this->pf_value.data(); // Alias to non constant HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Backwardly for (HighsInt i = pf_pivot_count - 1; i >= 0; i--) { @@ -1999,14 +1981,14 @@ void HFactor::btranPF(HVector& vector) const { void HFactor::ftranMPF(HVector& vector) const { // Alias to non constant HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Forwardly HighsInt pf_pivot_count = pf_pivot_value.size(); for (HighsInt i = 0; i < pf_pivot_count; i++) { solveMatrixT(pf_start[i * 2 + 1], pf_start[i * 2 + 2], pf_start[i * 2], - pf_start[i * 2 + 1], &pf_index[0], &pf_value[0], + pf_start[i * 2 + 1], pf_index.data(), pf_value.data(), pf_pivot_value[i], &rhs_count, rhs_index, rhs_array); } @@ -2017,13 +1999,13 @@ void HFactor::ftranMPF(HVector& vector) const { void HFactor::btranMPF(HVector& vector) const { // Alias to non constant HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Backwardly for (HighsInt i = pf_pivot_value.size() - 1; i >= 0; i--) { solveMatrixT(pf_start[i * 2], pf_start[i * 2 + 1], pf_start[i * 2 + 1], - pf_start[i * 2 + 2], &pf_index[0], &pf_value[0], + pf_start[i * 2 + 2], pf_index.data(), pf_value.data(), pf_pivot_value[i], &rhs_count, rhs_index, rhs_array); } @@ -2034,14 +2016,14 @@ void HFactor::btranMPF(HVector& vector) const { void HFactor::ftranAPF(HVector& vector) const { // Alias to non constant HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Backwardly HighsInt pf_pivot_count = pf_pivot_value.size(); for (HighsInt i = pf_pivot_count - 1; i >= 0; i--) { solveMatrixT(pf_start[i * 2 + 1], pf_start[i * 2 + 2], pf_start[i * 2], - pf_start[i * 2 + 1], &pf_index[0], &pf_value[0], + pf_start[i * 2 + 1], pf_index.data(), pf_value.data(), pf_pivot_value[i], &rhs_count, rhs_index, rhs_array); } @@ -2052,14 +2034,14 @@ void HFactor::ftranAPF(HVector& vector) const { void HFactor::btranAPF(HVector& vector) const { // Alias to non constant HighsInt rhs_count = vector.count; - HighsInt* rhs_index = &vector.index[0]; - double* rhs_array = &vector.array[0]; + HighsInt* rhs_index = vector.index.data(); + double* rhs_array = vector.array.data(); // Forwardly HighsInt pf_pivot_count = pf_pivot_value.size(); for (HighsInt i = 0; i < pf_pivot_count; i++) { solveMatrixT(pf_start[i * 2], pf_start[i * 2 + 1], pf_start[i * 2 + 1], - pf_start[i * 2 + 2], &pf_index[0], &pf_value[0], + pf_start[i * 2 + 2], pf_index.data(), pf_value.data(), pf_pivot_value[i], &rhs_count, rhs_index, rhs_array); } vector.count = rhs_count; @@ -2435,8 +2417,8 @@ void HFactor::updateFT(HVector* aq, HVector* ep, HighsInt iRow void HFactor::updatePF(HVector* aq, HighsInt iRow, HighsInt* hint) { // Check space const HighsInt column_count = aq->packCount; - const HighsInt* variable_index = &aq->packIndex[0]; - const double* columnArray = &aq->packValue[0]; + const HighsInt* variable_index = aq->packIndex.data(); + const double* columnArray = aq->packValue.data(); // Copy the pivotal column for (HighsInt i = 0; i < column_count; i++) { diff --git a/src/util/HFactor.h b/src/util/HFactor.h index 39b9b34711..45bc295eee 100644 --- a/src/util/HFactor.h +++ b/src/util/HFactor.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactor.h * @brief Basis matrix factorization, update and solves for HiGHS diff --git a/src/util/HFactorConst.h b/src/util/HFactorConst.h index 35e171c1dd..34c54c4723 100644 --- a/src/util/HFactorConst.h +++ b/src/util/HFactorConst.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactorConst.h * @brief Constants for basis matrix factorization, update and solves for HiGHS diff --git a/src/util/HFactorDebug.cpp b/src/util/HFactorDebug.cpp index ffc176cd3f..26ae99d25a 100644 --- a/src/util/HFactorDebug.cpp +++ b/src/util/HFactorDebug.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactorDebug.cpp * @brief diff --git a/src/util/HFactorDebug.h b/src/util/HFactorDebug.h index ef783fadf8..410230c55e 100644 --- a/src/util/HFactorDebug.h +++ b/src/util/HFactorDebug.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactorDebug.h * @brief diff --git a/src/util/HFactorExtend.cpp b/src/util/HFactorExtend.cpp index 4b04094db6..4ecfd271d5 100644 --- a/src/util/HFactorExtend.cpp +++ b/src/util/HFactorExtend.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactorExtend.cpp * @brief Types of solution classes diff --git a/src/util/HFactorRefactor.cpp b/src/util/HFactorRefactor.cpp index 7ef7309fc5..866227674d 100644 --- a/src/util/HFactorRefactor.cpp +++ b/src/util/HFactorRefactor.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactorRefactor.cpp * @brief Types of solution classes diff --git a/src/util/HFactorUtils.cpp b/src/util/HFactorUtils.cpp index 01b6121197..fdc49f6138 100644 --- a/src/util/HFactorUtils.cpp +++ b/src/util/HFactorUtils.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HFactorUtils.cpp * @brief Types of solution classes diff --git a/src/util/HSet.cpp b/src/util/HSet.cpp index 67d19e236c..269e866ea6 100644 --- a/src/util/HSet.cpp +++ b/src/util/HSet.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HSet.cpp * @brief diff --git a/src/util/HSet.h b/src/util/HSet.h index 18cfd71b20..f3dba8ca9f 100644 --- a/src/util/HSet.h +++ b/src/util/HSet.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HSet.h * @brief Set structure for HiGHS. @@ -42,7 +40,7 @@ class HSet { bool setup(const HighsInt size, //!< Dimension of the set to be initialised const HighsInt max_entry, //!< Maximum entry to be in the set. const bool output_flag = false, //!< Option for output - FILE* log_file_stream = NULL, //!< File stream for output + FILE* log_stream = NULL, //!< File stream for output const bool debug = false, //!< Debug mode const bool allow_assert = true //!< Allow asserts in debug ); diff --git a/src/util/HVector.h b/src/util/HVector.h index fc865c5caa..ad4a29de1b 100644 --- a/src/util/HVector.h +++ b/src/util/HVector.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HVector.h * @brief Vector structure for HiGHS diff --git a/src/util/HVectorBase.cpp b/src/util/HVectorBase.cpp index ba7cd90219..f0bf17624a 100644 --- a/src/util/HVectorBase.cpp +++ b/src/util/HVectorBase.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file simplex/HVectorBase.cpp * @brief diff --git a/src/util/HVectorBase.h b/src/util/HVectorBase.h index b0a6442886..c6b3c6fcc1 100644 --- a/src/util/HVectorBase.h +++ b/src/util/HVectorBase.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HVector.h * @brief Vector structure for HiGHS diff --git a/src/util/HighsCDouble.h b/src/util/HighsCDouble.h index 4bd57041fb..c7274abb4f 100644 --- a/src/util/HighsCDouble.h +++ b/src/util/HighsCDouble.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsCDouble.h diff --git a/src/util/HighsComponent.h b/src/util/HighsComponent.h index 42405afb7f..7244a3cda0 100644 --- a/src/util/HighsComponent.h +++ b/src/util/HighsComponent.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file HighsComponent.h * @brief The HiGHS class diff --git a/src/util/HighsDataStack.h b/src/util/HighsDataStack.h index 4a4be05601..02dc367a94 100644 --- a/src/util/HighsDataStack.h +++ b/src/util/HighsDataStack.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsDataStack.h * @brief A stack of unstructured data stored as bytes @@ -23,7 +21,7 @@ #include "util/HighsInt.h" -#if __GNUG__ && __GNUC__ < 5 +#if __GNUG__ && __GNUC__ < 5 && !defined(__clang__) #define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) #else #define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value diff --git a/src/util/HighsDisjointSets.h b/src/util/HighsDisjointSets.h index 6bc70f4ccb..c9515adc59 100644 --- a/src/util/HighsDisjointSets.h +++ b/src/util/HighsDisjointSets.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_UTIL_DISJOINT_SETS_H_ #define HIGHS_UTIL_DISJOINT_SETS_H_ diff --git a/src/util/HighsHash.cpp b/src/util/HighsHash.cpp index de57c291ae..39b2c261c7 100644 --- a/src/util/HighsHash.cpp +++ b/src/util/HighsHash.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "util/HighsHash.h" diff --git a/src/util/HighsHash.h b/src/util/HighsHash.h index 2877a5b9c1..8f91ab47b8 100644 --- a/src/util/HighsHash.h +++ b/src/util/HighsHash.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_UTIL_HASH_H_ #define HIGHS_UTIL_HASH_H_ @@ -39,7 +37,7 @@ #endif #endif -#if __GNUG__ && __GNUC__ < 5 +#if __GNUG__ && __GNUC__ < 5 && !defined(__clang__) #define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) #else #define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value @@ -759,7 +757,7 @@ struct HighsHashHelpers { // defined to be UINT16_MAX - |exponent| when the exponent is negative. // casting the exponent to a uint32_t directly would give wrong promotion // of negative exponents as UINT32_MAX - |exponent| and take up to many bits - // or possibly loose information after the 16 bit shift. For the mantissa we + // or possibly lose information after the 16 bit shift. For the mantissa we // take the 15 most significant bits, even though we could squeeze out a few // more of the exponent. We don't need more bits as this would make the // buckets very small and might miss more values that are equal within diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index 5d0e0e0d08..b2afcc7876 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -2,17 +2,17 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_UTIL_HASH_TREE_H_ #define HIGHS_UTIL_HASH_TREE_H_ +#include + #include "util/HighsHash.h" using std::memcpy; diff --git a/src/util/HighsInt.h b/src/util/HighsInt.h index 1cd4c84c84..2930d4985a 100644 --- a/src/util/HighsInt.h +++ b/src/util/HighsInt.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file HighsInt.h * @brief The definition for the integer type to use diff --git a/src/util/HighsIntegers.h b/src/util/HighsIntegers.h index 290e2685d2..5814e43b34 100644 --- a/src/util/HighsIntegers.h +++ b/src/util/HighsIntegers.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_UTIL_INTEGERS_H_ #define HIGHS_UTIL_INTEGERS_H_ diff --git a/src/util/HighsLinearSumBounds.cpp b/src/util/HighsLinearSumBounds.cpp index df3b8e20b2..dc929b712e 100644 --- a/src/util/HighsLinearSumBounds.cpp +++ b/src/util/HighsLinearSumBounds.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "util/HighsLinearSumBounds.h" diff --git a/src/util/HighsLinearSumBounds.h b/src/util/HighsLinearSumBounds.h index 2bae766e4a..c684824308 100644 --- a/src/util/HighsLinearSumBounds.h +++ b/src/util/HighsLinearSumBounds.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsLinearSumBounds.h * @brief Data structure to compute and update bounds on a linear sum of diff --git a/src/util/HighsMatrixPic.cpp b/src/util/HighsMatrixPic.cpp index f35c279965..b982921795 100644 --- a/src/util/HighsMatrixPic.cpp +++ b/src/util/HighsMatrixPic.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsMatrixPic.cpp * @brief Class-independent utilities for HiGHS diff --git a/src/util/HighsMatrixPic.h b/src/util/HighsMatrixPic.h index e1cf2696a5..117019b7cc 100644 --- a/src/util/HighsMatrixPic.h +++ b/src/util/HighsMatrixPic.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsMatrixPic.h * @brief Class-independent utilities for HiGHS diff --git a/src/util/HighsMatrixSlice.h b/src/util/HighsMatrixSlice.h index c3402d080b..2a5eea4a54 100644 --- a/src/util/HighsMatrixSlice.h +++ b/src/util/HighsMatrixSlice.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsMatrixSlice.h * @brief Provides a uniform interface to iterate rows and columns in different diff --git a/src/util/HighsMatrixUtils.cpp b/src/util/HighsMatrixUtils.cpp index 3c8776c795..da70362f5c 100644 --- a/src/util/HighsMatrixUtils.cpp +++ b/src/util/HighsMatrixUtils.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsMatrixUtils.cpp * @brief Class-independent utilities for HiGHS diff --git a/src/util/HighsMatrixUtils.h b/src/util/HighsMatrixUtils.h index 0dabdc3909..414e716df9 100644 --- a/src/util/HighsMatrixUtils.h +++ b/src/util/HighsMatrixUtils.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsMatrixUtils.h * @brief Class-independent utilities for HiGHS diff --git a/src/util/HighsRandom.h b/src/util/HighsRandom.h index 36b8622fcd..61fe84dbe6 100644 --- a/src/util/HighsRandom.h +++ b/src/util/HighsRandom.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsRandom.h * @brief Random number generators for HiGHS diff --git a/src/util/HighsRbTree.h b/src/util/HighsRbTree.h index a6a45883b2..5b3738306f 100644 --- a/src/util/HighsRbTree.h +++ b/src/util/HighsRbTree.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_RBTREE_H_ #define HIGHS_RBTREE_H_ diff --git a/src/util/HighsSort.cpp b/src/util/HighsSort.cpp index 15c9375bad..1bf59cf923 100644 --- a/src/util/HighsSort.cpp +++ b/src/util/HighsSort.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsSort.cpp * @brief Sorting routines for HiGHS @@ -332,8 +330,8 @@ void sortSetData(const HighsInt num_entries, vector& set, vector sort_set_vec(1 + num_entries); vector perm_vec(1 + num_entries); - HighsInt* sort_set = &sort_set_vec[0]; - HighsInt* perm = &perm_vec[0]; + HighsInt* sort_set = sort_set_vec.data(); + HighsInt* perm = perm_vec.data(); for (HighsInt ix = 0; ix < num_entries; ix++) { sort_set[1 + ix] = set[ix]; @@ -354,8 +352,8 @@ void sortSetData(const HighsInt num_entries, vector& set, vector sort_set_vec(1 + num_entries); vector perm_vec(1 + num_entries); - HighsInt* sort_set = &sort_set_vec[0]; - HighsInt* perm = &perm_vec[0]; + HighsInt* sort_set = sort_set_vec.data(); + HighsInt* perm = perm_vec.data(); for (HighsInt ix = 0; ix < num_entries; ix++) { sort_set[1 + ix] = set[ix]; diff --git a/src/util/HighsSort.h b/src/util/HighsSort.h index 74365c1cdb..7dbbccc932 100644 --- a/src/util/HighsSort.h +++ b/src/util/HighsSort.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsSort.h * @brief Sorting routines for HiGHS diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index 3d4199ebab..2bda44bd5f 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsSparseMatrix.cpp * @brief @@ -694,6 +692,17 @@ HighsStatus HighsSparseMatrix::assess(const HighsLogOptions& log_options, small_matrix_value, large_matrix_value); } +void HighsSparseMatrix::assessSmallValues(const HighsLogOptions& log_options, + const double small_matrix_value) { + double min_value = kHighsInf; + const HighsInt num_values = this->value_.size(); + for (HighsInt iX = 0; iX < num_values; iX++) + min_value = std::min(std::abs(this->value_[iX]), min_value); + if (min_value > small_matrix_value) return; + analyseVectorValues(&log_options, "Small values in matrix", num_values, + this->value_, false, ""); +} + bool HighsSparseMatrix::hasLargeValue(const double large_matrix_value) { for (HighsInt iEl = 0; iEl < this->numNz(); iEl++) if (std::abs(this->value_[iEl]) > large_matrix_value) return true; @@ -1245,6 +1254,11 @@ void HighsSparseMatrix::priceByRowWithSwitch( // density or during hyper-sparse PRICE if there is too much fill-in HighsInt next_index = from_index; // Possibly don't perform hyper-sparse PRICE based on historical density + // + // Ensure that result was set up for this number of columns, and + // that result.index is still of corect size + assert(HighsInt(result.size) == this->num_col_); + assert(HighsInt(result.index.size()) == this->num_col_); if (expected_density <= kHyperPriceDensity) { for (HighsInt ix = next_index; ix < column.count; ix++) { HighsInt iRow = column.index[ix]; @@ -1316,8 +1330,13 @@ void HighsSparseMatrix::priceByRowWithSwitch( } } else { if (quad_precision) { + // HVector result should have result.index of size this->num_col_ + // by virtue of result.setup. However, it will generally lose + // this property by virtue of the following move result.index = std::move(sum.nonzeroinds); HighsInt result_num_nz = result.index.size(); + // Restore the size of result.index + result.index.resize(this->num_col_); result.count = result_num_nz; for (HighsInt i = 0; i < result_num_nz; ++i) { HighsInt iRow = result.index[i]; diff --git a/src/util/HighsSparseMatrix.h b/src/util/HighsSparseMatrix.h index 7ff591e280..92646b1b41 100644 --- a/src/util/HighsSparseMatrix.h +++ b/src/util/HighsSparseMatrix.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsSparseMatrix.h * @brief @@ -69,6 +67,8 @@ class HighsSparseMatrix { const std::string matrix_name, const double small_matrix_value, const double large_matrix_value); + void assessSmallValues(const HighsLogOptions& log_options, + const double small_matrix_value); bool hasLargeValue(const double large_matrix_value); void considerColScaling(const HighsInt max_scale_factor_exponent, double* col_scale); diff --git a/src/util/HighsSparseVectorSum.h b/src/util/HighsSparseVectorSum.h index 76d655920f..307d5cd97e 100644 --- a/src/util/HighsSparseVectorSum.h +++ b/src/util/HighsSparseVectorSum.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_SPARSE_VECTOR_SUM_H_ #define HIGHS_SPARSE_VECTOR_SUM_H_ diff --git a/src/util/HighsSplay.h b/src/util/HighsSplay.h index 0f71aaf540..4963caa5ee 100644 --- a/src/util/HighsSplay.h +++ b/src/util/HighsSplay.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_SPLAY_H_ #define HIGHS_SPLAY_H_ diff --git a/src/util/HighsTimer.h b/src/util/HighsTimer.h index 6f1d3047f7..527d28da1d 100644 --- a/src/util/HighsTimer.h +++ b/src/util/HighsTimer.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsTimer.h * @brief Profiling facility for computational components in HiGHS @@ -16,6 +14,7 @@ #ifndef UTIL_HIGHSTIMER_H_ #define UTIL_HIGHSTIMER_H_ +#include #include #include #include diff --git a/src/util/HighsUtils.cpp b/src/util/HighsUtils.cpp index 8ad1db194a..ae20737af4 100644 --- a/src/util/HighsUtils.cpp +++ b/src/util/HighsUtils.cpp @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HighsUtils.cpp * @brief Class-independent utilities for HiGHS @@ -397,7 +395,10 @@ void analyseVectorValues(const HighsLogOptions* log_options, } } } - } + } // for (HighsInt ix = 0; ix < vecDim; ix++) + // If there are no nonzeros, min_abs_value retains its starting + // value of inf + if (!nNz) min_abs_value = 0; highsReportDevInfo( log_options, highsFormatToString( @@ -457,6 +458,7 @@ void analyseVectorValues(const HighsLogOptions* log_options, highsReportDevInfo( log_options, highsFormatToString("\n Value Count\n")); for (HighsInt ix = 0; ix < VLsZ; ix++) { + if (!VLsK[ix]) continue; HighsInt pct = ((100.0 * VLsK[ix]) / vecDim) + 0.5; highsReportDevInfo(log_options, highsFormatToString(" %12g %12" HIGHSINT_FORMAT diff --git a/src/util/HighsUtils.h b/src/util/HighsUtils.h index af68208ae2..587a1b9216 100644 --- a/src/util/HighsUtils.h +++ b/src/util/HighsUtils.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file util/HUtils.h * @brief Class-independent utilities for HiGHS diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 623d96675d..6925fb70c1 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -2,16 +2,16 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "util/stringutil.h" +#include + void strRemoveWhitespace(char* str) { char* dest = str; do @@ -101,8 +101,12 @@ int first_word_end(std::string& str, int start) { } std::string first_word(std::string& str, int start) { + // If start is (at least) the length of str, then next_word_start is + // negative, so there's no word, so return "" + if (start >= int(str.length())) return ""; const std::string chars = "\t\n\v\f\r "; int next_word_start = str.find_first_not_of(chars, start); int next_word_end = str.find_first_of(chars, next_word_start); + assert(next_word_start >= 0); return str.substr(next_word_start, next_word_end - next_word_start); } diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 1570256558..bd0032c1c6 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -2,13 +2,11 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2022 at the University of Edinburgh */ +/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ /* */ -/* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ -/* Feldmeier */ -/* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef STRINGUTIL_H #define STRINGUTIL_H