diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000000..710c4d81ab --- /dev/null +++ b/.bazelrc @@ -0,0 +1 @@ +common --noenable_bzlmod diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..3550a30f2d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/build-bazel.yml b/.github/workflows/build-bazel.yml index affeefeeb8..3d92177760 100644 --- a/.github/workflows/build-bazel.yml +++ b/.github/workflows/build-bazel.yml @@ -10,7 +10,7 @@ jobs: os: [macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: bazelbuild/setup-bazelisk@v2 diff --git a/.github/workflows/build-clang.yml b/.github/workflows/build-clang.yml index 6a2b78aea1..ff6add9d67 100644 --- a/.github/workflows/build-clang.yml +++ b/.github/workflows/build-clang.yml @@ -10,7 +10,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -34,7 +34,7 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C $BUILD_TYPE + run: ctest --parallel --timeout 300 --output-on-failure release: runs-on: ${{ matrix.os }} @@ -43,7 +43,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -67,7 +67,7 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C RELEASE + run: ctest --parallel --timeout 300 --output-on-failure -C RELEASE debug64: runs-on: ${{ matrix.os }} @@ -76,7 +76,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -100,7 +100,7 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C $BUILD_TYPE + run: ctest --parallel --timeout 300 --output-on-failure release64: runs-on: ${{ matrix.os }} @@ -109,7 +109,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -133,4 +133,4 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C RELEASE + run: ctest --parallel --timeout 300 --output-on-failure -C RELEASE diff --git a/.github/workflows/build-fast.yml b/.github/workflows/build-fast.yml index 247fa36c49..93f4af0adc 100644 --- a/.github/workflows/build-fast.yml +++ b/.github/workflows/build-fast.yml @@ -10,8 +10,7 @@ jobs: os: [macOS-latest] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -19,7 +18,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{runner.workspace}}/build - run: cmake -DFAST_BUILD=ON -DEXP=ON $GITHUB_WORKSPACE + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=ON -DCMAKE_BUILD_TYPE=RELEASE - name: Build working-directory: ${{runner.workspace}}/build @@ -29,21 +28,7 @@ jobs: - name: Test working-directory: ${{runner.workspace}}/build shell: bash - run: ctest - - # 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: | - cmake -E make_directory ${{runner.workspace}}/install \ - cmake -DFAST_BUILD=ON -DCMAKE_INSTALL_PREFIX=${{runner.workspace}}/install $GITHUB_WORKSPACE \ - cmake --build . --parallel \ - cmake --install . \ + run: ctest --parallel --timeout 300 --output-on-failure -C RELEASE fast-build-debug: runs-on: ${{ matrix.os }} @@ -52,7 +37,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -66,7 +51,7 @@ jobs: working-directory: ${{runner.workspace}}/build shell: bash # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --parallel --config DEBUG + run: cmake --build . --parallel - name: Test working-directory: ${{runner.workspace}}/build diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index a874e17270..f11a627bb6 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -10,7 +10,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -32,7 +32,38 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C $BUILD_TYPE + run: ctest --parallel --timeout 300 --output-on-failure + + debug_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Debug + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug release: runs-on: ${{ matrix.os }} @@ -41,7 +72,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -63,7 +94,38 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C Release + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + release_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Release debug64: runs-on: ${{ matrix.os }} @@ -72,7 +134,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -80,21 +142,52 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DHIGHSINT64=on -DFAST_BUILD=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DHIGHSINT64=on -DFAST_BUILD=OFF - 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 --build . --parallel --config Debug - name: Test working-directory: ${{runner.workspace}}/build shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C Release + run: ctest --parallel --timeout 300 --output-on-failure -C Debug + + debug64_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Debug + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug Release64: runs-on: ${{ matrix.os }} @@ -103,7 +196,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -125,4 +218,35 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C Release + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + Release64_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Release diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 0327074e03..d22309a96e 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,6 +1,6 @@ name: build-macos -on: [pull_request] +on: [push, pull_request] jobs: debug: @@ -10,7 +10,7 @@ jobs: os: [macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -32,7 +32,38 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C $BUILD_TYPE + run: ctest --parallel --timeout 300 --output-on-failure + + debug_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Debug + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug release: runs-on: ${{ matrix.os }} @@ -41,7 +72,7 @@ jobs: os: [macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -63,7 +94,38 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C Release + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + release_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug debug64: runs-on: ${{ matrix.os }} @@ -72,7 +134,7 @@ jobs: os: [macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -94,7 +156,38 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C Release + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + debug64_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Debug + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug Release64: runs-on: ${{ matrix.os }} @@ -103,7 +196,7 @@ jobs: os: [macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -125,4 +218,35 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel 2 --timeout 300 --output-on-failure -C Release + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + Release64_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DHIGHSINT64=on -DALL_TESTS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Release diff --git a/.github/workflows/build-meson.yml b/.github/workflows/build-meson.yml index b53c4e4aa5..f1bef1f5eb 100644 --- a/.github/workflows/build-meson.yml +++ b/.github/workflows/build-meson.yml @@ -1,4 +1,5 @@ -name: Meson Builds with Conda +name: build-meson + on: [push, pull_request] jobs: @@ -6,12 +7,15 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] # windows-latest takes to long + # macos-latest issue with micromamba action + # windows-latest takes to long + os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" fetch-depth: 0 + - name: Install Conda environment uses: mamba-org/setup-micromamba@v1 with: @@ -33,11 +37,14 @@ jobs: run: | meson setup bbdir -Duse_zlib=enabled -Dwith_tests=True meson test -C bbdir - - name: Test compiled highspy - shell: bash -l {0} - run: | - meson configure bbdir -Dwith_pybind11=True - meson compile -C bbdir - LD_LIBRARY_PATH=$(pwd)/bbdir/src \ - PYTHONPATH=$(pwd)/bbdir \ - python examples/call_highs_from_python.py + + # highspy no longer compiled with meson + # see if it can be done separately if it is needed for meson projects + # - name: Test compiled highspy + # shell: bash -l {0} + # run: | + # meson configure bbdir -Dwith_pybind11=True + # meson compile -C bbdir + # LD_LIBRARY_PATH=$(pwd)/bbdir/src \ + # PYTHONPATH=$(pwd)/bbdir \ + # python examples/call_highs_from_python.py diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index c11a33f4b9..609e47380a 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -41,12 +41,12 @@ jobs: ${{ matrix.target-prefix }}-cc ${{ matrix.target-prefix }}-ninja - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure CMake run: | mkdir build && cd build - cmake .. -DFAST_BUILD=${{ matrix.fast-build }} -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -DHIGHSINT64=${{ matrix.int64 }} + cmake .. -DFAST_BUILD=${{ matrix.fast-build }} -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -DHIGHSINT64=${{ matrix.int64 }} -DHIGHS_NO_DEFAULT_THREADS=ON - name: Build # Execute the build. You can specify a specific target with "--target " diff --git a/.github/workflows/build-nuget-package.yml b/.github/workflows/build-nuget-package.yml new file mode 100644 index 0000000000..f2c3cf84bd --- /dev/null +++ b/.github/workflows/build-nuget-package.yml @@ -0,0 +1,214 @@ +name: build-nuget-package + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # macos 12 is Intel + build_macos_12: + runs-on: macos-12 + # strategy: + # matrix: + # python: [3.11] + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/upload-artifact@v4 + with: + name: macos-x64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + # macos 14 is M1 (beta) + build_macos_14: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/upload-artifact@v4 + with: + name: macos-arm64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + # Build windows 32 and linux + build_windows_32: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build32 + + - name: Configure CMake win32 + shell: bash + working-directory: ${{runner.workspace}}/build32 + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON -A Win32 + + - name: Build win32 + shell: bash + working-directory: ${{runner.workspace}}/build32 + run: cmake --build . --config Release --parallel + + - uses: actions/upload-artifact@v4 + with: + name: win-x32 + path: ${{runner.workspace}}/build32/dotnet/Highs.Native/runtimes/win-x64/ + + build_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/upload-artifact@v4 + with: + name: linux-x64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + build_linux_arm64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build_linux_arm64 + + - name: Configure CMake linux-arm-64 + working-directory: ${{runner.workspace}}/build_linux_arm64 + shell: bash + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON -DCMAKE_TOOLCHAIN_FILE=$GITHUB_WORKSPACE/nuget/arm-toolchain.cmake + + - name: Build linux-arm-64 + working-directory: ${{runner.workspace}}/build_linux_arm64 + shell: bash + run: cmake --build . --config Release --parallel + + - uses: actions/upload-artifact@v4 + with: + name: linux-arm64 + path: ${{runner.workspace}}/build_linux_arm64/dotnet/Highs.Native/runtimes + + build_windows: + runs-on: windows-latest + needs: [build_macos_12, build_macos_14, build_windows_32, build_linux, build_linux_arm64] + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS Windows native + run: | + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config Release --parallel + + - name: Display structure of downloaded files + run: | + pwd + ls -R ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + - name: Download runtimes macos-x64 + uses: actions/download-artifact@v4 + with: + name: macos-x64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + - name: Download runtimes macos-arm64 + uses: actions/download-artifact@v4 + with: + name: macos-arm64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + - name: Download runtimes win-x32 + uses: actions/download-artifact@v4 + with: + name: win-x32 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes/win-x32 + + - name: Download runtimes + uses: actions/download-artifact@v4 + with: + name: linux-x64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + - name: Download runtimes + uses: actions/download-artifact@v4 + with: + name: linux-arm64 + path: ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + - name: Display structure of downloaded files + run: ls -R ${{runner.workspace}}/build/dotnet/Highs.Native/runtimes + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - uses: actions/upload-artifact@v4 + with: + name: nuget + path: ${{runner.workspace}}/build/dotnet/Highs.Native/bin/Release/*.nupkg + + dotnet_push: + runs-on: windows-latest + needs: [build_windows] + environment: + name: nuget + + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + + - name: Download nuget + uses: actions/download-artifact@v4 + with: + name: nuget + + - name: Dotnet push + run: dotnet nuget push "*.nupkg" --api-key ${{secrets.nuget_api_key}} --source https://api.nuget.org/v3/index.json + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-python-package.yml b/.github/workflows/build-python-package.yml new file mode 100644 index 0000000000..c408458a13 --- /dev/null +++ b/.github/workflows/build-python-package.yml @@ -0,0 +1,230 @@ +name: build-python-package + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: | + python3 -m pip install build --user + python3 -m build --sdist + + - name: Install sdist + run: | + ls dist + python3 -m pip install meson-python meson pybind11 ninja --user + python3 -m pip install dist/*.tar.gz + + - name: Test highspy + run: | + python3 -m pip install pytest --user + python3 -m pytest $GITHUB_WORKSPACE/tests + + build_sdist_mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: | + python3 -m pip install build --user --break-system-packages + python3 -m build --sdist + + - name: Install sdist + run: | + ls dist + python3 -m pip install meson-python meson pybind11 ninja --user --break-system-packages + python3 -m pip install dist/*.tar.gz --user --break-system-packages + + - name: Test highspy + run: | + python3 -m pip install pytest --user --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests + + build_sdist_win: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build sdist + run: | + python -m pip install build --user + python -m build --sdist + + - name: Install sdist + run: | + python3 -m pip install meson-python meson pybind11 ninja --user + $item = Get-ChildItem dist + python -m pip install "$item" --user + python -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python -m pip install pytest --user + python -m pytest tests + + build_wheel_linux: + # ubuntu 22 has a latest version of cmake, but setup-python + # does not seem to provide all necessary modules to find python + # from cmake. works on my machine, test the wheels manually + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # strategy: + # matrix: + # python: [3.12] + + - name: Install correct python version + uses: actions/setup-python@v5 + # with: + # python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 --version + python3 -m pip install cibuildwheel + python3 -m cibuildwheel --only cp310-manylinux_x86_64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 -m pip install wheelhouse/*.whl + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/tests + + + # macos 12 is Intel + build_wheel_macos_12: + runs-on: macos-12 + strategy: + matrix: + python: [3.11] + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 -m pip install cibuildwheel --break-system-packages + python3 -m cibuildwheel --only cp311-macosx_x86_64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 --version + python3 -m pip install wheelhouse/*.whl --break-system-packages + python3 -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python3 -m pip install pytest --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests + + # macos 13 is Intel + build_wheel_macos_13: + runs-on: macos-13 + strategy: + matrix: + python: [3.11] + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 -m pip install cibuildwheel --break-system-packages + python3 -m cibuildwheel --only cp311-macosx_x86_64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 --version + python3 -m pip install wheelhouse/*.whl --break-system-packages + python3 -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python3 -m pip install pytest --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests + + # macos 14 is M1 (beta) + build_wheel_macos_14: + runs-on: macos-14 + strategy: + matrix: + python: [3.11] + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 -m pip install cibuildwheel --break-system-packages + python3 -m cibuildwheel --only cp311-macosx_arm64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 --version + python3 -m pip install wheelhouse/*.whl --break-system-packages + python3 -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python3 -m pip install pytest --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests + + build_wheel_windows: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build wheel + run: | + python -m pip install cibuildwheel + python -m cibuildwheel --only cp39-win_amd64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + $item = Get-ChildItem wheelhouse + python -m pip install "$item" + python -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python -m pip install pytest + python -m pytest tests diff --git a/.github/workflows/build-wheels-push.yml b/.github/workflows/build-wheels-push.yml new file mode 100644 index 0000000000..394ea0987f --- /dev/null +++ b/.github/workflows/build-wheels-push.yml @@ -0,0 +1,128 @@ +name: build-wheels-push + +# on: push + +on: + release: + types: + - published + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_wheels: + name: Build wheel for ${{ matrix.python }}-${{ matrix.buildplat[1] }} + runs-on: ${{ matrix.buildplat[0] }} + environment: pypi + strategy: + # Ensure that a wheel builder finishes even if another fails + fail-fast: false + matrix: + # From NumPy + # Github Actions doesn't support pairing matrix values together, let's improvise + # https://github.com/github/feedback/discussions/7835#discussioncomment-1769026 + buildplat: + - [ubuntu-20.04, manylinux_x86_64] + - [ubuntu-20.04, manylinux_i686] + - [ubuntu-20.04, manylinux_aarch64] + - [ubuntu-20.04, musllinux_x86_64] # No OpenBlas, no test + - [ubuntu-20.04, musllinux_i686] + - [ubuntu-20.04, musllinux_aarch64] + - [macos-12, macosx_x86_64] + - [macos-14, macosx_arm64] + - [windows-2019, win_amd64] + - [windows-2019, win32] + python: ["cp38", "cp39","cp310", "cp311","cp312"] + + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU # Required for aarch64 builds + if: ${{ contains(matrix.buildplat[1], 'aarch64') }} + uses: docker/setup-qemu-action@v3 + with: + platforms: all + - name: Build wheels (aarch64) + if: ${{ contains(matrix.buildplat[1], 'aarch64') }} + uses: pypa/cibuildwheel@v2.19 + env: + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} + CIBW_ARCHS_LINUX: aarch64 + - name: Build wheels (not aarch64) + if: ${{ !contains(matrix.buildplat[1], 'aarch64') }} + uses: pypa/cibuildwheel@v2.19 + env: + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + shell: bash -l {0} + run: pipx run build --sdist + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + # upload_testpypi: + # name: >- + # Publish highspy to TestPyPI + # runs-on: ubuntu-latest + # needs: [build_wheels, build_sdist] + + # # upload to PyPI on every tag starting with 'v' + # # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + + # environment: + # name: testpypi + # url: https://testpypi.org/p/highspy + + # permissions: + # id-token: write # IMPORTANT: mandatory for trusted publishing + # steps: + # - uses: actions/download-artifact@v3 + # with: + # # unpacks default artifact into dist/ + # # if `name: artifact` is omitted, the action will create extra parent dir + # name: artifact + # path: dist + + # - name: Download all + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # repository-url: https://test.pypi.org/legacy/ + + upload_pypi: + name: >- + Publish highspy to PyPI + runs-on: ubuntu-latest + needs: [build_wheels, build_sdist] + + # upload to PyPI on every tag starting with 'v' + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + + environment: + name: pypi + url: https://pypi.org/p/highspy + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - uses: actions/download-artifact@v3 + with: + # unpacks default artifact into dist/ + # if `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - name: Download all + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 00d58eb8de..aa658d85a7 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -1,9 +1,7 @@ -# Python release WIP -name: Build wheels -on: [] -# release: -# types: -# - published +name: build-wheels + +on: [push] +# on: [pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -13,7 +11,6 @@ jobs: build_wheels: name: Build wheel for ${{ matrix.python }}-${{ matrix.buildplat[1] }} runs-on: ${{ matrix.buildplat[0] }} - environment: pypi strategy: # Ensure that a wheel builder finishes even if another fails fail-fast: false @@ -23,52 +20,42 @@ jobs: # https://github.com/github/feedback/discussions/7835#discussioncomment-1769026 buildplat: - [ubuntu-20.04, manylinux_x86_64] + - [ubuntu-20.04, manylinux_i686] + - [ubuntu-20.04, manylinux_aarch64] - [ubuntu-20.04, musllinux_x86_64] # No OpenBlas, no test + - [ubuntu-20.04, musllinux_i686] + - [ubuntu-20.04, musllinux_aarch64] - [macos-12, macosx_x86_64] - - [macos-12, macosx_arm64] + - [macos-14, macosx_arm64] - [windows-2019, win_amd64] - python: ["cp38", "cp39","cp310", "cp311"] + - [windows-2019, win32] + python: ["cp38", "cp39","cp310", "cp311","cp312"] steps: - - uses: actions/checkout@v3 - - name: Build wheels - uses: pypa/cibuildwheel@v2.12.3 + - uses: actions/checkout@v4 + - name: Set up QEMU # Required for aarch64 builds + if: ${{ contains(matrix.buildplat[1], 'aarch64') }} + uses: docker/setup-qemu-action@v3 + with: + platforms: all + - name: Build wheels (aarch64) + if: ${{ contains(matrix.buildplat[1], 'aarch64') }} + uses: pypa/cibuildwheel@v2.19 + env: + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} + CIBW_ARCHS_LINUX: aarch64 + - name: Build wheels (not aarch64) + if: ${{ !contains(matrix.buildplat[1], 'aarch64') }} + uses: pypa/cibuildwheel@v2.19 env: CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} - - uses: actions/upload-artifact@v3 - with: - path: ./wheelhouse/*.whl build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build sdist shell: bash -l {0} run: pipx run build --sdist - - - uses: actions/upload-artifact@v3 - with: - path: dist/*.tar.gz - - upload_pypi: - needs: [build_wheels, build_sdist] - runs-on: ubuntu-latest - # upload to PyPI on every tag starting with 'v' - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - # alternatively, to publish when a GitHub Release is created, use the following rule: - # if: github.event_name == 'release' && github.event.action == 'published' - steps: - - uses: actions/download-artifact@v3 - with: - # unpacks default artifact into dist/ - # if `name: artifact` is omitted, the action will create extra parent dir - name: artifact - path: dist - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 5e5bb6a365..f14d66e150 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -4,10 +4,10 @@ on: [push, pull_request] jobs: fast_build_release: - runs-on: windows-latest + runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory @@ -37,17 +37,52 @@ jobs: # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --timeout 300 --output-on-failure -C Release + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON + + fast_build_release_all_tests: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Release + - name: Install working-directory: ${{runner.workspace}}/build shell: bash # Execute the build. You can specify a specific target with "--target " run: cmake --install . - windows_release: - runs-on: windows-latest + windows_release_fb_off: + runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory @@ -62,7 +97,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHS_NO_DEFAULT_THREADS=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -81,7 +116,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory @@ -96,7 +131,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DHIGHS_NO_DEFAULT_THREADS=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -109,13 +144,13 @@ jobs: shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --output-on-failure -C Debug - - windows_debug: + run: ctest --output-on-failure -C Debug + + fast_build_debug_all_tests: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory @@ -130,7 +165,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON -DHIGHS_NO_DEFAULT_THREADS=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -145,11 +180,110 @@ jobs: # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --output-on-failure -C Debug - windows_release64: + windows_debug_fb_off: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Debug + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug + + windows_release64: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 300 --output-on-failure -C Release + + windows_release64_all_tests: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 300 --output-on-failure -C Release + + + windows_release64_fb_off: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory @@ -164,7 +298,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -183,7 +317,41 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug + + windows_debug64_all_tests: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory @@ -198,7 +366,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF -DHIGHSINT64=on + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -212,3 +380,37 @@ jobs: # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --output-on-failure -C Debug + + windows_debug64_fb_off: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug \ No newline at end of file diff --git a/.github/workflows/check-python-package.yml b/.github/workflows/check-python-package.yml new file mode 100644 index 0000000000..3d7462cc40 --- /dev/null +++ b/.github/workflows/check-python-package.yml @@ -0,0 +1,49 @@ +name: check-python-package + +on: [pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: | + python3 -m pip install build setuptools twine + python3 -m build --sdist + python3 -m build --wheel + twine check dist/* + + build_mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: | + python3 -m pip install build setuptools twine --break-system-packages + python3 -m build --sdist + python3 -m build --wheel + twine check dist/* + + build_win: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build sdist + run: | + py -m pip install build setuptools twine + py -m build --sdist + py -m build --wheel + py -m twine check dist/* diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index de1bbd2e4a..5dfb9d9591 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: DoozyX/clang-format-lint-action@v0.14 with: source: 'app/ src/Highs.h ./src/lp_data ./src/mip ./src/model ./src/simplex ./src/presolve ./src/simplex ./src/util ./src/test' diff --git a/.github/workflows/cmake-linux-cpp.yml b/.github/workflows/cmake-linux-cpp.yml new file mode 100644 index 0000000000..4f3f48e78b --- /dev/null +++ b/.github/workflows/cmake-linux-cpp.yml @@ -0,0 +1,101 @@ +name: cmake-linux-cpp + +on: [push, pull_request] + +jobs: + release: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure + + release_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test All + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure + + debug: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure + + debug_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test All + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure diff --git a/.github/workflows/cmake-macos-cpp.yml b/.github/workflows/cmake-macos-cpp.yml new file mode 100644 index 0000000000..d44c26a9a4 --- /dev/null +++ b/.github/workflows/cmake-macos-cpp.yml @@ -0,0 +1,101 @@ +name: cmake-macos-cpp + +on: [push, pull_request] + +jobs: + release: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure + + release_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test All + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure + + debug: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure + + debug_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test All + working-directory: ${{runner.workspace}}/build + run: ctest --parallel --timeout 300 --output-on-failure diff --git a/.github/workflows/cmake-windows-cpp.yml b/.github/workflows/cmake-windows-cpp.yml new file mode 100644 index 0000000000..5fadce34e6 --- /dev/null +++ b/.github/workflows/cmake-windows-cpp.yml @@ -0,0 +1,100 @@ +name: cmake-windows-cpp + +on: [push, pull_request] + +jobs: + release: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - 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 + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + run: ctest --timeout 300 --output-on-failure -C Release + + release_all_tests: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --parallel --config Release + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + debug: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - 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 + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + run: ctest --output-on-failure -C Debug + + debug_all_tests: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - 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 -DALL_TESTS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + run: ctest --output-on-failure -C Debug diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 15cafd5958..4908e82a5e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -9,7 +9,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: version: '1.9' diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml new file mode 100644 index 0000000000..a086c552d7 --- /dev/null +++ b/.github/workflows/sanitizers.yml @@ -0,0 +1,43 @@ +name: Test with sanitizers +on: [workflow_dispatch] + +jobs: + sanitizer: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + sanitizer: [address, undefined, thread] + steps: + - uses: actions/checkout@v4 + with: + submodules: "recursive" + fetch-depth: 0 + - name: Install Conda environment + uses: mamba-org/setup-micromamba@v1 + with: + environment-name: highsdev + create-args: >- + python==3.8 + meson + pkgconfig + ninja + zlib + catch2 + numpy + cache-environment: true + init-shell: >- + bash + zsh + - name: Build and test + shell: bash -l {0} + run: | + meson setup bddir -Duse_zlib=enabled -Dwith_tests=True -Db_sanitize=${{ matrix.sanitizer }} + meson test -C bddir -t 10 # Time x10 for the tests + - name: Upload log + uses: actions/upload-artifact@v4 + if: always() + with: + name: log_${{ matrix.sanitizer }}_${{ matrix.os }}.txt + path: bddir/meson-logs/testlog.txt diff --git a/.github/workflows/test-c-example.yml b/.github/workflows/test-c-example.yml index e517d35f78..0479adbd10 100644 --- a/.github/workflows/test-c-example.yml +++ b/.github/workflows/test-c-example.yml @@ -6,10 +6,10 @@ name: test-c-example on: [push, pull_request] jobs: - build: + fast-build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create Build Environment run: | mkdir build @@ -23,14 +23,42 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=ON \ -DFAST_BUILD=ON \ - -DIPX=OFF \ $GITHUB_WORKSPACE cmake --build . --config Release --parallel make install - name: Compile and test C example shell: bash run: | - g++ $GITHUB_WORKSPACE/examples/call_highs_from_c.c \ + gcc $GITHUB_WORKSPACE/examples/call_highs_from_c.c \ + -o c_example \ + -I installs/highs/include/highs \ + -L installs/highs/lib -lhighs + LD_LIBRARY_PATH=installs/highs/lib ./c_example + + fast-build-off: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Create Build Environment + run: | + mkdir build + mkdir installs + - name: Build HiGHS library + shell: bash + working-directory: build + run: | + cmake \ + -DCMAKE_INSTALL_PREFIX=../installs/highs \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON \ + -DFAST_BUILD=OFF \ + $GITHUB_WORKSPACE + cmake --build . --config Release --parallel + make install + - name: Compile and test C example + shell: bash + run: | + gcc $GITHUB_WORKSPACE/examples/call_highs_from_c.c \ -o c_example \ -I installs/highs/include/highs \ -L installs/highs/lib -lhighs diff --git a/.github/workflows/test-csharp-win.yml b/.github/workflows/test-csharp-win.yml new file mode 100644 index 0000000000..f55059ed60 --- /dev/null +++ b/.github/workflows/test-csharp-win.yml @@ -0,0 +1,56 @@ +name: test-csharp-win + +on: [push, pull_request] + +jobs: + fast_build_release: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - 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 -DCSHARP=ON + + - name: Build + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - name: Test + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + ls + ./bin/Release/csharpexample.exe + + fast_build_debug: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - 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 -DCSHARP=ON + + - name: Build + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Debug --parallel + + - name: Test + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + ls + ./bin/Debug/csharpexample.exe diff --git a/.github/workflows/test-exe.yml b/.github/workflows/test-exe.yml new file mode 100644 index 0000000000..44b132438d --- /dev/null +++ b/.github/workflows/test-exe.yml @@ -0,0 +1,92 @@ +name: test-exe + +on: [push, pull_request] + +jobs: + test_unix: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + + - 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 + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ./bin/highs $GITHUB_WORKSPACE/check/instances/25fv47.mps + + test_win: + runs-on: [windows-2019] + + steps: + - uses: actions/checkout@v4 + + - 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 + + - 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 + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ./bin/Release/highs.exe $GITHUB_WORKSPACE/check/instances/25fv47.mps + + + test_win_debug: + runs-on: [windows-latest] + + steps: + - uses: actions/checkout@v4 + + - 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 + + - 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 Debug + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ./bin/Debug/highs.exe $GITHUB_WORKSPACE/check/instances/25fv47.mps diff --git a/.github/workflows/test-fortran-macos.yml b/.github/workflows/test-fortran-macos.yml new file mode 100644 index 0000000000..e90da71869 --- /dev/null +++ b/.github/workflows/test-fortran-macos.yml @@ -0,0 +1,43 @@ +name: test-fortran-macos + +on: [push, pull_request] + +jobs: + fast_build_release: + runs-on: macos-12 + + + steps: + - uses: actions/checkout@v4 + + - uses: fortran-lang/setup-fortran@v1.6 + id: setup-fortran + with: + compiler: gcc + version: 11 + + # - name: Install GFortran + # run: brew install gfortran gcc + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + cmake --version + gfortran-11 --version + cmake $GITHUB_WORKSPACE -DFORTRAN=ON + + - name: Build + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + ls + ./bin/fortrantest diff --git a/.github/workflows/test-fortran-ubuntu.yml b/.github/workflows/test-fortran-ubuntu.yml new file mode 100644 index 0000000000..7030087313 --- /dev/null +++ b/.github/workflows/test-fortran-ubuntu.yml @@ -0,0 +1,46 @@ +name: test-fortran-ubuntu + +on: [push, pull_request] + +jobs: + fast_build_release: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + toolchain: + - {compiler: gcc, version: 13} + # - {compiler: intel, version: '2023.2'} + # - {compiler: nvidia-hpc, version: '23.11'} + include: + - os: ubuntu-latest + toolchain: {compiler: gcc, version: 12} + + steps: + - uses: actions/checkout@v4 + + - uses: fortran-lang/setup-fortran@v1 + id: setup-fortran + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - 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 -DFORTRAN=ON + + - name: Build + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --parallel + + - name: Test + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + ls + ./bin/fortrantest diff --git a/.github/workflows/test-nuget-macos.yml b/.github/workflows/test-nuget-macos.yml new file mode 100644 index 0000000000..32724a3691 --- /dev/null +++ b/.github/workflows/test-nuget-macos.yml @@ -0,0 +1,101 @@ +name: test-nuget-macos + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # macos 12 is Intel + build_macos_12: + runs-on: macos-12 + # strategy: + # matrix: + # python: [3.11] + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + + + # macos 14 is M1 (beta) + build_macos_14: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run diff --git a/.github/workflows/test-nuget-package.yml b/.github/workflows/test-nuget-package.yml new file mode 100644 index 0000000000..00fa60ca12 --- /dev/null +++ b/.github/workflows/test-nuget-package.yml @@ -0,0 +1,232 @@ +name: test-nuget-package + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # macos 12 is Intel + build_macos_12: + runs-on: macos-12 + # strategy: + # matrix: + # python: [3.11] + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + + + # macos 14 is M1 (beta) + build_macos_14: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + build_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + build_linux_8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + build_windows: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS Windows native + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source -n name ${{runner.workspace}}\nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + shell: bash + run: dotnet nuget push ./bin/Release/*.nupkg -s name + + - name: Create new project and test + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp ${{runner.workspace}}\HiGHS\examples\call_highs_from_csharp.cs . + dotnet add package Highs.Native -v 1.7.2 -s ${{runner.workspace}}\nugets + dotnet run diff --git a/.github/workflows/test-nuget-ubuntu.yml b/.github/workflows/test-nuget-ubuntu.yml new file mode 100644 index 0000000000..7ad24fe9e2 --- /dev/null +++ b/.github/workflows/test-nuget-ubuntu.yml @@ -0,0 +1,96 @@ +name: test-nuget-ubuntu + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + build_linux_8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source ${{runner.workspace}}/nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet nuget push ./bin/Release/*.nupkg -s ${{runner.workspace}}/nugets + + - name: Create new project and test + shell: bash + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp $GITHUB_WORKSPACE/examples/call_highs_from_csharp.cs . + dotnet add package Highs.Native -s ${{runner.workspace}}/nugets + dotnet run + + diff --git a/.github/workflows/test-nuget-win.yml b/.github/workflows/test-nuget-win.yml new file mode 100644 index 0000000000..965fce54d3 --- /dev/null +++ b/.github/workflows/test-nuget-win.yml @@ -0,0 +1,53 @@ +name: test-nuget-win + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Build HiGHS Windows native + run: | + cmake -E make_directory ${{runner.workspace}}/build + cmake -E make_directory ${{runner.workspace}}/nugets + cmake -E make_directory ${{runner.workspace}}/test_nuget + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCSHARP=ON -DBUILD_DOTNET=ON + + - name: Build + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config Release --parallel + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Dotnet pack + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + run: dotnet pack -c Release /p:Version=1.7.2 + + - name: Add local feed + run: dotnet nuget add source -n name ${{runner.workspace}}\nugets + + - name: Dotnet push to local feed + working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native + shell: bash + run: dotnet nuget push ./bin/Release/*.nupkg -s name + + - name: Create new project and test + working-directory: ${{runner.workspace}}/test_nuget + run: | + dotnet new console + rm Program.cs + cp ${{runner.workspace}}\HiGHS\examples\call_highs_from_csharp.cs . + dotnet add package Highs.Native -v 1.7.2 -s ${{runner.workspace}}\nugets + dotnet run diff --git a/.github/workflows/test-python-api.yml b/.github/workflows/test-python-api.yml deleted file mode 100644 index 80bc9edd08..0000000000 --- a/.github/workflows/test-python-api.yml +++ /dev/null @@ -1,27 +0,0 @@ -# python release WIP -name: test-python-api - -on: [] -#on: [push, pull_request] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-latest, ubuntu-latest] - python: [3.9] - steps: - - uses: actions/checkout@v3 - - name: Install correct python version - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python }} - - name: Test Python Interface - shell: bash - run: | - # No need to separately install highs, - # shared library lookups are good enough - pip install -vvv . - pip install pytest numpy - pytest -v ./highspy/tests/ diff --git a/.github/workflows/test-python-macos.yml b/.github/workflows/test-python-macos.yml new file mode 100644 index 0000000000..d6ddd5eef5 --- /dev/null +++ b/.github/workflows/test-python-macos.yml @@ -0,0 +1,32 @@ +name: test-python-macos + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + python: [3.11] + steps: + - uses: actions/checkout@v4 + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install build dependencies + run: python3 -m pip install numpy setuptools wheel pytest + + - name: Test Python Interface + run: | + python3 -m pip install . + pytest -v + + - name: Test Python Examples + run: | + python3 ./examples/call_highs_from_python_highspy.py + python3 ./examples/call_highs_from_python_mps.py + python3 ./examples/call_highs_from_python.py + python3 ./examples/minimal.py diff --git a/.github/workflows/test-python-ubuntu.yml b/.github/workflows/test-python-ubuntu.yml new file mode 100644 index 0000000000..f9345050bd --- /dev/null +++ b/.github/workflows/test-python-ubuntu.yml @@ -0,0 +1,32 @@ +name: test-python-ubuntu + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python: [3.11] + steps: + - uses: actions/checkout@v4 + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install build dependencies + run: python3 -m pip install numpy setuptools wheel pytest + + - name: Test Python Interface + run: | + python3 -m pip install . + pytest -v + + - name: Test Python Examples + run: | + python3 ./examples/call_highs_from_python_highspy.py + python3 ./examples/call_highs_from_python_mps.py + python3 ./examples/call_highs_from_python.py + python3 ./examples/minimal.py \ No newline at end of file diff --git a/.github/workflows/test-python-win.yml b/.github/workflows/test-python-win.yml new file mode 100644 index 0000000000..14bb8e147f --- /dev/null +++ b/.github/workflows/test-python-win.yml @@ -0,0 +1,54 @@ +name: test-python-win + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + # os: [self-hosted] + os: [windows-2019] + python: [3.12] + steps: + - uses: actions/checkout@v4 + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install build dependencies + run: python -m pip install numpy setuptools wheel pytest meson ninja delvewheel build + + - name: Build project + run: | + meson setup bbdir --prefix=${{ github.workspace }}/local_install + meson compile -C bbdir + + - name: Install project to custom directory + run: meson install -C bbdir + + - name: Build Python package + run: python -m build + + - name: Repair the wheel + shell: pwsh + run: | + $installedPath = "${{ github.workspace }}/local_install" + $wheels = Get-ChildItem -Path dist -Filter *.whl + foreach ($wheel in $wheels) { + delvewheel repair $wheel.FullName --add-path "$installedPath/bin;$installedPath/lib" -w repaired_wheels + } + + - name: Install the repaired wheel + run: | + $wheels = Get-ChildItem -Path repaired_wheels -Filter *.whl + foreach ($wheel in $wheels){ + pip install $wheel.FullName + } + + - name: Test Python Examples + run: | + python ./examples/call_highs_from_python_highspy.py + python ./examples/call_highs_from_python_mps.py + python ./examples/minimal.py diff --git a/.gitignore b/.gitignore index 994a233aa1..ee3e66a161 100644 --- a/.gitignore +++ b/.gitignore @@ -242,13 +242,14 @@ pip-log.txt #Virtual Studio directory .vscode/ +.vs/ #Unit test fallout #SCIP interface lpi_highs.cpp -highspy/highs_bindings.*.so +src/highspy/highs_bindings.*.so # Model written with HiGHSDEV=on HighsRunModel.mps @@ -271,6 +272,14 @@ qjh.mps *.whl bazel* +MODULE.bazel +MODULE.bazel.lock # webdemo build_webdemo + +CMakeSettings.json + +# Nix +.direnv/ +result diff --git a/BUILD.bazel b/BUILD.bazel index b7fbd960be..c8b6070d03 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,9 +1,9 @@ -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_binary") load("@bazel_skylib//rules:copy_file.bzl", "copy_file") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_binary", "cc_test") copy_file( name = "highs-config", - src = "HConfig.h.bazel", + src = "src/HConfig.h.bazel.in", out = "HConfig.h", visibility = ["//visibility:public"], ) @@ -18,22 +18,23 @@ cc_library( name = "highs", srcs = glob([ "extern/filereaderlp/*.cpp", - "extern/filereaderlp/*.cpp", - "src/*.cpp", + "extern/zlib/*.cpp", "src/interfaces/highs_c_api.cpp", - "src/io/*.cpp", - "src/ipm/*.cpp", - "src/ipm/ipx/*.cc", - "src/ipm/basiclu/*.c", - "src/lp_data/*.cpp", - "src/mip/*.cpp", - "src/model/*.cpp", - "src/parallel/*.cpp", - "src/presolve/*.cpp", - "src/qpsolver/*.cpp", - "src/simplex/*.cpp", - "src/test/*.cpp", - "src/util/*.cpp", + "src/io/*.cpp", + "src/ipm/*.cpp", + "src/ipm/ipx/*.cc", + "src/ipm/basiclu/*.c", + "src/lp_data/*.cpp", + "src/mip/*.cpp", + "src/model/*.cpp", + "src/parallel/*.cpp", + "src/pdlp/*.cpp", + "src/pdlp/cupdlp/*.c", + "src/presolve/*.cpp", + "src/qpsolver/*.cpp", + "src/simplex/*.cpp", + "src/test/*.cpp", + "src/util/*.cpp", ]), hdrs = glob([ "HConfig.h", @@ -41,7 +42,12 @@ cc_library( "src/qpsolver/*.hpp", "src/Highs.h", "extern/filereaderlp/*.hpp", - "extern/zstr/*.hpp"]), + "extern/zstr/*.hpp", + ]), + copts = [ + "-Wno-unused-variable", + "-Wno-unused-but-set-variable", + ], includes = [ "extern", # "extern/filereaderlp", @@ -59,9 +65,11 @@ cc_library( # "src/simplex", # "src/test", # "src/util", - "bazel-bin"], + "bazel-bin", + ], + linkopts = ["-lpthread"], visibility = ["//visibility:public"], - deps = [ + deps = [ "//:config", "@zlib", ], @@ -69,9 +77,99 @@ cc_library( cc_binary( name = "call-highs-example", - srcs= ["examples/call_highs_from_cpp.cpp"], + srcs = ["examples/call_highs_from_cpp.cpp"], + visibility = ["//visibility:public"], deps = [ "//:highs", ], - visibility = ["//visibility:public"] -) \ No newline at end of file +) + +## Add tests +copy_file( + name = "highs-check-config", + src = "check/HCheckConfig.h.bazel.in", + out = "HCheckConfig.h", +) + +cc_library( + name = "check-config", + srcs = ["HCheckConfig.h"], +) + +cc_library( + name = "test_lib", + testonly = True, + srcs = [ + "HCheckConfig.h", + "check/Avgas.cpp", + "check/TestMain.cpp", + ], + hdrs = [ + "check/Avgas.h", + "check/SpecialLps.h", + "check/matrix_multiplication.hpp", + "extern/catch.hpp", + ], + copts = ["-Iextern"], + data = glob(["check/instances/*"]), + includes = ["check"], + deps = [ + ":highs", + "//:check-config", + ], +) + +TEST_NAMES = [ + "TestAlienBasis", + "TestBasis", + "TestBasisSolves", + "TestCheckSolution", + "TestCrossover", + "TestDualize", + "TestEkk", + "TestFactor", + "TestFilereader", + "TestFreezeBasis", + "TestHighsGFkSolve", + "TestHighsHash", + "TestHighsHessian", + "TestHighsIntegers", + "TestHighsModel", + "TestHighsParallel", + "TestHighsRbTree", + "TestHotStart", + "TestHSet", + "TestICrash", + "TestInfo", + "TestIO", + "TestIpx", + "TestLogging", + "TestLpModification", + "TestLpOrientation", + "TestLpSolvers", + "TestLpValidation", + "TestMipSolver", + "TestPresolve", + "TestQpSolver", + "TestRanging", + "TestRays", + "TestSemiVariables", + "TestSetup", + "TestSort", + "TestSpecialLps", + "TestThrow", +] + +[cc_test( + name = name, + srcs = ["check/%s.cpp" % name], + copts = [ + "-Iextern", + "-Wno-unused-variable", + "-Wno-unused-but-set-variable", + ], + deps = [ + ":highs", + ":test_lib", + ], +) for name in TEST_NAMES] diff --git a/CMakeLists.txt b/CMakeLists.txt index 605a1e2754..aa59302874 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,9 @@ # means, adding the subdirectories for all CMake projects in this tree, and # finding external libraries and turning them into imported targets. -cmake_minimum_required(VERSION 3.15) +# Require CMake 3.15+ (matching scikit-build-core) Use new versions of all +# policies up to CMake 3.27 +cmake_minimum_required(VERSION 3.15...3.27) # set preference for clang compiler and intel compiler over gcc and other compilers include(Platform/${CMAKE_SYSTEM_NAME}-Determine-C OPTIONAL) @@ -15,7 +17,7 @@ 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) +include(set-version) set_version(VERSION) project(HIGHS VERSION ${VERSION} LANGUAGES CXX C) @@ -28,154 +30,202 @@ 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() - -# 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) - -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) +# Require out-of-source builds +file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) +if(EXISTS "${LOC_PATH}") + message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). + Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") 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(BUILD_TESTING "Build Tests" ON) + 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 (FORTRAN OR CSHARP) + set(BUILD_SHARED_LIBS ON) +endif() -# 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) +option(PYTHON_BUILD_SETUP "Build Python interface from setup.py" OFF) +message(STATUS "Build Python: ${PYTHON_BUILD_SETUP}") +if (PYTHON_BUILD_SETUP) + set(BUILD_CXX OFF) + set(BUILD_TESTING OFF) 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() +include(CMakeDependentOption) -# Require out-of-source builds -file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) -if(EXISTS "${LOC_PATH}") - message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). - Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") +CMAKE_DEPENDENT_OPTION(ALL_TESTS "Build all tests" OFF "BUILD_TESTING;BUILD_CXX" OFF) +message(STATUS "Build all tests: ${ALL_TESTS}") + +option(ZLIB "ZLIB" ON) +message(STATUS "ZLIB: ${ZLIB}") +if (PYTHON_BUILD_SETUP) + set(ZLIB OFF) endif() -option(BUILD_TESTING "Build Tests" ON) +# emscripten +option(EMSCRIPTEN_HTML "Emscripten HTML output" OFF) -if(DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) - message(STATUS "IPO / LTO as requested by user: ${CMAKE_INTERPROCEDURAL_OPTIMIZATION}") -elseif(LINUX AND (NOT MSVC) AND (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) - include(CheckIPOSupported) - check_ipo_supported(RESULT ipo_supported OUTPUT error) +if (BUILD_CXX) + # 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 (CMAKE_BUILD_TYPE STREQUAL Release) + set(HiGHSRELEASE ON) + add_compile_definitions("NDEBUG")# whether to use shared or static libraries + 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) + set(HiGHSRELEASE ON) + add_compile_definitions("NDEBUG") + endif() + endif() + message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + endif() - if(ipo_supported) - message(STATUS "IPO / LTO enabled") - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + include(GNUInstallDirs) + + if(UNIX) + option(BUILD_SHARED_LIBS "Build shared libraries (.so or .dyld)." ON) + 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() - message(STATUS "IPO / LTO not supported: <${error}>") + 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(TOLOWER ${OUTPUTCONFIG} OUTPUTCONFIG) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${OUTPUTCONFIG}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${OUTPUTCONFIG}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${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() -endif() -# emscripten -option(EMSCRIPTEN_HTML "Emscripten HTML output" OFF) + if(BUILD_SHARED_LIBS AND MSVC) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + endif() -set(CMAKE_MACOSX_RPATH ON) + if (BUILD_TESTING) + include(CTest) + endif() + + 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() + + # 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 current CMAKE_C_FLAGS and CMAKE_CXX_FLAGS when checking for IPO support, + # instead of defaults: https://cmake.org/cmake/help/latest/policy/CMP0138.html + if(MSVC AND BUILD_SHARED_LIBS) + # MSVC does support LTO, but WINDOWS_EXPORT_ALL_SYMBOLS does not work if + # LTO is enabled. + set(ipo_supported NO) + message(STATUS "IPO / LTO not supported on MSVC when building a shared library") + elseif(MINGW AND NOT CLANG) + # MinGW supports LTO, but it causes tests to fail at runtime like this: + # + # Mingw-w64 runtime failure: + # 32 bit pseudo relocation at 00007FF779C9D070 out of range, targeting 00007FFAAC101400, yielding the value 000000033246438C. + # + # TODO Figure out and fix the root cause of that, then remove this section. + set(ipo_supported NO) + message(STATUS "IPO / LTO not currently supported building HiGHS on MinGW") + else() + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") + cmake_policy(SET CMP0138 NEW) + endif() + + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_supported OUTPUT check_ipo_support_output) + message(STATUS "IPO / LTO supported by compiler: ${ipo_supported}") + endif() + + if(DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + # The user explicitly requested IPO. If it's not supported, CMake *should* + # produce an error: https://cmake.org/cmake/help/latest/policy/CMP0069.html + # However, we can give a more helpful error message ourselves. + message(STATUS "IPO / LTO: ${CMAKE_INTERPROCEDURAL_OPTIMIZATION} as requested by user") + if(CMAKE_INTERPROCEDURAL_OPTIMIZATION AND NOT ipo_supported) + message(SEND_ERROR + "IPO / LTO was requested through CMAKE_INTERPROCEDURAL_OPTIMIZATION, " + "but it is not supported by the compiler. The check failed with this output:\n" + "${check_ipo_support_output}") + endif() + elseif(NOT ipo_supported OR (APPLE AND FORTRAN)) + message(STATUS "IPO / LTO: disabled because it is not supported") + elseif(NOT BUILD_SHARED_LIBS) + # For a static library, we can't be sure whether the final linking will + # happen with IPO enabled, so we err on the side of caution. A better + # approach would be to request "fat LTO" in this case (for gcc/clang), to + # make the static library usable whether or not LTO is enabled at link + # time. Unfortunately CMake makes that impossible: + # https://gitlab.kitware.com/cmake/cmake/-/issues/23136 + message(STATUS + "IPO / LTO: disabled by default when building a static library; " + "set CMAKE_INTERPROCEDURAL_OPTIMIZATION=ON to enable") + else() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + message(STATUS "IPO / LTO: enabled") + endif() +endif() include(CheckCXXSourceCompiles) check_cxx_source_compiles( @@ -211,40 +261,32 @@ else() HIGHS_HAVE_BUILTIN_CLZ) endif() -include(CheckCXXCompilerFlag) +set(CMAKE_MACOSX_RPATH ON) -if(NOT FAST_BUILD) +if (BUILD_DOTNET) + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +else() + # use, i.e. don't skip the full RPATH for the build tree + set(CMAKE_SKIP_BUILD_RPATH FALSE) - option(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS "Export all symbols into the DLL" ON) + # when building, don't use the install RPATH already + # (but later on when installing) + set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +endif() +if(NOT FAST_BUILD) 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") + if (WIN32) + message("FLAG_MPOPCNT_SUPPORTED is not available on this architecture") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") + endif() elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)" AND NOT APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcntd") else() @@ -263,13 +305,25 @@ if(NOT(${HIGHSINT64} STREQUAL "OFF")) message(STATUS "HIGHSINT64: " ${HIGHSINT64}) endif() +# At the moment there is a threading bug on Windows; this is the workaround +option(HIGHS_NO_DEFAULT_THREADS "Disable multithreading" OFF) + +if(NOT(${HIGHS_NO_DEFAULT_THREADS} STREQUAL "OFF")) + message(STATUS "Default multithreading: disabled") +endif() + # If Visual Studio targets are being built. if(MSVC) - add_definitions(/W4) - 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_compile_options("$<$:/W0>") + # add_compile_options("$<$:/wd4018 /wd4061 /wd4100 /wd4101 /wd4127 /wd4189 /wd4244 /wd4245 /wd4267 /wd4324 /wd4365 /wd4389 /wd4456 /wd4457 /wd4458 /wd4459 /wd4514 /wd4701 /wd4820>") + # add_compile_options("$<$:/MP>") + add_compile_options("$<$:-D_CRT_SECURE_NO_WARNINGS>") + add_compile_options("$<$:/MP>") + + # Try to split large pdb files into objects. + # https://github.com/tensorflow/tensorflow/issues/31610 + add_compile_options("/Z7") + add_link_options("/DEBUG:FASTLINK") if(STDCALL) # /Gz - stdcall calling convention add_definitions(/Gz) @@ -284,9 +338,7 @@ endif() if(NOT FAST_BUILD OR FORTRAN) include(CheckLanguage) - if(NOT MSVC) - check_language("Fortran") - endif() + check_language("Fortran") if(CMAKE_Fortran_COMPILER) enable_language(Fortran) set(FORTRAN_FOUND ON) @@ -296,6 +348,7 @@ if(NOT FAST_BUILD OR FORTRAN) endif() if(NOT FAST_BUILD OR CSHARP) + include(CheckLanguage) check_language("CSharp") if(CMAKE_CSharp_COMPILER) enable_language(CSharp) @@ -305,17 +358,6 @@ if(NOT FAST_BUILD OR CSHARP) endif(CMAKE_CSharp_COMPILER) endif() -check_cxx_compiler_flag("-fno-omit-frame-pointer" NO_OMIT_FRAME_POINTER_FLAG_SUPPORTED) -if(NO_OMIT_FRAME_POINTER_FLAG_SUPPORTED) - set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_DEBUG} ${CMAKE_C_FLAGS_RELEASE} -fno-omit-frame-pointer") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_RELEASE} -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer") -else() - set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_DEBUG} ${CMAKE_C_FLAGS_RELEASE}") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_RELEASE}") -endif() - # uncomment for memory debugging # set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined") # set (CMAKE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_LINKER_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined") @@ -323,12 +365,11 @@ 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 -if(ZLIB) +if(ZLIB AND NOT TARGET ZLIB::ZLIB) find_package(ZLIB 1.2.3) endif() include(CPack) -set(CPACK_RESOURCE_FILE_LICENSE "${HIGHS_SOURCE_DIR}/COPYING") set(CPACK_PACKAGE_VERSION_MAJOR "${HIGHS_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${HIGHS_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${HIGHS_VERSION_PATCH}") @@ -347,8 +388,11 @@ else() endif() message(STATUS "Git hash: " ${GITHASH}) -string(TIMESTAMP TODAY "%Y-%m-%d") -message(STATUS "Compilation date: " ${TODAY}) +# Deprecate +# string(TIMESTAMP TODAY "%Y-%m-%d") +# message(STATUS "Compilation date: " ${TODAY}) + +configure_file(${HIGHS_SOURCE_DIR}/src/HConfig.h.in ${HIGHS_BINARY_DIR}/HConfig.h) if(NOT FAST_BUILD) # For the moment keep above coverage part in case we are testing at CI. @@ -425,18 +469,6 @@ if(NOT FAST_BUILD) endif() endif() - # whether to use shared or static libraries - option(SHARED "Build shared libraries" ON) - set(BUILD_SHARED_LIBS ${SHARED}) - message(STATUS "Build shared libraries: " ${SHARED}) - - if(CMAKE_BUILD_TYPE STREQUAL RELEASE) - set(HiGHSRELEASE ON) - add_compile_definitions("NDEBUG") - endif() - message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") - - configure_file(${HIGHS_SOURCE_DIR}/src/HConfig.h.in ${HIGHS_BINARY_DIR}/HConfig.h) include_directories( ${HIGHS_BINARY_DIR} ${HIGHS_SOURCE_DIR}/app @@ -444,6 +476,7 @@ if(NOT FAST_BUILD) ${HIGHS_SOURCE_DIR}/extern/zstr ${HIGHS_SOURCE_DIR}/src ${HIGHS_SOURCE_DIR}/src/io + ${HIGHS_SOURCE_DIR}/src/pdlp/cupdlp ${HIGHS_SOURCE_DIR}/src/ipm/ipx ${HIGHS_SOURCE_DIR}/src/ipm/basiclu ${HIGHS_SOURCE_DIR}/src/lp_data @@ -466,111 +499,48 @@ if(NOT FAST_BUILD) # enable_cxx_compiler_flag_if_supported("-D_GLIBCXX_DEBUG") # endif() - # use, i.e. don't skip the full RPATH for the build tree - set(CMAKE_SKIP_BUILD_RPATH FALSE) - - # when building, don't use the install RPATH already - # (but later on when installing) - set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) - set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - # Targets - enable_testing() + add_subdirectory(src) add_subdirectory(app) if(BUILD_TESTING) + enable_testing() add_subdirectory(check) endif() - add_subdirectory(src) else(FAST_BUILD) - - message(STATUS "FAST_BUILD set to on. - Note: The HiGHS team is preparing for our first official release. If you - experience any issues please let us know via email or on GitHub.") - - option(EXP "Experimental mode: run unit tests with doctest." OFF) - - if(CMAKE_BUILD_TYPE STREQUAL RELEASE) - set(HiGHSRELEASE ON) - add_compile_definitions("NDEBUG") - endif() - message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") - - # 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) - 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() - - # If wrapper are built, we need to have the install rpath in BINARY_DIR to package - if(BUILD_PYTHON) - set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - endif() - - include(CMakeDependentOption) + message(STATUS "FAST_BUILD set to on.") option(BUILD_EXAMPLES "Build examples" ON) message(STATUS "Build examples: ${BUILD_EXAMPLES}") 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_PYTHON_EXAMPLE "Build Python example" ON "BUILD_EXAMPLES;PYTHON" OFF) - message(STATUS "Build Python: ${BUILD_PYTHON_EXAMPLE}") + message(STATUS "Build C++ example: ${BUILD_CXX_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}") + message(STATUS "Build CSharp example: ${BUILD_CSHARP_EXAMPLE}") + CMAKE_DEPENDENT_OPTION(BUILD_DOTNET "Build dotnet package" OFF "BUILD_EXAMPLES;CSHARP" OFF) + message(STATUS "Build dotnet package: ${BUILD_DOTNET}") - if(PYTHON) - CMAKE_DEPENDENT_OPTION(BUILD_pybind11 "Build the pybind11 dependency Library" OFF - "NOT BUILD_DEPS" ON) - message(STATUS "Python: Build pybind11: ${BUILD_pybind11}") - - CMAKE_DEPENDENT_OPTION(BUILD_VENV "Create Python venv in BINARY_DIR/python/venv" OFF - "NOT BUILD_TESTING" ON) - message(STATUS "Python: Create venv: ${BUILD_VENV}") - - option(VENV_USE_SYSTEM_SITE_PACKAGES "Python venv can use system site packages" OFF) - message(STATUS "Python: Allow venv to use system site packages: ${VENV_USE_SYSTEM_SITE_PACKAGES}") - - option(FETCH_PYTHON_DEPS "Install python required modules if not available" ${BUILD_DEPS}) - message(STATUS "Python: Fetch dependencies: ${FETCH_PYTHON_DEPS}") - endif() - - option(JULIA "Build library and executable for Julia" OFF) + # CMAKE_DEPENDENT_OPTION(BUILD_CSHARP_EXAMPLE "Build Python example" ON "BUILD_EXAMPLES;PYTHON" OFF) + # message(STATUS "Build CSharp example: ${BUILD_CSHARP_EXAMPLE}") include(cpp-highs) - include(c-highs) - if(PYTHON) - include(python-highs) + if(BUILD_CXX) + add_subdirectory(app) + if(BUILD_TESTING) + enable_testing() + add_subdirectory(check) + endif() + # Add tests in examples/tests + add_subdirectory(examples) endif() - # Add tests in examples/tests - add_subdirectory(examples) + include(python-highs) - add_subdirectory(app) + option(USE_DOTNET_STD_21 "Use .Net Standard 2.1 support" ON) + message(STATUS ".Net: Use .Net Framework 2.1 support: ${USE_DOTNET_STD_21}") + + include(dotnet) if(EXP) add_executable(doctest) @@ -587,9 +557,4 @@ else(FAST_BUILD) target_link_libraries(doctest highs) endif() -# install(TARGETS highs EXPORT highs-targets -# LIBRARY -# ARCHIVE -# RUNTIME -# PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df3430a4b1..225f329932 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Welcome! This document explains some of the ways you can contribute to HiGHS. ## Code of Conduct -This project and everyone participating in it is governed by the [HiGHS Code of Conduct](https://github.com/ERGO-Code/HiGHS/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. +This project and everyone participating in it is governed by the [HiGHS Code of Conduct](https://github.com/ERGO-Code/HiGHS/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. ## Contact the HiGHS team diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000000..a5e198a407 --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,13 @@ +## Build changes + +## Code changes + +Added `int64_t mip_total_lp_iterations` to `HighsCallbackDataOut` and modified accessor function + +`Highs::writeSolution` and `Highs::writeBasis` now being done via `HighsIO` logging, so can be redirected to logging callback. + +Introduced `const double kHighsUndefined` as value of undefined values in a user solution. It's equal to `kHighsInf` + +Added `Highs::setSolution(const HighsInt num_entries, const HighsInt* index, const double* value);` to allow a sparse primal solution to be defined. + + diff --git a/LICENSE b/LICENSE.txt similarity index 97% rename from LICENSE rename to LICENSE.txt index 9c4bd44a7c..8b73d26838 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 HiGHS +Copyright (c) 2024 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/MODS.md b/MODS.md deleted file mode 100644 index 8e617a50bc..0000000000 --- a/MODS.md +++ /dev/null @@ -1,23 +0,0 @@ -# Modifications for v1.3.0 since v1.2.2 - -* Added (partial) Python wrapper `highspy` -* Highs::setSolution can now be used to give a solution to the simplex solver (#775) -* Highs::addVar; Highs::addVars; Highs::deleteVars(Interval/set/mask) introduced for more natural modelling -* logHeader now written as first output, even when using libraries (#784) -* Highs::presolve and Highs::postsolve completed -* Highs::resetGlobalScheduler added to reset the global scheduler -* write_solution_style option modified: - * "Old" HiGHS raw solution style now indicated by value `kSolutionStyleOldRaw = -1` - * Raw and pretty solution formats for Glpsol now indicated by values `kSolutionStyleGlpsolRaw = 2` and `kSolutionStyleGlpsolPretty = 3` -* Many minor fixes handling marginal LP fle behaviour -* Highs::crossover completed (#815) -* scaled_model_status_ removed from Highs (#814) -* Major revisions of CMake build system - -# Planned modifications for v1.3.0 - -# Planned modifications beyond v1.3.0 - -* Make use of HFactor in critical parts of IPX - - diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000000..9eff6f4a45 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,17 @@ +module( + name = "highs", + version = "1.7.2", +) + +bazel_dep( + name = "bazel_skylib", + version = "1.6.1", +) +bazel_dep( + name = "rules_cc", + version = "0.0.9", +) +bazel_dep( + name = "zlib", + version = "1.3.1.bcr.1", +) diff --git a/README.md b/README.md index 73a9515004..8ea121d8bd 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,45 @@ # HiGHS - Linear 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) + + +[![Build Status][fast_build_svg]][fast_build_link] +[![Build Status][linux_build_svg]][linux_build_link] +[![Build Status][macos_build_svg]][macos_build_link] +[![Build Status][windows_build_svg]][windows_build_link] +\ [![Conan Center](https://img.shields.io/conan/v/highs)](https://conan.io/center/recipes/highs) +\ [![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 - -- [HiGHS - Linear optimization software](#highs---linear-optimization-software) - - [Table of Contents](#table-of-contents) - - [About HiGHS](#about-highs) - - [Documentation](#documentation) +\ +[![NuGet version](https://img.shields.io/nuget/v/Highs.Native.svg)](https://www.nuget.org/packages/Highs.Native) +[![NuGet download](https://img.shields.io/nuget/dt/Highs.Native.svg)](https://www.nuget.org/packages/Highs.Native) + +[fast_build_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-fast.yml/badge.svg +[fast_build_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-fast.yml +[linux_build_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-linux.yml/badge.svg +[linux_build_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-linux.yml +[macos_build_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-macos.yml/badge.svg +[macos_build_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-macos.yml +[windows_build_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-windows.yml/badge.svg +[windows_build_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/build-windows.yml + +- [About HiGHS](#about-highs) +- [Documentation](#documentation) +- [Installation](#installation) + - [Build from source using CMake](#build-from-source-using-cmake) + - [Build with Meson*](#build-with-meson) + - [Build with Nix*](#build-with-nix) - [Precompiled binaries](#precompiled-binaries) - - [Compilation](#compilation) - - [Meson](#meson) - - [Interfaces](#interfaces) +- [Running HiGHS](#running-highs) +- [Interfaces](#interfaces) - [Python](#python) - - [Google Colab Example](#google-colab-example) - - [Reference](#reference) + - [C](#c) + - [CSharp](#csharp) + - [Fortran](#fortran) +- [Reference](#reference) -About HiGHS ------------ +## About HiGHS HiGHS is a high performance serial and parallel solver for large scale sparse linear optimization problems of the form @@ -35,125 +54,180 @@ Find out more about HiGHS at https://www.highs.dev. 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 -------------- +## Documentation Documentation is available at https://ergo-code.github.io/HiGHS/. -Precompiled binaries --------------------- +## Installation -Precompiled static executables are available for a variety of platforms at -https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases +### Build from source using CMake -_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._ +HiGHS uses CMake as build system, and requires at least version 3.15. To generate build files in a new subdirectory called 'build', run: -See https://ergo-code.github.io/HiGHS/stable/installation/#Precompiled-Binaries. +```shell + cmake -S . -B build + cmake --build build +``` +This installs the executable `bin/highs` and the library `lib/highs`. -Compilation ------------ +To test whether the compilation was successful, change into the build directory and run -HiGHS uses CMake as build system, and requires at least version 3.15. First setup a build folder and call CMake as follows +```shell + ctest +``` +More details on building with CMake can be found in `HiGHS/cmake/README.md`. - mkdir build - cd build - cmake .. +#### Build with Meson -Then compile the code using +As an alternative, HiGHS can be installed using the `meson` build interface: +``` sh +meson setup bbdir -Dwith_tests=True +meson test -C bbdir +``` +_The meson build files are provided by the community and are not officially supported by the HiGHS development team._ - cmake --build . +#### Build with Nix -This installs the executable `bin/highs`. +There is a nix flake that provides the `highs` binary: -As an alternative it is also possible to let cmake create the build folder and thus build everything from the HiGHS directory, as follows +```shell +nix run . +``` - cmake -S . -B build - cmake --build build +You can even run [without installing +anything](https://determinate.systems/posts/nix-run/), supposing you have +installed [nix](https://nixos.org/download.html): +```shell +nix run github:ERGO-Code/HiGHS +``` -To test whether the compilation was successful, run +The nix flake also provides the python package: - ctest +```shell +nix build .#highspy +tree result/ +``` -HiGHS can read MPS files and (CPLEX) LP files, and the following command -solves the model in `ml.mps` +And a devShell for testing it: - highs ml.mps +```shell +nix develop .#highspy +python +>>> import highspy +>>> highspy.Highs() +``` + +_The nix build files are provided by the community and are not officially supported by the HiGHS development team._ -HiGHS is installed using the command +### Precompiled binaries - cmake --install . +Precompiled static executables are available for a variety of platforms at +https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases -with the optional setting of `--prefix = The installation prefix CMAKE_INSTALL_PREFIX` if it is to be installed anywhere other than the default location. +_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._ -Meson ------ +See https://ergo-code.github.io/HiGHS/stable/installation/#Precompiled-Binaries. -HiGHs can also use the `meson` build interface: +## Running HiGHS -``` sh -meson setup bbdir -Dwith_tests=True -meson test -C bbdir +HiGHS can read MPS files and (CPLEX) LP files, and the following command +solves the model in `ml.mps` + +```shell + highs ml.mps ``` +#### 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: +``` +$ 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. +``` +For a full list of options, see the [options page](https://ergo-code.github.io/HiGHS/stable/options/definitions/) of the documentation website. -Interfaces ----------- +## Interfaces -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/stable/interfaces/other/. +There are HiGHS interfaces for C, C#, FORTRAN, and Python in `HiGHS/src/interfaces`, with example driver files in `HiGHS/examples/`. More on language and modelling interfaces can be found at https://ergo-code.github.io/HiGHS/stable/interfaces/other/. We are happy to give a reasonable level of support via email sent to highsopt@gmail.com. -Python ------- +### Python -There are two ways to build the Python interface to HiGHS. +The python package `highspy` is a thin wrapper around HiGHS and is available on [PyPi](https://pypi.org/project/highspy/). It can be easily installed via `pip` by running -__From PyPi__ +```shell +$ pip install highspy +``` -HiGHS has been added to PyPi, so should be installable using the command +Alternatively, `highspy` can be built from source. Download the HiGHS source code and run - pip install highspy +```shell +pip install . +``` +from the root directory. + +The HiGHS C++ library no longer needs to be separately installed. The python package `highspy` depends on the `numpy` package and `numpy` will be installed as well, if it is not already present. + +The installation can be tested using the small example `HiGHS/examples/call_highs_from_python_highspy.py`. -The installation can be tested using the example [minimal.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/minimal.py), yielding the output +The [Google Colab Example Notebook](https://colab.research.google.com/drive/1JmHF53OYfU-0Sp9bzLw-D2TQyRABSjHb?usp=sharing) also demonstrates how to call `highspy`. - 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 - 0 rows, 0 cols, 0 nonzeros - Presolve : Reductions: rows 0(-2); columns 0(-2); elements 0(-4) - Reduced to empty - Solving the original LP from the solution after postsolve - Model status : Optimal - Objective value : 1.0000000000e+00 - HiGHS run time : 0.00 +### C +The C API is in `HiGHS/src/interfaces/highs_c_api.h`. It is included in the default build. For more details, check out the documentation website https://ergo-code.github.io/HiGHS/. -or the more didactic [call_highs_from_python.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/call_highs_from_python.py). +### CSharp -__Directly__ +The nuget package Highs.Native is on https://www.nuget.org, at https://www.nuget.org/packages/Highs.Native/. -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 +It can be added to your C# project with `dotnet` + +```shell +dotnet add package Highs.Native --version 1.7.2 +``` - pip install ./ +The nuget package contains runtime libraries for -from the HiGHS directory. +* `win-x64` +* `win-x32` +* `linux-x64` +* `linux-arm64` +* `macos-x64` +* `macos-arm64` -You may also require +Details for building locally can be found in `nuget/README.md`. -* `pip install pybind11` -* `pip install pyomo` +### Fortran -The Python interface can then be tested as above. +The Fortran API is in `HiGHS/src/interfaces/highs_fortran_api.f90`. It is *not* included in the default build. For more details, check out the documentation website https://ergo-code.github.io/HiGHS/. -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 ---------- +## Reference If you use HiGHS in an academic context, please acknowledge this and cite the following article. diff --git a/Version.txt b/Version.txt index 62da96f2c0..ee0905004d 100644 --- a/Version.txt +++ b/Version.txt @@ -1,4 +1,4 @@ HIGHS_MAJOR=1 -HIGHS_MINOR=6 -HIGHS_PATCH=0 +HIGHS_MINOR=7 +HIGHS_PATCH=2 #PRE_RELEASE=YES diff --git a/WORKSPACE b/WORKSPACE index 0b7f49d096..b3466cc343 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,10 +2,20 @@ workspace(name = "highs") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository") +# Needed by Protobuf +git_repository( + name = "rules_python", + tag = "0.31.0", + remote = "https://github.com/bazelbuild/rules_python.git", +) + +load("@rules_python//python:repositories.bzl", "py_repositories") +py_repositories() + # Protobuf git_repository( name = "com_google_protobuf", - tag = "v3.19.4", + tag = "v26.1", remote = "https://github.com/protocolbuffers/protobuf.git", ) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index ae20c06ce5..0363d7dcab 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,26 +1,46 @@ if(FAST_BUILD) - # create highs binary using library without pic - add_executable(highs-bin) + if (BUILD_CXX) + # create highs binary using library without pic + add_executable(highs-bin) - target_sources(highs-bin PRIVATE RunHighs.cpp) + # Configure the config windows version file + if(MSVC) + string(REPLACE "." "," PROJECT_RC_VERSION "${PROJECT_VERSION}") + configure_file(${HIGHS_SOURCE_DIR}/version.rc.in + "${HIGHS_BINARY_DIR}/version.rc" @ONLY) + set(win_version_file ${HIGHS_BINARY_DIR}/version.rc) + else() + set(win_version_file) + endif() - target_include_directories(highs-bin PRIVATE - $ - ) + target_sources(highs-bin PRIVATE RunHighs.cpp ${win_version_file}) - if(UNIX) - target_compile_options(highs-bin PUBLIC "-Wno-unused-variable") - target_compile_options(highs-bin PUBLIC "-Wno-unused-const-variable") - endif() + target_include_directories(highs-bin PRIVATE + $ + ) - set_target_properties(highs-bin - PROPERTIES OUTPUT_NAME highs) + if(UNIX) + target_compile_options(highs-bin PUBLIC "-Wno-unused-variable") + target_compile_options(highs-bin PUBLIC "-Wno-unused-const-variable") + endif() - target_link_libraries(highs-bin highs) + set_target_properties(highs-bin + PROPERTIES OUTPUT_NAME highs) - # install the binary - install(TARGETS highs-bin EXPORT highs-targets - RUNTIME) + target_link_libraries(highs-bin highs) + + if(APPLE) + set_target_properties(highs-bin PROPERTIES INSTALL_RPATH + "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif (UNIX) + set_target_properties(highs-bin PROPERTIES INSTALL_RPATH + "$ORIGIN:$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") + endif() + + # install the binary + install(TARGETS highs-bin EXPORT highs-targets + RUNTIME) + endif() else() # create highs binary using library without pic add_executable(highs) diff --git a/app/RunHighs.cpp b/app/RunHighs.cpp index 53bbc459ef..183bb917c8 100644 --- a/app/RunHighs.cpp +++ b/app/RunHighs.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -64,7 +64,7 @@ int main(int argc, char** argv) { } // Solve the model HighsStatus run_status = highs.run(); - if (run_status == HighsStatus::kError) return (int)run_status; + if (run_status == HighsStatus::kError) return int(run_status); // highs.writeInfo("Info.md"); diff --git a/app/cxxopts.hpp b/app/cxxopts.hpp index 667700245d..080a7c0391 100644 --- a/app/cxxopts.hpp +++ b/app/cxxopts.hpp @@ -485,7 +485,7 @@ namespace cxxopts { if (negative) { - if (u > static_cast(-std::numeric_limits::min())) + if (u > static_cast(std::numeric_limits::min())) { throw argument_incorrect_type(text); } diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 5d046fb947..366a4c5924 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -1,263 +1,324 @@ include(CTest) -# prepare Catch library -set(CATCH_INCLUDE_DIR ${HIGHS_SOURCE_DIR}/src/extern/catch) -add_library(Catch INTERFACE) -target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) -target_include_directories(Catch INTERFACE ${HIGHS_SOURCE_DIR}/src) - -configure_file(${HIGHS_SOURCE_DIR}/check/HCheckConfig.h.in ${HIGHS_BINARY_DIR}/HCheckConfig.h) - -FILE(WRITE ${CMAKE_BINARY_DIR}/testoptions.txt -"mip_rel_gap=0.0 -mip_abs_gap=0.0") - -# Make test executable -set(TEST_SOURCES - TestHighsVersion.cpp - TestAlienBasis.cpp - TestDualize.cpp - TestCallbacks.cpp - TestCheckSolution.cpp - TestEkk.cpp - TestFactor.cpp - TestFreezeBasis.cpp - TestHotStart.cpp - TestMain.cpp - TestNames.cpp - TestOptions.cpp - TestIO.cpp - TestSort.cpp - TestSetup.cpp - TestFilereader.cpp - TestHighsGFkSolve.cpp - TestInfo.cpp - TestBasis.cpp - TestBasisSolves.cpp - TestCrossover.cpp - TestHighsHash.cpp - TestHighsIntegers.cpp - TestHighsParallel.cpp - TestHighsRbTree.cpp - TestHighsHessian.cpp - TestHighsModel.cpp - TestHighsSparseMatrix.cpp - TestHSet.cpp - TestICrash.cpp - TestLogging.cpp - TestLPFileFormat.cpp - TestLpValidation.cpp - TestLpModification.cpp - TestLpOrientation.cpp - TestPresolve.cpp - TestQpSolver.cpp - TestRays.cpp - TestRanging.cpp - TestSemiVariables.cpp - TestThrow.cpp - Avgas.cpp) - -if (NOT APPLE) - # Bug with updated IPX code and gas11. Maybe somehow related to the rpath on - # macOS (Lukas). Only triggered by gas11 with no presolve which is strange. - # may be an interface related issue which will pop up soon. - # works OK on linux. The test was added to doctest for macOS but still hanging. - set(TEST_SOURCES ${TEST_SOURCES} TestSpecialLps.cpp TestLpSolvers.cpp TestMipSolver.cpp) -endif() - -set (TEST_SOURCES ${TEST_SOURCES} TestIpx.cpp) - -add_executable(unit_tests ${TEST_SOURCES}) -if (UNIX) - target_compile_options(unit_tests PRIVATE "-Wno-unused-variable") - target_compile_options(unit_tests PRIVATE "-Wno-unused-const-variable") -endif() -target_link_libraries(unit_tests libhighs Catch) - -if(FORTRAN_FOUND) - set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) - include_directories(${HIGHS_SOURCE_DIR}/src) - add_executable(fortrantest TestFortranAPI.f90) - if (NOT FAST_BUILD) - target_link_libraries(fortrantest libhighs FortranHighs) +if (FORTRAN) + set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) + add_executable(fortrantest TestFortranAPI.f90) + 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) - endif(FORTRAN_FOUND) - -# check the C API -add_executable(capi_unit_tests TestCAPI.c) -target_link_libraries(capi_unit_tests libhighs) -add_test(NAME capi_unit_tests COMMAND capi_unit_tests) - -# Check whether test executable builds OK. -add_test(NAME unit-test-build - COMMAND ${CMAKE_COMMAND} - --build ${HIGHS_BINARY_DIR} - --target unit_tests - --config ${CMAKE_BUILD_TYPE} - ) - - -# Avoid that several build jobs try to concurretly build. -set_tests_properties(unit-test-build - PROPERTIES - RESOURCE_LOCK unittestbin) - -# create a binary running all the tests in the executable -add_test(NAME unit_tests_all COMMAND unit_tests --success) -set_tests_properties(unit_tests_all - PROPERTIES - DEPENDS unit-test-build) -set_tests_properties(unit_tests_all PROPERTIES TIMEOUT 10000) - -# 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 -# tests in TestMain.cpp and do not define CATCH_CONFIG_MAIN anywhere else. -# add_test(NAME correct-print-test COMMAND unit_tests correct-print) - -# -------------------------------------- -# Another way of adding the tests. Needs a script from github repo and a -# Catch2 installation. So add tests manually if there is no build issues. -# catch_discover_tests(unit_test) - -# -------------------------------------- -# Run instance tests. -# -# define the set of feasible instances -set(successInstances - "25fv47\;3149\; 5.5018458883\;" - "80bau3b\;3686\; 9.8722419241\;" - "adlittle\;74\; 2.2549496316\;" - "afiro\;22\;-4.6475314286\;" - "etamacro\;532\;-7.5571523330\;" - "greenbea\;5109\;-7.2555248130\;" - "shell\;623\; 1.2088253460\;" - "stair\;529\;-2.5126695119\;" - "standata\;72\; 1.2576995000\;" - "standgub\;68\; 1.2576995000\;" - "standmps\;218\; 1.4060175000\;" - ) - -set(infeasibleInstances - "bgetam\; infeasible" - "box1\; infeasible" - "ex72a\; infeasible" - "forest6\; infeasible" - "galenet\; infeasible" - "gams10am\; infeasible" -# "klein1\; infeasible" - "refinery\; infeasible" - "woodinfe\; infeasible" - ) - -set(unboundedInstances - "gas11\; unbounded" - ) - -set(failInstances - ) - -set(mipInstances - "small_mip\;3.2368421\;" - "flugpl\;1201500\;" - "lseu\;1120|1119.9999999\;" - "egout\;(568.1007|568.1006999)\;" - "gt2\;21166\;" - "rgn\;82.1999992\;" - "bell5\;(8966406.49152|8966406.491519|8966406.49151)\;" - "sp150x300d\;(69|68.9999999)\;" - "p0548\;(8691|8690.9999999)\;" - "dcmulti\;188182\;" - ) - -# define settings -set(settings - "--presolve=off" - "--presolve=on" - "--random_seed=1" - "--random_seed=2" - "--random_seed=3" -# "--random_seed=4" -# "--random_seed=5" -# "--parallel=on" - ) - -# define a macro to add tests -# -# add_instancetests takes an instance group and a status -# that the solver should report as arguments -macro(add_instancetests instances solutionstatus) -# 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) + target_link_libraries(fortrantest highs FortranHighs) endif() + target_include_directories(fortrantest PUBLIC + ${HIGHS_SOURCE_DIR}/src/interfaces + ${HIGHS_SOURCE_DIR}/check) +endif() - # specify the instance and the settings load command - 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() +if (NOT FAST_BUILD OR ALL_TESTS) + # prepare Catch library + set(CATCH_INCLUDE_DIR ${HIGHS_SOURCE_DIR}/extern) + add_library(Catch INTERFACE) + target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) + + configure_file(${HIGHS_SOURCE_DIR}/check/HCheckConfig.h.in ${HIGHS_BINARY_DIR}/HCheckConfig.h) + + FILE(WRITE ${CMAKE_BINARY_DIR}/testoptions.txt + "mip_rel_gap=0.0 + mip_abs_gap=0.0") + + # Make test executable + set(TEST_SOURCES + TestHighsVersion.cpp + TestAlienBasis.cpp + TestDualize.cpp + TestCallbacks.cpp + TestCheckSolution.cpp + TestEkk.cpp + TestFactor.cpp + TestFreezeBasis.cpp + TestHotStart.cpp + TestMain.cpp + TestNames.cpp + TestOptions.cpp + TestIO.cpp + TestSort.cpp + TestSetup.cpp + TestFilereader.cpp + TestHighsGFkSolve.cpp + TestInfo.cpp + TestBasis.cpp + TestBasisSolves.cpp + TestCrossover.cpp + TestHighsHash.cpp + TestHighsIntegers.cpp + TestHighsParallel.cpp + TestHighsRbTree.cpp + TestHighsHessian.cpp + TestHighsModel.cpp + TestHighsSparseMatrix.cpp + TestHSet.cpp + TestICrash.cpp + TestIpm.cpp + TestIpx.cpp + TestLogging.cpp + TestLPFileFormat.cpp + TestLpValidation.cpp + TestLpModification.cpp + TestLpOrientation.cpp + TestModelProperties.cpp + TestPdlp.cpp + TestPresolve.cpp + TestQpSolver.cpp + TestRays.cpp + TestRanging.cpp + TestSemiVariables.cpp + TestThrow.cpp + TestTspSolver.cpp + TestUserScale.cpp + Avgas.cpp) + + # todo: IG + if (NOT APPLE) + # Bug with updated IPX code and gas11. Maybe somehow related to the rpath on + # macOS (Lukas). Only triggered by gas11 with no presolve which is strange. + # may be an interface related issue which will pop up soon. + # works OK on linux. The test was added to doctest for macOS but still hanging. + set(TEST_SOURCES ${TEST_SOURCES} TestSpecialLps.cpp TestLpSolvers.cpp TestMipSolver.cpp) + endif() + + add_executable(unit_tests ${TEST_SOURCES}) + if (UNIX) + target_compile_options(unit_tests PRIVATE "-Wno-unused-variable") + target_compile_options(unit_tests PRIVATE "-Wno-unused-const-variable") + endif() + + if (FAST_BUILD) + target_link_libraries(unit_tests highs Catch) + else() + target_link_libraries(unit_tests libhighs Catch) + endif() + + include(GNUInstallDirs) + if(APPLE) + set_target_properties(unit_tests PROPERTIES INSTALL_RPATH + "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif(UNIX) + cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR + BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR} + OUTPUT_VARIABLE libdir_relative_path) + set_target_properties(unit_tests PROPERTIES + INSTALL_RPATH "$ORIGIN/${libdir_relative_path}") + endif() + + + # check the C API + add_executable(capi_unit_tests TestCAPI.c) + + if (FAST_BUILD) + target_link_libraries(capi_unit_tests highs) + else() + target_link_libraries(capi_unit_tests libhighs) + endif() + + add_test(NAME capi_unit_tests COMMAND capi_unit_tests) + + # Check whether test executable builds OK. + add_test(NAME unit-test-build + COMMAND ${CMAKE_COMMAND} + --build ${HIGHS_BINARY_DIR} + --target unit_tests + # --config ${CMAKE_BUILD_TYPE} + ) + + + # Avoid that several build jobs try to concurretly build. + set_tests_properties(unit-test-build + PROPERTIES + RESOURCE_LOCK unittestbin) + + # create a binary running all the tests in the executable + add_test(NAME unit_tests_all COMMAND unit_tests --success) + set_tests_properties(unit_tests_all + PROPERTIES + DEPENDS unit-test-build) + set_tests_properties(unit_tests_all PROPERTIES TIMEOUT 10000) + + # 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 + # tests in TestMain.cpp and do not define CATCH_CONFIG_MAIN anywhere else. + # add_test(NAME correct-print-test COMMAND unit_tests correct-print) + + # -------------------------------------- + # Another way of adding the tests. Needs a script from github repo and a + # Catch2 installation. So add tests manually if there is no build issues. + # catch_discover_tests(unit_test) + + # -------------------------------------- + # Run instance tests. + # + # define the set of feasible instances + set(successInstances + "25fv47\;3149\; 5.5018458883\;" + "80bau3b\;3686\; 9.8722419241\;" + "adlittle\;74\; 2.2549496316\;" + "afiro\;22\;-4.6475314286\;" + "etamacro\;532\;-7.5571523330\;" + "greenbea\;5109\;-7.2555248130\;" + "shell\;623\; 1.2088253460\;" + "stair\;529\;-2.5126695119\;" + "standata\;72\; 1.2576995000\;" + "standgub\;68\; 1.2576995000\;" + "standmps\;218\; 1.4060175000\;" + ) + + set(successMacArmInstances + "25fv47\;3103\; 5.5018458883\;" + "80bau3b\;3705\; 9.8722419241\;" + "adlittle\;74\; 2.2549496316\;" + "afiro\;22\;-4.6475314286\;" + "etamacro\;531\;-7.5571523330\;" + "greenbea\;5156\;-7.2555248130\;" + "shell\;623\; 1.2088253460\;" + "stair\;531\;-2.5126695119\;" + "standata\;72\; 1.2576995000\;" + "standgub\;68\; 1.2576995000\;" + "standmps\;218\; 1.4060175000\;" + ) + + set(infeasibleInstances + "bgetam\; infeasible" + "box1\; infeasible" + "ex72a\; infeasible" + "forest6\; infeasible" + "galenet\; infeasible" + "gams10am\; infeasible" + # "klein1\; infeasible" + "refinery\; infeasible" + "woodinfe\; infeasible" + ) + + set(unboundedInstances + "gas11\; unbounded" + ) + + set(failInstances + ) + + set(mipInstances + "small_mip\;3.2368421\;" + "flugpl\;1201500\;" + "lseu\;1120|1119.9999999\;" + "egout\;(568.1007|568.1006999)\;" + "gt2\;21166\;" + "rgn\;82.1999992\;" + "bell5\;(8966406.49152|8966406.491519|8966406.49151)\;" + "sp150x300d\;(69|68.9999999)\;" + "p0548\;(8691|8690.9999999)\;" + "dcmulti\;188182\;" + ) + + # define settings + set(settings + "--presolve=off" + "--presolve=on" + "--random_seed=1" + "--random_seed=2" + "--random_seed=3" + # "--random_seed=4" + # "--random_seed=5" + # "--parallel=on" + ) + + # define a macro to add tests + # + # add_instancetests takes an instance group and a status + # that the solver should report as arguments + macro(add_instancetests instances solutionstatus) + # 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 + 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") + endif() + + # loop over all settings + foreach(setting ${settings}) + if (FAST_BUILD) + add_test(NAME ${name}${setting} COMMAND $ ${setting} + ${inst}) + else() + add_test(NAME ${name}${setting} COMMAND $ ${setting} + ${inst}) + endif() + + 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(setting) + endforeach(instance) + endmacro(add_instancetests) + + # add tests for success and fail instances + if (APPLE AND (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64")) + add_instancetests(successMacArmInstances "Optimal") + else() + add_instancetests(successInstances "Optimal") + endif() + + add_instancetests(failInstances "Fail") + add_instancetests(infeasibleInstances "Infeasible") + #add_instancetests(unboundedInstances "Unbounded") + + foreach(instance ${mipInstances}) + list(GET instance 0 name) + list(GET instance 1 optval) + # specify the instance and the settings load command set(inst "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps") - endif() - # loop over all settings - foreach(setting ${settings}) - 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(setting) -endforeach(instance) -endmacro(add_instancetests) - -# add tests for success and fail instances -add_instancetests(successInstances "Optimal") -add_instancetests(failInstances "Fail") -add_instancetests(infeasibleInstances "Infeasible") -#add_instancetests(unboundedInstances "Unbounded") - -foreach(instance ${mipInstances}) - list(GET instance 0 name) - list(GET instance 1 optval) - # specify the instance and the settings load command - set(inst "${HIGHS_SOURCE_DIR}/check/instances/${name}.mps") - - foreach(setting ${settings}) - add_test(NAME ${name}${setting} COMMAND $ ${setting} --options_file ${CMAKE_BINARY_DIR}/testoptions.txt - ${inst}) - - set_tests_properties (${name}${setting} PROPERTIES - DEPENDS unit_tests_all) - - set_tests_properties (${name}${setting} PROPERTIES - PASS_REGULAR_EXPRESSION - "Status Optimal\n Primal bound ${optval}.*\n Dual bound ${optval}.*\n Solution status feasible\n ${optval}.* \\(objective\\)" - FAIL_REGULAR_EXPRESSION - "Solution status infeasible") - - endforeach(setting) -endforeach() + foreach(setting ${settings}) + if (FAST_BUILD) + add_test(NAME ${name}${setting} COMMAND $ ${setting} + --options_file ${CMAKE_BINARY_DIR}/testoptions.txt ${inst}) + else() + add_test(NAME ${name}${setting} COMMAND $ ${setting} + --options_file ${CMAKE_BINARY_DIR}/testoptions.txt ${inst}) + endif() + + set_tests_properties (${name}${setting} PROPERTIES + DEPENDS unit_tests_all) + + set_tests_properties (${name}${setting} PROPERTIES + PASS_REGULAR_EXPRESSION + "Status Optimal\n Primal bound ${optval}.*\n Dual bound ${optval}.*\n Solution status feasible\n ${optval}.* \\(objective\\)" + FAIL_REGULAR_EXPRESSION + "Solution status infeasible") + + endforeach(setting) + endforeach() + +endif() diff --git a/check/HCheckConfig.h.bazel.in b/check/HCheckConfig.h.bazel.in new file mode 100644 index 0000000000..e9fc05c28f --- /dev/null +++ b/check/HCheckConfig.h.bazel.in @@ -0,0 +1,6 @@ +#ifndef HCHECKCONFIG_H_ +#define HCHECKCONFIG_H_ + +#define HIGHS_DIR "." + +#endif /* HCHECKCONFIG_H_ */ diff --git a/check/SpecialLps.h b/check/SpecialLps.h index 65c4fff2e3..b535e41c06 100644 --- a/check/SpecialLps.h +++ b/check/SpecialLps.h @@ -332,6 +332,26 @@ class SpecialLps { optimal_objective = -optimal_objective; } + void ThreeDLp(HighsLp& lp, HighsModelStatus& require_model_status, + double& optimal_objective) { + lp.model_name_ = "distillation"; + lp.num_col_ = 3; + lp.num_row_ = 2; + lp.col_cost_ = {1, 2, 3}; + lp.col_lower_ = {0, 0, 0}; + lp.col_upper_ = {inf, inf, inf}; + lp.row_lower_ = {-inf, -inf}; + lp.row_upper_ = {3, 2}; + lp.a_matrix_.start_ = {0, 1, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 1, 2, 2}; + lp.sense_ = ObjSense::kMaximize; + lp.offset_ = 0; + lp.a_matrix_.format_ = MatrixFormat::kColwise; + require_model_status = HighsModelStatus::kOptimal; + optimal_objective = 7; + } + void reportIssue(const HighsInt issue, const bool dev_run = false) { if (dev_run) printf("\n *************\n * Issue %3" HIGHSINT_FORMAT diff --git a/check/TestBasis.cpp b/check/TestBasis.cpp index 5c5e5ec1f1..1a1c91f6c8 100644 --- a/check/TestBasis.cpp +++ b/check/TestBasis.cpp @@ -111,7 +111,7 @@ TEST_CASE("set-pathological-basis", "[highs_basis_data]") { // 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 + // pointer &basic_index[0] that caused Windows failure 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 @@ -156,6 +156,28 @@ TEST_CASE("Basis-no-basic", "[highs_basis_data]") { REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); } +TEST_CASE("Basis-singular", "[highs_basis_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {1, 1}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {1, 1}; + lp.row_lower_ = {1, 1}; + lp.row_upper_ = {2, 2}; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 2, 2, 4}; + highs.passModel(lp); + HighsBasis basis; + REQUIRE(highs.setBasis(basis) == HighsStatus::kError); + basis.col_status = {HighsBasisStatus::kBasic, HighsBasisStatus::kBasic}; + basis.row_status = {HighsBasisStatus::kLower, HighsBasisStatus::kLower}; + REQUIRE(highs.setBasis(basis) == HighsStatus::kOk); +} + // 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 diff --git a/check/TestCAPI.c b/check/TestCAPI.c index b50cba3a70..dff94ec653 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -12,9 +12,77 @@ const HighsInt dev_run = 0; const double double_equal_tolerance = 1e-5; +void checkGetCallbackDataOutPointer(const HighsCallbackDataOut* data_out, const char* name, HighsInt valid) { + const void* name_p = Highs_getCallbackDataOutItem(data_out, name); + if (valid) { + if (!name_p) printf("checkGetCallbackDataOutItem fail for %s (valid = %d)\n", name, (int)valid); + assert(name_p); + } else { + if (name_p) printf("checkGetCallbackDataOutItem fail for %s (valid = %d)\n", + name, (int)valid); + assert(!name_p); + } +} + +void checkGetCallbackDataOutHighsInt(const HighsCallbackDataOut* data_out, const char* name, HighsInt value) { + const void* name_p = Highs_getCallbackDataOutItem(data_out, name); + if (!name_p) { + printf("checkGetCallbackDataOutItem fail for %s\n", name); + assert(name_p); + } else { + HighsInt check_value = *(HighsInt*)(name_p); + HighsInt value_ok = check_value == value; + if (!value_ok) printf("checkGetCallbackDataOutItem fail for %s (%d = check_value != value = %d)\n", + name, (int)check_value, (int)value); + assert(value_ok); + } +} + +void checkGetCallbackDataOutInt(const HighsCallbackDataOut* data_out, const char* name, int value) { + const void* name_p = Highs_getCallbackDataOutItem(data_out, name); + if (!name_p) { + printf("checkGetCallbackDataOutInt fail for %s\n", name); + assert(name_p); + } else { + int check_value = *(int*)(name_p); + int value_ok = check_value == value; + if (!value_ok) printf("checkGetCallbackDataOutInt fail for %s (%d = check_value != value = %d)\n", + name, check_value, value); + assert(value_ok); + } +} + +void checkGetCallbackDataOutInt64(const HighsCallbackDataOut* data_out, const char* name, int64_t value) { + const void* name_p = Highs_getCallbackDataOutItem(data_out, name); + if (!name_p) { + printf("checkGetCallbackDataOutInt64 fail for %s\n", name); + assert(name_p); + } else { + int64_t check_value = *(int*)(name_p); + int value_ok = check_value == value; + if (!value_ok) printf("checkGetCallbackDataOutInt64 fail for %s (%d = check_value != value = %d)\n", + name, (int)check_value, (int)value); + assert(value_ok); + } +} + +void checkGetCallbackDataOutDouble(const HighsCallbackDataOut* data_out, const char* name, double value) { + const void* name_p = Highs_getCallbackDataOutItem(data_out, name); + if (!name_p) { + printf("checkGetCallbackDataOutDouble fail for %s\n", name); + assert(name_p); + } else { + double check_value = *(double*)(name_p); + double value_ok = check_value == value; + if (!value_ok) printf("checkGetCallbackDataOutDouble fail for %s (%g = check_value != value = %g)\n", + name, check_value, value); + assert(value_ok); + } +} + static void userCallback(const int callback_type, const char* message, - const struct HighsCallbackDataOut* data_out, - struct HighsCallbackDataIn* data_in, + const HighsCallbackDataOut* data_out, + HighsCallbackDataIn* data_in, void* user_callback_data) { // Extract the double value pointed to from void* user_callback_data const double local_callback_data = user_callback_data == NULL ? -1 : *(double*)user_callback_data; @@ -22,7 +90,78 @@ static void userCallback(const int callback_type, const char* message, if (callback_type == kHighsCallbackLogging) { if (dev_run) printf("userCallback(%11.4g): %s\n", local_callback_data, message); } else if (callback_type == kHighsCallbackMipImprovingSolution) { - if (dev_run) printf("userCallback(%11.4g): improving solution with objective = %g\n", local_callback_data, data_out->objective_function_value); + // Test the accessor function for data_out + // + // Check that passing an valid name returns a non-null pointer, + // and that the corresponding value is the same as obtained using + // the struct + const void* objective_function_value_p = + Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutObjectiveFunctionValueName); + assert(objective_function_value_p); + double objective_function_value = *(double*)(objective_function_value_p); + assert(objective_function_value == data_out->objective_function_value); + if (dev_run) printf("userCallback(%11.4g): improving solution with objective = %g\n", + local_callback_data, objective_function_value); + // Now test all more simply + checkGetCallbackDataOutInt(data_out, + kHighsCallbackDataOutLogTypeName, -1); + checkGetCallbackDataOutDouble(data_out, + kHighsCallbackDataOutRunningTimeName, + data_out->running_time); + checkGetCallbackDataOutHighsInt(data_out, + kHighsCallbackDataOutSimplexIterationCountName, + data_out->simplex_iteration_count); + checkGetCallbackDataOutHighsInt(data_out, + kHighsCallbackDataOutIpmIterationCountName, + data_out->ipm_iteration_count); + checkGetCallbackDataOutHighsInt(data_out, + kHighsCallbackDataOutPdlpIterationCountName, + data_out->pdlp_iteration_count); + checkGetCallbackDataOutDouble(data_out, + kHighsCallbackDataOutObjectiveFunctionValueName, + data_out->objective_function_value); + checkGetCallbackDataOutInt64(data_out, + kHighsCallbackDataOutMipNodeCountName, + data_out->mip_node_count); + checkGetCallbackDataOutDouble(data_out, + kHighsCallbackDataOutMipPrimalBoundName, + data_out->mip_primal_bound); + checkGetCallbackDataOutDouble(data_out, + kHighsCallbackDataOutMipDualBoundName, + data_out->mip_dual_bound); + checkGetCallbackDataOutDouble(data_out, + kHighsCallbackDataOutMipGapName, + data_out->mip_gap); + // Cutpool data structure is not assigned, so num_col, num_cut and + // num_nz are unassigned + // checkGetCallbackDataOutHighsInt(data_out, + // kHighsCallbackDataOutCutpoolNumColName, 0); + // checkGetCallbackDataOutHighsInt(data_out, + // kHighsCallbackDataOutCutpoolNumCutName, 0); + // checkGetCallbackDataOutHighsInt(data_out, + // kHighsCallbackDataOutCutpoolNumNzName, 0); + + // Check that passing an unrecognised name returns NULL + const void* foo_p = Highs_getCallbackDataOutItem(data_out, "foo"); + assert(!foo_p); + // Check that passing the name of an assigned vector returns + // non-NULL, and that the corresponding value is the same as + // obtained using the struct + const void* mip_solution_void_p = + Highs_getCallbackDataOutItem(data_out, + kHighsCallbackDataOutMipSolutionName); + assert(mip_solution_void_p); + double mip_solution0 = *(double*)(mip_solution_void_p); + assert(mip_solution0 == *(data_out->mip_solution)); + if (dev_run) printf("userCallback(%11.4g): improving solution with value[0] = %g\n", + local_callback_data, mip_solution0); + // Cutpool data structure is not assigned, so cannot check that + // passing names of the unassigned vectors returns NULL + // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolStartName)); + // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolIndexName)); + // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolValueName)); + // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolLowerName)); + // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolUpperName)); } else if (callback_type == kHighsCallbackMipLogging) { if (dev_run) printf("userCallback(%11.4g): MIP logging\n", local_callback_data); data_in->user_interrupt = 1; @@ -32,7 +171,7 @@ static void userCallback(const int callback_type, const char* message, } } -HighsInt intArraysEqual(const HighsInt dim, const HighsInt* array0, const HighsInt* array1) { +HighsInt highsIntArraysEqual(const HighsInt dim, const HighsInt* array0, const HighsInt* array1) { for (HighsInt ix = 0; ix < dim; ix++) if (array0[ix] != array1[ix]) return 0; return 1; } @@ -71,54 +210,9 @@ void version_api() { 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_compilationDate()); - } -} - -void minimal_api() { - HighsInt num_col = 2; - HighsInt num_row = 2; - HighsInt num_nz = 4; - HighsInt a_format = kHighsMatrixFormatRowwise; - HighsInt sense = kHighsObjSenseMinimize; - double offset = 0; - HighsInt i; - - double cc[2] = {1.0, -2.0}; - double cl[2] = {0.0, 0.0}; - double cu[2] = {10.0, 10.0}; - double rl[2] = {0.0, 0.0}; - double ru[2] = {2.0, 1.0}; - HighsInt a_start[3] = {0, 2, 4}; - HighsInt a_index[4] = {0, 1, 0, 1}; - double a_value[4] = {1.0, 2.0, 1.0, 3.0}; - - double* cv = (double*)malloc(sizeof(double) * num_col); - double* cd = (double*)malloc(sizeof(double) * num_col); - double* rv = (double*)malloc(sizeof(double) * num_row); - double* rd = (double*)malloc(sizeof(double) * num_row); - - HighsInt* cbs = (HighsInt*)malloc(sizeof(HighsInt) * num_col); - HighsInt* rbs = (HighsInt*)malloc(sizeof(HighsInt) * num_row); - - HighsInt model_status; - - HighsInt return_status = Highs_lpCall(num_col, num_row, num_nz, a_format, sense, offset, - cc, cl, cu, rl, ru, a_start, a_index, a_value, cv, - cd, rv, rd, cbs, rbs, &model_status); - assert( return_status == kHighsStatusOk ); - - if (dev_run) { - for (i = 0; i < num_col; i++) - printf("x%"HIGHSINT_FORMAT" = %lf\n", i, cv[i]); + // Compilation date is deprecated. + // printf("HiGHS compilation date %s\n", Highs_compilationDate()); } - - free(cv); - free(cd); - free(rv); - free(rd); - free(cbs); - free(rbs); } void minimal_api_lp() { @@ -179,7 +273,7 @@ void minimal_api_lp() { const HighsInt num_col = 2; const HighsInt num_row = 3; const HighsInt num_nz = 5; - HighsInt a_format = 1; + HighsInt a_format = kHighsMatrixFormatColwise; HighsInt sense = kHighsObjSenseMinimize; double offset = 0; @@ -206,11 +300,11 @@ void minimal_api_lp() { HighsInt model_status; HighsInt return_status = Highs_lpCall(num_col, num_row, num_nz, a_format, - sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, - a_start, a_index, a_value, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, + &model_status); assert( return_status == kHighsStatusOk ); @@ -254,7 +348,7 @@ void minimal_api_mip() { const HighsInt num_col = 3; const HighsInt num_row = 2; const HighsInt num_nz = 6; - HighsInt a_format = 1; + HighsInt a_format = kHighsMatrixFormatColwise; HighsInt sense = kHighsObjSenseMinimize; double offset = 0; @@ -280,23 +374,24 @@ void minimal_api_mip() { HighsInt return_status; return_status = Highs_mipCall(num_col, num_row, num_nz, a_format, - sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, - a_start, a_index, a_value, - integrality, - col_value, row_value, - &model_status); - // Should return error + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + integrality, + col_value, row_value, + &model_status); + // Should return error, with model status not set assert( return_status == kHighsStatusError ); + assert( model_status == kHighsModelStatusNotset ); // Correct integrality integrality[num_col-1] = kHighsVarTypeInteger; return_status = Highs_mipCall(num_col, num_row, num_nz, a_format, - sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, - a_start, a_index, a_value, - integrality, - col_value, row_value, - &model_status); + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + integrality, + col_value, row_value, + &model_status); // Should return OK assert( return_status == kHighsStatusOk ); @@ -369,6 +464,35 @@ void minimal_api_qp() { free(col_value); } +void minimal_api_illegal_lp() { + const double inf = 1e30; + HighsInt num_col = 2; + HighsInt num_row = 1; + HighsInt num_nz = 2; + HighsInt a_format = kHighsMatrixFormatRowwise; + HighsInt sense = kHighsObjSenseMinimize; + double offset = 0; + double col_cost[2] = {0.0, -1.0}; + double col_lower[2] = {-inf, -inf}; + double col_upper[2] = {inf, inf}; + double row_lower[1] = {-inf}; + double row_upper[1] = {2}; + HighsInt a_start[1] = {0}; + HighsInt a_index[2] = {0, -1}; // Illegal index + double a_value[2] = {1.0, 1.0}; + + HighsInt model_status; + HighsInt return_status = Highs_lpCall(num_col, num_row, num_nz, a_format, + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + NULL, NULL, NULL, NULL, + NULL, NULL, + &model_status); + // Should return error, with model status not set + assert( return_status == kHighsStatusError ); + assert( model_status == kHighsModelStatusNotset ); +} + void full_api() { void* highs = Highs_create(); @@ -431,8 +555,8 @@ void full_api() { assert( doubleArraysEqual(num_col, ck_cu, cu) ); assert( doubleArraysEqual(num_row, ck_rl, rl) ); assert( doubleArraysEqual(num_row, ck_ru, ru) ); - assert( intArraysEqual(num_col, ck_a_start, a_start) ); - assert( intArraysEqual(num_nz, ck_a_index, a_index) ); + assert( highsIntArraysEqual(num_col, ck_a_start, a_start) ); + assert( highsIntArraysEqual(num_nz, ck_a_index, a_index) ); assert( doubleArraysEqual(num_nz, ck_a_value, a_value) ); return_status = Highs_run(highs); @@ -985,9 +1109,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; @@ -1017,13 +1141,13 @@ void full_api_qp() { return_status = Highs_addCol(highs, -1.0, -inf, inf, 0, NULL, NULL); assert( return_status == kHighsStatusOk ); num_col++; - // Cannot solve the model until the Hessian has been replaced + // Can solve the model before the Hessian has been replaced return_status = Highs_run(highs); - assert( return_status == kHighsStatusError ); - assertIntValuesEqual("Run status for 2-d QP with illegal Hessian", return_status, -1); + assert( return_status == kHighsStatusOk ); + assertIntValuesEqual("Run status for 2-d QP with OK Hessian", return_status, 0); model_status = Highs_getModelStatus(highs); - assertIntValuesEqual("Model status for 2-d QP with illegal Hessian", model_status, 2); + assertIntValuesEqual("Model status for this 2-d QP with OK Hessian", model_status, kHighsModelStatusUnbounded); free(q_start); free(q_index); @@ -1133,6 +1257,124 @@ void full_api_qp() { } +void pass_presolve_get_lp() { + // Form and solve the LP + // Min f = 2x_0 + 3x_1 + // s.t. x_1 <= 6 + // 10 <= x_0 + 2x_1 <= 14 + // 8 <= 2x_0 + x_1 + // 0 <= x_0 <= 3; 1 <= x_1 + + void* highs; + + highs = Highs_create(); + const double kHighsInf = Highs_getInfinity(highs); + HighsInt model_status; + HighsInt return_status; + + Highs_setBoolOptionValue(highs, "output_flag", dev_run); + HighsInt a_format = kHighsMatrixFormatColwise; + HighsInt sense = kHighsObjSenseMinimize; + double offset = 0; + // Define the column costs, lower bounds and upper bounds + + const HighsInt num_col = 2; + const HighsInt num_row = 3; + const HighsInt num_nz = 5; + + double col_cost[2] = {2.0, 3.0}; + double col_lower[2] = {0.0, 1.0}; + double col_upper[2] = {3.0, kHighsInf}; + // Define the row lower bounds and upper bounds + double row_lower[3] = {-kHighsInf, 10.0, 8.0}; + double row_upper[3] = {6.0, 14.0, kHighsInf}; + HighsInt a_start[2] = {0, 2}; + HighsInt a_index[5] = {1, 2, 0, 1, 2}; + double a_value[5] = {1.0, 2.0, 1.0, 2.0, 1.0}; + + return_status = Highs_passLp(highs, num_col, num_row, num_nz, a_format, sense, offset, + col_cost, col_lower, col_upper, + row_lower, row_upper, + a_start, a_index, a_value); + assert( return_status == kHighsStatusOk ); + + return_status = Highs_presolve(highs); + assert( return_status == kHighsStatusOk ); + for (HighsInt k = 0; k < 2; k++) { + // Loop twice: once for col-wise; once for row-wise + HighsInt presolved_num_col = Highs_getPresolvedNumCol(highs); + HighsInt presolved_num_row = Highs_getPresolvedNumRow(highs); + HighsInt presolved_num_nz = Highs_getPresolvedNumNz(highs); + HighsInt presolved_a_format = k == 0 ? kHighsMatrixFormatColwise : kHighsMatrixFormatRowwise; + HighsInt presolved_sense; + double presolved_offset; + double* presolved_col_cost = (double*)malloc(sizeof(double) * presolved_num_col); + double* presolved_col_lower = (double*)malloc(sizeof(double) * presolved_num_col); + double* presolved_col_upper = (double*)malloc(sizeof(double) * presolved_num_col); + double* presolved_row_lower = (double*)malloc(sizeof(double) * presolved_num_row); + double* presolved_row_upper = (double*)malloc(sizeof(double) * presolved_num_row); + HighsInt* presolved_a_start = (HighsInt*)malloc(sizeof(HighsInt) * (presolved_num_col+1)); + HighsInt* presolved_a_index = (HighsInt*)malloc(sizeof(HighsInt) * presolved_num_nz); + double* presolved_a_value = (double*)malloc(sizeof(double) * presolved_num_nz); + + return_status = Highs_getPresolvedLp(highs, presolved_a_format, + &presolved_num_col, &presolved_num_row, &presolved_num_nz, + &presolved_sense, &presolved_offset, + presolved_col_cost, presolved_col_lower, presolved_col_upper, + presolved_row_lower, presolved_row_upper, + presolved_a_start, presolved_a_index, presolved_a_value, NULL); + assert( return_status == kHighsStatusOk ); + // Solve the presolved LP within a local version of HiGHS + void* local_highs; + local_highs = Highs_create(); + Highs_setBoolOptionValue(local_highs, "output_flag", dev_run); + Highs_setStringOptionValue(local_highs, "presolve", "off"); + return_status = Highs_passLp(local_highs, + presolved_num_col, presolved_num_row, presolved_num_nz, + presolved_a_format, presolved_sense, presolved_offset, + presolved_col_cost, presolved_col_lower, presolved_col_upper, + presolved_row_lower, presolved_row_upper, + presolved_a_start, presolved_a_index, presolved_a_value); + assert( return_status == kHighsStatusOk ); + return_status = Highs_run(local_highs); + + double* col_value = (double*)malloc(sizeof(double) * num_col); + double* col_dual = (double*)malloc(sizeof(double) * num_col); + double* row_dual = (double*)malloc(sizeof(double) * num_row); + + return_status = Highs_getSolution(local_highs, col_value, col_dual, NULL, row_dual); + assert( return_status == kHighsStatusOk ); + + return_status = Highs_postsolve(highs, col_value, col_dual, row_dual); + assert( return_status == kHighsStatusOk ); + + model_status = Highs_getModelStatus(highs); + assert( model_status == kHighsModelStatusOptimal ); + + // With just the primal solution, optimality cannot be determined + + return_status = Highs_postsolve(highs, col_value, NULL, NULL); + assert( return_status == kHighsStatusWarning ); + + model_status = Highs_getModelStatus(highs); + assert( model_status == kHighsModelStatusUnknown ); + + free(presolved_col_cost); + free(presolved_col_lower); + free(presolved_col_upper); + free(presolved_row_lower); + free(presolved_row_upper); + free(presolved_a_start); + free(presolved_a_index); + free(presolved_a_value); + free(col_value); + free(col_dual); + free(row_dual); + + + } +} + void options() { void* highs = Highs_create(); if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); @@ -1341,6 +1583,7 @@ void test_ranging() { free(row_bound_dn_in_var); free(row_bound_dn_ou_var); + Highs_destroy(highs); } void test_callback() { @@ -1393,6 +1636,79 @@ void test_callback() { Highs_startCallback(highs, kHighsCallbackMipImprovingSolution); Highs_run(highs); + Highs_destroy(highs); +} + +void test_getModel() { + void* highs; + highs = Highs_create(); + Highs_setBoolOptionValue(highs, "output_flag", dev_run); + const double inf = Highs_getInfinity(highs); + + HighsInt num_col = 2; + HighsInt num_row = 2; + HighsInt num_nz = 4; + HighsInt sense = -1; + double offset; + double col_cost[2] = {8, 10}; + double col_lower[2] = {0, 0}; + double col_upper[2] = {inf, inf}; + double row_lower[2] = {-inf, -inf}; + double row_upper[2] = {120, 210}; + HighsInt a_index[4] = {0, 1, 0, 1}; + double a_value[4] = {0.3, 0.5, 0.7, 0.5}; + HighsInt a_start[2] = {0, 2}; + Highs_addVars(highs, num_col, col_lower, col_upper); + Highs_changeColsCostByRange(highs, 0, num_col-1, col_cost); + Highs_addRows(highs, num_row, row_lower, row_upper, num_nz, a_start, a_index, a_value); + Highs_changeObjectiveSense(highs, sense); + Highs_run(highs); + + HighsInt ck_num_col; + HighsInt ck_num_row; + HighsInt ck_num_nz; + HighsInt ck_sense; + double ck_offset; + + // Get the model dimensions by passing array pointers as NULL + Highs_getLp(highs, kHighsMatrixFormatRowwise, + &ck_num_col, &ck_num_row, &ck_num_nz, + &ck_sense, &ck_offset, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL); + + assert( ck_num_col == num_col ); + assert( ck_num_row == num_row ); + assert( ck_num_nz == num_nz ); + // Motivated by #1712, ensure that the correct sense is returned when maximizing + assert( ck_sense == sense ); + + double* ck_col_cost = (double*)malloc(sizeof(double) * ck_num_col);; + double* ck_col_lower = (double*)malloc(sizeof(double) * ck_num_col); + double* ck_col_upper = (double*)malloc(sizeof(double) * ck_num_col); + double* ck_row_lower = (double*)malloc(sizeof(double) * ck_num_row); + double* ck_row_upper = (double*)malloc(sizeof(double) * ck_num_row); + HighsInt* ck_a_start = (HighsInt*)malloc(sizeof(HighsInt) * ck_num_col); + HighsInt* ck_a_index = (HighsInt*)malloc(sizeof(HighsInt) * ck_num_nz); + double* ck_a_value = (double*)malloc(sizeof(double) * num_nz); + + // Get the arrays + Highs_getLp(highs, kHighsMatrixFormatRowwise, + &ck_num_col, &ck_num_row, &ck_num_nz, + &ck_sense, &ck_offset, ck_col_cost, + ck_col_lower, ck_col_upper, ck_row_lower, + ck_row_upper, ck_a_start, ck_a_index, + ck_a_value, NULL); + + assert( doubleArraysEqual(num_col, ck_col_cost, col_cost) ); + assert( doubleArraysEqual(num_col, ck_col_lower, col_lower) ); + assert( doubleArraysEqual(num_col, ck_col_upper, col_upper) ); + assert( doubleArraysEqual(num_row, ck_row_lower, row_lower) ); + assert( doubleArraysEqual(num_row, ck_row_upper, row_upper) ); + assert( highsIntArraysEqual(num_col, ck_a_start, a_start) ); + assert( highsIntArraysEqual(num_nz, ck_a_index, a_index) ); + assert( doubleArraysEqual(num_nz, ck_a_value, a_value) ); } @@ -1412,7 +1728,7 @@ void test_setSolution() { strcat(model_file0, "\0"); char* substr = model_file0 + 1; memmove(model_file0, substr, strlen(substr) + 1); - int length = strlen(model_file0) + 1; + HighsInt length = strlen(model_file0) + 1; char model_file[length]; strcpy(model_file, model_file0); @@ -1421,9 +1737,9 @@ void test_setSolution() { Highs_readModel(highs, model_file); Highs_run(highs); - int iteration_count0; + HighsInt iteration_count0; Highs_getIntInfoValue(highs, "simplex_iteration_count", &iteration_count0); - int num_col = Highs_getNumCol(highs); + HighsInt num_col = Highs_getNumCol(highs); double* col_value = (double*)malloc(sizeof(double) * num_col); Highs_getSolution(highs, col_value, NULL, NULL, NULL); Highs_clear(highs); @@ -1432,9 +1748,9 @@ void test_setSolution() { Highs_readModel(highs, model_file); Highs_setSolution(highs, col_value, NULL, NULL, NULL); Highs_run(highs); - int iteration_count1; + HighsInt iteration_count1; Highs_getIntInfoValue(highs, "simplex_iteration_count", &iteration_count1); - int logic = iteration_count0 > iteration_count1; + HighsInt logic = iteration_count0 > iteration_count1; printf("Iteration counts are %d and %d\n", iteration_count0, iteration_count1); assertLogical("Dual", logic); @@ -1442,9 +1758,9 @@ void test_setSolution() { } */ int main() { + minimal_api_illegal_lp(); test_callback(); version_api(); - minimal_api(); full_api(); minimal_api_lp(); minimal_api_mip(); @@ -1453,10 +1769,12 @@ int main() { full_api_lp(); full_api_mip(); full_api_qp(); + pass_presolve_get_lp(); options(); test_getColsByRange(); test_passHessian(); test_ranging(); - // test_setSolution(); + test_getModel(); return 0; } + // test_setSolution(); diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 9df0a30904..d97bc0d271 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -4,6 +4,7 @@ #include "HCheckConfig.h" #include "Highs.h" #include "catch.hpp" +#include "lp_data/HighsCallback.h" const bool dev_run = false; @@ -25,99 +26,179 @@ using std::strlen; using std::strncmp; using std::strstr; +struct MipData { + HighsInt num_col; + HighsVarType* integrality; +}; + // Callback that saves message for comparison -static void myLogCallback(const int callback_type, const char* message, +HighsCallbackFunctionType myLogCallback = + [](int callback_type, const std::string& message, + const HighsCallbackDataOut* data_out, HighsCallbackDataIn* data_in, + void* user_callback_data) { strcpy(printed_log, message.c_str()); }; + +HighsCallbackFunctionType userMipSolutionCallback = + [](int callback_type, const std::string& message, + const HighsCallbackDataOut* data_out, HighsCallbackDataIn* data_in, + void* user_callback_data) { + if (dev_run) { + printf( + "MipSolutionCallback with objective = %15.8g and bounds [%15.8g, " + "%15.8g]", + data_out->objective_function_value, data_out->mip_dual_bound, + data_out->mip_primal_bound); + MipData callback_data = *(static_cast(user_callback_data)); + HighsInt num_col = callback_data.num_col; + HighsVarType* integrality = callback_data.integrality; + HighsInt num_integer = 0; + for (HighsInt iCol = 0; iCol < num_col; iCol++) + if (integrality[iCol] == HighsVarType::kInteger) num_integer++; + if (num_integer < 50) { + printf(" and solution ["); + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + if (integrality[iCol] != HighsVarType::kInteger) continue; + double value = data_out->mip_solution[iCol]; + if (std::abs(value) < 1e-5) { + printf("0"); + } else if (std::abs(value - 1) < 1e-5) { + printf("1"); + } else { + bool printed = false; + for (HighsInt k = 2; k < 10; k++) { + if (std::abs(value - k) < 1e-5) { + printf("%1d", int(k)); + printed = true; + } + } + if (printed) continue; + for (HighsInt k = 10; k < 999; k++) { + if (std::abs(value - k) < 1e-5) { + printf(" %d ", int(k)); + printed = true; + } + } + if (printed) continue; + printf("*"); + } + } + printf("]\n"); + } else { + printf("\n"); + } + fflush(stdout); + } + }; + +HighsCallbackFunctionType userInterruptCallback = + [](int callback_type, const std::string& message, + const HighsCallbackDataOut* data_out, HighsCallbackDataIn* data_in, + void* user_callback_data) { + // Extract local_callback_data from user_callback_data unless it + // is nullptr + if (callback_type == kCallbackMipImprovingSolution) { + // Use local_callback_data to maintain the objective value from + // the previous callback + assert(user_callback_data); + // Extract the double value pointed to from void* user_callback_data + const double local_callback_data = *(double*)user_callback_data; + if (dev_run) + printf( + "userCallback(type %2d; data %11.4g): %s with objective %g and " + "solution[0] = %g\n", + callback_type, local_callback_data, message.c_str(), + data_out->objective_function_value, data_out->mip_solution[0]); + REQUIRE(local_callback_data >= data_out->objective_function_value); + // Update the double value pointed to from void* user_callback_data + *(double*)user_callback_data = data_out->objective_function_value; + } else { + const int local_callback_data = + user_callback_data ? static_cast(reinterpret_cast( + user_callback_data)) + : kUserCallbackNoData; + if (user_callback_data) { + REQUIRE(local_callback_data == kUserCallbackData); + } else { + REQUIRE(local_callback_data == kUserCallbackNoData); + } + if (callback_type == kCallbackLogging) { + if (dev_run) printf("Callback: %s", message.c_str()); + // printf("userInterruptCallback(type %2d; data %2d): %s", + // callback_type, local_callback_data, + // message.c_str()); + } else if (callback_type == kCallbackSimplexInterrupt) { + if (dev_run) + printf( + "userInterruptCallback(type %2d; data %2d): %s with iteration " + "count = " + "%d\n", + callback_type, local_callback_data, message.c_str(), + int(data_out->simplex_iteration_count)); + data_in->user_interrupt = data_out->simplex_iteration_count > + adlittle_simplex_iteration_limit; + } else if (callback_type == kCallbackIpmInterrupt) { + if (dev_run) + printf( + "userInterruptCallback(type %2d; data %2d): %s with iteration " + "count = " + "%d\n", + callback_type, local_callback_data, message.c_str(), + int(data_out->ipm_iteration_count)); + data_in->user_interrupt = + data_out->ipm_iteration_count > adlittle_ipm_iteration_limit; + } else if (callback_type == kCallbackMipInterrupt) { + if (dev_run) + printf( + "userInterruptCallback(type %2d; data %2d): %s with Bounds " + "(%11.4g, %11.4g); Gap = %11.4g; Objective = " + "%g\n", + callback_type, local_callback_data, message.c_str(), + data_out->mip_dual_bound, data_out->mip_primal_bound, + data_out->mip_gap, data_out->objective_function_value); + data_in->user_interrupt = + data_out->objective_function_value < egout_objective_target; + } + } + }; + +HighsCallbackFunctionType userMipCutPoolCallback = + [](int callback_type, const std::string& message, + const HighsCallbackDataOut* data_out, HighsCallbackDataIn* data_in, + void* user_callback_data) { + if (dev_run) { + printf("userMipCutPoolCallback: dim(%2d, %2d, %2d)\n", + int(data_out->cutpool_num_col), int(data_out->cutpool_num_cut), + int(data_out->cutpool_num_nz)); + for (HighsInt iCut = 0; iCut < data_out->cutpool_num_cut; iCut++) { + printf("Cut %d\n", int(iCut)); + for (HighsInt iEl = data_out->cutpool_start[iCut]; + iEl < data_out->cutpool_start[iCut + 1]; iEl++) { + printf(" %2d %11.5g\n", int(data_out->cutpool_index[iEl]), + data_out->cutpool_value[iEl]); + } + } + } + }; + +std::function + userDataCallback = [](int callback_type, const std::string& message, const HighsCallbackDataOut* data_out, HighsCallbackDataIn* data_in, void* user_callback_data) { - strcpy(printed_log, message); -} - -static void userInterruptCallback(const int callback_type, const char* message, - const HighsCallbackDataOut* data_out, - HighsCallbackDataIn* data_in, - void* user_callback_data) { - // Extract local_callback_data from user_callback_data unless it - // is nullptr - if (callback_type == kCallbackMipImprovingSolution) { - // Use local_callback_data to maintain the objective value from - // the previous callback - assert(user_callback_data); - // Extract the double value pointed to from void* user_callback_data - const double local_callback_data = *(double*)user_callback_data; - if (dev_run) - printf( - "userCallback(type %2d; data %11.4g): %s with objective %g and " - "solution[0] = %g\n", - callback_type, local_callback_data, message, - data_out->objective_function_value, data_out->mip_solution[0]); - REQUIRE(local_callback_data >= data_out->objective_function_value); - // Update the double value pointed to from void* user_callback_data - *(double*)user_callback_data = data_out->objective_function_value; - } else { - const int local_callback_data = - user_callback_data - ? static_cast(reinterpret_cast(user_callback_data)) - : kUserCallbackNoData; - if (user_callback_data) { - REQUIRE(local_callback_data == kUserCallbackData); - } else { - REQUIRE(local_callback_data == kUserCallbackNoData); - } - if (callback_type == kCallbackLogging) { - if (dev_run) - printf("userInterruptCallback(type %2d; data %2d): %s", callback_type, - local_callback_data, message); - } else if (callback_type == kCallbackSimplexInterrupt) { - if (dev_run) - printf( - "userInterruptCallback(type %2d; data %2d): %s with iteration " - "count = " - "%d\n", - callback_type, local_callback_data, message, - int(data_out->simplex_iteration_count)); - data_in->user_interrupt = - data_out->simplex_iteration_count > adlittle_simplex_iteration_limit; - } else if (callback_type == kCallbackIpmInterrupt) { + assert(callback_type == kCallbackMipInterrupt || + callback_type == kCallbackMipLogging || + callback_type == kCallbackMipImprovingSolution); if (dev_run) printf( - "userInterruptCallback(type %2d; data %2d): %s with iteration " - "count = " - "%d\n", - callback_type, local_callback_data, message, - int(data_out->ipm_iteration_count)); - data_in->user_interrupt = - data_out->ipm_iteration_count > adlittle_ipm_iteration_limit; - } else if (callback_type == kCallbackMipInterrupt) { - if (dev_run) - printf( - "userInterruptCallback(type %2d; data %2d): %s with Bounds " - "(%11.4g, %11.4g); Gap = %11.4g; Objective = " - "%g\n", - callback_type, local_callback_data, message, - data_out->mip_dual_bound, data_out->mip_primal_bound, - data_out->mip_gap, data_out->objective_function_value); - data_in->user_interrupt = - data_out->objective_function_value < egout_objective_target; - } - } -} - -static void userDataCallback(const int callback_type, const char* message, - const HighsCallbackDataOut* data_out, - HighsCallbackDataIn* data_in, - void* user_callback_data) { - assert(callback_type == kCallbackMipInterrupt || - callback_type == kCallbackMipLogging || - callback_type == kCallbackMipImprovingSolution); - if (dev_run) - printf("userDataCallback: Node count = %" PRId64 - "; Time = %6.2f; " - "Bounds (%11.4g, %11.4g); Gap = %11.4g; Objective = %11.4g: %s\n", - data_out->mip_node_count, data_out->running_time, - data_out->mip_dual_bound, data_out->mip_primal_bound, - data_out->mip_gap, data_out->objective_function_value, message); -} + "userDataCallback: Node count = %" PRId64 + "; LP total iterations = %" PRId64 + "; Time = %6.2f; " + "Bounds (%11.4g, %11.4g); Gap = %11.4g; Objective = %11.4g: %s\n", + data_out->mip_node_count, data_out->mip_total_lp_iterations, + data_out->running_time, data_out->mip_dual_bound, + data_out->mip_primal_bound, data_out->mip_gap, + data_out->objective_function_value, message.c_str()); + }; TEST_CASE("my-callback-logging", "[highs-callback]") { bool output_flag = true; // Still runs quietly @@ -185,6 +266,21 @@ TEST_CASE("highs-callback-logging", "[highs-callback]") { highs.run(); } +TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + int user_callback_data = kUserCallbackData; + void* p_user_callback_data = + reinterpret_cast(static_cast(user_callback_data)); + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + highs.run(); + highs.setCallback(userInterruptCallback, p_user_callback_data); + highs.startCallback(kCallbackLogging); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); + if (dev_run) highs.writeBasis(""); +} + TEST_CASE("highs-callback-simplex-interrupt", "[highs-callback]") { std::string filename = std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; @@ -253,3 +349,34 @@ TEST_CASE("highs-callback-mip-data", "[highs-callback]") { highs.readModel(filename); highs.run(); } + +TEST_CASE("highs-callback-mip-solution", "[highs-callback]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/egout.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("presolve", kHighsOffString); + highs.readModel(filename); + // To print the values of the integer variables in the callback, + // need the number of columns and the integrality. Set this up in a + // struct to be passed via user_callback_data + HighsLp lp = highs.getLp(); + MipData user_callback_data; + user_callback_data.num_col = int(lp.num_col_); + user_callback_data.integrality = lp.integrality_.data(); + void* p_user_callback_data = &user_callback_data; + + highs.setCallback(userMipSolutionCallback, p_user_callback_data); + highs.startCallback(kCallbackMipSolution); + highs.run(); +} + +TEST_CASE("highs-callback-mip-cut-pool", "[highs-callback]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/flugpl.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + // MipData user_callback_data; + highs.setCallback(userMipCutPoolCallback); //, p_user_callback_data); + highs.startCallback(kCallbackMipGetCutPool); + highs.run(); +} diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index 3d210bf3da..d0f2309b1c 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -83,7 +83,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { if (dev_run) printf("Num nodes = %d\n", int(scratch_num_nodes)); std::string solution_file = model + ".sol"; - // if (dev_run) return_status = highs.writeSolution(""); + if (dev_run) return_status = highs.writeSolution(""); return_status = highs.writeSolution(solution_file); REQUIRE(return_status == HighsStatus::kOk); @@ -229,6 +229,57 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); } + const bool test6 = other_tests; + if (test6) { + if (dev_run) + printf( + "\n***************************\nSolving from sparse integer " + "solution\n"); + HighsInt num_integer_variable = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + if (lp.integrality_[iCol] == HighsVarType::kInteger) + num_integer_variable++; + + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + std::vector index; + std::vector value; + // Check that duplicate values are spotted + index.push_back(0); + value.push_back(0); + index.push_back(1); + value.push_back(1); + index.push_back(0); + value.push_back(2); + HighsInt num_entries = index.size(); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kWarning); + + index.clear(); + value.clear(); + std::vector is_set; + is_set.assign(lp.num_col_, false); + HighsInt num_to_set = 2; + assert(num_to_set > 0); + HighsRandom random; + for (HighsInt iSet = 0; iSet < num_to_set;) { + HighsInt iCol = random.integer(lp.num_col_); + if (lp.integrality_[iCol] != HighsVarType::kInteger) continue; + if (is_set[iCol]) continue; + is_set[iCol] = true; + index.push_back(iCol); + value.push_back(optimal_solution.col_value[iCol]); + iSet++; + } + num_entries = index.size(); + assert(num_entries == num_to_set); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kOk); + highs.run(); + REQUIRE(info.mip_node_count < scratch_num_nodes); + highs.clear(); + } + assert(other_tests); std::remove(solution_file.c_str()); } @@ -330,6 +381,20 @@ TEST_CASE("check-set-mip-solution-extra-row", "[highs_check_solution]") { std::remove(solution_file_name.c_str()); } +TEST_CASE("check-set-illegal-solution", "[highs_check_solution]") { + HighsStatus return_status; + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + const HighsLp& lp = highs.getLp(); + HighsSolution solution; + REQUIRE(highs.setSolution(solution) == HighsStatus::kError); + solution.col_value.assign(lp.num_col_, 0); + REQUIRE(highs.setSolution(solution) == HighsStatus::kOk); +} + void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, const HighsInt write_solution_style) { diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 86c8b63ed8..14ad114b6e 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -299,6 +299,21 @@ TEST_CASE("filereader-integrality-constraints", "[highs_filereader]") { REQUIRE(are_the_same); } +/* +TEST_CASE("filereader-nan", "[highs_filereader]") { + // Check that if + std::string model_file; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + model_file = std::string(HIGHS_DIR) + "/check/instances/nan0.mps"; + REQUIRE(highs.readModel(model_file) == HighsStatus::kError); + model_file = std::string(HIGHS_DIR) + "/check/instances/nan1.mps"; + REQUIRE(highs.readModel(model_file) == HighsStatus::kError); + model_file = std::string(HIGHS_DIR) + "/check/instances/nan2.mps"; + REQUIRE(highs.readModel(model_file) == HighsStatus::kError); +} +*/ + TEST_CASE("filereader-fixed-integer", "[highs_filereader]") { double objective_value; const double optimal_objective_value = 0; @@ -312,3 +327,19 @@ TEST_CASE("filereader-fixed-integer", "[highs_filereader]") { objective_value = highs.getInfo().objective_function_value; REQUIRE(objective_value == optimal_objective_value); } + +TEST_CASE("filereader-dD2e", "[highs_filereader]") { + // dD2e.mps is min -x1 - 2x2 with upper bounds 1.0D3 and 1.0d3 + // + // If read correctly, the optimal objective value is -3000 + double objective_value; + const double optimal_objective_value = -3000; + std::string model_file = std::string(HIGHS_DIR) + "/check/instances/dD2e.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + + REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + objective_value = highs.getInfo().objective_function_value; + REQUIRE(objective_value == optimal_objective_value); +} diff --git a/check/TestHighsVersion.cpp b/check/TestHighsVersion.cpp index afe03cdac0..0d41016f5b 100644 --- a/check/TestHighsVersion.cpp +++ b/check/TestHighsVersion.cpp @@ -13,6 +13,8 @@ TEST_CASE("HighsVersion", "[highs_version]") { const HighsInt major = highsVersionMajor(); const HighsInt minor = highsVersionMinor(); const HighsInt patch = highsVersionPatch(); + const std::string compilation = highsCompilationDate(); + const std::string githash = std::string(highsGithash()); std::stringstream ss; ss << major << "." << minor << "." << patch; std::string local_version = ss.str(); @@ -21,12 +23,39 @@ TEST_CASE("HighsVersion", "[highs_version]") { 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()); - printf("HiGHS compilation date: %s\n", highsCompilationDate()); + printf("HiGHS githash: %s\n", githash.c_str()); + // Compilation date is deprecated, but make sure that the + // deprecated method is still tested. + printf("HiGHS compilation date: %s\n", compilation.c_str()); printf("HiGHS local version: %s\n", local_version.c_str()); } REQUIRE(major == HIGHS_VERSION_MAJOR); REQUIRE(minor == HIGHS_VERSION_MINOR); REQUIRE(patch == HIGHS_VERSION_PATCH); - REQUIRE(local_version == version); + REQUIRE(githash == std::string(HIGHS_GITHASH)); + REQUIRE(version == local_version); + // Check that the corresponding methods + Highs highs; + const std::string version0 = highs.version(); + REQUIRE(version0 == version); + const HighsInt major0 = highs.versionMajor(); + REQUIRE(major0 == major); + const HighsInt minor0 = highs.versionMinor(); + REQUIRE(minor0 == minor); + const HighsInt patch0 = highs.versionPatch(); + REQUIRE(patch0 == patch); + const std::string githash0 = highs.githash(); + REQUIRE(githash0 == githash); + const std::string compilation0 = highs.compilationDate(); + REQUIRE(compilation == compilation); +} + +TEST_CASE("sizeof-highs-int", "[highs_version]") { + Highs highs; + HighsInt sizeof_highs_int = highs.getSizeofHighsInt(); +#ifdef HIGHSINT64 + REQUIRE(sizeof_highs_int == 8); +#else + REQUIRE(sizeof_highs_int == 4); +#endif } diff --git a/check/TestIO.cpp b/check/TestIO.cpp index b1a98807c1..e2dd42dc12 100644 --- a/check/TestIO.cpp +++ b/check/TestIO.cpp @@ -51,7 +51,7 @@ TEST_CASE("run-callback", "[highs_io]") { std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; Highs highs; if (!dev_run) highs.setOptionValue("output_flag", false); - highs.setLogCallback(userLogCallback); + // highs.setLogCallback(userLogCallback); highs.readModel(filename); highs.run(); } @@ -67,7 +67,8 @@ TEST_CASE("run-callback-data", "[highs_io]") { reinterpret_cast(static_cast(user_log_callback_data)); Highs highs; if (!dev_run) highs.setOptionValue("output_flag", false); - highs.setLogCallback(userLogCallback, p_user_log_callback_data); + // deprecated + // highs.setLogCallback(userLogCallback, p_user_log_callback_data); highs.readModel(filename); highs.run(); } diff --git a/check/TestIpm.cpp b/check/TestIpm.cpp new file mode 100644 index 0000000000..911d01a67a --- /dev/null +++ b/check/TestIpm.cpp @@ -0,0 +1,83 @@ +#include + +#include "Highs.h" +#include "catch.hpp" + +// I use dev_run to switch on/off printing and logging used for +// development of the unit test +const bool dev_run = false; +const double inf = kHighsInf; + +TEST_CASE("test-analytic-centre", "[highs_ipm]") { + // std::string model = "greenbea.mps"; + // std::string model = "adlittle.mps"; + std::string model = "afiro.mps"; + std::string filename = std::string(HIGHS_DIR) + "/check/instances/" + model; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + HighsLp lp = highs.getLp(); + lp.col_cost_.assign(lp.num_col_, 0); + highs.passModel(lp); + highs.setOptionValue("run_centring", true); + highs.setOptionValue("ipm_optimality_tolerance", 1e-2); + HighsStatus run_status = highs.run(); + REQUIRE(run_status == HighsStatus::kOk); +} + +TEST_CASE("test-analytic-centre-infeasible", "[highs_ipm]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_.assign(lp.num_col_, 0); + lp.col_lower_.assign(lp.num_col_, 0); + lp.col_upper_.assign(lp.num_col_, inf); + lp.row_lower_ = {-inf}; + lp.row_upper_ = {-1}; + lp.a_matrix_.start_ = {0, 1, 2}; + lp.a_matrix_.index_ = {0, 0}; + lp.a_matrix_.value_ = {1, 1}; + highs.passModel(lp); + highs.setOptionValue("presolve", kHighsOffString); + highs.setOptionValue("run_centring", true); + highs.setOptionValue("ipm_optimality_tolerance", 1e-2); + HighsStatus run_status = highs.run(); + REQUIRE(run_status == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); +} + +TEST_CASE("test-analytic-centre-box", "[highs_ipm]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsInt dim = 4; + HighsLp lp; + lp.num_col_ = dim; + lp.col_cost_.assign(dim, 0); + lp.col_lower_.assign(dim, -1); + lp.col_upper_.assign(dim, 1); + highs.passModel(lp); + + std::vector index = {0, 1}; + std::vector value = {1, 1}; + + const double root2 = std::sqrt(2.0); + highs.addRow(-root2, root2, 2, index.data(), value.data()); + value[1] = -1; + highs.addRow(-root2, root2, 2, index.data(), value.data()); + highs.setOptionValue("run_centring", true); + highs.setOptionValue("presolve", kHighsOffString); + highs.setOptionValue("ipm_optimality_tolerance", 1e-2); + HighsStatus run_status = highs.run(); + const HighsSolution& solution = highs.getSolution(); + double solution_norm = 0; + for (HighsInt ix = 0; ix < dim; ix++) { + if (dev_run) + printf("Analytic centre solution %d is %g\n", int(ix), + solution.col_value[ix]); + solution_norm += std::fabs(solution.col_value[ix]); + } + REQUIRE(solution_norm < 1e-6); + if (dev_run) printf("Analytic centre solution norm is %g\n", solution_norm); +} diff --git a/check/TestIpx.cpp b/check/TestIpx.cpp index a762c90bda..4866c746d3 100644 --- a/check/TestIpx.cpp +++ b/check/TestIpx.cpp @@ -43,6 +43,7 @@ TEST_CASE("test-ipx", "[highs_ipx]") { ipx::LpSolver lps; ipx::Parameters parameters; if (!dev_run) parameters.display = 0; + parameters.highs_logging = false; lps.SetParameters(parameters); // Solve the LP. diff --git a/check/TestLpModification.cpp b/check/TestLpModification.cpp index b7c7703f35..b34cf18cae 100644 --- a/check/TestLpModification.cpp +++ b/check/TestLpModification.cpp @@ -1,13 +1,14 @@ #include "Avgas.h" #include "HCheckConfig.h" #include "Highs.h" +#include "SpecialLps.h" #include "catch.hpp" #include "lp_data/HighsLpUtils.h" #include "util/HighsRandom.h" #include "util/HighsUtils.h" const bool dev_run = false; -const double inf = kHighsInf; +// const double inf = kHighsInf; const double double_equal_tolerance = 1e-5; void HighsStatusReport(const HighsLogOptions& log_options, std::string message, HighsStatus status); @@ -1842,3 +1843,97 @@ void messageReportMatrix(const char* message, const HighsInt num_col, message); reportMatrix(log_options, message, num_col, num_nz, start, index, value); } + +TEST_CASE("mod-duplicate-indices", "[highs_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const std::string filename = + std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + highs.readModel(filename); + std::vector lower = {0, 0, 0, 0}; + std::vector set0 = {5, 2, 7, 3}; + std::vector lower0 = {1, 1, 1, 1}; + std::vector upper0 = {1, 1, 1, 1}; + std::vector set1 = {5, 2, 7, 3, 2}; + std::vector lower1 = {0, 0, 0, 0, 0}; + std::vector upper1 = {1, 1, 1, 1, 1}; + REQUIRE(highs.changeColsBounds(HighsInt(set0.size()), set0.data(), + lower0.data(), + upper0.data()) == HighsStatus::kOk); + highs.run(); + double objective1 = highs.getInfo().objective_function_value; + // Reverting the change with duplicate index should fail + REQUIRE(highs.changeColsBounds(HighsInt(set1.size()), set1.data(), + lower1.data(), + upper1.data()) == HighsStatus::kError); + // Reverting the change without duplicate index should be OK + REQUIRE(highs.changeColsBounds(HighsInt(set0.size()), set0.data(), + lower.data(), + upper0.data()) == HighsStatus::kOk); + highs.run(); + double objective0 = highs.getInfo().objective_function_value; + REQUIRE(objective0 < objective1); + REQUIRE(objective0 == -7.75); +} + +TEST_CASE("resize-integrality", "[highs_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + SpecialLps special_lps; + HighsLp lp; + HighsModelStatus require_model_status; + double optimal_objective; + special_lps.distillationLp(lp, require_model_status, optimal_objective); + HighsInt original_num_col = lp.num_col_; + for (HighsInt k = 0; k < 4; k++) { + // k = 0: Add continuous column to LP, so final integrality.size() should be + // 0 + // + // k = 1: Add continuous column to IP, so final integrality.size() should be + // full + // + // k = 2: Add integer column to LP, so final integrality.size() should be + // full + // + // k = 3: Add integer column to IP, so final integrality.size() should be + // full + if (k == 1 || k == 3) { + lp.integrality_.assign(original_num_col, HighsVarType::kInteger); + } else { + lp.integrality_.clear(); + } + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + REQUIRE(highs.getNumCol() == original_num_col); + double cost = 0.0; + double lower = 0.0; + double upper = 1.0; + highs.addCols(1, &cost, &lower, &upper, 0, nullptr, nullptr, nullptr); + const std::vector& integrality = highs.getLp().integrality_; + if (k == 0 || k == 2) { + // Model is LP + REQUIRE(int(integrality.size()) == 0); + } else { + // Model is MIP + REQUIRE(int(integrality.size()) == int(original_num_col + 1)); + } + if (k >= 2) + REQUIRE(highs.changeColIntegrality(2, HighsVarType::kInteger) == + HighsStatus::kOk); + if (k == 0) { + // Model is LP + REQUIRE(int(integrality.size()) == 0); + } else { + // Model is MIP + REQUIRE(int(integrality.size()) == int(original_num_col + 1)); + } + } +} +TEST_CASE("modify-empty-model", "[highs_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + REQUIRE(highs.changeColIntegrality(0, HighsVarType::kInteger) == + HighsStatus::kError); + REQUIRE(highs.changeColCost(0, 1) == HighsStatus::kError); + REQUIRE(highs.changeColBounds(0, 1, 1) == HighsStatus::kError); + REQUIRE(highs.changeRowBounds(0, 1, 1) == HighsStatus::kError); +} diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index 073ba7cbbb..5202a1dee5 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -69,6 +69,10 @@ void testSolver(Highs& highs, const std::string solver, REQUIRE(info.crossover_iteration_count == default_iteration_count.crossover); } + // Following simplex or IPM+Crossover, nonbasic variables are on bounds + // complementarity_violation + REQUIRE(info.max_complementarity_violation == 0); + REQUIRE(info.sum_complementarity_violations == 0); // Only perform the time limit test if the solve time is large enough const double min_run_time_for_test = 0.001; @@ -279,7 +283,7 @@ TEST_CASE("LP-solver", "[highs_lp_solver]") { const HighsInfo& info = highs.getInfo(); REQUIRE(info.num_dual_infeasibilities == 0); - REQUIRE(info.simplex_iteration_count == 476); // 444); + REQUIRE(info.simplex_iteration_count == 472); // 476); // 444); HighsModelStatus model_status = highs.getModelStatus(); REQUIRE(model_status == HighsModelStatus::kOptimal); @@ -292,7 +296,7 @@ TEST_CASE("LP-solver", "[highs_lp_solver]") { return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); - REQUIRE(info.simplex_iteration_count == 621); // 584); // + REQUIRE(info.simplex_iteration_count == 592); // 621); // 584); // } TEST_CASE("mip-with-lp-solver", "[highs_lp_solver]") { diff --git a/check/TestLpValidation.cpp b/check/TestLpValidation.cpp index 14c70eba5f..bb0f71c1db 100644 --- a/check/TestLpValidation.cpp +++ b/check/TestLpValidation.cpp @@ -78,6 +78,35 @@ TEST_CASE("LP-dimension-validation", "[highs_data]") { if (dev_run) printf("Give valid row_upper.size()\n"); lp.row_upper_.resize(true_num_row); + REQUIRE(highs.passModel(lp) == HighsStatus::kError); + + if (dev_run) printf("Give valid a_matrix_.start_[0]\n"); + lp.a_matrix_.start_[0] = 0; + REQUIRE(highs.passModel(lp) == HighsStatus::kError); + + if (dev_run) + printf("Give valid a_matrix_.start_[2] and a_matrix_.start_[3]\n"); + lp.a_matrix_.start_[2] = 2; + lp.a_matrix_.start_[3] = 2; + REQUIRE(highs.passModel(lp) == HighsStatus::kError); + + if (dev_run) printf("Give valid a_matrix_.index_[0]\n"); + // Yields duplicate index, but values are still zero, so both are + // discarded and a warning is returned + lp.a_matrix_.index_[0] = 0; + REQUIRE(highs.passModel(lp) == HighsStatus::kWarning); + + if (dev_run) + printf("Give nonzero a_matrix_.value_[0] and a_matrix_.value_[1]\n"); + // Yields duplicate index, but values are still zero, so both are + // discarded and a warning is returned + lp.a_matrix_.value_[0] = 1; + lp.a_matrix_.value_[1] = 1; + // Now the duplicate indices yield an erorr + REQUIRE(highs.passModel(lp) == HighsStatus::kError); + + if (dev_run) printf("Give valid a_matrix_.index_[1]\n"); + lp.a_matrix_.index_[1] = 1; REQUIRE(highs.passModel(lp) == HighsStatus::kOk); /* @@ -337,7 +366,7 @@ TEST_CASE("LP-validation", "[highs_data]") { // for columns 9 and 10. // LP is found to be unbounded by presolve, but is primal - // infeasible. With isBoundInfeasible check in solveLp, + // infeasible. With infeasibleBoundsOk check in solveLp, // infeasiblility is identified before reaching a solver, so // presolve isn't called HighsStatus run_status; @@ -587,3 +616,65 @@ TEST_CASE("LP-change-coefficient", "[highs_data]") { highs.getInfo().objective_function_value); REQUIRE(delta_objective_value < 1e-8); } + +TEST_CASE("LP-illegal-empty-start-ok", "[highs_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.num_col_ = 0; + lp.num_row_ = 1; + lp.row_lower_ = {-inf}; + lp.row_upper_ = {1}; + lp.a_matrix_.start_ = {1}; + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + REQUIRE(highs.getLp().a_matrix_.start_[0] == 0); +} + +TEST_CASE("LP-row-wise", "[highs_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.sense_ = ObjSense::kMaximize; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {10, 25}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 2, 1, 4}; + lp.row_lower_ = {-inf, -inf}; + lp.row_upper_ = {80, 120}; + highs.passModel(lp); + highs.run(); +} + +TEST_CASE("LP-infeasible-bounds", "[highs_data]") { + Highs highs; + const HighsInfo& info = highs.getInfo(); + const HighsSolution& solution = highs.getSolution(); + double epsilon = 1e-10; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.sense_ = ObjSense::kMaximize; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {10, 25}; + lp.col_lower_ = {1, 2.5 + epsilon}; + lp.col_upper_ = {1 - epsilon, 2.5 - 2 * epsilon}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 2, 1, 4}; + lp.row_lower_ = {6, -inf}; + lp.row_upper_ = {6 - epsilon, 11 - epsilon}; + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + if (dev_run) highs.writeSolution("", 1); + + highs.changeColBounds(0, 0, -1); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); +} diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 5abd984895..13133db843 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -6,6 +6,10 @@ const bool dev_run = false; const double double_equal_tolerance = 1e-5; +bool objectiveOk(const double optimal_objective, + const double require_optimal_objective, + const bool dev_run = false); + void solve(Highs& highs, std::string presolve, const HighsModelStatus require_model_status, const double require_optimal_objective = 0, @@ -152,6 +156,20 @@ TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") { REQUIRE(std::fabs(info.mip_gap) < 1e-12); } +TEST_CASE("MIP-clear-integrality", "[highs_test_mip_solver]") { + SpecialLps special_lps; + HighsLp lp; + HighsModelStatus require_model_status; + double optimal_objective; + special_lps.distillationMip(lp, require_model_status, optimal_objective); + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + REQUIRE(highs.getLp().integrality_.size() > 0); + highs.clearIntegrality(); + REQUIRE(highs.getLp().integrality_.size() == 0); +} + TEST_CASE("MIP-nmck", "[highs_test_mip_solver]") { Highs highs; if (!dev_run) highs.setOptionValue("output_flag", false); @@ -570,9 +588,108 @@ TEST_CASE("MIP-objective-target", "[highs_test_mip_solver]") { REQUIRE(highs.getInfo().objective_function_value > egout_optimal_objective); } +TEST_CASE("MIP-max-offset-test", "[highs_test_mip_solver]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/egout.mps"; + const double offset = 100; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + highs.run(); + const double og_optimal_objective = highs.getInfo().objective_function_value; + HighsLp lp = highs.getLp(); + lp.offset_ = offset; + highs.passModel(lp); + highs.run(); + const double offset_optimal_objective = + highs.getInfo().objective_function_value; + REQUIRE(objectiveOk(offset + og_optimal_objective, offset_optimal_objective, + dev_run)); + + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) lp.col_cost_[iCol] *= -1; + lp.offset_ *= -1; + lp.sense_ = ObjSense::kMaximize; + highs.passModel(lp); + highs.run(); + const double max_offset_optimal_objective = + highs.getInfo().objective_function_value; + REQUIRE(objectiveOk(max_offset_optimal_objective, -offset_optimal_objective, + dev_run)); +} + +TEST_CASE("MIP-get-saved-solutions-presolve", "[highs_test_mip_solver]") { + const std::string solution_file = "MipImproving.sol"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("mip_improving_solution_save", true); + highs.setOptionValue("mip_improving_solution_report_sparse", true); + highs.setOptionValue("mip_improving_solution_file", solution_file); + // #1724: Add row to the example so that solution is non-zero + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_ = {1, 1}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {1, 1}; + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; + lp.row_lower_ = {1}; + lp.row_upper_ = {kHighsInf}; + lp.a_matrix_.num_col_ = 2; + lp.a_matrix_.num_row_ = 1; + lp.a_matrix_.start_ = {0, 1, 1}; + lp.a_matrix_.index_ = {0}; + lp.a_matrix_.value_ = {1}; + highs.passModel(lp); + 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 == 1); + 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()); +} + +TEST_CASE("IP-with-fract-bounds-no-presolve", "[highs_test_mip_solver]") { + Highs highs; + // No presolve + highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("presolve", "off"); + + // IP without constraints and fractional bounds on variables + HighsLp lp; + lp.num_col_ = 3; + lp.num_row_ = 0; + lp.col_cost_ = {1, -2, 3}; + lp.col_lower_ = {2.5, 2.5, 2.5}; + lp.col_upper_ = {6.5, 5.5, 7.5}; + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger, + HighsVarType::kInteger}; + + // Solve + highs.passModel(lp); + highs.run(); + + // Check status and optimal objective value + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(objectiveOk(highs.getInfo().objective_function_value, 2.0, dev_run)); + + // Fix an integer variable to a fractional value + lp.col_upper_[0] = 2.5; + + // Solve again + highs.passModel(lp); + highs.run(); + + // Infeasible + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); +} + bool objectiveOk(const double optimal_objective, - const double require_optimal_objective, - const bool dev_run = false) { + const double require_optimal_objective, const bool dev_run) { double error = std::fabs(optimal_objective - require_optimal_objective) / std::max(1.0, std::fabs(require_optimal_objective)); bool error_ok = error < 1e-10; diff --git a/check/TestModelProperties.cpp b/check/TestModelProperties.cpp new file mode 100644 index 0000000000..a78cd1df6c --- /dev/null +++ b/check/TestModelProperties.cpp @@ -0,0 +1,141 @@ +#include + +#include "Highs.h" +#include "catch.hpp" + +const bool dev_run = false; +const double inf = kHighsInf; + +TEST_CASE("simplest-ill-conditioning", "[highs_model_properties]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + const double epsilon = 1e-4; + const double ill_conditioning_bound = 1; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {2, 2 + epsilon}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {2, 2 + epsilon}; + lp.row_upper_ = {inf, inf}; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 1, 1, 1 + epsilon}; + highs.passModel(lp); + highs.run(); + if (dev_run) highs.writeSolution("", 1); + HighsIllConditioning ill_conditioning; + + const bool constraint = true; + highs.getIllConditioning(ill_conditioning, constraint); + REQUIRE(ill_conditioning.record.size() == 2); + // Both multipliers should be large + for (HighsInt iX = 0; iX < 2; iX++) { + REQUIRE(std::fabs(ill_conditioning.record[iX].multiplier) > 0.45); + REQUIRE(std::fabs(ill_conditioning.record[iX].multiplier) < 0.55); + } + highs.getIllConditioning(ill_conditioning, !constraint); + + REQUIRE(highs.getIllConditioning(ill_conditioning, constraint, 1, 0.1) == + HighsStatus::kOk); + REQUIRE(highs.getIllConditioning(ill_conditioning, constraint, 1, + ill_conditioning_bound) == HighsStatus::kOk); + REQUIRE(highs.getIllConditioning(ill_conditioning, constraint, 1, 10) == + HighsStatus::kOk); +} + +TEST_CASE("simple-ill-conditioning", "[highs_model_properties]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + const double epsilon = 1e-4; + lp.num_col_ = 3; + lp.num_row_ = 3; + lp.col_cost_ = {3, 2, 3 + epsilon}; + lp.col_lower_ = {0, 0, 0}; + lp.col_upper_ = {inf, inf, inf}; + lp.row_lower_ = {3, 2, 3 + epsilon}; + lp.row_upper_ = {inf, inf, inf}; + lp.a_matrix_.start_ = {0, 3, 5, 8}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {1, 1, 1, 1, 1, 1, 1, 1 + epsilon}; + + highs.passModel(lp); + highs.run(); + if (dev_run) highs.writeSolution("", 1); + HighsIllConditioning ill_conditioning; + const bool constraint = true; + highs.getIllConditioning(ill_conditioning, constraint); + REQUIRE(ill_conditioning.record.size() == 3); + // First two multipliers should be the large ones + for (HighsInt iX = 0; iX < 2; iX++) { + REQUIRE(std::fabs(ill_conditioning.record[iX].multiplier) > 0.45); + REQUIRE(std::fabs(ill_conditioning.record[iX].multiplier) < 0.55); + } + highs.getIllConditioning(ill_conditioning, !constraint); +} + +TEST_CASE("afiro-ill-conditioning", "[highs_model_properties]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/afiro.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + const HighsLp& lp = highs.getLp(); + HighsInt num_nz; + std::vector index(lp.num_col_); + std::vector value(lp.num_col_); + highs.run(); + const HighsBasis& highs_basis = highs.getBasis(); + lp.a_matrix_.getRow(0, num_nz, index.data(), value.data()); + if (dev_run) { + for (HighsInt iEl = 0; iEl < num_nz; iEl++) { + HighsInt iCol = index[iEl]; + printf("%s: %d %19.12g (%s)\n", lp.col_names_[iCol].c_str(), int(iCol), + value[iEl], + highs.basisStatusToString(highs_basis.col_status[iCol]).c_str()); + } + } + value[0] += 1e-4; + + const bool negate_bad_row = false; + if (negate_bad_row) + for (HighsInt iEl = 0; iEl < num_nz; iEl++) value[iEl] *= -1; + + highs.addRow(0, 0, num_nz, index.data(), value.data()); + HighsInt bad_row = lp.num_row_ - 1; + highs.passRowName(bad_row, "R09bad"); + // Find a nonbasic row to replace bad row in the basis + HighsInt nonbasic_row = -1; + for (HighsInt iRow = 1; iRow < lp.num_row_; iRow++) { + if (dev_run) { + printf("Row %d (%s) has status %s\n", int(iRow), + lp.row_names_[iRow].c_str(), + highs.basisStatusToString(highs_basis.row_status[iRow]).c_str()); + } + if (highs_basis.row_status[iRow] != HighsBasisStatus::kBasic) { + nonbasic_row = iRow; + break; + } + } + // Bad row should be basic - since it's been added + REQUIRE(highs_basis.row_status[bad_row] == HighsBasisStatus::kBasic); + REQUIRE(nonbasic_row >= 0); + HighsBasis basis = highs_basis; + // Make the bad row nonbasic at lower bound - it's FX - and make the + // nonbasic_row basic + basis.row_status[bad_row] = HighsBasisStatus::kLower; + basis.row_status[nonbasic_row] = HighsBasisStatus::kBasic; + highs.setBasis(basis); + if (dev_run) { + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + printf("Row %d (%s) has status %s\n", int(iRow), + lp.row_names_[iRow].c_str(), + highs.basisStatusToString(highs_basis.row_status[iRow]).c_str()); + } + } + HighsIllConditioning ill_conditioning; + const bool constraint = true; + highs.getIllConditioning(ill_conditioning, constraint); + highs.getIllConditioning(ill_conditioning, !constraint); +} diff --git a/check/TestOptions.cpp b/check/TestOptions.cpp index af5a9d6f44..f511f8caef 100644 --- a/check/TestOptions.cpp +++ b/check/TestOptions.cpp @@ -497,3 +497,18 @@ TEST_CASE("highs-options", "[highs_options]") { return_status = highs.setOptionValue("time_limit", 1); REQUIRE(return_status == HighsStatus::kOk); } + +TEST_CASE("inf-value-options", "[highs_options]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + std::string options_file = + std::string(HIGHS_DIR) + "/check/instances/WithInf.set"; + REQUIRE(highs.readOptions(options_file) == HighsStatus::kOk); + double value; + highs.getOptionValue("time_limit", value); + REQUIRE(value == kHighsInf); + highs.getOptionValue("objective_bound", value); + REQUIRE(value == -kHighsInf); + highs.getOptionValue("objective_target", value); + REQUIRE(value == kHighsInf); +} diff --git a/check/TestPdlp.cpp b/check/TestPdlp.cpp new file mode 100644 index 0000000000..0514e72dc5 --- /dev/null +++ b/check/TestPdlp.cpp @@ -0,0 +1,176 @@ +#include "HCheckConfig.h" +#include "Highs.h" +#include "SpecialLps.h" +#include "catch.hpp" + +const bool dev_run = false; +const double double_equal_tolerance = 1e-3; + +TEST_CASE("pdlp-distillation-lp", "[pdlp]") { + SpecialLps special_lps; + HighsLp lp; + + HighsModelStatus require_model_status; + double optimal_objective; + special_lps.distillationLp(lp, require_model_status, optimal_objective); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsInfo& info = highs.getInfo(); + const HighsOptions& options = highs.getOptions(); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + highs.setOptionValue("solver", kPdlpString); + highs.setOptionValue("presolve", kHighsOffString); + highs.setOptionValue("primal_feasibility_tolerance", 1e-4); + highs.setOptionValue("dual_feasibility_tolerance", 1e-4); + HighsStatus run_status = HighsStatus::kOk; + // First pass uses (HiGHS default) termination for PDLP solver to + // satisfy HiGHS primal/dual feasibility tolerances + bool optimal = true; + for (HighsInt k = 0; k < 2; k++) { + if (k == 1) { + // In second pass use native termination for PDLP solver, + // failing HiGHS optimality test + highs.setOptionValue("pdlp_native_termination", true); + optimal = false; + } + run_status = highs.run(); + if (dev_run) highs.writeSolution("", 1); + REQUIRE(std::abs(info.objective_function_value - optimal_objective) < + double_equal_tolerance); + if (optimal) { + REQUIRE(run_status == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + } else { + REQUIRE(run_status == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnknown); + } + } + HighsInt pdlp_iteration_count = highs.getInfo().pdlp_iteration_count; + REQUIRE(pdlp_iteration_count > 0); + REQUIRE(pdlp_iteration_count == 160); + // Now run with half the iteration count as the limit to test + // iteration limit termination + + highs.setOptionValue("pdlp_iteration_limit", pdlp_iteration_count / 2); + run_status = highs.run(); + + REQUIRE(run_status == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kIterationLimit); + pdlp_iteration_count = highs.getInfo().pdlp_iteration_count; + REQUIRE(pdlp_iteration_count > 0); + REQUIRE(pdlp_iteration_count == 79); +} + +TEST_CASE("pdlp-3d-lp", "[pdlp]") { + SpecialLps special_lps; + HighsLp lp; + + HighsModelStatus require_model_status; + double optimal_objective; + special_lps.ThreeDLp(lp, require_model_status, optimal_objective); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsInfo& info = highs.getInfo(); + const HighsOptions& options = highs.getOptions(); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + highs.setOptionValue("solver", kPdlpString); + highs.setOptionValue("presolve", kHighsOffString); + highs.setOptionValue("primal_feasibility_tolerance", 1e-4); + highs.setOptionValue("dual_feasibility_tolerance", 1e-4); + HighsStatus run_status = highs.run(); + if (dev_run) highs.writeSolution("", 1); + REQUIRE(std::abs(info.objective_function_value - optimal_objective) < + double_equal_tolerance); + const bool not_optimal = false; + if (not_optimal) { + REQUIRE(run_status == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnknown); + } else { + REQUIRE(run_status == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + } +} + +TEST_CASE("pdlp-boxed-row-lp", "[pdlp]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {-1, -2}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, 6}; + lp.row_lower_ = {3, -4}; + lp.row_upper_ = {10, 2}; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 1, 1, -1}; + double optimal_objective = -16; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsInfo& info = highs.getInfo(); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + highs.setOptionValue("solver", kPdlpString); + highs.setOptionValue("presolve", kHighsOffString); + HighsStatus run_status = highs.run(); + if (dev_run) highs.writeSolution("", 1); + REQUIRE(std::abs(info.objective_function_value - optimal_objective) < + double_equal_tolerance); + const bool not_optimal = false; + if (not_optimal) { + REQUIRE(run_status == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnknown); + } else { + REQUIRE(run_status == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + } +} + +TEST_CASE("pdlp-infeasible-lp", "[pdlp]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_ = {-1, -2}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf}; + lp.row_upper_ = {-1}; + lp.a_matrix_.start_ = {0, 1, 2}; + lp.a_matrix_.index_ = {0, 0}; + lp.a_matrix_.value_ = {1, 1}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + highs.setOptionValue("solver", kPdlpString); + highs.setOptionValue("presolve", kHighsOffString); + REQUIRE(highs.run() == HighsStatus::kOk); + if (dev_run) highs.writeSolution("", 1); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnboundedOrInfeasible); +} + +TEST_CASE("pdlp-unbounded-lp", "[pdlp]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_ = {-1, -2}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {1}; + lp.row_upper_ = {inf}; + lp.a_matrix_.start_ = {0, 1, 2}; + lp.a_matrix_.index_ = {0, 0}; + lp.a_matrix_.value_ = {1, 1}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + highs.setOptionValue("solver", kPdlpString); + highs.setOptionValue("presolve", kHighsOffString); + REQUIRE(highs.run() == HighsStatus::kOk); + if (dev_run) highs.writeSolution("", 1); + const bool not_unbounded = false; + if (not_unbounded) { + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnboundedOrInfeasible); + } else { + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnbounded); + } +} diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index c2366d6b5e..86c5dfc469 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -5,6 +5,10 @@ const bool dev_run = false; +bool doubleEqual(const double v0, const double v1) { + return std::fabs(v0 - v1) < 1e-8; +} + void presolveSolvePostsolve(const std::string& model_file, const bool solve_relaxation = false); @@ -14,6 +18,71 @@ TEST_CASE("presolve-solve-postsolve-lp", "[highs_test_presolve]") { presolveSolvePostsolve(model_file); } +TEST_CASE("postsolve-no-basis", "[highs_test_presolve]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/afiro.mps"; + highs.readModel(model_file); + highs.run(); + const double objective_function_value = + highs.getInfo().objective_function_value; + highs.clearSolver(); + highs.presolve(); + HighsLp presolved_lp = highs.getPresolvedLp(); + Highs highs1; + highs1.setOptionValue("output_flag", dev_run); + if (dev_run) + printf("presolved_lp.integrality_.size() = %d\n", + int(presolved_lp.integrality_.size())); + presolved_lp.integrality_.clear(); + highs1.setOptionValue("presolve", kHighsOffString); + highs1.passModel(presolved_lp); + highs1.run(); + HighsSolution solution = highs1.getSolution(); + HighsStatus status; + for (HighsInt k = 0; k < 2; k++) { + if (dev_run) + printf( + "Calling highs.postsolve(solution) with solution.col_value.size() = " + "%d solution.col_dual.size() = %d\n", + int(solution.col_value.size()), int(solution.col_dual.size())); + status = highs.postsolve(solution); + if (k == 0) { + // With dual values, optimality can be identified + REQUIRE(status == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + } else { + // Without dual values, optimality can't be identified + REQUIRE(status == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnknown); + } + REQUIRE(std::fabs(highs.getInfo().objective_function_value - + objective_function_value) <= + 1e-8 * std::max(1.0, std::fabs(objective_function_value))); + // Compare the primal solution for the reduced and original problem + HighsSolution postsolve_solution = highs.getSolution(); + const HighsInt* original_col_indices = highs.getPresolveOrigColsIndex(); + // const HighsInt* original_row_indices = + // highs.getPresolveOrigRowsIndex(); + if (dev_run) + printf( + "Presolved model Original model\n" + "Col Primal Col Primal\n"); + for (HighsInt iCol = 0; iCol < presolved_lp.num_col_; iCol++) { + HighsInt original_iCol = original_col_indices[iCol]; + if (dev_run) + printf("%3d %11.5g %3d %11.5g\n", int(iCol), solution.col_value[iCol], + int(original_iCol), postsolve_solution.col_value[original_iCol]); + REQUIRE(doubleEqual(solution.col_value[iCol], + postsolve_solution.col_value[original_iCol])); + } + solution.dual_valid = false; + solution.col_dual.clear(); + solution.row_dual.clear(); + } +} + TEST_CASE("presolve-solve-postsolve-mip", "[highs_test_presolve]") { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/flugpl.mps"; @@ -79,6 +148,30 @@ TEST_CASE("presolve", "[highs_test_presolve]") { REQUIRE(presolved_model.isEmpty()); } +TEST_CASE("empty-row", "[highs_test_presolve]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.num_col_ = 3; + lp.num_row_ = 1; + lp.col_cost_ = {-7.0, -6.0, -5.0}; + lp.col_lower_ = {-73.0, -83.0, -94.0}; + lp.col_upper_ = {62.0, 96.0, 62.0}; + lp.row_lower_ = {-19.0}; + lp.row_upper_ = {11.0}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 0}; + // LP has empty constraint matrix so doesn't need to be presolved, + // and shouldn't be since this would cause vacuous null pointer + // operation in util/HighsMatrixSlice.h (see #1531) + highs.passModel(lp); + highs.run(); + const HighsSolution& solution = highs.getSolution(); + const HighsBasis& basis = highs.getBasis(); + REQUIRE(HighsInt(solution.row_value.size()) == lp.num_row_); + REQUIRE(HighsInt(basis.row_status.size()) == lp.num_row_); +} + void presolveSolvePostsolve(const std::string& model_file, const bool solve_relaxation) { Highs highs0; @@ -125,3 +218,386 @@ void presolveSolvePostsolve(const std::string& model_file, REQUIRE(highs0.getInfo().simplex_iteration_count <= 0); } } + +HighsStatus zeroCostColSing() { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + + lp.a_matrix_.start_.push_back(0); + lp.a_matrix_.start_.push_back(1); + lp.a_matrix_.start_.push_back(2); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.value_.push_back(0.5); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.value_.push_back(0.5); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.row_lower_.push_back(0.1); + lp.row_upper_.push_back(0.9); + + lp.col_cost_.push_back(0); + lp.col_cost_.push_back(1); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsStatus status = highs.passModel(lp); + assert(status == HighsStatus::kOk); + + status = highs.run(); + return status; +} + +// handled by doubleton equality +HighsStatus colSingDoubletonEquality() { + HighsLp lp; + lp.num_col_ = 4; + lp.num_row_ = 2; + + lp.a_matrix_.format_ = MatrixFormat::kColwise; + + lp.a_matrix_.start_.push_back(0); + lp.a_matrix_.start_.push_back(2); + lp.a_matrix_.start_.push_back(3); + lp.a_matrix_.start_.push_back(4); + lp.a_matrix_.start_.push_back(5); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.index_.push_back(1); + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.index_.push_back(1); + lp.a_matrix_.index_.push_back(1); + + lp.a_matrix_.value_.push_back(0.5); + lp.a_matrix_.value_.push_back(0.5); + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.value_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + lp.col_upper_.push_back(1); + + lp.row_lower_.push_back(1); + lp.row_upper_.push_back(1); + + lp.row_lower_.push_back(0); + lp.row_upper_.push_back(1); + + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(2); + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(1); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsStatus status = highs.passModel(lp); + assert(status == HighsStatus::kOk); + + status = highs.run(); + return status; +} + +HighsStatus colSingDoubletonInequality() { + HighsLp lp; + lp.num_col_ = 4; + lp.num_row_ = 2; + + lp.a_matrix_.format_ = MatrixFormat::kColwise; + + lp.a_matrix_.start_.push_back(0); + lp.a_matrix_.start_.push_back(2); + lp.a_matrix_.start_.push_back(3); + lp.a_matrix_.start_.push_back(4); + lp.a_matrix_.start_.push_back(5); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.index_.push_back(1); + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.index_.push_back(1); + lp.a_matrix_.index_.push_back(1); + + lp.a_matrix_.value_.push_back(0.5); + lp.a_matrix_.value_.push_back(0.5); + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.value_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + lp.col_upper_.push_back(1); + + lp.row_lower_.push_back(0); + lp.row_upper_.push_back(1); + + lp.row_lower_.push_back(0); + lp.row_upper_.push_back(1); + + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(2); + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(1); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsStatus status = highs.passModel(lp); + assert(status == HighsStatus::kOk); + + status = highs.run(); + return status; +} + +// handled by doubleton equality +HighsStatus twoColSingDoubletonEquality() { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + + lp.a_matrix_.start_.push_back(0); + lp.a_matrix_.start_.push_back(1); + lp.a_matrix_.start_.push_back(2); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.index_.push_back(0); + + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.value_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.row_lower_.push_back(1); + lp.row_upper_.push_back(1); + + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(2); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsStatus status = highs.passModel(lp); + assert(status == HighsStatus::kOk); + + status = highs.run(); + return status; +} + +// handled by special case. +HighsStatus twoColSingDoubletonInequality() { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + + lp.a_matrix_.start_.push_back(0); + lp.a_matrix_.start_.push_back(1); + lp.a_matrix_.start_.push_back(2); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.index_.push_back(0); + + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.value_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.col_lower_.push_back(0); + lp.col_upper_.push_back(1); + + lp.row_lower_.push_back(0); + lp.row_upper_.push_back(1); + + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(2); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsStatus status = highs.passModel(lp); + assert(status == HighsStatus::kOk); + + highs.run(); + status = highs.run(); + return status; +} + +// No commas in test case name. +TEST_CASE("zero-cost", "[presolve-col-sing]") { + if (dev_run) std::cout << "Presolve 1." << std::endl; + HighsStatus status = zeroCostColSing(); + std::string str = highsStatusToString(status); + CHECK(str == "OK"); +} + +TEST_CASE("col-sing-doubleton-eq", "[presolve-col-sing]") { + if (dev_run) std::cout << "Presolve 2." << std::endl; + HighsStatus status = colSingDoubletonEquality(); + std::string str = highsStatusToString(status); + CHECK(str == "OK"); +} + +TEST_CASE("col-sing-doubleton-ineq", "[presolve-col-sing]") { + if (dev_run) std::cout << "Presolve 3." << std::endl; + HighsStatus status = colSingDoubletonInequality(); + std::string str = highsStatusToString(status); + CHECK(str == "OK"); +} + +TEST_CASE("two-col-sing-doubleton-eq", "[presolve-col-sing]") { + if (dev_run) std::cout << "Presolve 4." << std::endl; + HighsStatus status = twoColSingDoubletonEquality(); + std::string str = highsStatusToString(status); + CHECK(str == "OK"); +} + +TEST_CASE("two-col-sing-doubleton-ineq", "[presolve-col-sing]") { + if (dev_run) std::cout << "Presolve 5." << std::endl; + HighsStatus status = twoColSingDoubletonInequality(); + std::string str = highsStatusToString(status); + REQUIRE(str == "OK"); +} + +// test case failing +HighsStatus issue425() { + HighsLp lp; + lp.num_col_ = 4; + lp.num_row_ = 4; + + lp.a_matrix_.start_.push_back(0); + lp.a_matrix_.start_.push_back(3); + lp.a_matrix_.start_.push_back(5); + lp.a_matrix_.start_.push_back(6); + lp.a_matrix_.start_.push_back(7); + + lp.a_matrix_.index_.push_back(0); + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.index_.push_back(2); + lp.a_matrix_.value_.push_back(1); + lp.a_matrix_.index_.push_back(3); + lp.a_matrix_.value_.push_back(1); + + lp.a_matrix_.index_.push_back(1); + lp.a_matrix_.value_.push_back(2); + lp.a_matrix_.index_.push_back(3); + lp.a_matrix_.value_.push_back(1); + + lp.a_matrix_.index_.push_back(3); + lp.a_matrix_.value_.push_back(1); + + lp.a_matrix_.index_.push_back(3); + lp.a_matrix_.value_.push_back(1); + + lp.col_lower_.assign(lp.num_col_, 0); + lp.col_upper_.assign(lp.num_col_, kHighsInf); + + std::vector b{1, 2, 2, 4}; + lp.row_lower_ = b; + lp.row_upper_ = b; + + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(1); + lp.col_cost_.push_back(2); + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsStatus status = highs.passModel(lp); + assert(status == HighsStatus::kOk); + + status = highs.run(); + return status; +} + +TEST_CASE("presolve-issue-425", "[highs_test_presolve]") { + if (dev_run) { + std::cout << std::endl; + std::cout << "Presolve issue 425." << std::endl; + } + HighsStatus status = issue425(); + REQUIRE(status == HighsStatus::kOk); +} + +TEST_CASE("postsolve-reduced-to-empty", "[highs_test_presolve]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + // Read MIP model "egout" + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/egout.mps"; + highs.readModel(model_file); + + // Turn into LP + std::vector vars(highs.getNumCol(), 1); + std::vector integral(highs.getNumCol(), + HighsVarType::kContinuous); + highs.changeColsIntegrality(vars.data(), integral.data()); + + // Presolve + HighsStatus presolveStatus = highs.presolve(); + REQUIRE(presolveStatus == HighsStatus::kOk); + + // Presolve reduced the problem to empty + REQUIRE(highs.getModelPresolveStatus() == + HighsPresolveStatus::kReducedToEmpty); + + // Set up empty solution + HighsSolution hsol = HighsSolution(); + hsol.value_valid = true; + hsol.dual_valid = true; + + // Postsolve solution + HighsStatus postsolveStatus = highs.postsolve(hsol); + REQUIRE(postsolveStatus == HighsStatus::kOk); + + // Postsolved solution should be feasible / optimal + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.getInfo().num_primal_infeasibilities == 0); + REQUIRE(highs.getInfo().num_dual_infeasibilities == 0); +} + +TEST_CASE("write-presolved-model", "[highs_test_presolve]") { + std::string presolved_model_file = "temp.mps"; + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/afiro.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + highs.presolve(); + highs.writePresolvedModel(presolved_model_file); + // Read and solve the presolved model using a new Highs instance + Highs highs1; + highs1.setOptionValue("output_flag", dev_run); + highs1.readModel(presolved_model_file); + highs1.run(); + // Extract the optimal solution and basis + HighsSolution solution = highs1.getSolution(); + HighsBasis basis = highs1.getBasis(); + // Perform postsolve using the optimal solution and basis for the + // presolved model + highs.postsolve(solution, basis); + // The solution should be optimal, so no solver is run, and + // simplex_iteration_count is -1 + REQUIRE(highs.getInfo().simplex_iteration_count == -1); + std::remove(presolved_model_file.c_str()); +} diff --git a/check/TestQpSolver.cpp b/check/TestQpSolver.cpp index 369138fb00..55f43f77d3 100644 --- a/check/TestQpSolver.cpp +++ b/check/TestQpSolver.cpp @@ -1,3 +1,4 @@ +#include #include #include "HCheckConfig.h" @@ -219,12 +220,12 @@ TEST_CASE("test-qod", "[qpsolver]") { highs.addCol(-1, -inf, inf, 0, NULL, NULL); if (dev_run) highs.writeModel(""); - // Cannot solve the model until the Hessian has been replaced + // Can solve the model before the Hessian has been replaced return_status = highs.run(); - REQUIRE(return_status == HighsStatus::kError); + REQUIRE(return_status == HighsStatus::kOk); model_status = highs.getModelStatus(); - REQUIRE(model_status == HighsModelStatus::kModelError); + REQUIRE(model_status == HighsModelStatus::kUnbounded); // Pass the new Hessian hessian.dim_ = 2; @@ -600,3 +601,414 @@ TEST_CASE("test-semi-definite2", "[qpsolver]") { REQUIRE(fabs(solution.col_value[0] + 1) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - 2) < double_equal_tolerance); } + +void hessianProduct(const HighsHessian& hessian, const std::vector& arg, + std::vector& result) { + HighsInt dim = hessian.dim_; + assert(HighsInt(arg.size()) == dim); + result.resize(dim); + for (HighsInt iCol = 0; iCol < dim; iCol++) { + double sum = 0; + for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1]; + iEl++) + sum += hessian.value_[iEl] * arg[hessian.index_[iEl]]; + result[iCol] = sum; + } +} + +TEST_CASE("test-qp-modification", "[qpsolver]") { + // HighsStatus return_status; + // HighsModelStatus model_status; + // double required_objective_function_value; + + HighsModel model; + + HighsLp& lp = model.lp_; + HighsHessian& hessian = model.hessian_; + + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_ = {1.0, -1.0}; + lp.col_lower_ = {-inf, -inf}; + lp.col_upper_ = {inf, inf}; + lp.sense_ = ObjSense::kMinimize; + lp.offset_ = 0; + lp.row_lower_ = {-inf}; + lp.row_upper_ = {1}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2}; + lp.a_matrix_.index_ = {0, 1}; + lp.a_matrix_.value_ = {1.0, 1.0}; + hessian.dim_ = 1; + hessian.start_ = {0, 1}; + hessian.value_ = {1.0}; + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsModel& incumbent_model = highs.getModel(); + // Cannot have Hessian with index exceeding hessian.dim_-1 + hessian.index_ = {1}; + REQUIRE(highs.passModel(model) == HighsStatus::kError); + + // Correct the Hessian index + hessian.index_[0] = 0; + REQUIRE(highs.passModel(model) == HighsStatus::kOk); + if (dev_run) { + printf("\nNow solve the QP\n\n"); + incumbent_model.hessian_.print(); + } + highs.run(); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); + // Add a new variables and ensure that the Hessian dimension is correct + std::vector index = {0}; + std::vector value = {1}; + REQUIRE(highs.addCol(-1, 0, 1, 1, index.data(), value.data()) == + HighsStatus::kOk); + REQUIRE((incumbent_model.hessian_.dim_ == 0 || + incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); + if (dev_run) { + printf("\nNow solve the QP after adding new variable\n\n"); + incumbent_model.hessian_.print(); + } + highs.run(); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); + + HighsInt dim = incumbent_model.lp_.num_col_; + std::vector arg0; + std::vector arg1; + std::vector result0; + std::vector result1; + arg0.resize(dim); + HighsRandom random; + for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); + HighsHessian hessian0 = incumbent_model.hessian_; + arg1 = arg0; + + // Deleting column 1 removes no nonzeros from the Hessian + HighsInt delete_col = 1; + REQUIRE(highs.deleteCols(delete_col, delete_col) == HighsStatus::kOk); + REQUIRE((incumbent_model.hessian_.dim_ == 0 || + incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); + if (dev_run) { + printf("\nNow solve the QP after deleting column 1\n\n"); + incumbent_model.hessian_.print(); + } + highs.run(); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); + + dim--; + for (HighsInt iCol = delete_col; iCol < dim; iCol++) + arg1[iCol] = arg1[iCol + 1]; + arg0[delete_col] = 0; + hessianProduct(hessian0, arg0, result0); + for (HighsInt iCol = delete_col; iCol < dim; iCol++) + result0[iCol] = result0[iCol + 1]; + + arg1.resize(dim); + hessianProduct(incumbent_model.hessian_, arg1, result1); + for (HighsInt iCol = 0; iCol < dim; iCol++) + REQUIRE(result0[iCol] == result1[iCol]); + + // Deleting column 0 removes only nonzero from the Hessian, so problem is an + // LP + delete_col = 0; + REQUIRE(highs.deleteCols(delete_col, delete_col) == HighsStatus::kOk); + REQUIRE((incumbent_model.hessian_.dim_ == 0 || + incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); + if (dev_run) { + printf("\nNow solve the LP after deleting column 0\n\n"); + incumbent_model.hessian_.print(); + } + highs.run(); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); +} + +TEST_CASE("test-qp-delete-col", "[qpsolver]") { + HighsModel model; + HighsLp& lp = model.lp_; + HighsHessian& hessian = model.hessian_; + + lp.num_col_ = 5; + lp.num_row_ = 1; + lp.col_cost_ = {-2, -1, 0, 1, 2}; + lp.col_lower_ = {0, 0, 0, 0, 0}; + lp.col_upper_ = {inf, inf, inf, inf, inf}; + lp.sense_ = ObjSense::kMinimize; + lp.offset_ = 0; + lp.row_lower_ = {-inf}; + lp.row_upper_ = {1}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 5}; + lp.a_matrix_.index_ = {0, 1, 2, 3, 4}; + lp.a_matrix_.value_ = {1, 1, 1, 1, 1}; + hessian.dim_ = 5; + hessian.start_ = {0, 4, 7, 10, 11, 12}; + hessian.index_ = {0, 1, 3, 4, 1, 2, 4, 2, 3, 4, 3, 4}; + hessian.value_ = {11, 21, 41, 51, 22, 32, 52, 33, 43, 53, 44, 55}; + + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsModel& incumbent_model = highs.getModel(); + REQUIRE(highs.passModel(model) == HighsStatus::kOk); + if (dev_run) incumbent_model.hessian_.print(); + + HighsInt dim = incumbent_model.lp_.num_col_; + std::vector arg0; + std::vector arg1; + std::vector result0; + std::vector result1; + arg0.resize(dim); + HighsRandom random; + for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); + HighsHessian hessian0 = incumbent_model.hessian_; + arg1 = arg0; + + std::vector set = {1, 3}; + REQUIRE(highs.deleteCols(2, set.data()) == HighsStatus::kOk); + if (dev_run) incumbent_model.hessian_.print(); + + arg1[1] = arg1[2]; + arg1[2] = arg1[4]; + arg0[1] = 0; + arg0[3] = 0; + hessianProduct(hessian0, arg0, result0); + result0[1] = result0[2]; + result0[2] = result0[4]; + + dim = 3; + arg1.resize(dim); + hessianProduct(incumbent_model.hessian_, arg1, result1); + for (HighsInt iCol = 0; iCol < dim; iCol++) + REQUIRE(result0[iCol] == result1[iCol]); + + dim = 100; + lp.clear(); + lp.num_col_ = dim; + lp.num_row_ = 1; + + lp.col_cost_.resize(dim); + lp.col_lower_.resize(dim); + lp.col_upper_.resize(dim); + lp.a_matrix_.index_.resize(dim); + lp.a_matrix_.value_.resize(dim); + + for (HighsInt iCol = 0; iCol < dim; iCol++) { + lp.col_cost_[iCol] = double(iCol); + lp.col_lower_[iCol] = 0; + lp.col_upper_[iCol] = inf; + lp.a_matrix_.index_[iCol] = iCol; + lp.a_matrix_.value_[iCol] = 1; + } + lp.a_matrix_.start_.push_back(dim); + + lp.sense_ = ObjSense::kMinimize; + lp.offset_ = 0; + lp.row_lower_ = {-inf}; + lp.row_upper_ = {1}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + + hessian.clear(); + hessian.dim_ = dim; + std::vector hessian_col(dim); + for (HighsInt iCol = 0; iCol < dim; iCol++) { + hessian_col.assign(dim, 0); + HighsInt kmax = std::max(HighsInt(1), (dim - iCol) / 3); + hessian_col[iCol] = dim * iCol + iCol; + if (iCol < dim - 1) { + for (HighsInt k = 0; k < kmax; k++) { + HighsInt iRow = iCol + 1 + random.integer(dim - iCol - 1); + assert(iRow >= 0); + assert(iRow < dim); + hessian_col[iRow] = dim * iCol + iRow; + } + } + for (HighsInt iRow = iCol; iRow < dim; iRow++) { + if (hessian_col[iRow]) { + hessian.index_.push_back(iRow); + hessian.value_.push_back(hessian_col[iRow]); + } + } + hessian.start_.push_back(HighsInt(hessian.index_.size())); + } + + REQUIRE(highs.passModel(model) == HighsStatus::kOk); + if (dev_run && dim < 20) incumbent_model.hessian_.print(); + + arg0.resize(dim); + for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); + hessian0 = incumbent_model.hessian_; + arg1 = arg0; + + std::vector mask; + mask.assign(dim, 0); + + HighsInt kmax = std::max(HighsInt(1), dim / 3); + for (HighsInt k = 0; k < kmax; k++) { + HighsInt iRow = random.integer(dim); + assert(iRow >= 0); + assert(iRow < dim); + mask[iRow] = 1; + } + highs.deleteCols(mask.data()); + if (dev_run && dim < 20) incumbent_model.hessian_.print(); + + for (HighsInt iCol = 0; iCol < dim; iCol++) { + HighsInt iRow = mask[iCol]; + if (iRow < 0) { + arg0[iCol] = 0; + } else { + arg1[iRow] = arg1[iCol]; + } + } + hessianProduct(hessian0, arg0, result0); + for (HighsInt iCol = 0; iCol < dim; iCol++) { + HighsInt iRow = mask[iCol]; + if (iRow >= 0) result0[iRow] = result0[iCol]; + } + dim = incumbent_model.hessian_.dim_; + arg1.resize(dim); + hessianProduct(incumbent_model.hessian_, arg1, result1); + + for (HighsInt iCol = 0; iCol < dim; iCol++) { + REQUIRE(result0[iCol] == result1[iCol]); + } +} + +TEST_CASE("test-qp-hot-start", "[qpsolver]") { + // Test hot start + HighsStatus return_status; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsInfo& info = highs.getInfo(); + + for (HighsInt k = 0; k < 2; k++) { + if (dev_run) + printf( + "\n" + "===================\n" + "Hot start test %d\n" + "===================\n", + int(k)); + if (k == 1) { + const std::string filename = + std::string(HIGHS_DIR) + "/check/instances/primal1.mps"; + REQUIRE(highs.readModel(filename) == HighsStatus::kOk); + } else if (k == 2) { + const std::string filename = + std::string(HIGHS_DIR) + "/check/instances/qptestnw.lp"; + REQUIRE(highs.readModel(filename) == HighsStatus::kOk); + } else { + HighsModel model; + model.lp_.num_col_ = 2; + model.lp_.num_row_ = 1; + model.lp_.col_cost_ = {-2, -2}; + model.lp_.col_lower_ = {-inf, -inf}; + model.lp_.col_upper_ = {inf, inf}; + model.lp_.row_lower_ = {1}; + model.lp_.row_upper_ = {inf}; + model.lp_.a_matrix_.format_ = MatrixFormat::kRowwise; + model.lp_.a_matrix_.start_ = {0, 2}; + model.lp_.a_matrix_.index_ = {0, 1}; + model.lp_.a_matrix_.value_ = {1, 1}; + model.hessian_.dim_ = 2; + model.hessian_.start_ = {0, 1, 2}; + model.hessian_.index_ = {0, 1}; + model.hessian_.value_ = {2, 2}; + REQUIRE(highs.passModel(model) == HighsStatus::kOk); + } + return_status = highs.run(); + REQUIRE(return_status == HighsStatus::kOk); + + if (dev_run) highs.writeSolution("", 1); + + HighsBasis basis = highs.getBasis(); + HighsSolution solution = highs.getSolution(); + if (dev_run) printf("Saved basis has validity = %d\n", basis.valid); + + if (dev_run) + printf( + "================\n" + "Hot start re-run\n" + "================\n"); + return_status = highs.run(); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(info.qp_iteration_count == 0); + + if (dev_run) + printf( + "===========================\n" + "Hot start using saved basis\n" + "===========================\n"); + highs.setBasis(basis); + return_status = highs.run(); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(info.qp_iteration_count == 0); + + // QP Hot start needs a saved solution as well as a basis after + // clearSolver() + if (dev_run) + printf( + "==============================================================\n" + "Hot start using saved basis and solution after clearing solver\n" + "==============================================================\n"); + highs.clearSolver(); + highs.setSolution(solution); + highs.setBasis(basis); + return_status = highs.run(); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(info.qp_iteration_count == 0); + /* + if (dev_run) + printf("=================================================\n" + "Hot start using saved basis after clearing solver\n" + "=================================================\n"); + highs.clearSolver(); + highs.setBasis(basis); + return_status = highs.run(); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(info.qp_iteration_count == 0); + */ + // QP Hot start needs a saved solution as well as a basis after + // clearSolver() + if (dev_run) + printf( + "==============================================================\n" + "Hot start using alien basis and solution after clearing solver\n" + "==============================================================\n"); + highs.clearSolver(); + highs.setSolution(solution); + basis.alien = true; + highs.setBasis(basis); + return_status = highs.run(); + REQUIRE(return_status == HighsStatus::kOk); + REQUIRE(info.qp_iteration_count == 0); + } +} + +TEST_CASE("test-qp-terminations", "[qpsolver]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + const HighsInfo& info = highs.getInfo(); + std::string filename = + std::string(HIGHS_DIR) + "/check/instances/qptestnw.lp"; + REQUIRE(highs.readModel(filename) == HighsStatus::kOk); + + highs.setOptionValue("qp_iteration_limit", 1); + REQUIRE(highs.run() == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kIterationLimit); + highs.clearSolver(); + highs.setOptionValue("qp_iteration_limit", kHighsIInf); + + highs.setOptionValue("time_limit", 0); + REQUIRE(highs.run() == HighsStatus::kWarning); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kTimeLimit); + highs.setOptionValue("time_limit", kHighsInf); + + filename = std::string(HIGHS_DIR) + "/check/instances/primal1.mps"; + REQUIRE(highs.readModel(filename) == HighsStatus::kOk); + + highs.setOptionValue("qp_nullspace_limit", 1); + REQUIRE(highs.run() == HighsStatus::kError); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kSolveError); + highs.setOptionValue("qp_nullspace_limit", 4000); +} diff --git a/check/TestRanging.cpp b/check/TestRanging.cpp index bb5209c6f4..1f6494247a 100644 --- a/check/TestRanging.cpp +++ b/check/TestRanging.cpp @@ -101,7 +101,7 @@ void assessNewBounds(double& lower, double& upper) { bool modelStatusOk(Highs& highs) { if (highs.getModelStatus() == HighsModelStatus::kOptimal) return true; - if (highs.getModelStatus(true) == HighsModelStatus::kOptimal) return true; + // if (highs.getModelStatus(true) == HighsModelStatus::kOptimal) return true; return false; } diff --git a/check/TestRays.cpp b/check/TestRays.cpp index 018c2e425d..0b9f6ecfbf 100644 --- a/check/TestRays.cpp +++ b/check/TestRays.cpp @@ -534,3 +534,40 @@ TEST_CASE("Rays-464b", "[highs_test_rays]") { REQUIRE(ray_value[0] == ray_value[1]); REQUIRE(ray_value[0] > 0); } + +/* +TEST_CASE("Rays-infeasible-qp", "[highs_test_rays]") { + HighsModel model; + HighsLp& lp = model.lp_; + HighsHessian& hessian = model.hessian_; + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-1}; + lp.row_upper_ = {-1}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2}; + lp.a_matrix_.index_ = {0, 1}; + lp.a_matrix_.value_ = {1, 1}; + hessian.dim_ = 2; + hessian.start_ = {0, 1, 2}; + hessian.index_ = {0, 1}; + hessian.value_ = {1, 1}; + Highs highs; + //highs.setOptionValue("output_flag", dev_run); + REQUIRE(highs.passModel(model) == HighsStatus::kOk); + highs.run(); + // if (dev_run) + printf("Solved infeasible QP: status = %s\n", + highs.modelStatusToString(highs.getModelStatus()).c_str()); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + bool has_ray = false; + REQUIRE(highs.getDualRay(has_ray) == HighsStatus::kOk); + REQUIRE(has_ray == true); + std::vector ray_value; + ray_value.assign(2, NAN); + highs.getDualRay(has_ray, ray_value.data()); +} +*/ diff --git a/check/TestSpecialLps.cpp b/check/TestSpecialLps.cpp index 04082d4c02..66d99ed304 100644 --- a/check/TestSpecialLps.cpp +++ b/check/TestSpecialLps.cpp @@ -605,7 +605,10 @@ void unconstrained(Highs& highs) { lp.a_matrix_.start_ = {0, 0, 0}; lp.a_matrix_.format_ = MatrixFormat::kColwise; REQUIRE(highs.passModel(lp) == HighsStatus::kOk); - REQUIRE(highs.setOptionValue("presolve", "off") == HighsStatus::kOk); + // No need to turn off presolve, since unconstrained LPs are + // automatically solved directly + // + // REQUIRE(highs.setOptionValue("presolve", "off") == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); REQUIRE(highs.getObjectiveValue() == 1); diff --git a/check/TestTspSolver.cpp b/check/TestTspSolver.cpp new file mode 100644 index 0000000000..b4ad42fbda --- /dev/null +++ b/check/TestTspSolver.cpp @@ -0,0 +1,17 @@ +#include "HCheckConfig.h" +#include "Highs.h" +#include "catch.hpp" + +const bool dev_run = false; +const double double_equal_tolerance = 1e-5; + +TEST_CASE("tsp-p01", "[highs_test_tsp_solver]") { + std::string filename; + filename = std::string(HIGHS_DIR) + "/check/instances/p01.mps"; + const double optimal_obective_value = 263; + Highs highs; + if (!dev_run) highs.setOptionValue("output_flag", false); + highs.readModel(filename); + highs.run(); + REQUIRE(highs.getObjectiveValue() == optimal_obective_value); +} diff --git a/check/TestUserScale.cpp b/check/TestUserScale.cpp new file mode 100644 index 0000000000..96c2c76870 --- /dev/null +++ b/check/TestUserScale.cpp @@ -0,0 +1,250 @@ +#include + +#include "Highs.h" +#include "catch.hpp" + +const bool dev_run = false; +const double inf = kHighsInf; + +void checkModelScaling(const HighsInt user_bound_scale, + const HighsInt user_cost_scale, + const HighsModel& unscaled_model, + const HighsModel& scaled_model); + +void checkLpScaling(const HighsInt user_bound_scale, + const HighsInt user_cost_scale, const HighsLp& unscaled_lp, + const HighsLp& scaled_lp); + +void checkSolutionScaling(const HighsInt user_bound_scale, + const HighsInt user_cost_scale, + const HighsSolution& unscaled_solution, + const HighsSolution& scaled_solution); + +TEST_CASE("user-cost-scale-after-run", "[highs_user_scale]") { + std::string filename = + std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; + Highs highs; + const HighsInfo& info = highs.getInfo(); + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + highs.run(); + HighsInfo unscaled_info = info; + HighsSolution unscaled_solution = highs.getSolution(); + HighsLp unscaled_lp = highs.getLp(); + double max_primal_infeasibility = info.max_primal_infeasibility; + double max_dual_infeasibility = info.max_dual_infeasibility; + double sum_dual_infeasibilities = info.sum_dual_infeasibilities; + double objective_function_value = info.objective_function_value; + + HighsInt user_bound_scale = 10; + double user_bound_scale_value = std::pow(2, user_bound_scale); + highs.setOptionValue("user_bound_scale", user_bound_scale); + + HighsInt user_cost_scale = 30; + double user_cost_scale_value = std::pow(2, user_cost_scale); + highs.setOptionValue("user_cost_scale", user_cost_scale); + + HighsLp scaled_lp = highs.getLp(); + HighsSolution scaled_solution = highs.getSolution(); + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + checkSolutionScaling(user_bound_scale, user_cost_scale, unscaled_solution, + scaled_solution); + + REQUIRE(highs.getModelStatus() == HighsModelStatus::kNotset); + REQUIRE(info.dual_solution_status == kSolutionStatusInfeasible); + REQUIRE(info.objective_function_value == user_cost_scale_value * + user_bound_scale_value * + objective_function_value); + REQUIRE(info.num_dual_infeasibilities == kHighsIllegalInfeasibilityCount); + REQUIRE(info.max_dual_infeasibility == + user_cost_scale_value * max_dual_infeasibility); + REQUIRE(info.sum_dual_infeasibilities == + user_cost_scale_value * sum_dual_infeasibilities); +} + +TEST_CASE("user-cost-scale-after-load", "[highs_user_scale]") { + std::string filename = + std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; + Highs highs; + const HighsInfo& info = highs.getInfo(); + highs.setOptionValue("output_flag", dev_run); + + highs.readModel(filename); + HighsLp unscaled_lp = highs.getLp(); + + HighsInt user_bound_scale = 10; + double user_bound_scale_value = std::pow(2, user_bound_scale); + highs.setOptionValue("user_bound_scale", user_bound_scale); + + HighsInt user_cost_scale = 30; + double user_cost_scale_value = std::pow(2, user_cost_scale); + highs.setOptionValue("user_cost_scale", user_cost_scale); + + highs.readModel(filename); + HighsLp scaled_lp = highs.getLp(); + + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + // checkSolutionScaling(user_bound_scale, user_cost_scale, unscaled_solution, + // scaled_solution); + highs.run(); +} + +TEST_CASE("user-small-cost-scale", "[highs_user_scale]") { + Highs highs; + const HighsInfo& info = highs.getInfo(); + const HighsSolution& solution = highs.getSolution(); + highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("presolve", kHighsOffString); + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {10, 25}; + lp.sense_ = ObjSense::kMaximize; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf, -inf}; + lp.row_upper_ = {80, 120}; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 1, 2, 4}; + highs.passModel(lp); + highs.run(); + REQUIRE(solution.col_value[0] == 40); + REQUIRE(solution.col_value[1] == 20); + + highs.setOptionValue("user_cost_scale", -30); + highs.clearSolver(); + highs.run(); + if (dev_run) highs.writeSolution("", 1); + REQUIRE(solution.col_value[0] == 0); + REQUIRE(solution.col_value[1] == 0); + + highs.setOptionValue("user_cost_scale", 0); + + highs.run(); + REQUIRE(solution.col_value[0] == 40); + REQUIRE(solution.col_value[1] == 20); +} + +TEST_CASE("user-cost-scale-in-build", "[highs_user_scale]") { + Highs unscaled_highs; + Highs scaled_highs; + unscaled_highs.setOptionValue("output_flag", dev_run); + scaled_highs.setOptionValue("output_flag", dev_run); + const HighsLp& unscaled_lp = unscaled_highs.getLp(); + const HighsLp& scaled_lp = scaled_highs.getLp(); + const HighsInfo& info = scaled_highs.getInfo(); + const HighsSolution& solution = scaled_highs.getSolution(); + const HighsInt user_cost_scale = -30; + const HighsInt user_bound_scale = 10; + const double unscaled_col0_cost = 1e14; + unscaled_highs.addVar(0, inf); + scaled_highs.addVar(0, inf); + unscaled_highs.changeColCost(0, unscaled_col0_cost); + scaled_highs.changeColCost(0, unscaled_col0_cost); + + scaled_highs.setOptionValue("user_cost_scale", user_cost_scale); + scaled_highs.setOptionValue("user_bound_scale", user_bound_scale); + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + + const double unscaled_col1_cost = 1e12; + unscaled_highs.addVar(1, inf); + scaled_highs.addVar(1, inf); + unscaled_highs.changeColCost(1, unscaled_col1_cost); + scaled_highs.changeColCost(1, unscaled_col1_cost); + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + + std::vector index = {0, 1}; + std::vector value0 = {1, 2}; + std::vector value1 = {1, 4}; + unscaled_highs.addRow(-inf, 120, 2, index.data(), value0.data()); + scaled_highs.addRow(-inf, 120, 2, index.data(), value0.data()); + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + + unscaled_highs.addRow(-inf, 150, 2, index.data(), value1.data()); + scaled_highs.addRow(-inf, 150, 2, index.data(), value1.data()); + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + + std::vector cost = {0, 10}; + std::vector lower = {2, 4}; + std::vector upper = {inf, inf}; + std::vector matrix_start = {0, 2}; + std::vector matrix_index = {0, 1, 0, 1}; + std::vector matrix_value = {1, 1, 2, 4}; + unscaled_highs.addCols(2, cost.data(), lower.data(), upper.data(), 4, + matrix_start.data(), matrix_index.data(), + matrix_value.data()); + scaled_highs.addCols(2, cost.data(), lower.data(), upper.data(), 4, + matrix_start.data(), matrix_index.data(), + matrix_value.data()); + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); + + lower = {-inf, 0}; + upper = {120, 150}; + matrix_start = {0, 2}; + matrix_index = {0, 2, 1, 3}; + matrix_value = {1, 1, 2, 4}; + unscaled_highs.addRows(2, lower.data(), upper.data(), 4, matrix_start.data(), + matrix_index.data(), matrix_value.data()); + scaled_highs.addRows(2, lower.data(), upper.data(), 4, matrix_start.data(), + matrix_index.data(), matrix_value.data()); + + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_lp, scaled_lp); +} + +void checkModelScaling(const HighsInt user_bound_scale, + const HighsInt user_cost_scale, + const HighsModel& unscaled_model, + const HighsModel& scaled_model) { + checkLpScaling(user_bound_scale, user_cost_scale, unscaled_model.lp_, + scaled_model.lp_); +} + +void checkLpScaling(const HighsInt user_bound_scale, + const HighsInt user_cost_scale, const HighsLp& unscaled_lp, + const HighsLp& scaled_lp) { + const double user_bound_scale_value = std::pow(2, user_bound_scale); + const double user_cost_scale_value = std::pow(2, user_cost_scale); + REQUIRE(unscaled_lp.num_col_ == scaled_lp.num_col_); + REQUIRE(unscaled_lp.num_row_ == scaled_lp.num_row_); + for (HighsInt iCol = 0; iCol < unscaled_lp.num_col_; iCol++) { + REQUIRE(scaled_lp.col_cost_[iCol] == + unscaled_lp.col_cost_[iCol] * user_cost_scale_value); + if (unscaled_lp.col_lower_[iCol] > -inf) + REQUIRE(scaled_lp.col_lower_[iCol] == + unscaled_lp.col_lower_[iCol] * user_bound_scale_value); + if (unscaled_lp.col_upper_[iCol] < inf) + REQUIRE(scaled_lp.col_upper_[iCol] == + unscaled_lp.col_upper_[iCol] * user_bound_scale_value); + } + for (HighsInt iRow = 0; iRow < unscaled_lp.num_row_; iRow++) { + if (unscaled_lp.row_lower_[iRow] > -inf) + REQUIRE(scaled_lp.row_lower_[iRow] == + unscaled_lp.row_lower_[iRow] * user_bound_scale_value); + if (unscaled_lp.row_upper_[iRow] < inf) + REQUIRE(scaled_lp.row_upper_[iRow] == + unscaled_lp.row_upper_[iRow] * user_bound_scale_value); + } +} + +void checkSolutionScaling(const HighsInt user_bound_scale, + const HighsInt user_cost_scale, + const HighsSolution& unscaled_solution, + const HighsSolution& scaled_solution) { + const double user_bound_scale_value = std::pow(2, user_bound_scale); + const double user_cost_scale_value = std::pow(2, user_cost_scale); + for (HighsInt iCol = 0; iCol < HighsInt(unscaled_solution.col_value.size()); + iCol++) { + REQUIRE(scaled_solution.col_value[iCol] == + unscaled_solution.col_value[iCol] * user_bound_scale_value); + REQUIRE(scaled_solution.col_dual[iCol] == + unscaled_solution.col_dual[iCol] * user_cost_scale_value); + } + for (HighsInt iRow = 0; iRow < HighsInt(unscaled_solution.row_value.size()); + iRow++) { + REQUIRE(scaled_solution.row_value[iRow] == + unscaled_solution.row_value[iRow] * user_bound_scale_value); + REQUIRE(scaled_solution.row_dual[iRow] == + unscaled_solution.row_dual[iRow] * user_cost_scale_value); + } +} diff --git a/check/doctest/TestGas11.cpp b/check/doctest/TestGas11.cpp deleted file mode 100644 index 075a30e0a0..0000000000 --- a/check/doctest/TestGas11.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include "Highs.h" - -void solve(Highs& highs, std::string presolve, std::string solver, - const HighsModelStatus require_model_status, - const double require_optimal_objective = 0) { - const HighsInfo& info = highs.getHighsInfo(); - - REQUIRE(highs.setOptionValue("solver", solver) == HighsStatus::kOk); - - REQUIRE(highs.setOptionValue("presolve", presolve) == HighsStatus::kOk); - - REQUIRE(highs.setBasis() == HighsStatus::kOk); - - REQUIRE(highs.run() == HighsStatus::kOk); - - REQUIRE(highs.getModelStatus() == require_model_status); - - if (require_model_status == HighsModelStatus::kOptimal) { - // function not defined but not needed since Gas11 is infeasible. - // REQUIRE( - // objectiveOk(info.objective_function_value, - // require_optimal_objective)); - } - - REQUIRE(highs.resetOptions() == HighsStatus::kOk); -} - -void mpsGas11(Highs& highs) { - // Lots of trouble is caused by gas11 - const HighsModelStatus require_model_status = - HighsModelStatus::kUnbounded; - - std::string model = "gas11"; - std::string model_file; - model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; - REQUIRE(highs.readModel(model_file) == HighsStatus::kWarning); - - solve(highs, "on", "simplex", require_model_status); - solve(highs, "off", "simplex", require_model_status); - solve(highs, "on", "ipm", require_model_status); - solve(highs, "off", "ipm", require_model_status); -} - -TEST_CASE("LP-gas11") { - std::cout << std::endl; - std::cout << "LP-gas11" << std::endl; - Highs highs; - mpsGas11(highs); -} diff --git a/check/doctest/TestPresolveColumnSingletons.cpp b/check/doctest/TestPresolveColumnSingletons.cpp deleted file mode 100644 index 59a618fee7..0000000000 --- a/check/doctest/TestPresolveColumnSingletons.cpp +++ /dev/null @@ -1,277 +0,0 @@ -#include -#include "Highs.h" - -HighsInt factorial(HighsInt number) { return number <= 1 ? number : factorial(number - 1) * number; } - -TEST_CASE("testing the factorial function") { - CHECK(factorial(1) == 1); - CHECK(factorial(2) == 2); - CHECK(factorial(3) == 6); - CHECK(factorial(10) == 3628800); -} - -// New tests, for each of the special cases. -// - col sing doubleton equality -// - col sing doubleton inequality -// - test zero cost col sing - -// test zero cost col sing -HighsStatus zeroCostColSing() { - HighsLp lp; - lp.num_col_ = 2; - lp.num_row_ = 1; - - lp.a_matrix_.start_.push_back(0); - lp.a_matrix_.start_.push_back(1); - lp.a_matrix_.start_.push_back(2); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.value_.push_back(0.5); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.value_.push_back(0.5); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.row_lower_.push_back(0.1); - lp.row_upper_.push_back(0.9); - - lp.col_cost_.push_back(0); - lp.col_cost_.push_back(1); - - Highs highs; - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); - - status = highs.run(); - return status; -} - -// handled by doubleton equality -HighsStatus colSingDoubletonEquality() -{ - HighsLp lp; - lp.num_col_ = 4; - lp.num_row_ = 2; - - lp.a_matrix_.start_.push_back(0); - lp.a_matrix_.start_.push_back(2); - lp.a_matrix_.start_.push_back(3); - lp.a_matrix_.start_.push_back(4); - lp.a_matrix_.start_.push_back(5); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.index_.push_back(1); - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.index_.push_back(1); - lp.a_matrix_.index_.push_back(1); - - lp.a_matrix_.value_.push_back(0.5); - lp.a_matrix_.value_.push_back(0.5); - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.value_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - lp.col_upper_.push_back(1); - - lp.row_lower_.push_back(1); - lp.row_upper_.push_back(1); - - lp.row_lower_.push_back(0); - lp.row_upper_.push_back(1); - - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(2); - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(1); - - lp.format_ = MatrixFormat::kColwise; - - Highs highs; - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); - - status = highs.run(); - return status; -} - -HighsStatus colSingDoubletonInequality() -{ - HighsLp lp; - lp.num_col_ = 4; - lp.num_row_ = 2; - - lp.a_matrix_.start_.push_back(0); - lp.a_matrix_.start_.push_back(2); - lp.a_matrix_.start_.push_back(3); - lp.a_matrix_.start_.push_back(4); - lp.a_matrix_.start_.push_back(5); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.index_.push_back(1); - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.index_.push_back(1); - lp.a_matrix_.index_.push_back(1); - - lp.a_matrix_.value_.push_back(0.5); - lp.a_matrix_.value_.push_back(0.5); - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.value_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - lp.col_upper_.push_back(1); - - lp.row_lower_.push_back(0); - lp.row_upper_.push_back(1); - - lp.row_lower_.push_back(0); - lp.row_upper_.push_back(1); - - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(2); - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(1); - - lp.format_ = MatrixFormat::kColwise; - - Highs highs; - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); - - status = highs.run(); - return status; -} - -// handled by doubleton equality -HighsStatus twoColSingDoubletonEquality() -{ - HighsLp lp; - lp.num_col_ = 2; - lp.num_row_ = 1; - - lp.a_matrix_.start_.push_back(0); - lp.a_matrix_.start_.push_back(1); - lp.a_matrix_.start_.push_back(2); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.index_.push_back(0); - - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.value_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.row_lower_.push_back(1); - lp.row_upper_.push_back(1); - - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(2); - - Highs highs; - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); - - status = highs.run(); - return status; -} - -// handled by special case. -HighsStatus twoColSingDoubletonInequality() -{ - HighsLp lp; - lp.num_col_ = 2; - lp.num_row_ = 1; - - lp.a_matrix_.start_.push_back(0); - lp.a_matrix_.start_.push_back(1); - lp.a_matrix_.start_.push_back(2); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.index_.push_back(0); - - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.value_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.col_lower_.push_back(0); - lp.col_upper_.push_back(1); - - lp.row_lower_.push_back(0); - lp.row_upper_.push_back(1); - - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(2); - - Highs highs; - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); - - highs.run(); - status = highs.run(); - return status; -} - -// No commas in test case name. -TEST_CASE("zero-cost [presolve-col-sing]") { - std::cout << "Presolve 1." << std::endl; - HighsStatus status = zeroCostColSing(); - std::string str = highsStatusToString(status); - CHECK(str == "OK"); -} - -TEST_CASE("col-sing-doubleton-eq [presolve-col-sing]") { - std::cout << "Presolve 2." << std::endl; - HighsStatus status = colSingDoubletonEquality(); - std::string str = highsStatusToString(status); - CHECK(str == "OK"); -} - -TEST_CASE("col-sing-doubleton-ineq [presolve-col-sing]") { - std::cout << "Presolve 3." << std::endl; - HighsStatus status = colSingDoubletonInequality(); - std::string str = highsStatusToString(status); - CHECK(str == "OK"); -} - -TEST_CASE("two-col-sing-doubleton-eq [presolve-col-sing]") { - std::cout << "Presolve 4." << std::endl; - HighsStatus status = twoColSingDoubletonEquality(); - std::string str = highsStatusToString(status); - CHECK(str == "OK"); -} - -TEST_CASE("two-col-sing-doubleton-ineq [presolve-col-sing]") { - std::cout << "Presolve 5." << std::endl; - HighsStatus status = twoColSingDoubletonInequality(); - std::string str = highsStatusToString(status); - REQUIRE(str == "OK"); -} - diff --git a/check/doctest/TestPresolveIssue.cpp b/check/doctest/TestPresolveIssue.cpp deleted file mode 100644 index 985573827e..0000000000 --- a/check/doctest/TestPresolveIssue.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include -#include "Highs.h" - -// test case failing -HighsStatus issue425() { - HighsLp lp; - lp.num_col_ = 4; - lp.num_row_ = 4; - - lp.a_matrix_.start_.push_back(0); - lp.a_matrix_.start_.push_back(3); - lp.a_matrix_.start_.push_back(5); - lp.a_matrix_.start_.push_back(6); - lp.a_matrix_.start_.push_back(7); - - lp.a_matrix_.index_.push_back(0); - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.index_.push_back(2); - lp.a_matrix_.value_.push_back(1); - lp.a_matrix_.index_.push_back(3); - lp.a_matrix_.value_.push_back(1); - - lp.a_matrix_.index_.push_back(1); - lp.a_matrix_.value_.push_back(2); - lp.a_matrix_.index_.push_back(3); - lp.a_matrix_.value_.push_back(1); - - lp.a_matrix_.index_.push_back(3); - lp.a_matrix_.value_.push_back(1); - - lp.a_matrix_.index_.push_back(3); - lp.a_matrix_.value_.push_back(1); - - lp.col_lower_.assign(lp.num_col_, 0); - lp.col_upper_.assign(lp.num_col_, kHighsInf); - - std::vector b{1, 2, 2, 4}; - lp.row_lower_ = b; - lp.row_upper_ = b; - - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(1); - lp.col_cost_.push_back(2); - - Highs highs; - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); - - status = highs.run(); - return status; -} - - -TEST_CASE("presolve-issue-425") { - std::cout << std::endl; - std::cout << "Presolve issue 425." << std::endl; - HighsStatus status = issue425(); - REQUIRE(status == HighsStatus::kOk); -} diff --git a/check/instances/WithInf.set b/check/instances/WithInf.set new file mode 100644 index 0000000000..b1f996b822 --- /dev/null +++ b/check/instances/WithInf.set @@ -0,0 +1,3 @@ +time_limit = inf +objective_bound = -inf +objective_target = +inf diff --git a/check/instances/dD2e.mps b/check/instances/dD2e.mps new file mode 100644 index 0000000000..3c66a52348 --- /dev/null +++ b/check/instances/dD2e.mps @@ -0,0 +1,10 @@ +NAME D-Scientific +ROWS + N COST +COLUMNS + C1 COST -1.0 + C2 COST -2.0 +BOUNDS + UP BOUNDS C1 1.0D3 + UP BOUNDS C2 1.0d3 +ENDATA diff --git a/check/instances/nan0.mps b/check/instances/nan0.mps new file mode 100644 index 0000000000..50f9751f6a --- /dev/null +++ b/check/instances/nan0.mps @@ -0,0 +1,13 @@ +NAME CHIP +ROWS + L ASSEMBLY + L FINISHNG + N INCOME +COLUMNS + P1 ASSEMBLY 1.0 FINISHNG 1.0 + P1 INCOME nan + P2 ASSEMBLY 2.0 FINISHNG 4.0 + P2 INCOME -25.0 +RHS + RESOURCES ASSEMBLY 80.0 FINISHNG 120.0 +ENDATA diff --git a/check/instances/nan1.mps b/check/instances/nan1.mps new file mode 100644 index 0000000000..538365157d --- /dev/null +++ b/check/instances/nan1.mps @@ -0,0 +1,13 @@ +NAME CHIP +ROWS + L ASSEMBLY + L FINISHNG + N INCOME +COLUMNS + P1 ASSEMBLY 1.0 FINISHNG 1.0 + P1 INCOME -10.0 + P2 ASSEMBLY 2.0 FINISHNG nan + P2 INCOME -25.0 +RHS + RESOURCES ASSEMBLY 80.0 FINISHNG 120.0 +ENDATA diff --git a/check/instances/nan2.mps b/check/instances/nan2.mps new file mode 100644 index 0000000000..d74b705cb3 --- /dev/null +++ b/check/instances/nan2.mps @@ -0,0 +1,13 @@ +NAME CHIP +ROWS + L ASSEMBLY + L FINISHNG + N INCOME +COLUMNS + P1 ASSEMBLY 1.0 FINISHNG 1.0 + P1 INCOME -10.0 + P2 ASSEMBLY 2.0 FINISHNG 4.0 + P2 INCOME -25.0 +RHS + RESOURCES ASSEMBLY nan FINISHNG 120.0 +ENDATA diff --git a/check/instances/p01.mps b/check/instances/p01.mps new file mode 100644 index 0000000000..bfcfe9dc95 --- /dev/null +++ b/check/instances/p01.mps @@ -0,0 +1,909 @@ +NAME +ROWS + N Obj + E r0 + E r1 + E r2 + E r3 + E r4 + E r5 + E r6 + E r7 + E r8 + E r9 + E r10 + E r11 + E r12 + E r13 + E r14 + E r15 + E r16 + E r17 + E r18 + E r19 + E r20 + E r21 + E r22 + E r23 + E r24 + E r25 + E r26 + E r27 + E r28 + E r29 +COLUMNS + MARK0000 'MARKER' 'INTORG' + c0 Obj 29 + c0 r0 1 + c0 r16 1 + c1 Obj 82 + c1 r0 1 + c1 r17 1 + c2 Obj 46 + c2 r0 1 + c2 r18 1 + c3 Obj 68 + c3 r0 1 + c3 r19 1 + c4 Obj 52 + c4 r0 1 + c4 r20 1 + c5 Obj 72 + c5 r0 1 + c5 r21 1 + c6 Obj 42 + c6 r0 1 + c6 r22 1 + c7 Obj 51 + c7 r0 1 + c7 r23 1 + c8 Obj 55 + c8 r0 1 + c8 r24 1 + c9 Obj 29 + c9 r0 1 + c9 r25 1 + c10 Obj 74 + c10 r0 1 + c10 r26 1 + c11 Obj 23 + c11 r0 1 + c11 r27 1 + c12 Obj 72 + c12 r0 1 + c12 r28 1 + c13 Obj 46 + c13 r0 1 + c13 r29 1 + c14 Obj 29 + c14 r1 1 + c14 r15 1 + c15 Obj 55 + c15 r1 1 + c15 r17 1 + c16 Obj 46 + c16 r1 1 + c16 r18 1 + c17 Obj 42 + c17 r1 1 + c17 r19 1 + c18 Obj 43 + c18 r1 1 + c18 r20 1 + c19 Obj 43 + c19 r1 1 + c19 r21 1 + c20 Obj 23 + c20 r1 1 + c20 r22 1 + c21 Obj 23 + c21 r1 1 + c21 r23 1 + c22 Obj 31 + c22 r1 1 + c22 r24 1 + c23 Obj 41 + c23 r1 1 + c23 r25 1 + c24 Obj 51 + c24 r1 1 + c24 r26 1 + c25 Obj 11 + c25 r1 1 + c25 r27 1 + c26 Obj 52 + c26 r1 1 + c26 r28 1 + c27 Obj 21 + c27 r1 1 + c27 r29 1 + c28 Obj 82 + c28 r2 1 + c28 r15 1 + c29 Obj 55 + c29 r2 1 + c29 r16 1 + c30 Obj 68 + c30 r2 1 + c30 r18 1 + c31 Obj 46 + c31 r2 1 + c31 r19 1 + c32 Obj 55 + c32 r2 1 + c32 r20 1 + c33 Obj 23 + c33 r2 1 + c33 r21 1 + c34 Obj 43 + c34 r2 1 + c34 r22 1 + c35 Obj 41 + c35 r2 1 + c35 r23 1 + c36 Obj 29 + c36 r2 1 + c36 r24 1 + c37 Obj 79 + c37 r2 1 + c37 r25 1 + c38 Obj 21 + c38 r2 1 + c38 r26 1 + c39 Obj 64 + c39 r2 1 + c39 r27 1 + c40 Obj 31 + c40 r2 1 + c40 r28 1 + c41 Obj 51 + c41 r2 1 + c41 r29 1 + c42 Obj 46 + c42 r3 1 + c42 r15 1 + c43 Obj 46 + c43 r3 1 + c43 r16 1 + c44 Obj 68 + c44 r3 1 + c44 r17 1 + c45 Obj 82 + c45 r3 1 + c45 r19 1 + c46 Obj 15 + c46 r3 1 + c46 r20 1 + c47 Obj 72 + c47 r3 1 + c47 r21 1 + c48 Obj 31 + c48 r3 1 + c48 r22 1 + c49 Obj 62 + c49 r3 1 + c49 r23 1 + c50 Obj 42 + c50 r3 1 + c50 r24 1 + c51 Obj 21 + c51 r3 1 + c51 r25 1 + c52 Obj 51 + c52 r3 1 + c52 r26 1 + c53 Obj 51 + c53 r3 1 + c53 r27 1 + c54 Obj 43 + c54 r3 1 + c54 r28 1 + c55 Obj 64 + c55 r3 1 + c55 r29 1 + c56 Obj 68 + c56 r4 1 + c56 r15 1 + c57 Obj 42 + c57 r4 1 + c57 r16 1 + c58 Obj 46 + c58 r4 1 + c58 r17 1 + c59 Obj 82 + c59 r4 1 + c59 r18 1 + c60 Obj 74 + c60 r4 1 + c60 r20 1 + c61 Obj 23 + c61 r4 1 + c61 r21 1 + c62 Obj 52 + c62 r4 1 + c62 r22 1 + c63 Obj 21 + c63 r4 1 + c63 r23 1 + c64 Obj 46 + c64 r4 1 + c64 r24 1 + c65 Obj 82 + c65 r4 1 + c65 r25 1 + c66 Obj 58 + c66 r4 1 + c66 r26 1 + c67 Obj 46 + c67 r4 1 + c67 r27 1 + c68 Obj 65 + c68 r4 1 + c68 r28 1 + c69 Obj 23 + c69 r4 1 + c69 r29 1 + c70 Obj 52 + c70 r5 1 + c70 r15 1 + c71 Obj 43 + c71 r5 1 + c71 r16 1 + c72 Obj 55 + c72 r5 1 + c72 r17 1 + c73 Obj 15 + c73 r5 1 + c73 r18 1 + c74 Obj 74 + c74 r5 1 + c74 r19 1 + c75 Obj 61 + c75 r5 1 + c75 r21 1 + c76 Obj 23 + c76 r5 1 + c76 r22 1 + c77 Obj 55 + c77 r5 1 + c77 r23 1 + c78 Obj 31 + c78 r5 1 + c78 r24 1 + c79 Obj 33 + c79 r5 1 + c79 r25 1 + c80 Obj 37 + c80 r5 1 + c80 r26 1 + c81 Obj 51 + c81 r5 1 + c81 r27 1 + c82 Obj 29 + c82 r5 1 + c82 r28 1 + c83 Obj 59 + c83 r5 1 + c83 r29 1 + c84 Obj 72 + c84 r6 1 + c84 r15 1 + c85 Obj 43 + c85 r6 1 + c85 r16 1 + c86 Obj 23 + c86 r6 1 + c86 r17 1 + c87 Obj 72 + c87 r6 1 + c87 r18 1 + c88 Obj 23 + c88 r6 1 + c88 r19 1 + c89 Obj 61 + c89 r6 1 + c89 r20 1 + c90 Obj 42 + c90 r6 1 + c90 r22 1 + c91 Obj 23 + c91 r6 1 + c91 r23 1 + c92 Obj 31 + c92 r6 1 + c92 r24 1 + c93 Obj 77 + c93 r6 1 + c93 r25 1 + c94 Obj 37 + c94 r6 1 + c94 r26 1 + c95 Obj 51 + c95 r6 1 + c95 r27 1 + c96 Obj 46 + c96 r6 1 + c96 r28 1 + c97 Obj 33 + c97 r6 1 + c97 r29 1 + c98 Obj 42 + c98 r7 1 + c98 r15 1 + c99 Obj 23 + c99 r7 1 + c99 r16 1 + c100 Obj 43 + c100 r7 1 + c100 r17 1 + c101 Obj 31 + c101 r7 1 + c101 r18 1 + c102 Obj 52 + c102 r7 1 + c102 r19 1 + c103 Obj 23 + c103 r7 1 + c103 r20 1 + c104 Obj 42 + c104 r7 1 + c104 r21 1 + c105 Obj 33 + c105 r7 1 + c105 r23 1 + c106 Obj 15 + c106 r7 1 + c106 r24 1 + c107 Obj 37 + c107 r7 1 + c107 r25 1 + c108 Obj 33 + c108 r7 1 + c108 r26 1 + c109 Obj 33 + c109 r7 1 + c109 r27 1 + c110 Obj 31 + c110 r7 1 + c110 r28 1 + c111 Obj 37 + c111 r7 1 + c111 r29 1 + c112 Obj 51 + c112 r8 1 + c112 r15 1 + c113 Obj 23 + c113 r8 1 + c113 r16 1 + c114 Obj 41 + c114 r8 1 + c114 r17 1 + c115 Obj 62 + c115 r8 1 + c115 r18 1 + c116 Obj 21 + c116 r8 1 + c116 r19 1 + c117 Obj 55 + c117 r8 1 + c117 r20 1 + c118 Obj 23 + c118 r8 1 + c118 r21 1 + c119 Obj 33 + c119 r8 1 + c119 r22 1 + c120 Obj 29 + c120 r8 1 + c120 r24 1 + c121 Obj 62 + c121 r8 1 + c121 r25 1 + c122 Obj 46 + c122 r8 1 + c122 r26 1 + c123 Obj 29 + c123 r8 1 + c123 r27 1 + c124 Obj 51 + c124 r8 1 + c124 r28 1 + c125 Obj 11 + c125 r8 1 + c125 r29 1 + c126 Obj 55 + c126 r9 1 + c126 r15 1 + c127 Obj 31 + c127 r9 1 + c127 r16 1 + c128 Obj 29 + c128 r9 1 + c128 r17 1 + c129 Obj 42 + c129 r9 1 + c129 r18 1 + c130 Obj 46 + c130 r9 1 + c130 r19 1 + c131 Obj 31 + c131 r9 1 + c131 r20 1 + c132 Obj 31 + c132 r9 1 + c132 r21 1 + c133 Obj 15 + c133 r9 1 + c133 r22 1 + c134 Obj 29 + c134 r9 1 + c134 r23 1 + c135 Obj 51 + c135 r9 1 + c135 r25 1 + c136 Obj 21 + c136 r9 1 + c136 r26 1 + c137 Obj 41 + c137 r9 1 + c137 r27 1 + c138 Obj 23 + c138 r9 1 + c138 r28 1 + c139 Obj 37 + c139 r9 1 + c139 r29 1 + c140 Obj 29 + c140 r10 1 + c140 r15 1 + c141 Obj 41 + c141 r10 1 + c141 r16 1 + c142 Obj 79 + c142 r10 1 + c142 r17 1 + c143 Obj 21 + c143 r10 1 + c143 r18 1 + c144 Obj 82 + c144 r10 1 + c144 r19 1 + c145 Obj 33 + c145 r10 1 + c145 r20 1 + c146 Obj 77 + c146 r10 1 + c146 r21 1 + c147 Obj 37 + c147 r10 1 + c147 r22 1 + c148 Obj 62 + c148 r10 1 + c148 r23 1 + c149 Obj 51 + c149 r10 1 + c149 r24 1 + c150 Obj 65 + c150 r10 1 + c150 r26 1 + c151 Obj 42 + c151 r10 1 + c151 r27 1 + c152 Obj 59 + c152 r10 1 + c152 r28 1 + c153 Obj 61 + c153 r10 1 + c153 r29 1 + c154 Obj 74 + c154 r11 1 + c154 r15 1 + c155 Obj 51 + c155 r11 1 + c155 r16 1 + c156 Obj 21 + c156 r11 1 + c156 r17 1 + c157 Obj 51 + c157 r11 1 + c157 r18 1 + c158 Obj 58 + c158 r11 1 + c158 r19 1 + c159 Obj 37 + c159 r11 1 + c159 r20 1 + c160 Obj 37 + c160 r11 1 + c160 r21 1 + c161 Obj 33 + c161 r11 1 + c161 r22 1 + c162 Obj 46 + c162 r11 1 + c162 r23 1 + c163 Obj 21 + c163 r11 1 + c163 r24 1 + c164 Obj 65 + c164 r11 1 + c164 r25 1 + c165 Obj 61 + c165 r11 1 + c165 r27 1 + c166 Obj 11 + c166 r11 1 + c166 r28 1 + c167 Obj 55 + c167 r11 1 + c167 r29 1 + c168 Obj 23 + c168 r12 1 + c168 r15 1 + c169 Obj 11 + c169 r12 1 + c169 r16 1 + c170 Obj 64 + c170 r12 1 + c170 r17 1 + c171 Obj 51 + c171 r12 1 + c171 r18 1 + c172 Obj 46 + c172 r12 1 + c172 r19 1 + c173 Obj 51 + c173 r12 1 + c173 r20 1 + c174 Obj 51 + c174 r12 1 + c174 r21 1 + c175 Obj 33 + c175 r12 1 + c175 r22 1 + c176 Obj 29 + c176 r12 1 + c176 r23 1 + c177 Obj 41 + c177 r12 1 + c177 r24 1 + c178 Obj 42 + c178 r12 1 + c178 r25 1 + c179 Obj 61 + c179 r12 1 + c179 r26 1 + c180 Obj 62 + c180 r12 1 + c180 r28 1 + c181 Obj 23 + c181 r12 1 + c181 r29 1 + c182 Obj 72 + c182 r13 1 + c182 r15 1 + c183 Obj 52 + c183 r13 1 + c183 r16 1 + c184 Obj 31 + c184 r13 1 + c184 r17 1 + c185 Obj 43 + c185 r13 1 + c185 r18 1 + c186 Obj 65 + c186 r13 1 + c186 r19 1 + c187 Obj 29 + c187 r13 1 + c187 r20 1 + c188 Obj 46 + c188 r13 1 + c188 r21 1 + c189 Obj 31 + c189 r13 1 + c189 r22 1 + c190 Obj 51 + c190 r13 1 + c190 r23 1 + c191 Obj 23 + c191 r13 1 + c191 r24 1 + c192 Obj 59 + c192 r13 1 + c192 r25 1 + c193 Obj 11 + c193 r13 1 + c193 r26 1 + c194 Obj 62 + c194 r13 1 + c194 r27 1 + c195 Obj 59 + c195 r13 1 + c195 r29 1 + c196 Obj 46 + c196 r14 1 + c196 r15 1 + c197 Obj 21 + c197 r14 1 + c197 r16 1 + c198 Obj 51 + c198 r14 1 + c198 r17 1 + c199 Obj 64 + c199 r14 1 + c199 r18 1 + c200 Obj 23 + c200 r14 1 + c200 r19 1 + c201 Obj 59 + c201 r14 1 + c201 r20 1 + c202 Obj 33 + c202 r14 1 + c202 r21 1 + c203 Obj 37 + c203 r14 1 + c203 r22 1 + c204 Obj 11 + c204 r14 1 + c204 r23 1 + c205 Obj 37 + c205 r14 1 + c205 r24 1 + c206 Obj 61 + c206 r14 1 + c206 r25 1 + c207 Obj 55 + c207 r14 1 + c207 r26 1 + c208 Obj 23 + c208 r14 1 + c208 r27 1 + c209 Obj 59 + c209 r14 1 + c209 r28 1 + MARK0001 'MARKER' 'INTEND' +RHS + RHS_V r0 1 + RHS_V r1 1 + RHS_V r2 1 + RHS_V r3 1 + RHS_V r4 1 + RHS_V r5 1 + RHS_V r6 1 + RHS_V r7 1 + RHS_V r8 1 + RHS_V r9 1 + RHS_V r10 1 + RHS_V r11 1 + RHS_V r12 1 + RHS_V r13 1 + RHS_V r14 1 + RHS_V r15 1 + RHS_V r16 1 + RHS_V r17 1 + RHS_V r18 1 + RHS_V r19 1 + RHS_V r20 1 + RHS_V r21 1 + RHS_V r22 1 + RHS_V r23 1 + RHS_V r24 1 + RHS_V r25 1 + RHS_V r26 1 + RHS_V r27 1 + RHS_V r28 1 + RHS_V r29 1 +BOUNDS + BV BOUND c0 + BV BOUND c1 + BV BOUND c2 + BV BOUND c3 + BV BOUND c4 + BV BOUND c5 + BV BOUND c6 + BV BOUND c7 + BV BOUND c8 + BV BOUND c9 + BV BOUND c10 + BV BOUND c11 + BV BOUND c12 + BV BOUND c13 + BV BOUND c14 + BV BOUND c15 + BV BOUND c16 + BV BOUND c17 + BV BOUND c18 + BV BOUND c19 + BV BOUND c20 + BV BOUND c21 + BV BOUND c22 + BV BOUND c23 + BV BOUND c24 + BV BOUND c25 + BV BOUND c26 + BV BOUND c27 + BV BOUND c28 + BV BOUND c29 + BV BOUND c30 + BV BOUND c31 + BV BOUND c32 + BV BOUND c33 + BV BOUND c34 + BV BOUND c35 + BV BOUND c36 + BV BOUND c37 + BV BOUND c38 + BV BOUND c39 + BV BOUND c40 + BV BOUND c41 + BV BOUND c42 + BV BOUND c43 + BV BOUND c44 + BV BOUND c45 + BV BOUND c46 + BV BOUND c47 + BV BOUND c48 + BV BOUND c49 + BV BOUND c50 + BV BOUND c51 + BV BOUND c52 + BV BOUND c53 + BV BOUND c54 + BV BOUND c55 + BV BOUND c56 + BV BOUND c57 + BV BOUND c58 + BV BOUND c59 + BV BOUND c60 + BV BOUND c61 + BV BOUND c62 + BV BOUND c63 + BV BOUND c64 + BV BOUND c65 + BV BOUND c66 + BV BOUND c67 + BV BOUND c68 + BV BOUND c69 + BV BOUND c70 + BV BOUND c71 + BV BOUND c72 + BV BOUND c73 + BV BOUND c74 + BV BOUND c75 + BV BOUND c76 + BV BOUND c77 + BV BOUND c78 + BV BOUND c79 + BV BOUND c80 + BV BOUND c81 + BV BOUND c82 + BV BOUND c83 + BV BOUND c84 + BV BOUND c85 + BV BOUND c86 + BV BOUND c87 + BV BOUND c88 + BV BOUND c89 + BV BOUND c90 + BV BOUND c91 + BV BOUND c92 + BV BOUND c93 + BV BOUND c94 + BV BOUND c95 + BV BOUND c96 + BV BOUND c97 + BV BOUND c98 + BV BOUND c99 + BV BOUND c100 + BV BOUND c101 + BV BOUND c102 + BV BOUND c103 + BV BOUND c104 + BV BOUND c105 + BV BOUND c106 + BV BOUND c107 + BV BOUND c108 + BV BOUND c109 + BV BOUND c110 + BV BOUND c111 + BV BOUND c112 + BV BOUND c113 + BV BOUND c114 + BV BOUND c115 + BV BOUND c116 + BV BOUND c117 + BV BOUND c118 + BV BOUND c119 + BV BOUND c120 + BV BOUND c121 + BV BOUND c122 + BV BOUND c123 + BV BOUND c124 + BV BOUND c125 + BV BOUND c126 + BV BOUND c127 + BV BOUND c128 + BV BOUND c129 + BV BOUND c130 + BV BOUND c131 + BV BOUND c132 + BV BOUND c133 + BV BOUND c134 + BV BOUND c135 + BV BOUND c136 + BV BOUND c137 + BV BOUND c138 + BV BOUND c139 + BV BOUND c140 + BV BOUND c141 + BV BOUND c142 + BV BOUND c143 + BV BOUND c144 + BV BOUND c145 + BV BOUND c146 + BV BOUND c147 + BV BOUND c148 + BV BOUND c149 + BV BOUND c150 + BV BOUND c151 + BV BOUND c152 + BV BOUND c153 + BV BOUND c154 + BV BOUND c155 + BV BOUND c156 + BV BOUND c157 + BV BOUND c158 + BV BOUND c159 + BV BOUND c160 + BV BOUND c161 + BV BOUND c162 + BV BOUND c163 + BV BOUND c164 + BV BOUND c165 + BV BOUND c166 + BV BOUND c167 + BV BOUND c168 + BV BOUND c169 + BV BOUND c170 + BV BOUND c171 + BV BOUND c172 + BV BOUND c173 + BV BOUND c174 + BV BOUND c175 + BV BOUND c176 + BV BOUND c177 + BV BOUND c178 + BV BOUND c179 + BV BOUND c180 + BV BOUND c181 + BV BOUND c182 + BV BOUND c183 + BV BOUND c184 + BV BOUND c185 + BV BOUND c186 + BV BOUND c187 + BV BOUND c188 + BV BOUND c189 + BV BOUND c190 + BV BOUND c191 + BV BOUND c192 + BV BOUND c193 + BV BOUND c194 + BV BOUND c195 + BV BOUND c196 + BV BOUND c197 + BV BOUND c198 + BV BOUND c199 + BV BOUND c200 + BV BOUND c201 + BV BOUND c202 + BV BOUND c203 + BV BOUND c204 + BV BOUND c205 + BV BOUND c206 + BV BOUND c207 + BV BOUND c208 + BV BOUND c209 +ENDATA diff --git a/check/instances/primal1.mps b/check/instances/primal1.mps new file mode 100644 index 0000000000..7930e6e3c8 --- /dev/null +++ b/check/instances/primal1.mps @@ -0,0 +1,3909 @@ +NAME PRIMAL1 +ROWS + N OBJ.FUNC + L R------1 + L R------2 + L R------3 + L R------4 + L R------5 + L R------6 + L R------7 + L R------8 + L R------9 + L R-----10 + L R-----11 + L R-----12 + L R-----13 + L R-----14 + L R-----15 + L R-----16 + L R-----17 + L R-----18 + L R-----19 + L R-----20 + L R-----21 + L R-----22 + L R-----23 + L R-----24 + L R-----25 + L R-----26 + L R-----27 + L R-----28 + L R-----29 + L R-----30 + L R-----31 + L R-----32 + L R-----33 + L R-----34 + L R-----35 + L R-----36 + L R-----37 + L R-----38 + L R-----39 + L R-----40 + L R-----41 + L R-----42 + L R-----43 + L R-----44 + L R-----45 + L R-----46 + L R-----47 + L R-----48 + L R-----49 + L R-----50 + L R-----51 + L R-----52 + L R-----53 + L R-----54 + L R-----55 + L R-----56 + L R-----57 + L R-----58 + L R-----59 + L R-----60 + L R-----61 + L R-----62 + L R-----63 + L R-----64 + L R-----65 + L R-----66 + L R-----67 + L R-----68 + L R-----69 + L R-----70 + L R-----71 + L R-----72 + L R-----73 + L R-----74 + L R-----75 + L R-----76 + L R-----77 + L R-----78 + L R-----79 + L R-----80 + L R-----81 + L R-----82 + L R-----83 + L R-----84 + L R-----85 +COLUMNS + C------1 OBJ.FUNC -.100000e+01 R------1 0.100000e+01 + C------1 R------2 0.100000e+01 R------3 0.100000e+01 + C------1 R------4 0.100000e+01 R------5 0.100000e+01 + C------1 R------6 0.100000e+01 R------7 0.100000e+01 + C------1 R------8 0.100000e+01 R------9 0.100000e+01 + C------1 R-----10 0.100000e+01 R-----11 0.100000e+01 + C------1 R-----12 0.100000e+01 R-----13 0.100000e+01 + C------1 R-----14 0.100000e+01 R-----15 0.100000e+01 + C------1 R-----16 0.100000e+01 R-----17 0.100000e+01 + C------1 R-----18 0.100000e+01 R-----19 0.100000e+01 + C------1 R-----20 0.100000e+01 R-----21 0.100000e+01 + C------1 R-----22 0.100000e+01 R-----23 0.100000e+01 + C------1 R-----24 0.100000e+01 R-----25 0.100000e+01 + C------1 R-----26 0.100000e+01 R-----27 0.100000e+01 + C------1 R-----28 0.100000e+01 R-----29 0.100000e+01 + C------1 R-----30 0.100000e+01 R-----31 0.100000e+01 + C------1 R-----32 0.100000e+01 R-----33 0.100000e+01 + C------1 R-----34 0.100000e+01 R-----35 0.100000e+01 + C------1 R-----36 0.100000e+01 R-----37 0.100000e+01 + C------1 R-----38 0.100000e+01 R-----39 0.100000e+01 + C------1 R-----40 0.100000e+01 R-----41 0.100000e+01 + C------1 R-----42 0.100000e+01 R-----43 0.100000e+01 + C------1 R-----44 0.100000e+01 R-----45 0.100000e+01 + C------1 R-----46 0.100000e+01 R-----47 0.100000e+01 + C------1 R-----48 0.100000e+01 R-----49 0.100000e+01 + C------1 R-----50 0.100000e+01 R-----51 0.100000e+01 + C------1 R-----52 0.100000e+01 R-----53 0.100000e+01 + C------1 R-----54 0.100000e+01 R-----55 0.100000e+01 + C------1 R-----56 0.100000e+01 R-----57 0.100000e+01 + C------1 R-----58 0.100000e+01 R-----59 0.100000e+01 + C------1 R-----60 0.100000e+01 R-----61 0.100000e+01 + C------1 R-----62 0.100000e+01 R-----63 0.100000e+01 + C------1 R-----64 0.100000e+01 R-----65 0.100000e+01 + C------1 R-----66 0.100000e+01 R-----67 0.100000e+01 + C------1 R-----68 0.100000e+01 R-----69 0.100000e+01 + C------1 R-----70 0.100000e+01 R-----71 0.100000e+01 + C------1 R-----72 0.100000e+01 R-----73 0.100000e+01 + C------1 R-----74 0.100000e+01 R-----75 0.100000e+01 + C------1 R-----76 0.100000e+01 R-----77 0.100000e+01 + C------1 R-----78 0.100000e+01 R-----79 0.100000e+01 + C------1 R-----80 0.100000e+01 R-----81 0.100000e+01 + C------1 R-----82 0.100000e+01 R-----83 0.100000e+01 + C------1 R-----84 0.100000e+01 R-----85 0.100000e+01 + C------2 OBJ.FUNC 0.000000e+00 + C------3 R------1 -.100000e+01 R------4 0.100000e+01 + C------3 R------6 -.100000e+01 R------7 0.100000e+01 + C------3 R-----10 -.100000e+01 R-----16 0.100000e+01 + C------3 R-----18 -.100000e+01 R-----19 0.100000e+01 + C------3 R-----25 -.100000e+01 R-----26 -.100000e+01 + C------3 R-----27 -.100000e+01 R-----31 -.100000e+01 + C------3 R-----34 -.100000e+01 R-----35 0.100000e+01 + C------3 R-----41 -.100000e+01 R-----45 -.100000e+01 + C------3 R-----46 -.100000e+01 R-----51 0.100000e+01 + C------3 R-----53 -.100000e+01 R-----60 0.100000e+01 + C------3 R-----67 -.100000e+01 R-----68 0.100000e+01 + C------3 R-----75 -.100000e+01 R-----76 0.100000e+01 + C------3 R-----77 -.100000e+01 R-----78 0.100000e+01 + C------3 R-----79 -.100000e+01 R-----84 -.100000e+01 + C------3 R-----85 0.100000e+01 + C------4 OBJ.FUNC 0.000000e+00 + C------5 R------2 0.100000e+01 R------6 0.100000e+01 + C------5 R------8 -.100000e+01 R-----12 0.100000e+01 + C------5 R-----13 0.100000e+01 R-----14 -.100000e+01 + C------5 R-----17 0.100000e+01 R-----18 0.100000e+01 + C------5 R-----22 0.100000e+01 R-----24 -.100000e+01 + C------5 R-----25 0.100000e+01 R-----28 0.100000e+01 + C------5 R-----31 0.100000e+01 R-----38 0.100000e+01 + C------5 R-----43 -.100000e+01 R-----48 0.100000e+01 + C------5 R-----49 -.100000e+01 R-----56 -.100000e+01 + C------5 R-----57 0.100000e+01 R-----61 -.100000e+01 + C------5 R-----62 0.100000e+01 R-----63 -.100000e+01 + C------5 R-----65 -.100000e+01 R-----66 0.100000e+01 + C------5 R-----67 0.100000e+01 R-----68 -.100000e+01 + C------5 R-----69 0.100000e+01 R-----70 0.100000e+01 + C------5 R-----71 -.100000e+01 R-----74 -.100000e+01 + C------5 R-----75 0.100000e+01 R-----77 0.100000e+01 + C------5 R-----78 -.100000e+01 R-----83 -.100000e+01 + C------5 R-----84 0.100000e+01 + C------6 R------1 0.100000e+01 R-----12 -.100000e+01 + C------6 R-----14 0.100000e+01 R-----16 -.100000e+01 + C------6 R-----21 0.100000e+01 R-----22 -.100000e+01 + C------6 R-----27 0.100000e+01 R-----28 -.100000e+01 + C------6 R-----29 0.100000e+01 R-----32 0.100000e+01 + C------6 R-----36 0.100000e+01 R-----47 0.100000e+01 + C------6 R-----48 -.100000e+01 R-----49 0.100000e+01 + C------6 R-----64 0.100000e+01 R-----71 0.100000e+01 + C------6 R-----76 -.100000e+01 R-----82 0.100000e+01 + C------7 R------2 -.100000e+01 R------4 -.100000e+01 + C------7 R------7 -.100000e+01 R------8 0.100000e+01 + C------7 R-----10 0.100000e+01 R-----13 -.100000e+01 + C------7 R-----17 -.100000e+01 R-----19 -.100000e+01 + C------7 R-----21 -.100000e+01 R-----24 0.100000e+01 + C------7 R-----26 0.100000e+01 R-----29 -.100000e+01 + C------7 R-----32 -.100000e+01 R-----34 0.100000e+01 + C------7 R-----35 -.100000e+01 R-----36 -.100000e+01 + C------7 R-----38 -.100000e+01 R-----41 0.100000e+01 + C------7 R-----43 0.100000e+01 R-----45 0.100000e+01 + C------7 R-----46 0.100000e+01 R-----47 -.100000e+01 + C------7 R-----51 -.100000e+01 R-----53 0.100000e+01 + C------7 R-----56 0.100000e+01 R-----57 -.100000e+01 + C------7 R-----60 -.100000e+01 R-----61 0.100000e+01 + C------7 R-----62 -.100000e+01 R-----63 0.100000e+01 + C------7 R-----64 -.100000e+01 R-----65 0.100000e+01 + C------7 R-----66 -.100000e+01 R-----69 -.100000e+01 + C------7 R-----70 -.100000e+01 R-----74 0.100000e+01 + C------7 R-----79 0.100000e+01 R-----82 -.100000e+01 + C------7 R-----83 0.100000e+01 R-----85 -.100000e+01 + C------8 OBJ.FUNC 0.000000e+00 + C------9 R------1 -.100000e+01 R------2 0.100000e+01 + C------9 R------3 -.100000e+01 R------4 0.100000e+01 + C------9 R------5 -.100000e+01 R------7 0.100000e+01 + C------9 R------8 0.100000e+01 R------9 0.100000e+01 + C------9 R-----10 0.100000e+01 R-----11 0.100000e+01 + C------9 R-----12 -.100000e+01 R-----15 -.100000e+01 + C------9 R-----19 0.100000e+01 R-----21 -.100000e+01 + C------9 R-----27 -.100000e+01 R-----28 0.100000e+01 + C------9 R-----29 -.100000e+01 R-----31 0.100000e+01 + C------9 R-----33 -.100000e+01 R-----34 0.100000e+01 + C------9 R-----40 -.100000e+01 R-----42 -.100000e+01 + C------9 R-----43 -.100000e+01 R-----44 -.100000e+01 + C------9 R-----45 0.100000e+01 R-----46 0.100000e+01 + C------9 R-----48 -.100000e+01 R-----49 -.100000e+01 + C------9 R-----50 -.100000e+01 R-----55 0.100000e+01 + C------9 R-----57 0.100000e+01 R-----58 0.100000e+01 + C------9 R-----59 -.100000e+01 R-----60 -.100000e+01 + C------9 R-----62 -.100000e+01 R-----66 -.100000e+01 + C------9 R-----68 0.100000e+01 R-----69 -.100000e+01 + C------9 R-----70 0.100000e+01 R-----71 -.100000e+01 + C------9 R-----72 -.100000e+01 R-----76 0.100000e+01 + C------9 R-----77 -.100000e+01 R-----78 -.100000e+01 + C------9 R-----79 0.100000e+01 R-----83 0.100000e+01 + C------9 R-----84 -.100000e+01 R-----85 0.100000e+01 + C-----10 R------1 0.100000e+01 R------4 -.100000e+01 + C-----10 R------7 -.100000e+01 R-----12 0.100000e+01 + C-----10 R-----13 0.100000e+01 R-----15 0.100000e+01 + C-----10 R-----16 0.100000e+01 R-----17 0.100000e+01 + C-----10 R-----18 -.100000e+01 R-----19 -.100000e+01 + C-----10 R-----22 0.100000e+01 R-----30 0.100000e+01 + C-----10 R-----34 -.100000e+01 R-----35 0.100000e+01 + C-----10 R-----36 -.100000e+01 R-----38 0.100000e+01 + C-----10 R-----39 0.100000e+01 R-----40 0.100000e+01 + C-----10 R-----43 0.100000e+01 R-----44 0.100000e+01 + C-----10 R-----53 0.100000e+01 R-----57 -.100000e+01 + C-----10 R-----60 0.100000e+01 R-----62 0.100000e+01 + C-----10 R-----64 -.100000e+01 R-----72 0.100000e+01 + C-----10 R-----73 0.100000e+01 R-----76 -.100000e+01 + C-----10 R-----78 0.100000e+01 R-----82 -.100000e+01 + C-----10 R-----84 0.100000e+01 R-----85 -.100000e+01 + C-----11 OBJ.FUNC 0.000000e+00 + C-----12 R------2 -.100000e+01 R------3 0.100000e+01 + C-----12 R------5 0.100000e+01 R------8 -.100000e+01 + C-----12 R------9 -.100000e+01 R-----10 -.100000e+01 + C-----12 R-----11 -.100000e+01 R-----13 -.100000e+01 + C-----12 R-----16 -.100000e+01 R-----17 -.100000e+01 + C-----12 R-----18 0.100000e+01 R-----21 0.100000e+01 + C-----12 R-----22 -.100000e+01 R-----27 0.100000e+01 + C-----12 R-----28 -.100000e+01 R-----29 0.100000e+01 + C-----12 R-----30 -.100000e+01 R-----31 -.100000e+01 + C-----12 R-----33 0.100000e+01 R-----35 -.100000e+01 + C-----12 R-----36 0.100000e+01 R-----38 -.100000e+01 + C-----12 R-----39 -.100000e+01 R-----42 0.100000e+01 + C-----12 R-----45 -.100000e+01 R-----46 -.100000e+01 + C-----12 R-----48 0.100000e+01 R-----49 0.100000e+01 + C-----12 R-----50 0.100000e+01 R-----53 -.100000e+01 + C-----12 R-----55 -.100000e+01 R-----58 -.100000e+01 + C-----12 R-----59 0.100000e+01 R-----64 0.100000e+01 + C-----12 R-----66 0.100000e+01 R-----68 -.100000e+01 + C-----12 R-----69 0.100000e+01 R-----70 -.100000e+01 + C-----12 R-----71 0.100000e+01 R-----73 -.100000e+01 + C-----12 R-----77 0.100000e+01 R-----79 -.100000e+01 + C-----12 R-----82 0.100000e+01 R-----83 -.100000e+01 + C-----13 OBJ.FUNC 0.000000e+00 + C-----14 R------5 0.100000e+01 R------6 -.100000e+01 + C-----14 R------7 -.100000e+01 R------9 -.100000e+01 + C-----14 R-----12 -.100000e+01 R-----15 -.100000e+01 + C-----14 R-----19 -.100000e+01 R-----20 0.100000e+01 + C-----14 R-----21 -.100000e+01 R-----22 0.100000e+01 + C-----14 R-----25 -.100000e+01 R-----28 0.100000e+01 + C-----14 R-----29 -.100000e+01 R-----30 -.100000e+01 + C-----14 R-----31 0.100000e+01 R-----33 0.100000e+01 + C-----14 R-----34 -.100000e+01 R-----35 -.100000e+01 + C-----14 R-----38 -.100000e+01 R-----39 0.100000e+01 + C-----14 R-----40 -.100000e+01 R-----41 -.100000e+01 + C-----14 R-----46 -.100000e+01 R-----48 -.100000e+01 + C-----14 R-----49 0.100000e+01 R-----51 -.100000e+01 + C-----14 R-----52 -.100000e+01 R-----53 -.100000e+01 + C-----14 R-----56 0.100000e+01 R-----57 -.100000e+01 + C-----14 R-----58 0.100000e+01 R-----59 -.100000e+01 + C-----14 R-----61 0.100000e+01 R-----62 -.100000e+01 + C-----14 R-----65 0.100000e+01 R-----66 0.100000e+01 + C-----14 R-----72 -.100000e+01 R-----73 0.100000e+01 + C-----14 R-----74 0.100000e+01 R-----83 0.100000e+01 + C-----15 OBJ.FUNC 0.000000e+00 + C-----16 OBJ.FUNC 0.000000e+00 + C-----17 R------2 0.100000e+01 R------3 -.100000e+01 + C-----17 R------5 -.100000e+01 R------6 0.100000e+01 + C-----17 R------7 0.100000e+01 R------8 0.100000e+01 + C-----17 R------9 0.100000e+01 R-----12 0.100000e+01 + C-----17 R-----15 0.100000e+01 R-----19 0.100000e+01 + C-----17 R-----20 -.100000e+01 R-----22 -.100000e+01 + C-----17 R-----23 -.100000e+01 R-----25 0.100000e+01 + C-----17 R-----29 0.100000e+01 R-----30 0.100000e+01 + C-----17 R-----31 -.100000e+01 R-----33 -.100000e+01 + C-----17 R-----34 0.100000e+01 R-----35 0.100000e+01 + C-----17 R-----37 -.100000e+01 R-----38 0.100000e+01 + C-----17 R-----39 -.100000e+01 R-----40 0.100000e+01 + C-----17 R-----41 0.100000e+01 R-----43 0.100000e+01 + C-----17 R-----46 0.100000e+01 R-----47 -.100000e+01 + C-----17 R-----48 0.100000e+01 R-----49 -.100000e+01 + C-----17 R-----51 0.100000e+01 R-----52 0.100000e+01 + C-----17 R-----53 0.100000e+01 R-----56 -.100000e+01 + C-----17 R-----57 0.100000e+01 R-----58 -.100000e+01 + C-----17 R-----61 -.100000e+01 R-----62 0.100000e+01 + C-----17 R-----63 0.100000e+01 R-----65 -.100000e+01 + C-----17 R-----66 -.100000e+01 R-----71 -.100000e+01 + C-----17 R-----72 0.100000e+01 R-----73 -.100000e+01 + C-----17 R-----83 -.100000e+01 + C-----18 R------2 -.100000e+01 R------3 0.100000e+01 + C-----18 R------8 -.100000e+01 R-----21 0.100000e+01 + C-----18 R-----23 0.100000e+01 R-----28 -.100000e+01 + C-----18 R-----37 0.100000e+01 R-----43 -.100000e+01 + C-----18 R-----47 0.100000e+01 R-----59 0.100000e+01 + C-----18 R-----63 -.100000e+01 R-----71 0.100000e+01 + C-----18 R-----74 -.100000e+01 + C-----19 OBJ.FUNC 0.000000e+00 + C-----20 OBJ.FUNC 0.000000e+00 + C-----21 OBJ.FUNC 0.000000e+00 + C-----22 R------2 -.100000e+01 R------3 -.100000e+01 + C-----22 R------4 0.100000e+01 R------5 0.100000e+01 + C-----22 R------6 -.100000e+01 R------7 -.100000e+01 + C-----22 R-----11 0.100000e+01 R-----13 0.100000e+01 + C-----22 R-----15 -.100000e+01 R-----18 -.100000e+01 + C-----22 R-----20 0.100000e+01 R-----22 -.100000e+01 + C-----22 R-----23 -.100000e+01 R-----24 0.100000e+01 + C-----22 R-----25 -.100000e+01 R-----26 -.100000e+01 + C-----22 R-----27 0.100000e+01 R-----29 -.100000e+01 + C-----22 R-----32 -.100000e+01 R-----34 -.100000e+01 + C-----22 R-----35 -.100000e+01 R-----36 0.100000e+01 + C-----22 R-----37 -.100000e+01 R-----38 -.100000e+01 + C-----22 R-----40 -.100000e+01 R-----41 0.100000e+01 + C-----22 R-----43 -.100000e+01 R-----46 0.100000e+01 + C-----22 R-----47 -.100000e+01 R-----51 -.100000e+01 + C-----22 R-----53 -.100000e+01 R-----54 0.100000e+01 + C-----22 R-----55 -.100000e+01 R-----58 0.100000e+01 + C-----22 R-----60 -.100000e+01 R-----61 -.100000e+01 + C-----22 R-----63 -.100000e+01 R-----64 0.100000e+01 + C-----22 R-----66 0.100000e+01 R-----69 0.100000e+01 + C-----22 R-----70 -.100000e+01 R-----71 -.100000e+01 + C-----22 R-----72 0.100000e+01 R-----73 -.100000e+01 + C-----22 R-----74 0.100000e+01 R-----75 -.100000e+01 + C-----22 R-----76 0.100000e+01 R-----77 0.100000e+01 + C-----23 R------2 0.100000e+01 R------4 -.100000e+01 + C-----23 R------5 -.100000e+01 R------7 0.100000e+01 + C-----23 R-----11 -.100000e+01 R-----13 -.100000e+01 + C-----23 R-----14 -.100000e+01 R-----18 0.100000e+01 + C-----23 R-----19 -.100000e+01 R-----22 0.100000e+01 + C-----23 R-----24 -.100000e+01 R-----25 0.100000e+01 + C-----23 R-----26 0.100000e+01 R-----27 -.100000e+01 + C-----23 R-----29 0.100000e+01 R-----30 -.100000e+01 + C-----23 R-----32 0.100000e+01 R-----35 0.100000e+01 + C-----23 R-----38 0.100000e+01 R-----41 -.100000e+01 + C-----23 R-----44 -.100000e+01 R-----46 -.100000e+01 + C-----23 R-----47 0.100000e+01 R-----48 -.100000e+01 + C-----23 R-----51 0.100000e+01 R-----53 0.100000e+01 + C-----23 R-----54 -.100000e+01 R-----56 0.100000e+01 + C-----23 R-----61 0.100000e+01 R-----63 0.100000e+01 + C-----23 R-----64 -.100000e+01 R-----66 -.100000e+01 + C-----23 R-----68 -.100000e+01 R-----69 -.100000e+01 + C-----23 R-----70 0.100000e+01 R-----71 0.100000e+01 + C-----23 R-----75 0.100000e+01 R-----76 -.100000e+01 + C-----23 R-----77 -.100000e+01 R-----81 0.100000e+01 + C-----23 R-----82 0.100000e+01 R-----83 0.100000e+01 + C-----24 R------6 0.100000e+01 R------8 0.100000e+01 + C-----24 R------9 0.100000e+01 R-----14 0.100000e+01 + C-----24 R-----15 0.100000e+01 R-----17 0.100000e+01 + C-----24 R-----19 0.100000e+01 R-----23 0.100000e+01 + C-----24 R-----30 0.100000e+01 R-----34 0.100000e+01 + C-----24 R-----36 -.100000e+01 R-----37 0.100000e+01 + C-----24 R-----40 0.100000e+01 R-----44 0.100000e+01 + C-----24 R-----48 0.100000e+01 R-----56 -.100000e+01 + C-----24 R-----58 -.100000e+01 R-----67 0.100000e+01 + C-----24 R-----68 0.100000e+01 R-----72 -.100000e+01 + C-----24 R-----83 -.100000e+01 + C-----25 R------3 0.100000e+01 R------8 -.100000e+01 + C-----25 R------9 -.100000e+01 R-----17 -.100000e+01 + C-----25 R-----20 -.100000e+01 R-----43 0.100000e+01 + C-----25 R-----55 0.100000e+01 R-----60 0.100000e+01 + C-----25 R-----67 -.100000e+01 R-----73 0.100000e+01 + C-----25 R-----74 -.100000e+01 R-----81 -.100000e+01 + C-----25 R-----82 -.100000e+01 + C-----26 R------1 0.100000e+01 R------4 0.100000e+01 + C-----26 R------5 -.100000e+01 R------6 0.100000e+01 + C-----26 R------7 -.100000e+01 R------8 0.100000e+01 + C-----26 R------9 -.100000e+01 R-----10 -.100000e+01 + C-----26 R-----11 0.100000e+01 R-----12 -.100000e+01 + C-----26 R-----14 -.100000e+01 R-----23 -.100000e+01 + C-----26 R-----25 0.100000e+01 R-----29 -.100000e+01 + C-----26 R-----33 -.100000e+01 R-----35 -.100000e+01 + C-----26 R-----36 -.100000e+01 R-----43 0.100000e+01 + C-----26 R-----44 -.100000e+01 R-----45 0.100000e+01 + C-----26 R-----46 0.100000e+01 R-----47 -.100000e+01 + C-----26 R-----48 -.100000e+01 R-----51 -.100000e+01 + C-----26 R-----53 -.100000e+01 R-----55 -.100000e+01 + C-----26 R-----57 0.100000e+01 R-----59 -.100000e+01 + C-----26 R-----60 0.100000e+01 R-----62 0.100000e+01 + C-----26 R-----63 0.100000e+01 R-----65 -.100000e+01 + C-----26 R-----66 -.100000e+01 R-----68 0.100000e+01 + C-----26 R-----69 -.100000e+01 R-----70 0.100000e+01 + C-----26 R-----72 0.100000e+01 R-----74 -.100000e+01 + C-----26 R-----76 -.100000e+01 R-----77 -.100000e+01 + C-----26 R-----78 0.100000e+01 R-----81 0.100000e+01 + C-----26 R-----85 -.100000e+01 + C-----27 OBJ.FUNC 0.000000e+00 + C-----28 R------9 0.100000e+01 R-----14 0.100000e+01 + C-----28 R-----17 -.100000e+01 R-----18 0.100000e+01 + C-----28 R-----23 0.100000e+01 R-----24 -.100000e+01 + C-----28 R-----27 0.100000e+01 R-----28 -.100000e+01 + C-----28 R-----29 0.100000e+01 R-----31 -.100000e+01 + C-----28 R-----34 0.100000e+01 R-----36 0.100000e+01 + C-----28 R-----39 -.100000e+01 R-----42 -.100000e+01 + C-----28 R-----43 -.100000e+01 R-----48 0.100000e+01 + C-----28 R-----49 0.100000e+01 R-----52 -.100000e+01 + C-----28 R-----54 0.100000e+01 R-----57 -.100000e+01 + C-----28 R-----58 0.100000e+01 R-----59 0.100000e+01 + C-----28 R-----60 -.100000e+01 R-----62 -.100000e+01 + C-----28 R-----63 -.100000e+01 R-----64 0.100000e+01 + C-----28 R-----65 0.100000e+01 R-----66 0.100000e+01 + C-----28 R-----67 -.100000e+01 R-----68 -.100000e+01 + C-----28 R-----69 0.100000e+01 R-----70 -.100000e+01 + C-----28 R-----72 -.100000e+01 R-----74 0.100000e+01 + C-----28 R-----76 0.100000e+01 R-----77 0.100000e+01 + C-----28 R-----78 -.100000e+01 R-----81 -.100000e+01 + C-----28 R-----84 0.100000e+01 + C-----29 R-----44 0.100000e+01 R-----50 0.100000e+01 + C-----29 R-----58 -.100000e+01 + C-----30 OBJ.FUNC 0.000000e+00 + C-----31 R------1 -.100000e+01 R------4 -.100000e+01 + C-----31 R------5 0.100000e+01 R------6 -.100000e+01 + C-----31 R------7 0.100000e+01 R------8 -.100000e+01 + C-----31 R-----10 0.100000e+01 R-----11 -.100000e+01 + C-----31 R-----12 0.100000e+01 R-----17 0.100000e+01 + C-----31 R-----18 -.100000e+01 R-----24 0.100000e+01 + C-----31 R-----25 -.100000e+01 R-----27 -.100000e+01 + C-----31 R-----28 0.100000e+01 R-----31 0.100000e+01 + C-----31 R-----33 0.100000e+01 R-----34 -.100000e+01 + C-----31 R-----35 0.100000e+01 R-----39 0.100000e+01 + C-----31 R-----42 0.100000e+01 R-----45 -.100000e+01 + C-----31 R-----46 -.100000e+01 R-----47 0.100000e+01 + C-----31 R-----49 -.100000e+01 R-----50 -.100000e+01 + C-----31 R-----51 0.100000e+01 R-----52 0.100000e+01 + C-----31 R-----53 0.100000e+01 R-----54 -.100000e+01 + C-----31 R-----55 0.100000e+01 R-----64 -.100000e+01 + C-----31 R-----67 0.100000e+01 R-----84 -.100000e+01 + C-----31 R-----85 0.100000e+01 + C-----32 R------7 0.100000e+01 R------8 -.100000e+01 + C-----32 R------9 -.100000e+01 R-----10 -.100000e+01 + C-----32 R-----13 0.100000e+01 R-----15 0.100000e+01 + C-----32 R-----16 0.100000e+01 R-----17 0.100000e+01 + C-----32 R-----19 0.100000e+01 R-----20 -.100000e+01 + C-----32 R-----21 -.100000e+01 R-----22 0.100000e+01 + C-----32 R-----23 0.100000e+01 R-----27 -.100000e+01 + C-----32 R-----32 0.100000e+01 R-----33 -.100000e+01 + C-----32 R-----34 -.100000e+01 R-----39 0.100000e+01 + C-----32 R-----42 -.100000e+01 R-----43 -.100000e+01 + C-----32 R-----45 0.100000e+01 R-----46 0.100000e+01 + C-----32 R-----50 0.100000e+01 R-----51 -.100000e+01 + C-----32 R-----54 -.100000e+01 R-----57 0.100000e+01 + C-----32 R-----61 -.100000e+01 R-----62 0.100000e+01 + C-----32 R-----64 -.100000e+01 R-----65 -.100000e+01 + C-----32 R-----67 0.100000e+01 R-----68 -.100000e+01 + C-----32 R-----69 0.100000e+01 R-----71 0.100000e+01 + C-----32 R-----72 0.100000e+01 R-----73 0.100000e+01 + C-----32 R-----74 -.100000e+01 R-----75 0.100000e+01 + C-----32 R-----76 -.100000e+01 R-----77 -.100000e+01 + C-----32 R-----78 0.100000e+01 R-----81 0.100000e+01 + C-----32 R-----83 -.100000e+01 + C-----33 OBJ.FUNC 0.000000e+00 + C-----34 OBJ.FUNC 0.000000e+00 + C-----35 R------3 -.100000e+01 R------8 0.100000e+01 + C-----35 R------9 0.100000e+01 R-----11 0.100000e+01 + C-----35 R-----17 -.100000e+01 R-----20 0.100000e+01 + C-----35 R-----21 0.100000e+01 R-----29 0.100000e+01 + C-----35 R-----42 0.100000e+01 R-----43 0.100000e+01 + C-----35 R-----45 -.100000e+01 R-----51 0.100000e+01 + C-----35 R-----52 0.100000e+01 R-----53 0.100000e+01 + C-----35 R-----56 -.100000e+01 R-----61 0.100000e+01 + C-----35 R-----67 -.100000e+01 R-----68 0.100000e+01 + C-----35 R-----71 -.100000e+01 R-----73 -.100000e+01 + C-----35 R-----74 0.100000e+01 R-----77 0.100000e+01 + C-----35 R-----78 -.100000e+01 R-----80 0.100000e+01 + C-----35 R-----81 -.100000e+01 R-----83 0.100000e+01 + C-----36 R------3 0.100000e+01 R------7 -.100000e+01 + C-----36 R-----10 0.100000e+01 R-----11 -.100000e+01 + C-----36 R-----13 -.100000e+01 R-----15 -.100000e+01 + C-----36 R-----16 -.100000e+01 R-----19 -.100000e+01 + C-----36 R-----22 -.100000e+01 R-----23 -.100000e+01 + C-----36 R-----27 0.100000e+01 R-----29 -.100000e+01 + C-----36 R-----32 -.100000e+01 R-----33 0.100000e+01 + C-----36 R-----34 0.100000e+01 R-----39 -.100000e+01 + C-----36 R-----46 -.100000e+01 R-----50 -.100000e+01 + C-----36 R-----52 -.100000e+01 R-----53 -.100000e+01 + C-----36 R-----54 0.100000e+01 R-----56 0.100000e+01 + C-----36 R-----57 -.100000e+01 R-----62 -.100000e+01 + C-----36 R-----64 0.100000e+01 R-----65 0.100000e+01 + C-----36 R-----69 -.100000e+01 R-----72 -.100000e+01 + C-----36 R-----75 -.100000e+01 R-----76 0.100000e+01 + C-----36 R-----80 -.100000e+01 + C-----37 OBJ.FUNC 0.000000e+00 + C-----38 OBJ.FUNC 0.000000e+00 + C-----39 R------2 0.100000e+01 R------3 -.100000e+01 + C-----39 R------5 -.100000e+01 R------7 0.100000e+01 + C-----39 R------9 -.100000e+01 R-----10 0.100000e+01 + C-----39 R-----13 0.100000e+01 R-----17 -.100000e+01 + C-----39 R-----18 0.100000e+01 R-----20 -.100000e+01 + C-----39 R-----21 0.100000e+01 R-----22 0.100000e+01 + C-----39 R-----24 -.100000e+01 R-----25 0.100000e+01 + C-----39 R-----28 -.100000e+01 R-----29 0.100000e+01 + C-----39 R-----31 -.100000e+01 R-----32 0.100000e+01 + C-----39 R-----35 0.100000e+01 R-----38 0.100000e+01 + C-----39 R-----40 -.100000e+01 R-----41 -.100000e+01 + C-----39 R-----43 -.100000e+01 R-----44 -.100000e+01 + C-----39 R-----45 0.100000e+01 R-----47 0.100000e+01 + C-----39 R-----48 -.100000e+01 R-----49 0.100000e+01 + C-----39 R-----52 0.100000e+01 R-----56 -.100000e+01 + C-----39 R-----57 0.100000e+01 R-----58 0.100000e+01 + C-----39 R-----60 -.100000e+01 R-----63 0.100000e+01 + C-----39 R-----64 0.100000e+01 R-----66 -.100000e+01 + C-----39 R-----67 -.100000e+01 R-----70 0.100000e+01 + C-----39 R-----71 -.100000e+01 R-----73 -.100000e+01 + C-----39 R-----77 0.100000e+01 R-----78 -.100000e+01 + C-----39 R-----79 0.100000e+01 R-----80 0.100000e+01 + C-----39 R-----83 0.100000e+01 R-----85 0.100000e+01 + C-----40 R------2 -.100000e+01 R------7 -.100000e+01 + C-----40 R-----10 -.100000e+01 R-----11 0.100000e+01 + C-----40 R-----14 -.100000e+01 R-----15 -.100000e+01 + C-----40 R-----16 0.100000e+01 R-----19 -.100000e+01 + C-----40 R-----20 0.100000e+01 R-----23 -.100000e+01 + C-----40 R-----27 0.100000e+01 R-----29 -.100000e+01 + C-----40 R-----31 0.100000e+01 R-----35 -.100000e+01 + C-----40 R-----39 0.100000e+01 R-----41 0.100000e+01 + C-----40 R-----46 0.100000e+01 R-----50 0.100000e+01 + C-----40 R-----54 0.100000e+01 R-----65 0.100000e+01 + C-----40 R-----66 0.100000e+01 R-----69 0.100000e+01 + C-----40 R-----72 0.100000e+01 R-----77 -.100000e+01 + C-----40 R-----85 -.100000e+01 + C-----41 R-----37 -.100000e+01 R-----45 -.100000e+01 + C-----42 R------3 0.100000e+01 R------5 0.100000e+01 + C-----42 R------9 0.100000e+01 R-----11 -.100000e+01 + C-----42 R-----13 -.100000e+01 R-----14 0.100000e+01 + C-----42 R-----15 0.100000e+01 R-----16 -.100000e+01 + C-----42 R-----17 0.100000e+01 R-----18 -.100000e+01 + C-----42 R-----19 0.100000e+01 R-----21 -.100000e+01 + C-----42 R-----22 -.100000e+01 R-----23 0.100000e+01 + C-----42 R-----24 0.100000e+01 R-----25 -.100000e+01 + C-----42 R-----27 -.100000e+01 R-----28 0.100000e+01 + C-----42 R-----32 -.100000e+01 R-----37 0.100000e+01 + C-----42 R-----38 -.100000e+01 R-----39 -.100000e+01 + C-----42 R-----40 0.100000e+01 R-----43 0.100000e+01 + C-----42 R-----44 0.100000e+01 R-----46 -.100000e+01 + C-----42 R-----47 -.100000e+01 R-----48 0.100000e+01 + C-----42 R-----49 -.100000e+01 R-----50 -.100000e+01 + C-----42 R-----52 -.100000e+01 R-----54 -.100000e+01 + C-----42 R-----56 0.100000e+01 R-----57 -.100000e+01 + C-----42 R-----58 -.100000e+01 R-----60 0.100000e+01 + C-----42 R-----63 -.100000e+01 R-----64 -.100000e+01 + C-----42 R-----65 -.100000e+01 R-----67 0.100000e+01 + C-----42 R-----69 -.100000e+01 R-----70 -.100000e+01 + C-----42 R-----71 0.100000e+01 R-----72 -.100000e+01 + C-----42 R-----73 0.100000e+01 R-----78 0.100000e+01 + C-----42 R-----79 -.100000e+01 R-----80 -.100000e+01 + C-----42 R-----83 -.100000e+01 + C-----43 OBJ.FUNC 0.000000e+00 + C-----44 OBJ.FUNC 0.000000e+00 + C-----45 OBJ.FUNC 0.000000e+00 + C-----46 R------1 -.100000e+01 R------6 -.100000e+01 + C-----46 R------8 0.100000e+01 R-----12 -.100000e+01 + C-----46 R-----13 -.100000e+01 R-----16 0.100000e+01 + C-----46 R-----18 -.100000e+01 R-----21 -.100000e+01 + C-----46 R-----24 0.100000e+01 R-----25 -.100000e+01 + C-----46 R-----26 0.100000e+01 R-----27 -.100000e+01 + C-----46 R-----29 -.100000e+01 R-----32 -.100000e+01 + C-----46 R-----36 -.100000e+01 R-----37 0.100000e+01 + C-----46 R-----38 -.100000e+01 R-----43 0.100000e+01 + C-----46 R-----44 0.100000e+01 R-----45 0.100000e+01 + C-----46 R-----48 -.100000e+01 R-----50 -.100000e+01 + C-----46 R-----56 0.100000e+01 R-----57 -.100000e+01 + C-----46 R-----58 0.100000e+01 R-----59 -.100000e+01 + C-----46 R-----60 0.100000e+01 R-----61 0.100000e+01 + C-----46 R-----62 -.100000e+01 R-----63 0.100000e+01 + C-----46 R-----64 -.100000e+01 R-----65 0.100000e+01 + C-----46 R-----68 0.100000e+01 R-----69 -.100000e+01 + C-----46 R-----70 -.100000e+01 R-----74 0.100000e+01 + C-----46 R-----75 -.100000e+01 R-----77 -.100000e+01 + C-----46 R-----78 0.100000e+01 R-----82 -.100000e+01 + C-----46 R-----83 0.100000e+01 R-----84 -.100000e+01 + C-----47 OBJ.FUNC 0.000000e+00 + C-----48 OBJ.FUNC 0.000000e+00 + C-----49 R------1 0.100000e+01 R------6 0.100000e+01 + C-----49 R------8 -.100000e+01 R-----12 0.100000e+01 + C-----49 R-----13 0.100000e+01 R-----16 -.100000e+01 + C-----49 R-----18 0.100000e+01 R-----21 0.100000e+01 + C-----49 R-----24 -.100000e+01 R-----25 0.100000e+01 + C-----49 R-----26 -.100000e+01 R-----27 0.100000e+01 + C-----49 R-----29 0.100000e+01 R-----32 0.100000e+01 + C-----49 R-----36 0.100000e+01 R-----37 -.100000e+01 + C-----49 R-----38 0.100000e+01 R-----43 -.100000e+01 + C-----49 R-----44 -.100000e+01 R-----45 -.100000e+01 + C-----49 R-----48 0.100000e+01 R-----50 0.100000e+01 + C-----49 R-----56 -.100000e+01 R-----57 0.100000e+01 + C-----49 R-----58 -.100000e+01 R-----59 0.100000e+01 + C-----49 R-----60 -.100000e+01 R-----61 -.100000e+01 + C-----49 R-----62 0.100000e+01 R-----63 -.100000e+01 + C-----49 R-----64 0.100000e+01 R-----65 -.100000e+01 + C-----49 R-----68 -.100000e+01 R-----69 0.100000e+01 + C-----49 R-----70 0.100000e+01 R-----74 -.100000e+01 + C-----49 R-----75 0.100000e+01 R-----77 0.100000e+01 + C-----49 R-----78 -.100000e+01 R-----82 0.100000e+01 + C-----49 R-----83 -.100000e+01 R-----84 0.100000e+01 + C-----50 OBJ.FUNC 0.000000e+00 + C-----51 OBJ.FUNC 0.000000e+00 + C-----52 R------1 0.100000e+01 R------5 -.100000e+01 + C-----52 R-----33 -.100000e+01 R-----44 -.100000e+01 + C-----52 R-----66 -.100000e+01 R-----73 0.100000e+01 + C-----53 R------3 -.100000e+01 R------4 0.100000e+01 + C-----53 R------6 0.100000e+01 R------7 0.100000e+01 + C-----53 R-----11 0.100000e+01 R-----12 0.100000e+01 + C-----53 R-----13 0.100000e+01 R-----14 -.100000e+01 + C-----53 R-----15 0.100000e+01 R-----19 0.100000e+01 + C-----53 R-----20 -.100000e+01 R-----25 0.100000e+01 + C-----53 R-----26 -.100000e+01 R-----30 0.100000e+01 + C-----53 R-----35 0.100000e+01 R-----38 0.100000e+01 + C-----53 R-----40 0.100000e+01 R-----41 0.100000e+01 + C-----53 R-----42 -.100000e+01 R-----44 0.100000e+01 + C-----53 R-----46 0.100000e+01 R-----47 -.100000e+01 + C-----53 R-----49 -.100000e+01 R-----51 0.100000e+01 + C-----53 R-----52 0.100000e+01 R-----53 0.100000e+01 + C-----53 R-----56 -.100000e+01 R-----57 0.100000e+01 + C-----53 R-----58 -.100000e+01 R-----61 -.100000e+01 + C-----53 R-----62 0.100000e+01 R-----65 -.100000e+01 + C-----53 R-----66 0.100000e+01 R-----69 0.100000e+01 + C-----53 R-----70 0.100000e+01 R-----71 -.100000e+01 + C-----53 R-----72 0.100000e+01 R-----73 -.100000e+01 + C-----53 R-----74 -.100000e+01 R-----75 0.100000e+01 + C-----53 R-----83 -.100000e+01 R-----84 0.100000e+01 + C-----54 OBJ.FUNC 0.000000e+00 + C-----55 R------1 -.100000e+01 R------3 0.100000e+01 + C-----55 R------4 -.100000e+01 R------5 0.100000e+01 + C-----55 R------6 -.100000e+01 R------7 -.100000e+01 + C-----55 R-----11 -.100000e+01 R-----12 -.100000e+01 + C-----55 R-----13 -.100000e+01 R-----14 0.100000e+01 + C-----55 R-----15 -.100000e+01 R-----19 -.100000e+01 + C-----55 R-----20 0.100000e+01 R-----25 -.100000e+01 + C-----55 R-----26 0.100000e+01 R-----30 -.100000e+01 + C-----55 R-----33 0.100000e+01 R-----35 -.100000e+01 + C-----55 R-----38 -.100000e+01 R-----40 -.100000e+01 + C-----55 R-----41 -.100000e+01 R-----42 0.100000e+01 + C-----55 R-----46 -.100000e+01 R-----47 0.100000e+01 + C-----55 R-----49 0.100000e+01 R-----51 -.100000e+01 + C-----55 R-----52 -.100000e+01 R-----53 -.100000e+01 + C-----55 R-----56 0.100000e+01 R-----57 -.100000e+01 + C-----55 R-----58 0.100000e+01 R-----61 0.100000e+01 + C-----55 R-----62 -.100000e+01 R-----65 0.100000e+01 + C-----55 R-----69 -.100000e+01 R-----70 -.100000e+01 + C-----55 R-----71 0.100000e+01 R-----72 -.100000e+01 + C-----55 R-----74 0.100000e+01 R-----75 -.100000e+01 + C-----55 R-----83 0.100000e+01 R-----84 -.100000e+01 + C-----56 OBJ.FUNC 0.000000e+00 + C-----57 R------1 0.100000e+01 R-----25 -.100000e+01 + C-----57 R-----32 -.100000e+01 R-----73 0.100000e+01 + C-----57 R-----75 -.100000e+01 + C-----58 OBJ.FUNC 0.000000e+00 + C-----59 R------2 -.100000e+01 R------5 0.100000e+01 + C-----59 R------6 -.100000e+01 R------8 -.100000e+01 + C-----59 R-----10 0.100000e+01 R-----14 0.100000e+01 + C-----59 R-----15 -.100000e+01 R-----16 -.100000e+01 + C-----59 R-----17 -.100000e+01 R-----18 0.100000e+01 + C-----59 R-----21 0.100000e+01 R-----22 -.100000e+01 + C-----59 R-----23 -.100000e+01 R-----27 0.100000e+01 + C-----59 R-----28 -.100000e+01 R-----29 0.100000e+01 + C-----59 R-----31 -.100000e+01 R-----32 0.100000e+01 + C-----59 R-----33 0.100000e+01 R-----34 0.100000e+01 + C-----59 R-----37 -.100000e+01 R-----39 -.100000e+01 + C-----59 R-----40 -.100000e+01 R-----42 0.100000e+01 + C-----59 R-----43 -.100000e+01 R-----48 0.100000e+01 + C-----59 R-----50 0.100000e+01 R-----51 -.100000e+01 + C-----59 R-----54 0.100000e+01 R-----55 -.100000e+01 + C-----59 R-----59 0.100000e+01 R-----60 -.100000e+01 + C-----59 R-----63 -.100000e+01 R-----64 0.100000e+01 + C-----59 R-----67 -.100000e+01 R-----72 -.100000e+01 + C-----59 R-----73 -.100000e+01 R-----76 0.100000e+01 + C-----59 R-----77 0.100000e+01 R-----78 -.100000e+01 + C-----59 R-----79 0.100000e+01 R-----80 0.100000e+01 + C-----59 R-----81 -.100000e+01 R-----85 0.100000e+01 + C-----60 R------2 0.100000e+01 R------5 -.100000e+01 + C-----60 R------6 0.100000e+01 R-----10 -.100000e+01 + C-----60 R-----12 -.100000e+01 R-----14 -.100000e+01 + C-----60 R-----16 0.100000e+01 R-----21 -.100000e+01 + C-----60 R-----23 0.100000e+01 R-----25 0.100000e+01 + C-----60 R-----27 -.100000e+01 R-----28 0.100000e+01 + C-----60 R-----30 -.100000e+01 R-----31 0.100000e+01 + C-----60 R-----33 -.100000e+01 R-----37 0.100000e+01 + C-----60 R-----39 0.100000e+01 R-----42 -.100000e+01 + C-----60 R-----46 -.100000e+01 R-----48 -.100000e+01 + C-----60 R-----51 0.100000e+01 R-----53 0.100000e+01 + C-----60 R-----54 -.100000e+01 R-----55 0.100000e+01 + C-----60 R-----59 -.100000e+01 R-----60 0.100000e+01 + C-----60 R-----63 0.100000e+01 R-----64 -.100000e+01 + C-----60 R-----67 0.100000e+01 R-----68 -.100000e+01 + C-----60 R-----70 0.100000e+01 R-----72 0.100000e+01 + C-----60 R-----75 0.100000e+01 R-----76 -.100000e+01 + C-----60 R-----77 -.100000e+01 R-----78 0.100000e+01 + C-----60 R-----79 -.100000e+01 R-----81 0.100000e+01 + C-----60 R-----85 -.100000e+01 + C-----61 R------1 -.100000e+01 R------8 0.100000e+01 + C-----61 R-----12 0.100000e+01 R-----15 0.100000e+01 + C-----61 R-----17 0.100000e+01 R-----18 -.100000e+01 + C-----61 R-----22 0.100000e+01 R-----29 -.100000e+01 + C-----61 R-----30 0.100000e+01 R-----34 -.100000e+01 + C-----61 R-----40 0.100000e+01 R-----43 0.100000e+01 + C-----61 R-----46 0.100000e+01 R-----50 -.100000e+01 + C-----61 R-----53 -.100000e+01 R-----68 0.100000e+01 + C-----61 R-----70 -.100000e+01 R-----80 -.100000e+01 + C-----62 R------2 -.100000e+01 R------4 -.100000e+01 + C-----62 R------5 -.100000e+01 R------9 -.100000e+01 + C-----62 R-----13 0.100000e+01 R-----16 0.100000e+01 + C-----62 R-----20 -.100000e+01 R-----21 -.100000e+01 + C-----62 R-----22 0.100000e+01 R-----23 -.100000e+01 + C-----62 R-----27 -.100000e+01 R-----28 -.100000e+01 + C-----62 R-----31 -.100000e+01 R-----32 0.100000e+01 + C-----62 R-----34 -.100000e+01 R-----40 -.100000e+01 + C-----62 R-----42 -.100000e+01 R-----45 0.100000e+01 + C-----62 R-----46 0.100000e+01 R-----47 -.100000e+01 + C-----62 R-----48 -.100000e+01 R-----49 0.100000e+01 + C-----62 R-----52 -.100000e+01 R-----54 -.100000e+01 + C-----62 R-----58 0.100000e+01 R-----59 -.100000e+01 + C-----62 R-----60 0.100000e+01 R-----61 -.100000e+01 + C-----62 R-----62 0.100000e+01 R-----65 -.100000e+01 + C-----62 R-----66 -.100000e+01 R-----67 0.100000e+01 + C-----62 R-----68 -.100000e+01 R-----70 -.100000e+01 + C-----62 R-----73 0.100000e+01 R-----78 0.100000e+01 + C-----62 R-----80 0.100000e+01 R-----84 -.100000e+01 + C-----62 R-----85 0.100000e+01 + C-----63 OBJ.FUNC 0.000000e+00 + C-----64 R------3 -.100000e+01 R------4 0.100000e+01 + C-----64 R------5 0.100000e+01 R-----11 0.100000e+01 + C-----64 R-----17 -.100000e+01 R-----20 0.100000e+01 + C-----64 R-----26 -.100000e+01 R-----27 0.100000e+01 + C-----64 R-----28 0.100000e+01 R-----31 0.100000e+01 + C-----64 R-----37 -.100000e+01 R-----45 -.100000e+01 + C-----64 R-----50 0.100000e+01 R-----54 0.100000e+01 + C-----64 R-----60 -.100000e+01 R-----61 0.100000e+01 + C-----64 R-----65 0.100000e+01 R-----66 0.100000e+01 + C-----64 R-----67 -.100000e+01 R-----69 0.100000e+01 + C-----64 R-----70 0.100000e+01 R-----71 -.100000e+01 + C-----64 R-----73 -.100000e+01 R-----75 0.100000e+01 + C-----64 R-----78 -.100000e+01 R-----79 0.100000e+01 + C-----64 R-----80 -.100000e+01 R-----81 -.100000e+01 + C-----64 R-----82 0.100000e+01 R-----84 0.100000e+01 + C-----65 OBJ.FUNC 0.000000e+00 + C-----66 R------2 0.100000e+01 R------3 0.100000e+01 + C-----66 R------9 0.100000e+01 R-----11 -.100000e+01 + C-----66 R-----13 -.100000e+01 R-----16 -.100000e+01 + C-----66 R-----17 0.100000e+01 R-----21 0.100000e+01 + C-----66 R-----22 -.100000e+01 R-----23 0.100000e+01 + C-----66 R-----26 0.100000e+01 R-----32 -.100000e+01 + C-----66 R-----34 0.100000e+01 R-----37 0.100000e+01 + C-----66 R-----40 0.100000e+01 R-----42 0.100000e+01 + C-----66 R-----46 -.100000e+01 R-----47 0.100000e+01 + C-----66 R-----48 0.100000e+01 R-----49 -.100000e+01 + C-----66 R-----50 -.100000e+01 R-----52 0.100000e+01 + C-----66 R-----58 -.100000e+01 R-----59 0.100000e+01 + C-----66 R-----62 -.100000e+01 R-----68 0.100000e+01 + C-----66 R-----69 -.100000e+01 R-----71 0.100000e+01 + C-----66 R-----75 -.100000e+01 R-----79 -.100000e+01 + C-----66 R-----81 0.100000e+01 R-----82 -.100000e+01 + C-----66 R-----85 -.100000e+01 + C-----67 OBJ.FUNC 0.000000e+00 + C-----68 R------2 0.100000e+01 R------3 -.100000e+01 + C-----68 R------4 0.100000e+01 R------6 0.100000e+01 + C-----68 R------7 0.100000e+01 R-----12 0.100000e+01 + C-----68 R-----15 -.100000e+01 R-----16 0.100000e+01 + C-----68 R-----19 0.100000e+01 R-----22 0.100000e+01 + C-----68 R-----23 0.100000e+01 R-----25 0.100000e+01 + C-----68 R-----26 -.100000e+01 R-----28 0.100000e+01 + C-----68 R-----29 -.100000e+01 R-----31 0.100000e+01 + C-----68 R-----32 -.100000e+01 R-----35 0.100000e+01 + C-----68 R-----38 0.100000e+01 R-----39 0.100000e+01 + C-----68 R-----44 0.100000e+01 R-----46 -.100000e+01 + C-----68 R-----49 -.100000e+01 R-----50 -.100000e+01 + C-----68 R-----53 -.100000e+01 R-----55 0.100000e+01 + C-----68 R-----56 -.100000e+01 R-----58 -.100000e+01 + C-----68 R-----60 -.100000e+01 R-----62 0.100000e+01 + C-----68 R-----66 0.100000e+01 R-----70 0.100000e+01 + C-----68 R-----71 -.100000e+01 R-----73 -.100000e+01 + C-----68 R-----75 0.100000e+01 R-----78 0.100000e+01 + C-----68 R-----79 -.100000e+01 R-----80 -.100000e+01 + C-----68 R-----81 0.100000e+01 R-----84 0.100000e+01 + C-----68 R-----85 -.100000e+01 + C-----69 R------1 0.100000e+01 R-----23 -.100000e+01 + C-----69 R-----39 -.100000e+01 R-----72 -.100000e+01 + C-----69 R-----73 0.100000e+01 R-----75 -.100000e+01 + C-----69 R-----82 -.100000e+01 + C-----70 R------1 -.100000e+01 R-----50 0.100000e+01 + C-----71 R-----59 0.100000e+01 + C-----72 R------2 -.100000e+01 R------3 0.100000e+01 + C-----72 R------4 -.100000e+01 R------6 -.100000e+01 + C-----72 R------7 -.100000e+01 R-----11 0.100000e+01 + C-----72 R-----12 -.100000e+01 R-----15 0.100000e+01 + C-----72 R-----16 -.100000e+01 R-----19 -.100000e+01 + C-----72 R-----20 -.100000e+01 R-----22 -.100000e+01 + C-----72 R-----25 -.100000e+01 R-----26 0.100000e+01 + C-----72 R-----28 -.100000e+01 R-----29 0.100000e+01 + C-----72 R-----31 -.100000e+01 R-----32 0.100000e+01 + C-----72 R-----33 -.100000e+01 R-----35 -.100000e+01 + C-----72 R-----38 -.100000e+01 R-----44 -.100000e+01 + C-----72 R-----46 0.100000e+01 R-----49 0.100000e+01 + C-----72 R-----52 0.100000e+01 R-----53 0.100000e+01 + C-----72 R-----55 -.100000e+01 R-----56 0.100000e+01 + C-----72 R-----58 0.100000e+01 R-----59 -.100000e+01 + C-----72 R-----60 0.100000e+01 R-----62 -.100000e+01 + C-----72 R-----66 -.100000e+01 R-----70 -.100000e+01 + C-----72 R-----71 0.100000e+01 R-----72 0.100000e+01 + C-----72 R-----78 -.100000e+01 R-----79 0.100000e+01 + C-----72 R-----80 0.100000e+01 R-----81 -.100000e+01 + C-----72 R-----82 0.100000e+01 R-----84 -.100000e+01 + C-----72 R-----85 0.100000e+01 + C-----73 R-----11 -.100000e+01 R-----20 0.100000e+01 + C-----73 R-----33 0.100000e+01 R-----52 -.100000e+01 + C-----74 R------3 -.100000e+01 R-----12 0.100000e+01 + C-----74 R-----16 0.100000e+01 R-----17 -.100000e+01 + C-----74 R-----18 0.100000e+01 R-----22 0.100000e+01 + C-----74 R-----24 0.100000e+01 R-----26 -.100000e+01 + C-----74 R-----29 -.100000e+01 R-----32 -.100000e+01 + C-----74 R-----33 0.100000e+01 R-----35 0.100000e+01 + C-----74 R-----37 -.100000e+01 R-----46 -.100000e+01 + C-----74 R-----56 -.100000e+01 R-----58 0.100000e+01 + C-----74 R-----62 0.100000e+01 R-----64 0.100000e+01 + C-----74 R-----71 -.100000e+01 R-----74 0.100000e+01 + C-----74 R-----80 0.100000e+01 R-----81 -.100000e+01 + C-----74 R-----83 0.100000e+01 + C-----75 OBJ.FUNC 0.000000e+00 + C-----76 R------2 -.100000e+01 R------9 -.100000e+01 + C-----76 R-----11 0.100000e+01 R-----12 -.100000e+01 + C-----76 R-----13 0.100000e+01 R-----18 -.100000e+01 + C-----76 R-----21 -.100000e+01 R-----24 -.100000e+01 + C-----76 R-----32 0.100000e+01 R-----33 -.100000e+01 + C-----76 R-----34 -.100000e+01 R-----35 -.100000e+01 + C-----76 R-----39 0.100000e+01 R-----40 -.100000e+01 + C-----76 R-----42 -.100000e+01 R-----46 0.100000e+01 + C-----76 R-----47 -.100000e+01 R-----48 -.100000e+01 + C-----76 R-----49 0.100000e+01 R-----50 0.100000e+01 + C-----76 R-----56 0.100000e+01 R-----59 -.100000e+01 + C-----76 R-----64 -.100000e+01 R-----68 -.100000e+01 + C-----76 R-----69 0.100000e+01 R-----72 0.100000e+01 + C-----76 R-----74 -.100000e+01 R-----75 0.100000e+01 + C-----76 R-----79 0.100000e+01 R-----82 0.100000e+01 + C-----76 R-----83 -.100000e+01 R-----85 0.100000e+01 + C-----77 OBJ.FUNC 0.000000e+00 + C-----78 OBJ.FUNC 0.000000e+00 + C-----79 R------2 0.100000e+01 R------3 0.100000e+01 + C-----79 R------9 0.100000e+01 R-----11 -.100000e+01 + C-----79 R-----13 -.100000e+01 R-----16 -.100000e+01 + C-----79 R-----17 0.100000e+01 R-----21 0.100000e+01 + C-----79 R-----22 -.100000e+01 R-----26 0.100000e+01 + C-----79 R-----29 0.100000e+01 R-----34 0.100000e+01 + C-----79 R-----37 0.100000e+01 R-----39 -.100000e+01 + C-----79 R-----40 0.100000e+01 R-----42 0.100000e+01 + C-----79 R-----47 0.100000e+01 R-----48 0.100000e+01 + C-----79 R-----49 -.100000e+01 R-----50 -.100000e+01 + C-----79 R-----58 -.100000e+01 R-----59 0.100000e+01 + C-----79 R-----62 -.100000e+01 R-----68 0.100000e+01 + C-----79 R-----69 -.100000e+01 R-----71 0.100000e+01 + C-----79 R-----72 -.100000e+01 R-----75 -.100000e+01 + C-----79 R-----79 -.100000e+01 R-----80 -.100000e+01 + C-----79 R-----81 0.100000e+01 R-----82 -.100000e+01 + C-----79 R-----85 -.100000e+01 + C-----80 OBJ.FUNC 0.000000e+00 + C-----81 OBJ.FUNC 0.000000e+00 + C-----82 R------1 0.100000e+01 R------3 -.100000e+01 + C-----82 R-----37 -.100000e+01 R-----73 0.100000e+01 + C-----83 OBJ.FUNC 0.000000e+00 + C-----84 R------2 0.100000e+01 R------5 -.100000e+01 + C-----84 R------6 0.100000e+01 R------8 0.100000e+01 + C-----84 R------9 0.100000e+01 R-----10 0.100000e+01 + C-----84 R-----11 -.100000e+01 R-----24 0.100000e+01 + C-----84 R-----25 0.100000e+01 R-----28 -.100000e+01 + C-----84 R-----30 0.100000e+01 R-----32 -.100000e+01 + C-----84 R-----33 0.100000e+01 R-----34 0.100000e+01 + C-----84 R-----35 0.100000e+01 R-----36 0.100000e+01 + C-----84 R-----37 0.100000e+01 R-----38 0.100000e+01 + C-----84 R-----39 -.100000e+01 R-----40 0.100000e+01 + C-----84 R-----41 0.100000e+01 R-----44 0.100000e+01 + C-----84 R-----47 0.100000e+01 R-----48 0.100000e+01 + C-----84 R-----50 -.100000e+01 R-----51 0.100000e+01 + C-----84 R-----53 -.100000e+01 R-----56 -.100000e+01 + C-----84 R-----59 0.100000e+01 R-----63 0.100000e+01 + C-----84 R-----64 0.100000e+01 R-----67 -.100000e+01 + C-----84 R-----68 0.100000e+01 R-----69 -.100000e+01 + C-----84 R-----71 -.100000e+01 R-----73 -.100000e+01 + C-----84 R-----74 0.100000e+01 R-----75 -.100000e+01 + C-----84 R-----77 0.100000e+01 R-----78 -.100000e+01 + C-----84 R-----79 -.100000e+01 R-----82 -.100000e+01 + C-----85 R------1 -.100000e+01 R------2 -.100000e+01 + C-----85 R------3 0.100000e+01 R------5 0.100000e+01 + C-----85 R------6 -.100000e+01 R------8 -.100000e+01 + C-----85 R------9 -.100000e+01 R-----10 -.100000e+01 + C-----85 R-----11 0.100000e+01 R-----24 -.100000e+01 + C-----85 R-----25 -.100000e+01 R-----28 0.100000e+01 + C-----85 R-----30 -.100000e+01 R-----32 0.100000e+01 + C-----85 R-----33 -.100000e+01 R-----34 -.100000e+01 + C-----85 R-----35 -.100000e+01 R-----36 -.100000e+01 + C-----85 R-----38 -.100000e+01 R-----39 0.100000e+01 + C-----85 R-----40 -.100000e+01 R-----41 -.100000e+01 + C-----85 R-----44 -.100000e+01 R-----47 -.100000e+01 + C-----85 R-----48 -.100000e+01 R-----50 0.100000e+01 + C-----85 R-----51 -.100000e+01 R-----53 0.100000e+01 + C-----85 R-----56 0.100000e+01 R-----59 -.100000e+01 + C-----85 R-----63 -.100000e+01 R-----64 -.100000e+01 + C-----85 R-----67 0.100000e+01 R-----68 -.100000e+01 + C-----85 R-----69 0.100000e+01 R-----71 0.100000e+01 + C-----85 R-----74 -.100000e+01 R-----75 0.100000e+01 + C-----85 R-----77 -.100000e+01 R-----78 0.100000e+01 + C-----85 R-----79 0.100000e+01 R-----82 0.100000e+01 + C-----86 OBJ.FUNC 0.000000e+00 + C-----87 R------1 0.100000e+01 R------3 -.100000e+01 + C-----87 R------5 0.100000e+01 R-----13 0.100000e+01 + C-----87 R-----14 0.100000e+01 R-----20 0.100000e+01 + C-----87 R-----22 0.100000e+01 R-----23 0.100000e+01 + C-----87 R-----26 -.100000e+01 R-----27 0.100000e+01 + C-----87 R-----29 0.100000e+01 R-----31 0.100000e+01 + C-----87 R-----33 0.100000e+01 R-----37 -.100000e+01 + C-----87 R-----38 0.100000e+01 R-----39 -.100000e+01 + C-----87 R-----45 -.100000e+01 R-----48 0.100000e+01 + C-----87 R-----55 0.100000e+01 R-----56 -.100000e+01 + C-----87 R-----57 0.100000e+01 R-----59 0.100000e+01 + C-----87 R-----60 -.100000e+01 R-----66 0.100000e+01 + C-----87 R-----73 -.100000e+01 R-----75 0.100000e+01 + C-----87 R-----78 -.100000e+01 R-----80 -.100000e+01 + C-----87 R-----81 -.100000e+01 R-----82 0.100000e+01 + C-----88 R------1 -.100000e+01 R------2 -.100000e+01 + C-----88 R------7 0.100000e+01 R------8 -.100000e+01 + C-----88 R-----16 0.100000e+01 R-----18 -.100000e+01 + C-----88 R-----19 0.100000e+01 R-----20 -.100000e+01 + C-----88 R-----24 -.100000e+01 R-----30 -.100000e+01 + C-----88 R-----31 -.100000e+01 R-----33 -.100000e+01 + C-----88 R-----35 0.100000e+01 R-----36 -.100000e+01 + C-----88 R-----40 -.100000e+01 R-----44 0.100000e+01 + C-----88 R-----47 -.100000e+01 R-----54 -.100000e+01 + C-----88 R-----59 -.100000e+01 R-----60 0.100000e+01 + C-----88 R-----61 -.100000e+01 R-----63 -.100000e+01 + C-----88 R-----65 -.100000e+01 R-----66 -.100000e+01 + C-----88 R-----74 -.100000e+01 R-----76 0.100000e+01 + C-----88 R-----78 0.100000e+01 R-----80 0.100000e+01 + C-----88 R-----84 -.100000e+01 R-----85 0.100000e+01 + C-----89 R------2 0.100000e+01 R------3 0.100000e+01 + C-----89 R------5 -.100000e+01 R------7 -.100000e+01 + C-----89 R------8 0.100000e+01 R-----13 -.100000e+01 + C-----89 R-----14 -.100000e+01 R-----16 -.100000e+01 + C-----89 R-----18 0.100000e+01 R-----19 -.100000e+01 + C-----89 R-----22 -.100000e+01 R-----23 -.100000e+01 + C-----89 R-----24 0.100000e+01 R-----26 0.100000e+01 + C-----89 R-----27 -.100000e+01 R-----29 -.100000e+01 + C-----89 R-----30 0.100000e+01 R-----35 -.100000e+01 + C-----89 R-----36 0.100000e+01 R-----37 0.100000e+01 + C-----89 R-----38 -.100000e+01 R-----39 0.100000e+01 + C-----89 R-----40 0.100000e+01 R-----44 -.100000e+01 + C-----89 R-----45 0.100000e+01 R-----47 0.100000e+01 + C-----89 R-----48 -.100000e+01 R-----54 0.100000e+01 + C-----89 R-----55 -.100000e+01 R-----56 0.100000e+01 + C-----89 R-----57 -.100000e+01 R-----61 0.100000e+01 + C-----89 R-----63 0.100000e+01 R-----65 0.100000e+01 + C-----89 R-----73 0.100000e+01 R-----74 0.100000e+01 + C-----89 R-----75 -.100000e+01 R-----76 -.100000e+01 + C-----89 R-----81 0.100000e+01 R-----82 -.100000e+01 + C-----89 R-----84 0.100000e+01 R-----85 -.100000e+01 + C-----90 OBJ.FUNC 0.000000e+00 + C-----91 OBJ.FUNC 0.000000e+00 + C-----92 R------1 0.100000e+01 R------6 0.100000e+01 + C-----92 R------7 -.100000e+01 R------8 0.100000e+01 + C-----92 R------9 -.100000e+01 R-----10 -.100000e+01 + C-----92 R-----14 -.100000e+01 R-----15 -.100000e+01 + C-----92 R-----18 -.100000e+01 R-----21 -.100000e+01 + C-----92 R-----22 -.100000e+01 R-----23 -.100000e+01 + C-----92 R-----24 0.100000e+01 R-----27 0.100000e+01 + C-----92 R-----39 0.100000e+01 R-----40 -.100000e+01 + C-----92 R-----42 -.100000e+01 R-----43 0.100000e+01 + C-----92 R-----45 0.100000e+01 R-----48 -.100000e+01 + C-----92 R-----49 -.100000e+01 R-----52 -.100000e+01 + C-----92 R-----56 0.100000e+01 R-----58 -.100000e+01 + C-----92 R-----59 -.100000e+01 R-----60 0.100000e+01 + C-----92 R-----62 0.100000e+01 R-----64 -.100000e+01 + C-----92 R-----65 0.100000e+01 R-----66 0.100000e+01 + C-----92 R-----67 0.100000e+01 R-----68 -.100000e+01 + C-----92 R-----72 0.100000e+01 R-----73 0.100000e+01 + C-----92 R-----74 -.100000e+01 R-----76 -.100000e+01 + C-----92 R-----77 -.100000e+01 R-----78 0.100000e+01 + C-----92 R-----85 -.100000e+01 + C-----93 R------1 -.100000e+01 R------8 -.100000e+01 + C-----93 R-----10 0.100000e+01 R-----33 0.100000e+01 + C-----93 R-----65 -.100000e+01 R-----85 0.100000e+01 + C-----94 R------3 -.100000e+01 R-----13 0.100000e+01 + C-----94 R-----17 -.100000e+01 R-----18 0.100000e+01 + C-----94 R-----22 0.100000e+01 R-----26 -.100000e+01 + C-----94 R-----32 0.100000e+01 R-----36 0.100000e+01 + C-----94 R-----37 -.100000e+01 R-----39 -.100000e+01 + C-----94 R-----45 -.100000e+01 R-----49 0.100000e+01 + C-----94 R-----56 -.100000e+01 R-----58 0.100000e+01 + C-----94 R-----60 -.100000e+01 R-----64 0.100000e+01 + C-----94 R-----67 -.100000e+01 R-----71 -.100000e+01 + C-----94 R-----73 -.100000e+01 R-----74 0.100000e+01 + C-----94 R-----76 0.100000e+01 R-----77 0.100000e+01 + C-----94 R-----78 -.100000e+01 R-----81 -.100000e+01 + C-----94 R-----83 0.100000e+01 + C-----95 OBJ.FUNC 0.000000e+00 + C-----96 OBJ.FUNC 0.000000e+00 + C-----97 R------3 0.100000e+01 R------6 -.100000e+01 + C-----97 R------7 0.100000e+01 R------9 0.100000e+01 + C-----97 R-----13 -.100000e+01 R-----14 0.100000e+01 + C-----97 R-----15 0.100000e+01 R-----17 0.100000e+01 + C-----97 R-----21 0.100000e+01 R-----23 0.100000e+01 + C-----97 R-----24 -.100000e+01 R-----26 0.100000e+01 + C-----97 R-----27 -.100000e+01 R-----32 -.100000e+01 + C-----97 R-----33 -.100000e+01 R-----36 -.100000e+01 + C-----97 R-----37 0.100000e+01 R-----40 0.100000e+01 + C-----97 R-----42 0.100000e+01 R-----43 -.100000e+01 + C-----97 R-----48 0.100000e+01 R-----52 0.100000e+01 + C-----97 R-----59 0.100000e+01 R-----62 -.100000e+01 + C-----97 R-----66 -.100000e+01 R-----68 0.100000e+01 + C-----97 R-----71 0.100000e+01 R-----72 -.100000e+01 + C-----97 R-----81 0.100000e+01 R-----83 -.100000e+01 + C-----98 R------3 -.100000e+01 R------8 0.100000e+01 + C-----98 R------9 0.100000e+01 R-----10 0.100000e+01 + C-----98 R-----11 0.100000e+01 R-----15 0.100000e+01 + C-----98 R-----17 -.100000e+01 R-----24 0.100000e+01 + C-----98 R-----26 -.100000e+01 R-----30 0.100000e+01 + C-----98 R-----34 0.100000e+01 R-----39 -.100000e+01 + C-----98 R-----45 -.100000e+01 R-----46 0.100000e+01 + C-----98 R-----52 0.100000e+01 R-----53 0.100000e+01 + C-----98 R-----58 0.100000e+01 R-----60 -.100000e+01 + C-----98 R-----61 0.100000e+01 R-----63 0.100000e+01 + C-----98 R-----67 -.100000e+01 R-----71 -.100000e+01 + C-----98 R-----72 0.100000e+01 R-----73 -.100000e+01 + C-----98 R-----74 0.100000e+01 R-----78 -.100000e+01 + C-----98 R-----79 0.100000e+01 R-----80 -.100000e+01 + C-----98 R-----81 -.100000e+01 R-----83 0.100000e+01 + C-----99 R------4 0.100000e+01 R------5 -.100000e+01 + C-----99 R-----10 -.100000e+01 R-----11 -.100000e+01 + C-----99 R-----12 -.100000e+01 R-----14 -.100000e+01 + C-----99 R-----15 -.100000e+01 R-----18 -.100000e+01 + C-----99 R-----19 0.100000e+01 R-----20 -.100000e+01 + C-----99 R-----21 -.100000e+01 R-----22 -.100000e+01 + C-----99 R-----23 -.100000e+01 R-----26 0.100000e+01 + C-----99 R-----30 -.100000e+01 R-----31 -.100000e+01 + C-----99 R-----42 -.100000e+01 R-----43 0.100000e+01 + C-----99 R-----44 0.100000e+01 R-----45 0.100000e+01 + C-----99 R-----46 -.100000e+01 R-----48 -.100000e+01 + C-----99 R-----49 -.100000e+01 R-----50 -.100000e+01 + C-----99 R-----52 -.100000e+01 R-----54 0.100000e+01 + C-----99 R-----55 -.100000e+01 R-----58 -.100000e+01 + C-----99 R-----60 0.100000e+01 R-----63 -.100000e+01 + C-----99 R-----64 -.100000e+01 R-----65 0.100000e+01 + C-----99 R-----68 0.100000e+01 R-----69 -.100000e+01 + C-----99 R-----70 -.100000e+01 R-----75 -.100000e+01 + C-----99 R-----76 0.100000e+01 R-----78 0.100000e+01 + C-----99 R-----79 -.100000e+01 R-----80 0.100000e+01 + C-----99 R-----82 -.100000e+01 + C----100 R------1 0.100000e+01 R------4 -.100000e+01 + C----100 R------5 0.100000e+01 R------9 -.100000e+01 + C----100 R-----13 0.100000e+01 R-----14 0.100000e+01 + C----100 R-----17 0.100000e+01 R-----20 0.100000e+01 + C----100 R-----22 0.100000e+01 R-----23 0.100000e+01 + C----100 R-----25 -.100000e+01 R-----27 0.100000e+01 + C----100 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----100 R-----31 0.100000e+01 R-----33 0.100000e+01 + C----100 R-----44 -.100000e+01 R-----48 0.100000e+01 + C----100 R-----54 -.100000e+01 R-----55 0.100000e+01 + C----100 R-----57 0.100000e+01 R-----61 -.100000e+01 + C----100 R-----66 0.100000e+01 R-----67 0.100000e+01 + C----100 R-----68 -.100000e+01 R-----71 0.100000e+01 + C----100 R-----74 -.100000e+01 R-----75 0.100000e+01 + C----100 R-----76 -.100000e+01 R-----77 -.100000e+01 + C----100 R-----82 0.100000e+01 R-----84 -.100000e+01 + C----101 OBJ.FUNC 0.000000e+00 + C----102 R------1 -.100000e+01 R------3 0.100000e+01 + C----102 R------8 -.100000e+01 R-----12 0.100000e+01 + C----102 R-----13 -.100000e+01 R-----18 0.100000e+01 + C----102 R-----19 -.100000e+01 R-----21 0.100000e+01 + C----102 R-----24 -.100000e+01 R-----25 0.100000e+01 + C----102 R-----27 -.100000e+01 R-----28 0.100000e+01 + C----102 R-----29 -.100000e+01 R-----33 -.100000e+01 + C----102 R-----34 -.100000e+01 R-----39 0.100000e+01 + C----102 R-----42 0.100000e+01 R-----43 -.100000e+01 + C----102 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----102 R-----53 -.100000e+01 R-----57 -.100000e+01 + C----102 R-----64 0.100000e+01 R-----65 -.100000e+01 + C----102 R-----66 -.100000e+01 R-----69 0.100000e+01 + C----102 R-----70 0.100000e+01 R-----72 -.100000e+01 + C----102 R-----73 0.100000e+01 R-----77 0.100000e+01 + C----102 R-----81 0.100000e+01 R-----83 -.100000e+01 + C----102 R-----84 0.100000e+01 + C----103 OBJ.FUNC 0.000000e+00 + C----104 OBJ.FUNC 0.000000e+00 + C----105 OBJ.FUNC 0.000000e+00 + C----106 R------1 -.100000e+01 R------2 -.100000e+01 + C----106 R------5 -.100000e+01 R------6 -.100000e+01 + C----106 R------7 0.100000e+01 R-----11 0.100000e+01 + C----106 R-----12 -.100000e+01 R-----15 0.100000e+01 + C----106 R-----16 0.100000e+01 R-----18 -.100000e+01 + C----106 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----106 R-----21 -.100000e+01 R-----25 -.100000e+01 + C----106 R-----26 0.100000e+01 R-----27 -.100000e+01 + C----106 R-----28 -.100000e+01 R-----30 -.100000e+01 + C----106 R-----31 -.100000e+01 R-----33 -.100000e+01 + C----106 R-----36 -.100000e+01 R-----37 0.100000e+01 + C----106 R-----38 -.100000e+01 R-----42 -.100000e+01 + C----106 R-----45 0.100000e+01 R-----46 0.100000e+01 + C----106 R-----47 -.100000e+01 R-----48 -.100000e+01 + C----106 R-----52 0.100000e+01 R-----53 0.100000e+01 + C----106 R-----55 -.100000e+01 R-----56 0.100000e+01 + C----106 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----106 R-----60 0.100000e+01 R-----64 -.100000e+01 + C----106 R-----66 -.100000e+01 R-----70 -.100000e+01 + C----106 R-----72 0.100000e+01 R-----77 -.100000e+01 + C----106 R-----78 0.100000e+01 R-----79 0.100000e+01 + C----106 R-----80 0.100000e+01 R-----84 -.100000e+01 + C----106 R-----85 0.100000e+01 + C----107 R-----23 -.100000e+01 R-----30 0.100000e+01 + C----107 R-----52 -.100000e+01 R-----72 -.100000e+01 + C----108 OBJ.FUNC 0.000000e+00 + C----109 R------1 0.100000e+01 R------2 0.100000e+01 + C----109 R------5 0.100000e+01 R------6 0.100000e+01 + C----109 R------7 -.100000e+01 R-----11 -.100000e+01 + C----109 R-----12 0.100000e+01 R-----15 -.100000e+01 + C----109 R-----16 -.100000e+01 R-----18 0.100000e+01 + C----109 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----109 R-----21 0.100000e+01 R-----23 0.100000e+01 + C----109 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----109 R-----27 0.100000e+01 R-----28 0.100000e+01 + C----109 R-----31 0.100000e+01 R-----33 0.100000e+01 + C----109 R-----36 0.100000e+01 R-----37 -.100000e+01 + C----109 R-----38 0.100000e+01 R-----42 0.100000e+01 + C----109 R-----45 -.100000e+01 R-----46 -.100000e+01 + C----109 R-----47 0.100000e+01 R-----48 0.100000e+01 + C----109 R-----53 -.100000e+01 R-----55 0.100000e+01 + C----109 R-----56 -.100000e+01 R-----58 -.100000e+01 + C----109 R-----59 0.100000e+01 R-----60 -.100000e+01 + C----109 R-----64 0.100000e+01 R-----66 0.100000e+01 + C----109 R-----70 0.100000e+01 R-----77 0.100000e+01 + C----109 R-----78 -.100000e+01 R-----79 -.100000e+01 + C----109 R-----80 -.100000e+01 R-----84 0.100000e+01 + C----109 R-----85 -.100000e+01 + C----110 R------2 -.100000e+01 R------9 -.100000e+01 + C----110 R-----11 0.100000e+01 R-----12 -.100000e+01 + C----110 R-----13 0.100000e+01 R-----18 -.100000e+01 + C----110 R-----21 -.100000e+01 R-----23 -.100000e+01 + C----110 R-----24 -.100000e+01 R-----32 0.100000e+01 + C----110 R-----33 -.100000e+01 R-----34 -.100000e+01 + C----110 R-----35 -.100000e+01 R-----39 0.100000e+01 + C----110 R-----40 -.100000e+01 R-----42 -.100000e+01 + C----110 R-----46 0.100000e+01 R-----47 -.100000e+01 + C----110 R-----48 -.100000e+01 R-----49 0.100000e+01 + C----110 R-----50 0.100000e+01 R-----52 -.100000e+01 + C----110 R-----56 0.100000e+01 R-----59 -.100000e+01 + C----110 R-----62 0.100000e+01 R-----64 -.100000e+01 + C----110 R-----68 -.100000e+01 R-----69 0.100000e+01 + C----110 R-----74 -.100000e+01 R-----75 0.100000e+01 + C----110 R-----79 0.100000e+01 R-----82 0.100000e+01 + C----110 R-----83 -.100000e+01 R-----85 0.100000e+01 + C----111 OBJ.FUNC 0.000000e+00 + C----112 OBJ.FUNC 0.000000e+00 + C----113 R------2 0.100000e+01 R------9 0.100000e+01 + C----113 R-----11 -.100000e+01 R-----12 0.100000e+01 + C----113 R-----13 -.100000e+01 R-----18 0.100000e+01 + C----113 R-----21 0.100000e+01 R-----23 0.100000e+01 + C----113 R-----24 0.100000e+01 R-----32 -.100000e+01 + C----113 R-----33 0.100000e+01 R-----34 0.100000e+01 + C----113 R-----35 0.100000e+01 R-----39 -.100000e+01 + C----113 R-----40 0.100000e+01 R-----42 0.100000e+01 + C----113 R-----46 -.100000e+01 R-----47 0.100000e+01 + C----113 R-----48 0.100000e+01 R-----49 -.100000e+01 + C----113 R-----50 -.100000e+01 R-----52 0.100000e+01 + C----113 R-----56 -.100000e+01 R-----59 0.100000e+01 + C----113 R-----62 -.100000e+01 R-----64 0.100000e+01 + C----113 R-----68 0.100000e+01 R-----69 -.100000e+01 + C----113 R-----74 0.100000e+01 R-----75 -.100000e+01 + C----113 R-----79 -.100000e+01 R-----82 -.100000e+01 + C----113 R-----83 0.100000e+01 R-----85 -.100000e+01 + C----114 OBJ.FUNC 0.000000e+00 + C----115 OBJ.FUNC 0.000000e+00 + C----116 OBJ.FUNC 0.000000e+00 + C----117 R------2 -.100000e+01 R------3 0.100000e+01 + C----117 R------4 -.100000e+01 R------5 0.100000e+01 + C----117 R------7 -.100000e+01 R-----10 -.100000e+01 + C----117 R-----11 -.100000e+01 R-----12 0.100000e+01 + C----117 R-----17 0.100000e+01 R-----19 -.100000e+01 + C----117 R-----20 0.100000e+01 R-----22 0.100000e+01 + C----117 R-----25 -.100000e+01 R-----34 -.100000e+01 + C----117 R-----36 0.100000e+01 R-----39 0.100000e+01 + C----117 R-----40 0.100000e+01 R-----42 0.100000e+01 + C----117 R-----43 0.100000e+01 R-----44 0.100000e+01 + C----117 R-----46 -.100000e+01 R-----47 0.100000e+01 + C----117 R-----48 0.100000e+01 R-----52 -.100000e+01 + C----117 R-----54 -.100000e+01 R-----57 -.100000e+01 + C----117 R-----58 -.100000e+01 R-----59 0.100000e+01 + C----117 R-----60 0.100000e+01 R-----61 -.100000e+01 + C----117 R-----66 0.100000e+01 R-----70 -.100000e+01 + C----117 R-----71 0.100000e+01 R-----73 0.100000e+01 + C----117 R-----75 -.100000e+01 R-----78 0.100000e+01 + C----117 R-----79 -.100000e+01 R-----80 -.100000e+01 + C----117 R-----85 -.100000e+01 + C----118 R------2 0.100000e+01 R------3 -.100000e+01 + C----118 R------4 0.100000e+01 R------5 -.100000e+01 + C----118 R------7 0.100000e+01 R------8 0.100000e+01 + C----118 R-----10 0.100000e+01 R-----11 0.100000e+01 + C----118 R-----12 -.100000e+01 R-----17 -.100000e+01 + C----118 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----118 R-----25 0.100000e+01 R-----33 -.100000e+01 + C----118 R-----34 0.100000e+01 R-----36 -.100000e+01 + C----118 R-----37 0.100000e+01 R-----40 -.100000e+01 + C----118 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----118 R-----44 -.100000e+01 R-----45 0.100000e+01 + C----118 R-----46 0.100000e+01 R-----47 -.100000e+01 + C----118 R-----48 -.100000e+01 R-----52 0.100000e+01 + C----118 R-----54 0.100000e+01 R-----57 0.100000e+01 + C----118 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----118 R-----60 -.100000e+01 R-----61 0.100000e+01 + C----118 R-----66 -.100000e+01 R-----70 0.100000e+01 + C----118 R-----71 -.100000e+01 R-----73 -.100000e+01 + C----118 R-----75 0.100000e+01 R-----78 -.100000e+01 + C----118 R-----79 0.100000e+01 R-----80 0.100000e+01 + C----118 R-----85 0.100000e+01 + C----119 R------8 -.100000e+01 R-----22 -.100000e+01 + C----119 R-----39 -.100000e+01 + C----120 OBJ.FUNC 0.000000e+00 + C----121 R-----33 0.100000e+01 R-----37 -.100000e+01 + C----121 R-----45 -.100000e+01 + C----122 R------2 0.100000e+01 R------3 -.100000e+01 + C----122 R------6 0.100000e+01 R-----12 0.100000e+01 + C----122 R-----13 0.100000e+01 R-----22 0.100000e+01 + C----122 R-----23 0.100000e+01 R-----25 0.100000e+01 + C----122 R-----26 -.100000e+01 R-----28 0.100000e+01 + C----122 R-----31 0.100000e+01 R-----37 -.100000e+01 + C----122 R-----38 0.100000e+01 R-----39 0.100000e+01 + C----122 R-----45 -.100000e+01 R-----55 0.100000e+01 + C----122 R-----56 -.100000e+01 R-----57 0.100000e+01 + C----122 R-----60 -.100000e+01 R-----62 0.100000e+01 + C----122 R-----66 0.100000e+01 R-----69 0.100000e+01 + C----122 R-----70 0.100000e+01 R-----71 -.100000e+01 + C----122 R-----73 -.100000e+01 R-----75 0.100000e+01 + C----122 R-----78 -.100000e+01 R-----84 0.100000e+01 + C----123 R------2 -.100000e+01 R------5 0.100000e+01 + C----123 R------6 -.100000e+01 R------7 -.100000e+01 + C----123 R------8 -.100000e+01 R-----10 -.100000e+01 + C----123 R-----11 -.100000e+01 R-----13 -.100000e+01 + C----123 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----123 R-----18 0.100000e+01 R-----19 -.100000e+01 + C----123 R-----20 -.100000e+01 R-----21 0.100000e+01 + C----123 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----123 R-----24 -.100000e+01 R-----27 0.100000e+01 + C----123 R-----28 -.100000e+01 R-----31 -.100000e+01 + C----123 R-----35 -.100000e+01 R-----36 0.100000e+01 + C----123 R-----39 -.100000e+01 R-----40 0.100000e+01 + C----123 R-----43 -.100000e+01 R-----46 -.100000e+01 + C----123 R-----50 -.100000e+01 R-----51 0.100000e+01 + C----123 R-----53 -.100000e+01 R-----54 -.100000e+01 + C----123 R-----55 -.100000e+01 R-----56 0.100000e+01 + C----123 R-----57 -.100000e+01 R-----59 0.100000e+01 + C----123 R-----60 0.100000e+01 R-----61 -.100000e+01 + C----123 R-----62 -.100000e+01 R-----65 -.100000e+01 + C----123 R-----69 -.100000e+01 R-----70 -.100000e+01 + C----123 R-----71 0.100000e+01 R-----72 -.100000e+01 + C----123 R-----73 0.100000e+01 R-----74 -.100000e+01 + C----123 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----123 R-----78 0.100000e+01 R-----83 -.100000e+01 + C----123 R-----84 -.100000e+01 + C----124 R------3 0.100000e+01 R------5 -.100000e+01 + C----124 R------7 0.100000e+01 R------8 0.100000e+01 + C----124 R-----10 0.100000e+01 R-----11 0.100000e+01 + C----124 R-----12 -.100000e+01 R-----15 0.100000e+01 + C----124 R-----16 0.100000e+01 R-----18 -.100000e+01 + C----124 R-----19 0.100000e+01 R-----20 0.100000e+01 + C----124 R-----21 -.100000e+01 R-----24 0.100000e+01 + C----124 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----124 R-----27 -.100000e+01 R-----35 0.100000e+01 + C----124 R-----36 -.100000e+01 R-----37 0.100000e+01 + C----124 R-----38 -.100000e+01 R-----40 -.100000e+01 + C----124 R-----43 0.100000e+01 R-----45 0.100000e+01 + C----124 R-----46 0.100000e+01 R-----48 -.100000e+01 + C----124 R-----50 0.100000e+01 R-----51 -.100000e+01 + C----124 R-----53 0.100000e+01 R-----54 0.100000e+01 + C----124 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----124 R-----61 0.100000e+01 R-----65 0.100000e+01 + C----124 R-----66 -.100000e+01 R-----72 0.100000e+01 + C----124 R-----74 0.100000e+01 R-----76 -.100000e+01 + C----124 R-----83 0.100000e+01 + C----125 OBJ.FUNC 0.000000e+00 + C----126 OBJ.FUNC 0.000000e+00 + C----127 R-----48 0.100000e+01 R-----58 -.100000e+01 + C----128 R-----73 0.100000e+01 + C----129 OBJ.FUNC 0.000000e+00 + C----130 R------3 -.100000e+01 R-----29 -.100000e+01 + C----131 R------2 0.100000e+01 R------4 0.100000e+01 + C----131 R------6 0.100000e+01 R------8 0.100000e+01 + C----131 R-----11 0.100000e+01 R-----12 0.100000e+01 + C----131 R-----13 -.100000e+01 R-----14 -.100000e+01 + C----131 R-----22 -.100000e+01 R-----24 0.100000e+01 + C----131 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----131 R-----28 0.100000e+01 R-----30 0.100000e+01 + C----131 R-----31 0.100000e+01 R-----32 -.100000e+01 + C----131 R-----35 0.100000e+01 R-----37 -.100000e+01 + C----131 R-----38 0.100000e+01 R-----39 0.100000e+01 + C----131 R-----40 0.100000e+01 R-----41 0.100000e+01 + C----131 R-----43 0.100000e+01 R-----44 0.100000e+01 + C----131 R-----49 -.100000e+01 R-----50 -.100000e+01 + C----131 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----131 R-----55 0.100000e+01 R-----56 -.100000e+01 + C----131 R-----58 -.100000e+01 R-----63 0.100000e+01 + C----131 R-----68 0.100000e+01 R-----69 -.100000e+01 + C----131 R-----70 0.100000e+01 R-----71 -.100000e+01 + C----131 R-----72 0.100000e+01 R-----73 -.100000e+01 + C----131 R-----75 0.100000e+01 R-----79 -.100000e+01 + C----131 R-----80 -.100000e+01 R-----85 -.100000e+01 + C----132 R------5 0.100000e+01 R-----10 0.100000e+01 + C----132 R-----11 -.100000e+01 R-----21 0.100000e+01 + C----132 R-----23 -.100000e+01 R-----33 0.100000e+01 + C----132 R-----39 -.100000e+01 R-----47 0.100000e+01 + C----132 R-----50 0.100000e+01 R-----52 -.100000e+01 + C----132 R-----72 -.100000e+01 R-----75 -.100000e+01 + C----132 R-----82 -.100000e+01 R-----85 0.100000e+01 + C----133 R------2 -.100000e+01 R------3 0.100000e+01 + C----133 R------4 -.100000e+01 R------5 -.100000e+01 + C----133 R------6 -.100000e+01 R------8 -.100000e+01 + C----133 R-----10 -.100000e+01 R-----12 -.100000e+01 + C----133 R-----13 0.100000e+01 R-----14 0.100000e+01 + C----133 R-----21 -.100000e+01 R-----22 0.100000e+01 + C----133 R-----23 0.100000e+01 R-----24 -.100000e+01 + C----133 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----133 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----133 R-----30 -.100000e+01 R-----31 -.100000e+01 + C----133 R-----32 0.100000e+01 R-----33 -.100000e+01 + C----133 R-----35 -.100000e+01 R-----37 0.100000e+01 + C----133 R-----38 -.100000e+01 R-----40 -.100000e+01 + C----133 R-----41 -.100000e+01 R-----43 -.100000e+01 + C----133 R-----44 -.100000e+01 R-----47 -.100000e+01 + C----133 R-----49 0.100000e+01 R-----51 -.100000e+01 + C----133 R-----55 -.100000e+01 R-----56 0.100000e+01 + C----133 R-----58 0.100000e+01 R-----63 -.100000e+01 + C----133 R-----68 -.100000e+01 R-----69 0.100000e+01 + C----133 R-----70 -.100000e+01 R-----71 0.100000e+01 + C----133 R-----79 0.100000e+01 R-----80 0.100000e+01 + C----133 R-----82 0.100000e+01 + C----134 OBJ.FUNC 0.000000e+00 + C----135 R------1 -.100000e+01 + C----136 OBJ.FUNC 0.000000e+00 + C----137 R------2 -.100000e+01 R------4 0.100000e+01 + C----137 R------6 0.100000e+01 R------8 0.100000e+01 + C----137 R------9 -.100000e+01 R-----10 -.100000e+01 + C----137 R-----11 0.100000e+01 R-----12 0.100000e+01 + C----137 R-----18 -.100000e+01 R-----20 0.100000e+01 + C----137 R-----24 -.100000e+01 R-----27 0.100000e+01 + C----137 R-----28 0.100000e+01 R-----31 0.100000e+01 + C----137 R-----34 -.100000e+01 R-----38 0.100000e+01 + C----137 R-----39 0.100000e+01 R-----40 -.100000e+01 + C----137 R-----42 -.100000e+01 R-----43 0.100000e+01 + C----137 R-----44 -.100000e+01 R-----45 0.100000e+01 + C----137 R-----47 -.100000e+01 R-----50 0.100000e+01 + C----137 R-----51 0.100000e+01 R-----55 -.100000e+01 + C----137 R-----56 0.100000e+01 R-----59 -.100000e+01 + C----137 R-----62 0.100000e+01 R-----67 0.100000e+01 + C----137 R-----69 0.100000e+01 R-----70 0.100000e+01 + C----137 R-----73 0.100000e+01 R-----74 -.100000e+01 + C----137 R-----75 0.100000e+01 R-----76 -.100000e+01 + C----137 R-----77 -.100000e+01 R-----79 0.100000e+01 + C----137 R-----82 0.100000e+01 R-----83 -.100000e+01 + C----138 R------2 0.100000e+01 R------3 0.100000e+01 + C----138 R------4 -.100000e+01 R------6 -.100000e+01 + C----138 R------7 0.100000e+01 R------8 -.100000e+01 + C----138 R------9 0.100000e+01 R-----10 0.100000e+01 + C----138 R-----12 -.100000e+01 R-----14 0.100000e+01 + C----138 R-----15 0.100000e+01 R-----17 0.100000e+01 + C----138 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----138 R-----23 0.100000e+01 R-----26 0.100000e+01 + C----138 R-----27 -.100000e+01 R-----28 -.100000e+01 + C----138 R-----29 0.100000e+01 R-----31 -.100000e+01 + C----138 R-----34 0.100000e+01 R-----36 -.100000e+01 + C----138 R-----37 0.100000e+01 R-----38 -.100000e+01 + C----138 R-----40 0.100000e+01 R-----41 -.100000e+01 + C----138 R-----42 0.100000e+01 R-----43 -.100000e+01 + C----138 R-----44 0.100000e+01 R-----47 0.100000e+01 + C----138 R-----51 -.100000e+01 R-----52 0.100000e+01 + C----138 R-----53 0.100000e+01 R-----59 0.100000e+01 + C----138 R-----64 -.100000e+01 R-----70 -.100000e+01 + C----138 R-----81 0.100000e+01 R-----85 0.100000e+01 + C----139 R------1 0.100000e+01 R------3 -.100000e+01 + C----139 R------7 -.100000e+01 R-----11 -.100000e+01 + C----139 R-----14 -.100000e+01 R-----15 -.100000e+01 + C----139 R-----17 -.100000e+01 R-----18 0.100000e+01 + C----139 R-----19 -.100000e+01 R-----23 -.100000e+01 + C----139 R-----24 0.100000e+01 R-----26 -.100000e+01 + C----139 R-----29 -.100000e+01 R-----36 0.100000e+01 + C----139 R-----37 -.100000e+01 R-----39 -.100000e+01 + C----139 R-----41 0.100000e+01 R-----45 -.100000e+01 + C----139 R-----50 -.100000e+01 R-----52 -.100000e+01 + C----139 R-----53 -.100000e+01 R-----55 0.100000e+01 + C----139 R-----56 -.100000e+01 R-----62 -.100000e+01 + C----139 R-----64 0.100000e+01 R-----67 -.100000e+01 + C----139 R-----69 -.100000e+01 R-----73 -.100000e+01 + C----139 R-----74 0.100000e+01 R-----75 -.100000e+01 + C----139 R-----76 0.100000e+01 R-----77 0.100000e+01 + C----139 R-----79 -.100000e+01 R-----81 -.100000e+01 + C----139 R-----82 -.100000e+01 R-----83 0.100000e+01 + C----139 R-----85 -.100000e+01 + C----140 R------1 -.100000e+01 R------4 0.100000e+01 + C----140 R------7 -.100000e+01 R-----11 0.100000e+01 + C----140 R-----12 -.100000e+01 R-----18 -.100000e+01 + C----140 R-----20 0.100000e+01 R-----25 -.100000e+01 + C----140 R-----27 0.100000e+01 R-----28 0.100000e+01 + C----140 R-----29 -.100000e+01 R-----31 0.100000e+01 + C----140 R-----32 -.100000e+01 R-----33 -.100000e+01 + C----140 R-----34 -.100000e+01 R-----35 -.100000e+01 + C----140 R-----38 -.100000e+01 R-----39 0.100000e+01 + C----140 R-----41 -.100000e+01 R-----44 -.100000e+01 + C----140 R-----48 -.100000e+01 R-----53 -.100000e+01 + C----140 R-----56 0.100000e+01 R-----59 -.100000e+01 + C----140 R-----61 0.100000e+01 R-----65 0.100000e+01 + C----140 R-----66 -.100000e+01 R-----69 0.100000e+01 + C----140 R-----76 -.100000e+01 R-----79 0.100000e+01 + C----140 R-----80 -.100000e+01 R-----82 0.100000e+01 + C----140 R-----84 0.100000e+01 + C----141 R-----54 0.100000e+01 R-----66 0.100000e+01 + C----142 R------1 0.100000e+01 R------2 -.100000e+01 + C----142 R------4 -.100000e+01 R------6 -.100000e+01 + C----142 R-----10 -.100000e+01 R-----11 -.100000e+01 + C----142 R-----14 -.100000e+01 R-----15 -.100000e+01 + C----142 R-----19 -.100000e+01 R-----21 -.100000e+01 + C----142 R-----23 -.100000e+01 R-----24 0.100000e+01 + C----142 R-----27 -.100000e+01 R-----30 -.100000e+01 + C----142 R-----36 0.100000e+01 R-----41 0.100000e+01 + C----142 R-----52 -.100000e+01 R-----54 -.100000e+01 + C----142 R-----55 0.100000e+01 R-----57 -.100000e+01 + C----142 R-----60 0.100000e+01 R-----61 -.100000e+01 + C----142 R-----62 -.100000e+01 R-----69 -.100000e+01 + C----142 R-----71 -.100000e+01 R-----74 0.100000e+01 + C----142 R-----76 0.100000e+01 R-----78 0.100000e+01 + C----142 R-----79 -.100000e+01 R-----80 0.100000e+01 + C----142 R-----81 -.100000e+01 R-----82 -.100000e+01 + C----142 R-----84 -.100000e+01 R-----85 -.100000e+01 + C----143 R------2 0.100000e+01 R------6 0.100000e+01 + C----143 R------7 0.100000e+01 R------8 0.100000e+01 + C----143 R-----10 0.100000e+01 R-----12 0.100000e+01 + C----143 R-----14 0.100000e+01 R-----15 0.100000e+01 + C----143 R-----18 0.100000e+01 R-----19 0.100000e+01 + C----143 R-----20 -.100000e+01 R-----21 0.100000e+01 + C----143 R-----22 0.100000e+01 R-----23 0.100000e+01 + C----143 R-----24 -.100000e+01 R-----25 0.100000e+01 + C----143 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----143 R-----30 0.100000e+01 R-----31 -.100000e+01 + C----143 R-----32 0.100000e+01 R-----33 0.100000e+01 + C----143 R-----34 0.100000e+01 R-----35 0.100000e+01 + C----143 R-----36 -.100000e+01 R-----38 0.100000e+01 + C----143 R-----44 0.100000e+01 R-----48 0.100000e+01 + C----143 R-----52 0.100000e+01 R-----53 0.100000e+01 + C----143 R-----55 -.100000e+01 R-----56 -.100000e+01 + C----143 R-----57 0.100000e+01 R-----59 0.100000e+01 + C----143 R-----60 -.100000e+01 R-----62 0.100000e+01 + C----143 R-----65 -.100000e+01 R-----71 0.100000e+01 + C----143 R-----74 -.100000e+01 R-----78 -.100000e+01 + C----143 R-----81 0.100000e+01 R-----85 0.100000e+01 + C----144 R------8 -.100000e+01 R-----22 -.100000e+01 + C----144 R-----39 -.100000e+01 + C----145 OBJ.FUNC 0.000000e+00 + C----146 R------2 0.100000e+01 R------5 -.100000e+01 + C----146 R------6 0.100000e+01 R------8 0.100000e+01 + C----146 R------9 -.100000e+01 R-----10 -.100000e+01 + C----146 R-----15 0.100000e+01 R-----16 0.100000e+01 + C----146 R-----17 0.100000e+01 R-----18 -.100000e+01 + C----146 R-----19 0.100000e+01 R-----21 -.100000e+01 + C----146 R-----22 0.100000e+01 R-----24 0.100000e+01 + C----146 R-----27 -.100000e+01 R-----28 0.100000e+01 + C----146 R-----29 -.100000e+01 R-----31 0.100000e+01 + C----146 R-----32 -.100000e+01 R-----33 -.100000e+01 + C----146 R-----34 -.100000e+01 R-----36 -.100000e+01 + C----146 R-----37 0.100000e+01 R-----39 0.100000e+01 + C----146 R-----43 0.100000e+01 R-----45 0.100000e+01 + C----146 R-----46 0.100000e+01 R-----48 -.100000e+01 + C----146 R-----50 -.100000e+01 R-----52 0.100000e+01 + C----146 R-----54 -.100000e+01 R-----55 0.100000e+01 + C----146 R-----59 -.100000e+01 R-----60 0.100000e+01 + C----146 R-----63 0.100000e+01 R-----64 -.100000e+01 + C----146 R-----66 -.100000e+01 R-----67 0.100000e+01 + C----146 R-----68 0.100000e+01 R-----72 0.100000e+01 + C----146 R-----73 0.100000e+01 R-----76 -.100000e+01 + C----146 R-----77 -.100000e+01 R-----78 0.100000e+01 + C----146 R-----79 -.100000e+01 R-----80 -.100000e+01 + C----146 R-----81 0.100000e+01 R-----85 -.100000e+01 + C----147 OBJ.FUNC 0.000000e+00 + C----148 OBJ.FUNC 0.000000e+00 + C----149 R------5 0.100000e+01 R-----14 0.100000e+01 + C----149 R-----17 -.100000e+01 R-----18 0.100000e+01 + C----149 R-----20 0.100000e+01 R-----21 0.100000e+01 + C----149 R-----23 0.100000e+01 R-----26 -.100000e+01 + C----149 R-----27 0.100000e+01 R-----29 0.100000e+01 + C----149 R-----32 0.100000e+01 R-----33 0.100000e+01 + C----149 R-----36 0.100000e+01 R-----37 -.100000e+01 + C----149 R-----42 0.100000e+01 R-----45 -.100000e+01 + C----149 R-----47 0.100000e+01 R-----48 0.100000e+01 + C----149 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----149 R-----56 -.100000e+01 R-----58 -.100000e+01 + C----149 R-----59 0.100000e+01 R-----60 -.100000e+01 + C----149 R-----64 0.100000e+01 R-----66 0.100000e+01 + C----149 R-----67 -.100000e+01 R-----69 0.100000e+01 + C----149 R-----73 -.100000e+01 R-----77 0.100000e+01 + C----149 R-----78 -.100000e+01 R-----81 -.100000e+01 + C----149 R-----82 0.100000e+01 + C----150 R------2 -.100000e+01 R------6 -.100000e+01 + C----150 R------8 -.100000e+01 R------9 0.100000e+01 + C----150 R-----10 0.100000e+01 R-----14 -.100000e+01 + C----150 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----150 R-----19 -.100000e+01 R-----20 -.100000e+01 + C----150 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----150 R-----24 -.100000e+01 R-----26 0.100000e+01 + C----150 R-----28 -.100000e+01 R-----31 -.100000e+01 + C----150 R-----34 0.100000e+01 R-----39 -.100000e+01 + C----150 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----150 R-----46 -.100000e+01 R-----47 -.100000e+01 + C----150 R-----49 -.100000e+01 R-----52 -.100000e+01 + C----150 R-----54 0.100000e+01 R-----55 -.100000e+01 + C----150 R-----56 0.100000e+01 R-----58 0.100000e+01 + C----150 R-----63 -.100000e+01 R-----68 -.100000e+01 + C----150 R-----69 -.100000e+01 R-----72 -.100000e+01 + C----150 R-----76 0.100000e+01 R-----79 0.100000e+01 + C----150 R-----80 0.100000e+01 R-----82 -.100000e+01 + C----150 R-----85 0.100000e+01 + C----151 OBJ.FUNC 0.000000e+00 + C----152 R------4 0.100000e+01 R-----11 0.100000e+01 + C----152 R-----67 -.100000e+01 R-----70 0.100000e+01 + C----152 R-----75 0.100000e+01 + C----153 R------1 0.100000e+01 R------3 0.100000e+01 + C----153 R-----10 0.100000e+01 R-----12 -.100000e+01 + C----153 R-----21 0.100000e+01 R-----27 0.100000e+01 + C----153 R-----36 0.100000e+01 R-----41 -.100000e+01 + C----153 R-----44 -.100000e+01 R-----47 0.100000e+01 + C----153 R-----48 -.100000e+01 R-----50 0.100000e+01 + C----153 R-----59 -.100000e+01 R-----66 -.100000e+01 + C----153 R-----73 0.100000e+01 R-----76 -.100000e+01 + C----153 R-----83 0.100000e+01 R-----84 0.100000e+01 + C----153 R-----85 0.100000e+01 + C----154 R------1 -.100000e+01 R------4 -.100000e+01 + C----154 R------7 -.100000e+01 R------9 -.100000e+01 + C----154 R-----10 -.100000e+01 R-----11 -.100000e+01 + C----154 R-----20 0.100000e+01 R-----21 -.100000e+01 + C----154 R-----22 0.100000e+01 R-----25 -.100000e+01 + C----154 R-----27 -.100000e+01 R-----29 -.100000e+01 + C----154 R-----30 -.100000e+01 R-----34 -.100000e+01 + C----154 R-----35 -.100000e+01 R-----36 -.100000e+01 + C----154 R-----38 -.100000e+01 R-----40 -.100000e+01 + C----154 R-----49 0.100000e+01 R-----50 -.100000e+01 + C----154 R-----51 -.100000e+01 R-----53 -.100000e+01 + C----154 R-----56 0.100000e+01 R-----57 -.100000e+01 + C----154 R-----58 0.100000e+01 R-----61 0.100000e+01 + C----154 R-----62 -.100000e+01 R-----65 0.100000e+01 + C----154 R-----66 0.100000e+01 R-----69 0.100000e+01 + C----154 R-----70 -.100000e+01 R-----74 0.100000e+01 + C----154 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----154 R-----82 0.100000e+01 R-----84 -.100000e+01 + C----154 R-----85 -.100000e+01 + C----155 R-----19 -.100000e+01 R-----72 -.100000e+01 + C----156 R------3 -.100000e+01 R------6 -.100000e+01 + C----156 R------9 0.100000e+01 R-----14 0.100000e+01 + C----156 R-----15 -.100000e+01 R-----17 -.100000e+01 + C----156 R-----23 0.100000e+01 R-----29 0.100000e+01 + C----156 R-----34 0.100000e+01 R-----42 0.100000e+01 + C----156 R-----45 -.100000e+01 R-----46 -.100000e+01 + C----156 R-----48 0.100000e+01 R-----52 -.100000e+01 + C----156 R-----58 -.100000e+01 R-----59 0.100000e+01 + C----156 R-----60 -.100000e+01 R-----69 -.100000e+01 + C----156 R-----71 0.100000e+01 R-----82 -.100000e+01 + C----157 R------6 0.100000e+01 R------7 0.100000e+01 + C----157 R-----12 0.100000e+01 R-----14 -.100000e+01 + C----157 R-----15 0.100000e+01 R-----17 0.100000e+01 + C----157 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----157 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----157 R-----25 0.100000e+01 R-----30 0.100000e+01 + C----157 R-----35 0.100000e+01 R-----38 0.100000e+01 + C----157 R-----40 0.100000e+01 R-----41 0.100000e+01 + C----157 R-----42 -.100000e+01 R-----44 0.100000e+01 + C----157 R-----45 0.100000e+01 R-----46 0.100000e+01 + C----157 R-----47 -.100000e+01 R-----49 -.100000e+01 + C----157 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----157 R-----53 0.100000e+01 R-----56 -.100000e+01 + C----157 R-----57 0.100000e+01 R-----60 0.100000e+01 + C----157 R-----61 -.100000e+01 R-----62 0.100000e+01 + C----157 R-----65 -.100000e+01 R-----67 0.100000e+01 + C----157 R-----71 -.100000e+01 R-----72 0.100000e+01 + C----157 R-----73 -.100000e+01 R-----74 -.100000e+01 + C----157 R-----83 -.100000e+01 + C----158 OBJ.FUNC 0.000000e+00 + C----159 R------1 -.100000e+01 R------3 -.100000e+01 + C----159 R------4 0.100000e+01 R-----12 -.100000e+01 + C----159 R-----13 0.100000e+01 R-----14 0.100000e+01 + C----159 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----159 R-----18 0.100000e+01 R-----19 -.100000e+01 + C----159 R-----20 0.100000e+01 R-----21 0.100000e+01 + C----159 R-----23 0.100000e+01 R-----25 -.100000e+01 + C----159 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----159 R-----32 0.100000e+01 R-----33 0.100000e+01 + C----159 R-----34 0.100000e+01 R-----42 0.100000e+01 + C----159 R-----45 -.100000e+01 R-----49 0.100000e+01 + C----159 R-----54 0.100000e+01 R-----55 -.100000e+01 + C----159 R-----57 -.100000e+01 R-----59 0.100000e+01 + C----159 R-----60 -.100000e+01 R-----61 0.100000e+01 + C----159 R-----65 0.100000e+01 R-----66 0.100000e+01 + C----159 R-----67 -.100000e+01 R-----72 -.100000e+01 + C----159 R-----73 -.100000e+01 R-----74 0.100000e+01 + C----159 R-----76 0.100000e+01 R-----78 -.100000e+01 + C----159 R-----79 0.100000e+01 R-----81 -.100000e+01 + C----159 R-----82 0.100000e+01 R-----83 -.100000e+01 + C----159 R-----85 0.100000e+01 + C----160 R------1 0.100000e+01 R------2 0.100000e+01 + C----160 R------3 0.100000e+01 R------4 -.100000e+01 + C----160 R------7 -.100000e+01 R------8 0.100000e+01 + C----160 R-----13 -.100000e+01 R-----15 0.100000e+01 + C----160 R-----17 0.100000e+01 R-----18 -.100000e+01 + C----160 R-----20 -.100000e+01 R-----23 -.100000e+01 + C----160 R-----24 0.100000e+01 R-----29 -.100000e+01 + C----160 R-----30 -.100000e+01 R-----31 0.100000e+01 + C----160 R-----34 -.100000e+01 R-----35 -.100000e+01 + C----160 R-----38 0.100000e+01 R-----39 0.100000e+01 + C----160 R-----40 -.100000e+01 R-----41 -.100000e+01 + C----160 R-----43 0.100000e+01 R-----46 -.100000e+01 + C----160 R-----51 -.100000e+01 R-----52 -.100000e+01 + C----160 R-----53 0.100000e+01 R-----55 0.100000e+01 + C----160 R-----59 -.100000e+01 R-----61 -.100000e+01 + C----160 R-----62 -.100000e+01 R-----63 0.100000e+01 + C----160 R-----65 -.100000e+01 R-----68 -.100000e+01 + C----160 R-----72 0.100000e+01 R-----73 0.100000e+01 + C----160 R-----74 -.100000e+01 R-----78 0.100000e+01 + C----160 R-----81 0.100000e+01 R-----82 -.100000e+01 + C----160 R-----83 0.100000e+01 R-----84 0.100000e+01 + C----160 R-----85 -.100000e+01 + C----161 R------2 -.100000e+01 R------7 0.100000e+01 + C----161 R------8 -.100000e+01 R-----12 0.100000e+01 + C----161 R-----14 -.100000e+01 R-----15 -.100000e+01 + C----161 R-----16 0.100000e+01 R-----19 0.100000e+01 + C----161 R-----21 -.100000e+01 R-----24 -.100000e+01 + C----161 R-----25 0.100000e+01 R-----28 0.100000e+01 + C----161 R-----30 0.100000e+01 R-----31 -.100000e+01 + C----161 R-----32 -.100000e+01 R-----33 -.100000e+01 + C----161 R-----35 0.100000e+01 R-----38 -.100000e+01 + C----161 R-----39 -.100000e+01 R-----40 0.100000e+01 + C----161 R-----41 0.100000e+01 R-----42 -.100000e+01 + C----161 R-----43 -.100000e+01 R-----45 0.100000e+01 + C----161 R-----46 0.100000e+01 R-----49 -.100000e+01 + C----161 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----161 R-----53 -.100000e+01 R-----54 -.100000e+01 + C----161 R-----57 0.100000e+01 R-----60 0.100000e+01 + C----161 R-----62 0.100000e+01 R-----63 -.100000e+01 + C----161 R-----66 -.100000e+01 R-----67 0.100000e+01 + C----161 R-----68 0.100000e+01 R-----76 -.100000e+01 + C----161 R-----79 -.100000e+01 R-----84 -.100000e+01 + C----162 OBJ.FUNC 0.000000e+00 + C----163 OBJ.FUNC 0.000000e+00 + C----164 R-----38 -.100000e+01 R-----40 -.100000e+01 + C----164 R-----69 -.100000e+01 R-----73 0.100000e+01 + C----165 OBJ.FUNC 0.000000e+00 + C----166 OBJ.FUNC 0.000000e+00 + C----167 OBJ.FUNC 0.000000e+00 + C----168 R------4 0.100000e+01 R------5 0.100000e+01 + C----168 R------6 -.100000e+01 R------7 0.100000e+01 + C----168 R------8 0.100000e+01 R------9 -.100000e+01 + C----168 R-----14 -.100000e+01 R-----15 0.100000e+01 + C----168 R-----16 -.100000e+01 R-----18 0.100000e+01 + C----168 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----168 R-----21 -.100000e+01 R-----25 -.100000e+01 + C----168 R-----26 -.100000e+01 R-----27 0.100000e+01 + C----168 R-----30 0.100000e+01 R-----31 0.100000e+01 + C----168 R-----35 0.100000e+01 R-----37 -.100000e+01 + C----168 R-----38 0.100000e+01 R-----40 0.100000e+01 + C----168 R-----42 -.100000e+01 R-----43 0.100000e+01 + C----168 R-----44 -.100000e+01 R-----45 -.100000e+01 + C----168 R-----46 0.100000e+01 R-----48 -.100000e+01 + C----168 R-----50 0.100000e+01 R-----53 0.100000e+01 + C----168 R-----54 0.100000e+01 R-----55 -.100000e+01 + C----168 R-----57 -.100000e+01 R-----58 -.100000e+01 + C----168 R-----60 -.100000e+01 R-----61 0.100000e+01 + C----168 R-----64 -.100000e+01 R-----65 0.100000e+01 + C----168 R-----66 0.100000e+01 R-----68 0.100000e+01 + C----168 R-----69 0.100000e+01 R-----72 -.100000e+01 + C----168 R-----73 -.100000e+01 R-----76 0.100000e+01 + C----168 R-----77 -.100000e+01 R-----79 0.100000e+01 + C----168 R-----80 -.100000e+01 R-----81 -.100000e+01 + C----168 R-----82 0.100000e+01 R-----83 -.100000e+01 + C----168 R-----84 0.100000e+01 + C----169 R------4 -.100000e+01 R------5 -.100000e+01 + C----169 R------6 0.100000e+01 R------7 -.100000e+01 + C----169 R------8 -.100000e+01 R------9 0.100000e+01 + C----169 R-----14 0.100000e+01 R-----15 -.100000e+01 + C----169 R-----16 0.100000e+01 R-----18 -.100000e+01 + C----169 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----169 R-----21 0.100000e+01 R-----25 0.100000e+01 + C----169 R-----26 0.100000e+01 R-----27 -.100000e+01 + C----169 R-----30 -.100000e+01 R-----31 -.100000e+01 + C----169 R-----35 -.100000e+01 R-----37 0.100000e+01 + C----169 R-----42 0.100000e+01 R-----43 -.100000e+01 + C----169 R-----44 0.100000e+01 R-----45 0.100000e+01 + C----169 R-----46 -.100000e+01 R-----48 0.100000e+01 + C----169 R-----50 -.100000e+01 R-----53 -.100000e+01 + C----169 R-----54 -.100000e+01 R-----55 0.100000e+01 + C----169 R-----57 0.100000e+01 R-----58 0.100000e+01 + C----169 R-----60 0.100000e+01 R-----61 -.100000e+01 + C----169 R-----64 0.100000e+01 R-----65 -.100000e+01 + C----169 R-----66 -.100000e+01 R-----68 -.100000e+01 + C----169 R-----72 0.100000e+01 R-----76 -.100000e+01 + C----169 R-----77 0.100000e+01 R-----79 -.100000e+01 + C----169 R-----80 0.100000e+01 R-----81 0.100000e+01 + C----169 R-----82 -.100000e+01 R-----83 0.100000e+01 + C----169 R-----84 -.100000e+01 + C----170 R------2 0.100000e+01 R------4 -.100000e+01 + C----170 R------5 -.100000e+01 R-----11 -.100000e+01 + C----170 R-----18 0.100000e+01 R-----22 0.100000e+01 + C----170 R-----25 0.100000e+01 R-----27 -.100000e+01 + C----170 R-----30 -.100000e+01 R-----32 0.100000e+01 + C----170 R-----35 0.100000e+01 R-----36 0.100000e+01 + C----170 R-----38 0.100000e+01 R-----41 -.100000e+01 + C----170 R-----42 0.100000e+01 R-----44 -.100000e+01 + C----170 R-----45 0.100000e+01 R-----47 0.100000e+01 + C----170 R-----48 -.100000e+01 R-----49 0.100000e+01 + C----170 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----170 R-----54 -.100000e+01 R-----57 -.100000e+01 + C----170 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----170 R-----62 -.100000e+01 R-----63 0.100000e+01 + C----170 R-----66 -.100000e+01 R-----70 0.100000e+01 + C----170 R-----74 0.100000e+01 R-----80 0.100000e+01 + C----170 R-----83 0.100000e+01 R-----84 -.100000e+01 + C----171 R------3 -.100000e+01 R------4 0.100000e+01 + C----171 R------5 0.100000e+01 R------7 -.100000e+01 + C----171 R------9 -.100000e+01 R-----11 0.100000e+01 + C----171 R-----16 0.100000e+01 R-----17 -.100000e+01 + C----171 R-----18 -.100000e+01 R-----20 0.100000e+01 + C----171 R-----24 0.100000e+01 R-----25 -.100000e+01 + C----171 R-----26 -.100000e+01 R-----27 0.100000e+01 + C----171 R-----28 0.100000e+01 R-----29 -.100000e+01 + C----171 R-----31 0.100000e+01 R-----32 -.100000e+01 + C----171 R-----34 -.100000e+01 R-----35 -.100000e+01 + C----171 R-----36 -.100000e+01 R-----38 -.100000e+01 + C----171 R-----39 0.100000e+01 R-----40 -.100000e+01 + C----171 R-----41 0.100000e+01 R-----46 0.100000e+01 + C----171 R-----51 -.100000e+01 R-----53 -.100000e+01 + C----171 R-----54 0.100000e+01 R-----60 -.100000e+01 + C----171 R-----65 0.100000e+01 R-----66 0.100000e+01 + C----171 R-----67 -.100000e+01 R-----69 0.100000e+01 + C----171 R-----70 -.100000e+01 R-----72 0.100000e+01 + C----171 R-----75 -.100000e+01 R-----78 0.100000e+01 + C----171 R-----80 -.100000e+01 + C----172 OBJ.FUNC 0.000000e+00 + C----173 R------3 0.100000e+01 R------8 0.100000e+01 + C----173 R------9 0.100000e+01 R-----14 0.100000e+01 + C----173 R-----17 0.100000e+01 R-----19 0.100000e+01 + C----173 R-----20 -.100000e+01 R-----23 0.100000e+01 + C----173 R-----24 -.100000e+01 R-----28 -.100000e+01 + C----173 R-----30 0.100000e+01 R-----31 -.100000e+01 + C----173 R-----34 0.100000e+01 R-----40 0.100000e+01 + C----173 R-----42 -.100000e+01 R-----44 0.100000e+01 + C----173 R-----45 -.100000e+01 R-----46 -.100000e+01 + C----173 R-----47 -.100000e+01 R-----48 0.100000e+01 + C----173 R-----52 -.100000e+01 R-----53 0.100000e+01 + C----173 R-----58 -.100000e+01 R-----60 0.100000e+01 + C----173 R-----61 0.100000e+01 R-----62 0.100000e+01 + C----173 R-----63 -.100000e+01 R-----65 -.100000e+01 + C----173 R-----67 0.100000e+01 R-----69 -.100000e+01 + C----173 R-----74 -.100000e+01 R-----75 0.100000e+01 + C----173 R-----78 -.100000e+01 R-----81 -.100000e+01 + C----173 R-----83 -.100000e+01 R-----84 0.100000e+01 + C----174 R------2 -.100000e+01 R------7 0.100000e+01 + C----174 R------8 -.100000e+01 R-----14 -.100000e+01 + C----174 R-----16 -.100000e+01 R-----19 -.100000e+01 + C----174 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----174 R-----26 0.100000e+01 R-----29 0.100000e+01 + C----174 R-----39 -.100000e+01 R-----49 -.100000e+01 + C----174 R-----57 0.100000e+01 R-----59 0.100000e+01 + C----174 R-----61 -.100000e+01 R-----72 -.100000e+01 + C----174 R-----81 0.100000e+01 + C----175 OBJ.FUNC 0.000000e+00 + C----176 OBJ.FUNC 0.000000e+00 + C----177 R------4 -.100000e+01 R------7 0.100000e+01 + C----177 R-----10 -.100000e+01 R-----11 -.100000e+01 + C----177 R-----12 -.100000e+01 R-----13 -.100000e+01 + C----177 R-----16 0.100000e+01 R-----19 0.100000e+01 + C----177 R-----21 -.100000e+01 R-----29 0.100000e+01 + C----177 R-----30 -.100000e+01 R-----32 0.100000e+01 + C----177 R-----41 -.100000e+01 R-----42 0.100000e+01 + C----177 R-----44 -.100000e+01 R-----47 0.100000e+01 + C----177 R-----48 -.100000e+01 R-----49 0.100000e+01 + C----177 R-----51 0.100000e+01 R-----54 -.100000e+01 + C----177 R-----57 -.100000e+01 R-----60 0.100000e+01 + C----177 R-----62 -.100000e+01 R-----66 -.100000e+01 + C----177 R-----68 0.100000e+01 R-----73 0.100000e+01 + C----177 R-----77 -.100000e+01 R-----78 0.100000e+01 + C----177 R-----79 -.100000e+01 R-----80 0.100000e+01 + C----177 R-----82 0.100000e+01 R-----84 -.100000e+01 + C----178 R------1 -.100000e+01 R------3 0.100000e+01 + C----178 R------4 0.100000e+01 R------7 -.100000e+01 + C----178 R-----10 0.100000e+01 R-----11 0.100000e+01 + C----178 R-----15 0.100000e+01 R-----18 -.100000e+01 + C----178 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----178 R-----29 -.100000e+01 R-----30 0.100000e+01 + C----178 R-----32 -.100000e+01 R-----34 -.100000e+01 + C----178 R-----38 -.100000e+01 R-----40 0.100000e+01 + C----178 R-----41 0.100000e+01 R-----43 0.100000e+01 + C----178 R-----44 0.100000e+01 R-----50 -.100000e+01 + C----178 R-----70 -.100000e+01 R-----72 0.100000e+01 + C----178 R-----75 -.100000e+01 R-----80 -.100000e+01 + C----178 R-----83 0.100000e+01 + C----179 OBJ.FUNC 0.000000e+00 + C----180 OBJ.FUNC 0.000000e+00 + C----181 R------1 0.100000e+01 R------3 -.100000e+01 + C----181 R-----12 0.100000e+01 R-----13 0.100000e+01 + C----181 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----181 R-----18 0.100000e+01 R-----19 -.100000e+01 + C----181 R-----21 0.100000e+01 R-----25 0.100000e+01 + C----181 R-----26 -.100000e+01 R-----34 0.100000e+01 + C----181 R-----38 0.100000e+01 R-----40 -.100000e+01 + C----181 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----181 R-----47 -.100000e+01 R-----48 0.100000e+01 + C----181 R-----49 -.100000e+01 R-----50 0.100000e+01 + C----181 R-----51 -.100000e+01 R-----54 0.100000e+01 + C----181 R-----57 0.100000e+01 R-----60 -.100000e+01 + C----181 R-----62 0.100000e+01 R-----66 0.100000e+01 + C----181 R-----68 -.100000e+01 R-----70 0.100000e+01 + C----181 R-----72 -.100000e+01 R-----73 -.100000e+01 + C----181 R-----75 0.100000e+01 R-----77 0.100000e+01 + C----181 R-----78 -.100000e+01 R-----79 0.100000e+01 + C----181 R-----82 -.100000e+01 R-----83 -.100000e+01 + C----181 R-----84 0.100000e+01 + C----182 R------1 0.100000e+01 R------3 -.100000e+01 + C----182 R------4 0.100000e+01 R------9 0.100000e+01 + C----182 R-----10 0.100000e+01 R-----12 0.100000e+01 + C----182 R-----13 -.100000e+01 R-----15 -.100000e+01 + C----182 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----182 R-----22 -.100000e+01 R-----24 0.100000e+01 + C----182 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----182 R-----28 0.100000e+01 R-----29 -.100000e+01 + C----182 R-----30 0.100000e+01 R-----34 0.100000e+01 + C----182 R-----35 0.100000e+01 R-----36 0.100000e+01 + C----182 R-----37 -.100000e+01 R-----40 0.100000e+01 + C----182 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----182 R-----46 -.100000e+01 R-----48 0.100000e+01 + C----182 R-----49 -.100000e+01 R-----57 -.100000e+01 + C----182 R-----58 -.100000e+01 R-----60 -.100000e+01 + C----182 R-----61 0.100000e+01 R-----64 0.100000e+01 + C----182 R-----65 0.100000e+01 R-----69 -.100000e+01 + C----182 R-----71 -.100000e+01 R-----73 -.100000e+01 + C----182 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----182 R-----78 -.100000e+01 R-----84 0.100000e+01 + C----182 R-----85 -.100000e+01 + C----183 OBJ.FUNC 0.000000e+00 + C----184 R------3 0.100000e+01 R------4 -.100000e+01 + C----184 R------7 -.100000e+01 R-----10 -.100000e+01 + C----184 R-----14 -.100000e+01 R-----16 -.100000e+01 + C----184 R-----17 0.100000e+01 R-----20 -.100000e+01 + C----184 R-----24 -.100000e+01 R-----26 0.100000e+01 + C----184 R-----32 -.100000e+01 R-----34 -.100000e+01 + C----184 R-----36 -.100000e+01 R-----37 0.100000e+01 + C----184 R-----39 0.100000e+01 R-----42 -.100000e+01 + C----184 R-----48 -.100000e+01 R-----50 -.100000e+01 + C----184 R-----53 -.100000e+01 R-----56 0.100000e+01 + C----184 R-----60 0.100000e+01 R-----67 0.100000e+01 + C----184 R-----71 0.100000e+01 R-----73 0.100000e+01 + C----184 R-----76 -.100000e+01 R-----78 0.100000e+01 + C----184 R-----79 -.100000e+01 R-----82 -.100000e+01 + C----185 OBJ.FUNC 0.000000e+00 + C----186 R------1 -.100000e+01 R------7 0.100000e+01 + C----186 R------9 -.100000e+01 R-----12 -.100000e+01 + C----186 R-----13 0.100000e+01 R-----14 0.100000e+01 + C----186 R-----15 0.100000e+01 R-----16 0.100000e+01 + C----186 R-----17 -.100000e+01 R-----19 0.100000e+01 + C----186 R-----22 0.100000e+01 R-----25 -.100000e+01 + C----186 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----186 R-----30 -.100000e+01 R-----32 0.100000e+01 + C----186 R-----35 -.100000e+01 R-----39 -.100000e+01 + C----186 R-----40 -.100000e+01 R-----41 -.100000e+01 + C----186 R-----46 0.100000e+01 R-----49 0.100000e+01 + C----186 R-----50 0.100000e+01 R-----53 0.100000e+01 + C----186 R-----56 -.100000e+01 R-----57 0.100000e+01 + C----186 R-----58 0.100000e+01 R-----61 -.100000e+01 + C----186 R-----64 -.100000e+01 R-----67 -.100000e+01 + C----186 R-----69 0.100000e+01 R-----75 0.100000e+01 + C----186 R-----79 0.100000e+01 R-----82 0.100000e+01 + C----186 R-----84 -.100000e+01 R-----85 0.100000e+01 + C----187 R-----65 -.100000e+01 + C----188 R------2 -.100000e+01 R------3 0.100000e+01 + C----188 R------7 -.100000e+01 R------9 -.100000e+01 + C----188 R-----11 -.100000e+01 R-----15 -.100000e+01 + C----188 R-----17 0.100000e+01 R-----18 -.100000e+01 + C----188 R-----23 -.100000e+01 R-----26 0.100000e+01 + C----188 R-----27 -.100000e+01 R-----29 0.100000e+01 + C----188 R-----30 -.100000e+01 R-----32 0.100000e+01 + C----188 R-----33 -.100000e+01 R-----34 -.100000e+01 + C----188 R-----36 -.100000e+01 R-----37 0.100000e+01 + C----188 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----188 R-----44 -.100000e+01 R-----46 0.100000e+01 + C----188 R-----51 -.100000e+01 R-----54 -.100000e+01 + C----188 R-----56 0.100000e+01 R-----58 -.100000e+01 + C----188 R-----60 0.100000e+01 R-----63 -.100000e+01 + C----188 R-----65 -.100000e+01 R-----67 0.100000e+01 + C----188 R-----71 0.100000e+01 R-----76 -.100000e+01 + C----188 R-----78 0.100000e+01 R-----80 -.100000e+01 + C----188 R-----81 0.100000e+01 R-----83 -.100000e+01 + C----189 OBJ.FUNC 0.000000e+00 + C----190 R------1 0.100000e+01 R------2 0.100000e+01 + C----190 R------5 0.100000e+01 R------6 0.100000e+01 + C----190 R------8 0.100000e+01 R------9 0.100000e+01 + C----190 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----190 R-----18 0.100000e+01 R-----20 0.100000e+01 + C----190 R-----21 0.100000e+01 R-----22 -.100000e+01 + C----190 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----190 R-----27 0.100000e+01 R-----29 -.100000e+01 + C----190 R-----30 0.100000e+01 R-----33 0.100000e+01 + C----190 R-----34 0.100000e+01 R-----35 0.100000e+01 + C----190 R-----36 0.100000e+01 R-----38 0.100000e+01 + C----190 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----190 R-----43 0.100000e+01 R-----44 0.100000e+01 + C----190 R-----49 -.100000e+01 R-----50 -.100000e+01 + C----190 R-----51 0.100000e+01 R-----52 -.100000e+01 + C----190 R-----53 -.100000e+01 R-----54 0.100000e+01 + C----190 R-----55 0.100000e+01 R-----56 -.100000e+01 + C----190 R-----57 -.100000e+01 R-----63 0.100000e+01 + C----190 R-----65 0.100000e+01 R-----70 0.100000e+01 + C----190 R-----71 -.100000e+01 R-----72 -.100000e+01 + C----190 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----190 R-----79 -.100000e+01 R-----81 -.100000e+01 + C----190 R-----82 -.100000e+01 R-----83 0.100000e+01 + C----190 R-----85 -.100000e+01 + C----191 R-----35 -.100000e+01 + C----192 OBJ.FUNC 0.000000e+00 + C----193 R------1 -.100000e+01 R------3 -.100000e+01 + C----193 R------5 -.100000e+01 R------6 -.100000e+01 + C----193 R------7 0.100000e+01 R------8 -.100000e+01 + C----193 R-----11 0.100000e+01 R-----15 0.100000e+01 + C----193 R-----16 0.100000e+01 R-----20 -.100000e+01 + C----193 R-----21 -.100000e+01 R-----22 0.100000e+01 + C----193 R-----23 0.100000e+01 R-----25 -.100000e+01 + C----193 R-----32 -.100000e+01 R-----37 -.100000e+01 + C----193 R-----38 -.100000e+01 R-----41 -.100000e+01 + C----193 R-----46 -.100000e+01 R-----49 0.100000e+01 + C----193 R-----50 0.100000e+01 R-----52 0.100000e+01 + C----193 R-----53 0.100000e+01 R-----55 -.100000e+01 + C----193 R-----57 0.100000e+01 R-----58 0.100000e+01 + C----193 R-----60 -.100000e+01 R-----67 -.100000e+01 + C----193 R-----70 -.100000e+01 R-----72 0.100000e+01 + C----193 R-----75 0.100000e+01 R-----78 -.100000e+01 + C----193 R-----79 0.100000e+01 R-----80 0.100000e+01 + C----193 R-----82 0.100000e+01 R-----85 0.100000e+01 + C----194 R------2 -.100000e+01 R------3 -.100000e+01 + C----194 R------4 0.100000e+01 R------9 -.100000e+01 + C----194 R-----10 0.100000e+01 R-----11 0.100000e+01 + C----194 R-----12 0.100000e+01 R-----14 -.100000e+01 + C----194 R-----15 -.100000e+01 R-----21 0.100000e+01 + C----194 R-----25 -.100000e+01 R-----27 0.100000e+01 + C----194 R-----36 -.100000e+01 R-----41 0.100000e+01 + C----194 R-----42 -.100000e+01 R-----45 -.100000e+01 + C----194 R-----48 0.100000e+01 R-----55 -.100000e+01 + C----194 R-----57 0.100000e+01 R-----58 -.100000e+01 + C----194 R-----59 0.100000e+01 R-----60 -.100000e+01 + C----194 R-----63 -.100000e+01 R-----64 0.100000e+01 + C----194 R-----65 -.100000e+01 R-----66 0.100000e+01 + C----194 R-----69 -.100000e+01 R-----77 0.100000e+01 + C----194 R-----78 -.100000e+01 R-----79 0.100000e+01 + C----194 R-----83 -.100000e+01 R-----84 0.100000e+01 + C----194 R-----85 0.100000e+01 + C----195 R------1 0.100000e+01 R------3 0.100000e+01 + C----195 R------4 -.100000e+01 R------6 -.100000e+01 + C----195 R------7 0.100000e+01 R------9 0.100000e+01 + C----195 R-----10 -.100000e+01 R-----12 -.100000e+01 + C----195 R-----16 -.100000e+01 R-----18 0.100000e+01 + C----195 R-----19 -.100000e+01 R-----22 -.100000e+01 + C----195 R-----24 -.100000e+01 R-----25 0.100000e+01 + C----195 R-----26 0.100000e+01 R-----31 -.100000e+01 + C----195 R-----34 0.100000e+01 R-----36 0.100000e+01 + C----195 R-----37 -.100000e+01 R-----38 0.100000e+01 + C----195 R-----39 -.100000e+01 R-----47 -.100000e+01 + C----195 R-----48 -.100000e+01 R-----51 0.100000e+01 + C----195 R-----52 -.100000e+01 R-----53 0.100000e+01 + C----195 R-----56 -.100000e+01 R-----60 0.100000e+01 + C----195 R-----66 -.100000e+01 R-----67 0.100000e+01 + C----195 R-----68 -.100000e+01 R-----70 0.100000e+01 + C----195 R-----72 -.100000e+01 R-----75 0.100000e+01 + C----195 R-----80 0.100000e+01 R-----81 -.100000e+01 + C----196 OBJ.FUNC 0.000000e+00 + C----197 R------1 -.100000e+01 R------6 0.100000e+01 + C----197 R-----15 0.100000e+01 R-----20 0.100000e+01 + C----197 R-----24 0.100000e+01 R-----26 -.100000e+01 + C----197 R-----31 0.100000e+01 R-----37 0.100000e+01 + C----197 R-----42 0.100000e+01 R-----43 0.100000e+01 + C----197 R-----45 0.100000e+01 R-----47 0.100000e+01 + C----197 R-----49 -.100000e+01 R-----52 0.100000e+01 + C----197 R-----53 -.100000e+01 R-----55 0.100000e+01 + C----197 R-----56 0.100000e+01 R-----57 -.100000e+01 + C----197 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----197 R-----61 -.100000e+01 R-----63 0.100000e+01 + C----197 R-----64 -.100000e+01 R-----65 0.100000e+01 + C----197 R-----68 0.100000e+01 R-----69 0.100000e+01 + C----197 R-----70 -.100000e+01 R-----73 0.100000e+01 + C----197 R-----75 -.100000e+01 R-----77 -.100000e+01 + C----197 R-----78 0.100000e+01 R-----79 -.100000e+01 + C----197 R-----81 0.100000e+01 R-----83 0.100000e+01 + C----197 R-----84 -.100000e+01 R-----85 -.100000e+01 + C----198 R------2 0.100000e+01 R------7 -.100000e+01 + C----198 R-----11 -.100000e+01 R-----14 0.100000e+01 + C----198 R-----16 0.100000e+01 R-----18 -.100000e+01 + C----198 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----198 R-----21 -.100000e+01 R-----22 0.100000e+01 + C----198 R-----27 -.100000e+01 R-----34 -.100000e+01 + C----198 R-----38 -.100000e+01 R-----39 0.100000e+01 + C----198 R-----41 -.100000e+01 R-----43 -.100000e+01 + C----198 R-----49 0.100000e+01 R-----51 -.100000e+01 + C----198 R-----61 0.100000e+01 R-----67 -.100000e+01 + C----198 R-----72 0.100000e+01 R-----73 -.100000e+01 + C----198 R-----80 -.100000e+01 + C----199 OBJ.FUNC 0.000000e+00 + C----200 OBJ.FUNC 0.000000e+00 + C----201 OBJ.FUNC 0.000000e+00 + C----202 R------3 0.100000e+01 R-----37 0.100000e+01 + C----203 OBJ.FUNC 0.000000e+00 + C----204 R------1 -.100000e+01 R------2 -.100000e+01 + C----204 R------5 0.100000e+01 R------7 0.100000e+01 + C----204 R-----12 0.100000e+01 R-----13 0.100000e+01 + C----204 R-----14 0.100000e+01 R-----15 0.100000e+01 + C----204 R-----16 0.100000e+01 R-----19 0.100000e+01 + C----204 R-----20 -.100000e+01 R-----21 0.100000e+01 + C----204 R-----23 0.100000e+01 R-----24 -.100000e+01 + C----204 R-----25 -.100000e+01 R-----27 -.100000e+01 + C----204 R-----28 0.100000e+01 R-----29 0.100000e+01 + C----204 R-----32 0.100000e+01 R-----34 -.100000e+01 + C----204 R-----38 -.100000e+01 R-----39 0.100000e+01 + C----204 R-----43 0.100000e+01 R-----44 -.100000e+01 + C----204 R-----45 0.100000e+01 R-----46 0.100000e+01 + C----204 R-----47 -.100000e+01 R-----50 0.100000e+01 + C----204 R-----52 0.100000e+01 R-----53 0.100000e+01 + C----204 R-----59 -.100000e+01 R-----60 0.100000e+01 + C----204 R-----61 -.100000e+01 R-----62 0.100000e+01 + C----204 R-----64 -.100000e+01 R-----65 -.100000e+01 + C----204 R-----66 -.100000e+01 R-----67 0.100000e+01 + C----204 R-----68 -.100000e+01 R-----72 0.100000e+01 + C----204 R-----73 0.100000e+01 R-----77 -.100000e+01 + C----204 R-----79 0.100000e+01 R-----81 0.100000e+01 + C----204 R-----82 0.100000e+01 R-----83 0.100000e+01 + C----204 R-----84 -.100000e+01 R-----85 0.100000e+01 + C----205 R------1 0.100000e+01 R------2 0.100000e+01 + C----205 R------3 -.100000e+01 R------5 -.100000e+01 + C----205 R------7 -.100000e+01 R-----12 -.100000e+01 + C----205 R-----13 -.100000e+01 R-----14 -.100000e+01 + C----205 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----205 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----205 R-----21 -.100000e+01 R-----23 -.100000e+01 + C----205 R-----24 0.100000e+01 R-----25 0.100000e+01 + C----205 R-----27 0.100000e+01 R-----28 -.100000e+01 + C----205 R-----29 -.100000e+01 R-----32 -.100000e+01 + C----205 R-----34 0.100000e+01 R-----37 -.100000e+01 + C----205 R-----38 0.100000e+01 R-----39 -.100000e+01 + C----205 R-----43 -.100000e+01 R-----44 0.100000e+01 + C----205 R-----45 -.100000e+01 R-----46 -.100000e+01 + C----205 R-----47 0.100000e+01 R-----50 -.100000e+01 + C----205 R-----52 -.100000e+01 R-----53 -.100000e+01 + C----205 R-----59 0.100000e+01 R-----60 -.100000e+01 + C----205 R-----61 0.100000e+01 R-----62 -.100000e+01 + C----205 R-----64 0.100000e+01 R-----65 0.100000e+01 + C----205 R-----66 0.100000e+01 R-----67 -.100000e+01 + C----205 R-----68 0.100000e+01 R-----72 -.100000e+01 + C----205 R-----73 -.100000e+01 R-----77 0.100000e+01 + C----205 R-----79 -.100000e+01 R-----81 -.100000e+01 + C----205 R-----82 -.100000e+01 R-----83 -.100000e+01 + C----205 R-----84 0.100000e+01 R-----85 -.100000e+01 + C----206 OBJ.FUNC 0.000000e+00 + C----207 R------1 0.100000e+01 R------2 -.100000e+01 + C----207 R------6 0.100000e+01 R-----10 0.100000e+01 + C----207 R-----17 -.100000e+01 R-----18 0.100000e+01 + C----207 R-----31 0.100000e+01 R-----38 -.100000e+01 + C----207 R-----41 0.100000e+01 R-----42 -.100000e+01 + C----207 R-----44 0.100000e+01 R-----45 0.100000e+01 + C----207 R-----46 0.100000e+01 R-----47 -.100000e+01 + C----207 R-----49 -.100000e+01 R-----53 0.100000e+01 + C----207 R-----55 -.100000e+01 R-----64 -.100000e+01 + C----207 R-----65 -.100000e+01 R-----66 -.100000e+01 + C----207 R-----67 0.100000e+01 R-----75 0.100000e+01 + C----207 R-----77 0.100000e+01 R-----78 -.100000e+01 + C----207 R-----79 0.100000e+01 R-----80 0.100000e+01 + C----208 OBJ.FUNC 0.000000e+00 + C----209 R------1 -.100000e+01 R------4 -.100000e+01 + C----209 R------5 -.100000e+01 R------7 -.100000e+01 + C----209 R------9 -.100000e+01 R-----10 -.100000e+01 + C----209 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----209 R-----23 0.100000e+01 R-----24 0.100000e+01 + C----209 R-----28 -.100000e+01 R-----29 -.100000e+01 + C----209 R-----31 -.100000e+01 R-----33 -.100000e+01 + C----209 R-----35 -.100000e+01 R-----37 -.100000e+01 + C----209 R-----39 0.100000e+01 R-----40 -.100000e+01 + C----209 R-----43 0.100000e+01 R-----46 -.100000e+01 + C----209 R-----49 0.100000e+01 R-----53 -.100000e+01 + C----209 R-----55 0.100000e+01 R-----56 0.100000e+01 + C----209 R-----60 -.100000e+01 R-----61 0.100000e+01 + C----209 R-----62 -.100000e+01 R-----65 0.100000e+01 + C----209 R-----67 -.100000e+01 R-----68 0.100000e+01 + C----209 R-----70 -.100000e+01 R-----71 0.100000e+01 + C----209 R-----72 -.100000e+01 R-----73 -.100000e+01 + C----209 R-----74 0.100000e+01 R-----76 -.100000e+01 + C----209 R-----78 0.100000e+01 R-----81 0.100000e+01 + C----209 R-----82 -.100000e+01 + C----210 R------3 -.100000e+01 R------5 0.100000e+01 + C----210 R------6 -.100000e+01 R-----13 -.100000e+01 + C----210 R-----16 0.100000e+01 R-----18 -.100000e+01 + C----210 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----210 R-----21 -.100000e+01 R-----22 0.100000e+01 + C----210 R-----23 -.100000e+01 R-----28 0.100000e+01 + C----210 R-----30 -.100000e+01 R-----32 -.100000e+01 + C----210 R-----33 0.100000e+01 R-----36 -.100000e+01 + C----210 R-----39 -.100000e+01 R-----41 -.100000e+01 + C----210 R-----42 0.100000e+01 R-----43 -.100000e+01 + C----210 R-----45 -.100000e+01 R-----50 -.100000e+01 + C----210 R-----51 -.100000e+01 R-----52 -.100000e+01 + C----210 R-----54 -.100000e+01 R-----59 0.100000e+01 + C----210 R-----73 0.100000e+01 R-----74 -.100000e+01 + C----210 R-----76 0.100000e+01 R-----79 -.100000e+01 + C----210 R-----81 -.100000e+01 R-----85 -.100000e+01 + C----211 R------2 0.100000e+01 R------3 0.100000e+01 + C----211 R------4 0.100000e+01 R------7 0.100000e+01 + C----211 R------9 0.100000e+01 R-----13 0.100000e+01 + C----211 R-----15 0.100000e+01 R-----17 0.100000e+01 + C----211 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----211 R-----21 0.100000e+01 R-----22 -.100000e+01 + C----211 R-----24 -.100000e+01 R-----29 0.100000e+01 + C----211 R-----30 0.100000e+01 R-----32 0.100000e+01 + C----211 R-----35 0.100000e+01 R-----36 0.100000e+01 + C----211 R-----37 0.100000e+01 R-----38 0.100000e+01 + C----211 R-----40 0.100000e+01 R-----44 -.100000e+01 + C----211 R-----47 0.100000e+01 R-----50 0.100000e+01 + C----211 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----211 R-----54 0.100000e+01 R-----56 -.100000e+01 + C----211 R-----59 -.100000e+01 R-----60 0.100000e+01 + C----211 R-----61 -.100000e+01 R-----62 0.100000e+01 + C----211 R-----64 0.100000e+01 R-----66 0.100000e+01 + C----211 R-----68 -.100000e+01 R-----70 0.100000e+01 + C----211 R-----71 -.100000e+01 R-----72 0.100000e+01 + C----211 R-----75 -.100000e+01 R-----77 -.100000e+01 + C----211 R-----80 -.100000e+01 R-----82 0.100000e+01 + C----211 R-----85 0.100000e+01 + C----212 R------2 0.100000e+01 R------4 -.100000e+01 + C----212 R------5 0.100000e+01 R------7 0.100000e+01 + C----212 R------9 0.100000e+01 R-----10 0.100000e+01 + C----212 R-----14 0.100000e+01 R-----15 0.100000e+01 + C----212 R-----16 0.100000e+01 R-----19 0.100000e+01 + C----212 R-----20 -.100000e+01 R-----23 0.100000e+01 + C----212 R-----24 -.100000e+01 R-----29 0.100000e+01 + C----212 R-----33 0.100000e+01 R-----34 -.100000e+01 + C----212 R-----35 0.100000e+01 R-----36 0.100000e+01 + C----212 R-----38 -.100000e+01 R-----40 0.100000e+01 + C----212 R-----41 0.100000e+01 R-----42 -.100000e+01 + C----212 R-----47 0.100000e+01 R-----48 0.100000e+01 + C----212 R-----49 -.100000e+01 R-----55 0.100000e+01 + C----212 R-----57 -.100000e+01 R-----65 0.100000e+01 + C----212 R-----68 -.100000e+01 R-----69 0.100000e+01 + C----212 R-----74 0.100000e+01 R-----77 0.100000e+01 + C----212 R-----80 -.100000e+01 R-----83 -.100000e+01 + C----213 OBJ.FUNC 0.000000e+00 + C----214 R------1 -.100000e+01 R------2 -.100000e+01 + C----214 R------5 -.100000e+01 R------6 -.100000e+01 + C----214 R------7 -.100000e+01 R------8 -.100000e+01 + C----214 R-----10 -.100000e+01 R-----11 -.100000e+01 + C----214 R-----14 -.100000e+01 R-----15 -.100000e+01 + C----214 R-----16 -.100000e+01 R-----18 -.100000e+01 + C----214 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----214 R-----21 -.100000e+01 R-----23 -.100000e+01 + C----214 R-----24 0.100000e+01 R-----25 -.100000e+01 + C----214 R-----26 0.100000e+01 R-----29 -.100000e+01 + C----214 R-----33 -.100000e+01 R-----35 -.100000e+01 + C----214 R-----36 -.100000e+01 R-----40 -.100000e+01 + C----214 R-----42 0.100000e+01 R-----45 -.100000e+01 + C----214 R-----46 -.100000e+01 R-----47 -.100000e+01 + C----214 R-----48 -.100000e+01 R-----50 -.100000e+01 + C----214 R-----55 -.100000e+01 R-----57 0.100000e+01 + C----214 R-----58 -.100000e+01 R-----61 0.100000e+01 + C----214 R-----63 0.100000e+01 R-----64 -.100000e+01 + C----214 R-----68 0.100000e+01 R-----69 -.100000e+01 + C----214 R-----70 0.100000e+01 R-----72 0.100000e+01 + C----214 R-----74 -.100000e+01 R-----75 -.100000e+01 + C----214 R-----80 0.100000e+01 R-----81 0.100000e+01 + C----214 R-----83 0.100000e+01 R-----84 -.100000e+01 + C----215 R-----58 0.100000e+01 + C----216 OBJ.FUNC 0.000000e+00 + C----217 R------1 0.100000e+01 R------4 0.100000e+01 + C----217 R------6 0.100000e+01 R------8 0.100000e+01 + C----217 R------9 -.100000e+01 R-----11 0.100000e+01 + C----217 R-----18 0.100000e+01 R-----21 0.100000e+01 + C----217 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----217 R-----34 0.100000e+01 R-----38 0.100000e+01 + C----217 R-----41 -.100000e+01 R-----45 0.100000e+01 + C----217 R-----46 0.100000e+01 R-----49 0.100000e+01 + C----217 R-----50 0.100000e+01 R-----61 -.100000e+01 + C----217 R-----63 -.100000e+01 R-----64 0.100000e+01 + C----217 R-----65 -.100000e+01 R-----70 -.100000e+01 + C----217 R-----72 -.100000e+01 R-----75 0.100000e+01 + C----217 R-----77 -.100000e+01 R-----81 -.100000e+01 + C----217 R-----84 0.100000e+01 + C----218 OBJ.FUNC 0.000000e+00 + C----219 R------1 0.100000e+01 R-----43 -.100000e+01 + C----220 OBJ.FUNC 0.000000e+00 + C----221 R------2 0.100000e+01 R------4 -.100000e+01 + C----221 R------5 0.100000e+01 R------9 0.100000e+01 + C----221 R-----10 0.100000e+01 R-----12 -.100000e+01 + C----221 R-----18 0.100000e+01 R-----20 -.100000e+01 + C----221 R-----25 0.100000e+01 R-----30 0.100000e+01 + C----221 R-----35 0.100000e+01 R-----38 -.100000e+01 + C----221 R-----40 0.100000e+01 R-----41 -.100000e+01 + C----221 R-----47 0.100000e+01 R-----49 -.100000e+01 + C----221 R-----51 -.100000e+01 R-----53 -.100000e+01 + C----221 R-----55 0.100000e+01 R-----57 -.100000e+01 + C----221 R-----61 0.100000e+01 R-----63 0.100000e+01 + C----221 R-----65 0.100000e+01 R-----66 -.100000e+01 + C----221 R-----68 -.100000e+01 R-----70 -.100000e+01 + C----221 R-----74 0.100000e+01 R-----77 0.100000e+01 + C----221 R-----80 -.100000e+01 R-----84 0.100000e+01 + C----221 R-----85 -.100000e+01 + C----222 R------4 0.100000e+01 R------6 0.100000e+01 + C----222 R------8 0.100000e+01 R------9 -.100000e+01 + C----222 R-----12 0.100000e+01 R-----20 0.100000e+01 + C----222 R-----21 0.100000e+01 R-----26 -.100000e+01 + C----222 R-----28 0.100000e+01 R-----30 -.100000e+01 + C----222 R-----31 0.100000e+01 R-----33 0.100000e+01 + C----222 R-----36 0.100000e+01 R-----38 0.100000e+01 + C----222 R-----41 0.100000e+01 R-----43 0.100000e+01 + C----222 R-----48 0.100000e+01 R-----51 0.100000e+01 + C----222 R-----61 -.100000e+01 R-----63 -.100000e+01 + C----222 R-----64 0.100000e+01 R-----65 -.100000e+01 + C----222 R-----66 0.100000e+01 R-----68 0.100000e+01 + C----222 R-----70 0.100000e+01 R-----72 -.100000e+01 + C----222 R-----77 -.100000e+01 R-----81 -.100000e+01 + C----223 R------1 -.100000e+01 R------2 -.100000e+01 + C----223 R------5 -.100000e+01 R------6 -.100000e+01 + C----223 R------8 -.100000e+01 R-----10 -.100000e+01 + C----223 R-----18 -.100000e+01 R-----21 -.100000e+01 + C----223 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----223 R-----28 -.100000e+01 R-----31 -.100000e+01 + C----223 R-----33 -.100000e+01 R-----35 -.100000e+01 + C----223 R-----36 -.100000e+01 R-----40 -.100000e+01 + C----223 R-----47 -.100000e+01 R-----48 -.100000e+01 + C----223 R-----49 0.100000e+01 R-----53 0.100000e+01 + C----223 R-----55 -.100000e+01 R-----57 0.100000e+01 + C----223 R-----64 -.100000e+01 R-----72 0.100000e+01 + C----223 R-----74 -.100000e+01 R-----80 0.100000e+01 + C----223 R-----81 0.100000e+01 R-----84 -.100000e+01 + C----223 R-----85 0.100000e+01 + C----224 R------3 -.100000e+01 R------4 0.100000e+01 + C----224 R------7 -.100000e+01 R------9 0.100000e+01 + C----224 R-----11 -.100000e+01 R-----12 0.100000e+01 + C----224 R-----13 -.100000e+01 R-----14 -.100000e+01 + C----224 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----224 R-----17 -.100000e+01 R-----19 -.100000e+01 + C----224 R-----20 0.100000e+01 R-----22 -.100000e+01 + C----224 R-----23 -.100000e+01 R-----24 0.100000e+01 + C----224 R-----27 0.100000e+01 R-----29 -.100000e+01 + C----224 R-----30 0.100000e+01 R-----32 -.100000e+01 + C----224 R-----34 0.100000e+01 R-----37 -.100000e+01 + C----224 R-----38 0.100000e+01 R-----39 -.100000e+01 + C----224 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----224 R-----44 0.100000e+01 R-----45 -.100000e+01 + C----224 R-----46 -.100000e+01 R-----50 -.100000e+01 + C----224 R-----51 0.100000e+01 R-----52 -.100000e+01 + C----224 R-----54 0.100000e+01 R-----56 -.100000e+01 + C----224 R-----58 -.100000e+01 R-----59 0.100000e+01 + C----224 R-----60 -.100000e+01 R-----61 0.100000e+01 + C----224 R-----62 -.100000e+01 R-----63 0.100000e+01 + C----224 R-----65 0.100000e+01 R-----66 0.100000e+01 + C----224 R-----67 -.100000e+01 R-----68 0.100000e+01 + C----224 R-----69 -.100000e+01 R-----70 0.100000e+01 + C----224 R-----71 -.100000e+01 R-----73 -.100000e+01 + C----224 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----224 R-----77 0.100000e+01 R-----78 -.100000e+01 + C----224 R-----79 -.100000e+01 R-----82 -.100000e+01 + C----224 R-----83 0.100000e+01 + C----225 OBJ.FUNC 0.000000e+00 + C----226 OBJ.FUNC 0.000000e+00 + C----227 R------3 0.100000e+01 R-----17 0.100000e+01 + C----227 R-----24 -.100000e+01 R-----26 0.100000e+01 + C----227 R-----34 -.100000e+01 R-----37 0.100000e+01 + C----227 R-----45 0.100000e+01 R-----49 -.100000e+01 + C----227 R-----51 -.100000e+01 R-----56 0.100000e+01 + C----227 R-----57 -.100000e+01 R-----59 -.100000e+01 + C----227 R-----60 0.100000e+01 R-----67 0.100000e+01 + C----227 R-----68 -.100000e+01 R-----71 0.100000e+01 + C----227 R-----73 0.100000e+01 R-----76 -.100000e+01 + C----227 R-----78 0.100000e+01 R-----80 -.100000e+01 + C----227 R-----81 0.100000e+01 R-----85 -.100000e+01 + C----228 R------4 -.100000e+01 R------7 0.100000e+01 + C----228 R------9 -.100000e+01 R-----11 0.100000e+01 + C----228 R-----12 -.100000e+01 R-----13 0.100000e+01 + C----228 R-----14 0.100000e+01 R-----15 0.100000e+01 + C----228 R-----16 0.100000e+01 R-----19 0.100000e+01 + C----228 R-----20 -.100000e+01 R-----22 0.100000e+01 + C----228 R-----23 0.100000e+01 R-----26 -.100000e+01 + C----228 R-----27 -.100000e+01 R-----29 0.100000e+01 + C----228 R-----30 -.100000e+01 R-----32 0.100000e+01 + C----228 R-----38 -.100000e+01 R-----39 0.100000e+01 + C----228 R-----41 -.100000e+01 R-----42 -.100000e+01 + C----228 R-----44 -.100000e+01 R-----46 0.100000e+01 + C----228 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----228 R-----52 0.100000e+01 R-----54 -.100000e+01 + C----228 R-----57 0.100000e+01 R-----58 0.100000e+01 + C----228 R-----61 -.100000e+01 R-----62 0.100000e+01 + C----228 R-----63 -.100000e+01 R-----65 -.100000e+01 + C----228 R-----66 -.100000e+01 R-----69 0.100000e+01 + C----228 R-----70 -.100000e+01 R-----75 0.100000e+01 + C----228 R-----77 -.100000e+01 R-----79 0.100000e+01 + C----228 R-----80 0.100000e+01 R-----81 -.100000e+01 + C----228 R-----82 0.100000e+01 R-----83 -.100000e+01 + C----228 R-----85 0.100000e+01 + C----229 OBJ.FUNC 0.000000e+00 + C----230 R------1 0.100000e+01 + C----231 OBJ.FUNC 0.000000e+00 + C----232 R------1 -.100000e+01 R------3 0.100000e+01 + C----232 R-----10 -.100000e+01 R-----21 -.100000e+01 + C----232 R-----29 0.100000e+01 R-----32 -.100000e+01 + C----233 R------2 -.100000e+01 R------3 -.100000e+01 + C----233 R------5 -.100000e+01 R------6 -.100000e+01 + C----233 R------8 -.100000e+01 R-----11 -.100000e+01 + C----233 R-----18 -.100000e+01 R-----23 -.100000e+01 + C----233 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----233 R-----28 -.100000e+01 R-----29 -.100000e+01 + C----233 R-----32 0.100000e+01 R-----33 -.100000e+01 + C----233 R-----35 -.100000e+01 R-----36 -.100000e+01 + C----233 R-----39 -.100000e+01 R-----43 -.100000e+01 + C----233 R-----47 -.100000e+01 R-----48 -.100000e+01 + C----233 R-----49 0.100000e+01 R-----52 -.100000e+01 + C----233 R-----53 0.100000e+01 R-----55 -.100000e+01 + C----233 R-----57 0.100000e+01 R-----64 -.100000e+01 + C----233 R-----75 -.100000e+01 R-----80 0.100000e+01 + C----233 R-----81 0.100000e+01 R-----82 -.100000e+01 + C----233 R-----84 -.100000e+01 R-----85 0.100000e+01 + C----234 R-----11 0.100000e+01 R-----23 0.100000e+01 + C----234 R-----31 -.100000e+01 R-----39 0.100000e+01 + C----234 R-----40 -.100000e+01 R-----52 0.100000e+01 + C----234 R-----72 0.100000e+01 R-----74 -.100000e+01 + C----234 R-----75 0.100000e+01 R-----82 0.100000e+01 + C----235 R------2 0.100000e+01 R------5 0.100000e+01 + C----235 R------6 0.100000e+01 R------8 0.100000e+01 + C----235 R-----10 0.100000e+01 R-----18 0.100000e+01 + C----235 R-----21 0.100000e+01 R-----25 0.100000e+01 + C----235 R-----26 -.100000e+01 R-----28 0.100000e+01 + C----235 R-----31 0.100000e+01 R-----33 0.100000e+01 + C----235 R-----35 0.100000e+01 R-----36 0.100000e+01 + C----235 R-----40 0.100000e+01 R-----43 0.100000e+01 + C----235 R-----47 0.100000e+01 R-----48 0.100000e+01 + C----235 R-----49 -.100000e+01 R-----53 -.100000e+01 + C----235 R-----55 0.100000e+01 R-----57 -.100000e+01 + C----235 R-----64 0.100000e+01 R-----72 -.100000e+01 + C----235 R-----74 0.100000e+01 R-----80 -.100000e+01 + C----235 R-----81 -.100000e+01 R-----84 0.100000e+01 + C----235 R-----85 -.100000e+01 + C----236 R------3 0.100000e+01 R------7 -.100000e+01 + C----236 R------9 -.100000e+01 R-----10 -.100000e+01 + C----236 R-----11 -.100000e+01 R-----12 -.100000e+01 + C----236 R-----14 -.100000e+01 R-----17 0.100000e+01 + C----236 R-----20 -.100000e+01 R-----24 -.100000e+01 + C----236 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----236 R-----30 -.100000e+01 R-----36 -.100000e+01 + C----236 R-----37 0.100000e+01 R-----40 -.100000e+01 + C----236 R-----41 -.100000e+01 R-----42 -.100000e+01 + C----236 R-----45 0.100000e+01 R-----48 -.100000e+01 + C----236 R-----53 -.100000e+01 R-----56 0.100000e+01 + C----236 R-----60 0.100000e+01 R-----62 -.100000e+01 + C----236 R-----64 -.100000e+01 R-----71 0.100000e+01 + C----236 R-----73 0.100000e+01 R-----78 0.100000e+01 + C----236 R-----79 -.100000e+01 R-----81 0.100000e+01 + C----236 R-----84 -.100000e+01 + C----237 R------2 0.100000e+01 R------4 -.100000e+01 + C----237 R------6 0.100000e+01 R------7 0.100000e+01 + C----237 R------8 0.100000e+01 R-----10 0.100000e+01 + C----237 R-----11 0.100000e+01 R-----14 0.100000e+01 + C----237 R-----15 0.100000e+01 R-----17 -.100000e+01 + C----237 R-----20 0.100000e+01 R-----22 0.100000e+01 + C----237 R-----23 0.100000e+01 R-----24 0.100000e+01 + C----237 R-----27 -.100000e+01 R-----29 0.100000e+01 + C----237 R-----31 0.100000e+01 R-----39 0.100000e+01 + C----237 R-----42 0.100000e+01 R-----43 0.100000e+01 + C----237 R-----45 -.100000e+01 R-----46 0.100000e+01 + C----237 R-----47 0.100000e+01 R-----52 0.100000e+01 + C----237 R-----53 0.100000e+01 R-----55 0.100000e+01 + C----237 R-----56 -.100000e+01 R-----57 0.100000e+01 + C----237 R-----58 -.100000e+01 R-----59 -.100000e+01 + C----237 R-----60 -.100000e+01 R-----62 0.100000e+01 + C----237 R-----64 0.100000e+01 R-----65 0.100000e+01 + C----237 R-----66 -.100000e+01 R-----68 0.100000e+01 + C----237 R-----70 0.100000e+01 R-----73 -.100000e+01 + C----237 R-----75 0.100000e+01 R-----76 -.100000e+01 + C----237 R-----78 -.100000e+01 R-----79 0.100000e+01 + C----237 R-----81 -.100000e+01 R-----84 0.100000e+01 + C----237 R-----85 0.100000e+01 + C----238 R------2 -.100000e+01 R------3 -.100000e+01 + C----238 R------4 0.100000e+01 R------6 -.100000e+01 + C----238 R------8 -.100000e+01 R------9 0.100000e+01 + C----238 R-----12 0.100000e+01 R-----15 -.100000e+01 + C----238 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----238 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----238 R-----27 0.100000e+01 R-----29 -.100000e+01 + C----238 R-----30 0.100000e+01 R-----31 -.100000e+01 + C----238 R-----34 0.100000e+01 R-----36 0.100000e+01 + C----238 R-----37 -.100000e+01 R-----40 0.100000e+01 + C----238 R-----41 0.100000e+01 R-----43 -.100000e+01 + C----238 R-----46 -.100000e+01 R-----47 -.100000e+01 + C----238 R-----48 0.100000e+01 R-----52 -.100000e+01 + C----238 R-----55 -.100000e+01 R-----57 -.100000e+01 + C----238 R-----59 0.100000e+01 R-----65 -.100000e+01 + C----238 R-----66 0.100000e+01 R-----68 -.100000e+01 + C----238 R-----70 -.100000e+01 R-----71 -.100000e+01 + C----238 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----238 R-----85 -.100000e+01 + C----239 OBJ.FUNC 0.000000e+00 + C----240 OBJ.FUNC 0.000000e+00 + C----241 R-----34 -.100000e+01 R-----39 -.100000e+01 + C----241 R-----58 0.100000e+01 + C----242 OBJ.FUNC 0.000000e+00 + C----243 R------3 0.100000e+01 R------5 -.100000e+01 + C----243 R------6 -.100000e+01 R-----13 -.100000e+01 + C----243 R-----15 -.100000e+01 R-----16 -.100000e+01 + C----243 R-----20 -.100000e+01 R-----21 -.100000e+01 + C----243 R-----26 0.100000e+01 R-----28 -.100000e+01 + C----243 R-----36 -.100000e+01 R-----37 0.100000e+01 + C----243 R-----45 0.100000e+01 R-----46 -.100000e+01 + C----243 R-----48 -.100000e+01 R-----50 -.100000e+01 + C----243 R-----52 -.100000e+01 R-----53 -.100000e+01 + C----243 R-----56 0.100000e+01 R-----58 -.100000e+01 + C----243 R-----59 -.100000e+01 R-----60 0.100000e+01 + C----243 R-----64 -.100000e+01 R-----65 -.100000e+01 + C----243 R-----66 -.100000e+01 R-----67 0.100000e+01 + C----243 R-----71 0.100000e+01 R-----73 0.100000e+01 + C----243 R-----80 0.100000e+01 R-----81 0.100000e+01 + C----243 R-----85 -.100000e+01 + C----244 R------1 0.100000e+01 R------2 0.100000e+01 + C----244 R------4 0.100000e+01 R------8 0.100000e+01 + C----244 R-----11 -.100000e+01 R-----20 0.100000e+01 + C----244 R-----29 -.100000e+01 R-----30 0.100000e+01 + C----244 R-----31 0.100000e+01 R-----33 0.100000e+01 + C----244 R-----34 -.100000e+01 R-----36 0.100000e+01 + C----244 R-----37 -.100000e+01 R-----38 -.100000e+01 + C----244 R-----41 0.100000e+01 R-----42 -.100000e+01 + C----244 R-----45 -.100000e+01 R-----47 0.100000e+01 + C----244 R-----49 -.100000e+01 R-----51 0.100000e+01 + C----244 R-----54 0.100000e+01 R-----55 -.100000e+01 + C----244 R-----59 0.100000e+01 R-----62 -.100000e+01 + C----244 R-----63 0.100000e+01 R-----65 0.100000e+01 + C----244 R-----66 0.100000e+01 R-----68 0.100000e+01 + C----244 R-----70 -.100000e+01 R-----72 -.100000e+01 + C----244 R-----74 0.100000e+01 R-----77 -.100000e+01 + C----244 R-----79 -.100000e+01 R-----81 -.100000e+01 + C----244 R-----84 0.100000e+01 + C----245 R------1 -.100000e+01 R------2 -.100000e+01 + C----245 R------3 -.100000e+01 R------4 -.100000e+01 + C----245 R------5 0.100000e+01 R------6 0.100000e+01 + C----245 R------8 -.100000e+01 R-----11 0.100000e+01 + C----245 R-----13 0.100000e+01 R-----15 0.100000e+01 + C----245 R-----16 0.100000e+01 R-----21 0.100000e+01 + C----245 R-----26 -.100000e+01 R-----28 0.100000e+01 + C----245 R-----29 0.100000e+01 R-----30 -.100000e+01 + C----245 R-----31 -.100000e+01 R-----33 -.100000e+01 + C----245 R-----34 0.100000e+01 R-----38 0.100000e+01 + C----245 R-----41 -.100000e+01 R-----42 0.100000e+01 + C----245 R-----46 0.100000e+01 R-----47 -.100000e+01 + C----245 R-----48 0.100000e+01 R-----49 0.100000e+01 + C----245 R-----50 0.100000e+01 R-----51 -.100000e+01 + C----245 R-----52 0.100000e+01 R-----53 0.100000e+01 + C----245 R-----54 -.100000e+01 R-----55 0.100000e+01 + C----245 R-----56 -.100000e+01 R-----58 0.100000e+01 + C----245 R-----60 -.100000e+01 R-----62 0.100000e+01 + C----245 R-----63 -.100000e+01 R-----64 0.100000e+01 + C----245 R-----67 -.100000e+01 R-----68 -.100000e+01 + C----245 R-----70 0.100000e+01 R-----71 -.100000e+01 + C----245 R-----72 0.100000e+01 R-----73 -.100000e+01 + C----245 R-----74 -.100000e+01 R-----77 0.100000e+01 + C----245 R-----79 0.100000e+01 R-----80 -.100000e+01 + C----245 R-----84 -.100000e+01 R-----85 0.100000e+01 + C----246 OBJ.FUNC 0.000000e+00 + C----247 OBJ.FUNC 0.000000e+00 + C----248 R------3 0.100000e+01 R------9 -.100000e+01 + C----248 R-----11 -.100000e+01 R-----13 0.100000e+01 + C----248 R-----15 0.100000e+01 R-----17 0.100000e+01 + C----248 R-----18 -.100000e+01 R-----24 -.100000e+01 + C----248 R-----26 0.100000e+01 R-----31 -.100000e+01 + C----248 R-----32 0.100000e+01 R-----34 -.100000e+01 + C----248 R-----37 -.100000e+01 R-----38 -.100000e+01 + C----248 R-----40 -.100000e+01 R-----41 -.100000e+01 + C----248 R-----45 -.100000e+01 R-----46 0.100000e+01 + C----248 R-----50 0.100000e+01 R-----53 0.100000e+01 + C----248 R-----55 -.100000e+01 R-----56 0.100000e+01 + C----248 R-----57 0.100000e+01 R-----58 0.100000e+01 + C----248 R-----60 0.100000e+01 R-----61 -.100000e+01 + C----248 R-----62 -.100000e+01 R-----67 0.100000e+01 + C----248 R-----68 -.100000e+01 R-----69 0.100000e+01 + C----248 R-----70 -.100000e+01 R-----71 0.100000e+01 + C----248 R-----72 -.100000e+01 R-----73 0.100000e+01 + C----248 R-----76 -.100000e+01 R-----77 -.100000e+01 + C----248 R-----79 0.100000e+01 R-----80 0.100000e+01 + C----248 R-----81 -.100000e+01 R-----85 0.100000e+01 + C----249 R-----23 0.100000e+01 R-----39 0.100000e+01 + C----249 R-----42 -.100000e+01 R-----72 0.100000e+01 + C----249 R-----75 0.100000e+01 R-----79 -.100000e+01 + C----249 R-----82 0.100000e+01 + C----250 R------1 0.100000e+01 R-----21 -.100000e+01 + C----251 OBJ.FUNC 0.000000e+00 + C----252 R------3 -.100000e+01 R------4 0.100000e+01 + C----252 R------9 0.100000e+01 R-----13 -.100000e+01 + C----252 R-----15 -.100000e+01 R-----17 -.100000e+01 + C----252 R-----18 0.100000e+01 R-----21 0.100000e+01 + C----252 R-----23 -.100000e+01 R-----24 0.100000e+01 + C----252 R-----26 -.100000e+01 R-----31 0.100000e+01 + C----252 R-----32 -.100000e+01 R-----34 0.100000e+01 + C----252 R-----37 0.100000e+01 R-----38 0.100000e+01 + C----252 R-----39 -.100000e+01 R-----40 0.100000e+01 + C----252 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----252 R-----45 0.100000e+01 R-----46 -.100000e+01 + C----252 R-----50 -.100000e+01 R-----52 -.100000e+01 + C----252 R-----53 -.100000e+01 R-----55 0.100000e+01 + C----252 R-----56 -.100000e+01 R-----57 -.100000e+01 + C----252 R-----58 -.100000e+01 R-----60 -.100000e+01 + C----252 R-----61 0.100000e+01 R-----62 0.100000e+01 + C----252 R-----67 -.100000e+01 R-----68 0.100000e+01 + C----252 R-----69 -.100000e+01 R-----70 0.100000e+01 + C----252 R-----71 -.100000e+01 R-----73 -.100000e+01 + C----252 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----252 R-----77 0.100000e+01 R-----80 -.100000e+01 + C----252 R-----81 0.100000e+01 R-----82 -.100000e+01 + C----252 R-----85 -.100000e+01 + C----253 R------1 -.100000e+01 R------4 -.100000e+01 + C----253 R-----11 0.100000e+01 R-----52 0.100000e+01 + C----254 R------1 -.100000e+01 R------5 0.100000e+01 + C----254 R------7 0.100000e+01 R------9 0.100000e+01 + C----254 R-----10 0.100000e+01 R-----11 0.100000e+01 + C----254 R-----12 0.100000e+01 R-----13 -.100000e+01 + C----254 R-----14 0.100000e+01 R-----15 0.100000e+01 + C----254 R-----17 -.100000e+01 R-----18 0.100000e+01 + C----254 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----254 R-----21 0.100000e+01 R-----22 0.100000e+01 + C----254 R-----23 0.100000e+01 R-----25 0.100000e+01 + C----254 R-----27 -.100000e+01 R-----30 0.100000e+01 + C----254 R-----31 0.100000e+01 R-----32 -.100000e+01 + C----254 R-----38 0.100000e+01 R-----40 0.100000e+01 + C----254 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----254 R-----44 0.100000e+01 R-----45 -.100000e+01 + C----254 R-----46 0.100000e+01 R-----48 0.100000e+01 + C----254 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----254 R-----55 0.100000e+01 R-----56 -.100000e+01 + C----254 R-----60 -.100000e+01 R-----64 0.100000e+01 + C----254 R-----66 -.100000e+01 R-----68 0.100000e+01 + C----254 R-----70 0.100000e+01 R-----72 -.100000e+01 + C----254 R-----73 -.100000e+01 R-----75 0.100000e+01 + C----254 R-----78 -.100000e+01 R-----79 0.100000e+01 + C----254 R-----80 -.100000e+01 R-----81 -.100000e+01 + C----254 R-----83 -.100000e+01 R-----84 0.100000e+01 + C----254 R-----85 0.100000e+01 + C----255 R------1 0.100000e+01 R------8 0.100000e+01 + C----255 R-----65 0.100000e+01 + C----256 R------3 0.100000e+01 R------7 -.100000e+01 + C----256 R------9 -.100000e+01 R-----10 -.100000e+01 + C----256 R-----11 -.100000e+01 R-----12 -.100000e+01 + C----256 R-----14 -.100000e+01 R-----17 0.100000e+01 + C----256 R-----20 -.100000e+01 R-----24 -.100000e+01 + C----256 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----256 R-----30 -.100000e+01 R-----34 -.100000e+01 + C----256 R-----36 -.100000e+01 R-----37 0.100000e+01 + C----256 R-----39 0.100000e+01 R-----40 -.100000e+01 + C----256 R-----41 -.100000e+01 R-----42 -.100000e+01 + C----256 R-----45 0.100000e+01 R-----48 -.100000e+01 + C----256 R-----53 -.100000e+01 R-----56 0.100000e+01 + C----256 R-----60 0.100000e+01 R-----62 -.100000e+01 + C----256 R-----64 -.100000e+01 R-----71 0.100000e+01 + C----256 R-----73 0.100000e+01 R-----78 0.100000e+01 + C----256 R-----79 -.100000e+01 R-----80 0.100000e+01 + C----256 R-----81 0.100000e+01 R-----84 -.100000e+01 + C----257 OBJ.FUNC 0.000000e+00 + C----258 R-----39 -.100000e+01 + C----259 R------3 -.100000e+01 R------5 -.100000e+01 + C----259 R------8 -.100000e+01 R-----13 0.100000e+01 + C----259 R-----15 -.100000e+01 R-----18 -.100000e+01 + C----259 R-----19 0.100000e+01 R-----21 -.100000e+01 + C----259 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----259 R-----24 0.100000e+01 R-----26 -.100000e+01 + C----259 R-----27 0.100000e+01 R-----31 -.100000e+01 + C----259 R-----32 0.100000e+01 R-----34 0.100000e+01 + C----259 R-----36 0.100000e+01 R-----37 -.100000e+01 + C----259 R-----38 -.100000e+01 R-----44 -.100000e+01 + C----259 R-----46 -.100000e+01 R-----51 -.100000e+01 + C----259 R-----52 -.100000e+01 R-----53 0.100000e+01 + C----259 R-----55 -.100000e+01 R-----62 0.100000e+01 + C----259 R-----65 -.100000e+01 R-----66 0.100000e+01 + C----259 R-----68 -.100000e+01 R-----70 -.100000e+01 + C----259 R-----71 -.100000e+01 R-----72 0.100000e+01 + C----259 R-----75 -.100000e+01 R-----83 0.100000e+01 + C----259 R-----85 -.100000e+01 + C----260 OBJ.FUNC 0.000000e+00 + C----261 R------2 0.100000e+01 R------6 0.100000e+01 + C----261 R------9 -.100000e+01 R-----12 -.100000e+01 + C----261 R-----13 0.100000e+01 R-----15 0.100000e+01 + C----261 R-----17 -.100000e+01 R-----18 0.100000e+01 + C----261 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----261 R-----21 0.100000e+01 R-----23 0.100000e+01 + C----261 R-----24 -.100000e+01 R-----27 0.100000e+01 + C----261 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----261 R-----35 0.100000e+01 R-----36 -.100000e+01 + C----261 R-----38 0.100000e+01 R-----39 -.100000e+01 + C----261 R-----40 -.100000e+01 R-----41 -.100000e+01 + C----261 R-----42 -.100000e+01 R-----45 -.100000e+01 + C----261 R-----46 0.100000e+01 R-----48 -.100000e+01 + C----261 R-----49 0.100000e+01 R-----52 0.100000e+01 + C----261 R-----54 0.100000e+01 R-----56 -.100000e+01 + C----261 R-----57 0.100000e+01 R-----58 0.100000e+01 + C----261 R-----67 -.100000e+01 R-----69 0.100000e+01 + C----261 R-----70 0.100000e+01 R-----72 0.100000e+01 + C----261 R-----75 0.100000e+01 R-----76 -.100000e+01 + C----261 R-----77 0.100000e+01 R-----81 -.100000e+01 + C----261 R-----85 0.100000e+01 + C----262 R------2 -.100000e+01 R------6 -.100000e+01 + C----262 R------8 -.100000e+01 R------9 0.100000e+01 + C----262 R-----12 0.100000e+01 R-----13 -.100000e+01 + C----262 R-----15 -.100000e+01 R-----17 0.100000e+01 + C----262 R-----18 -.100000e+01 R-----19 -.100000e+01 + C----262 R-----20 0.100000e+01 R-----21 -.100000e+01 + C----262 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----262 R-----24 0.100000e+01 R-----27 -.100000e+01 + C----262 R-----28 0.100000e+01 R-----29 -.100000e+01 + C----262 R-----36 0.100000e+01 R-----37 -.100000e+01 + C----262 R-----38 -.100000e+01 R-----40 0.100000e+01 + C----262 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----262 R-----46 -.100000e+01 R-----48 0.100000e+01 + C----262 R-----49 -.100000e+01 R-----52 -.100000e+01 + C----262 R-----54 -.100000e+01 R-----56 0.100000e+01 + C----262 R-----57 -.100000e+01 R-----58 -.100000e+01 + C----262 R-----67 0.100000e+01 R-----69 -.100000e+01 + C----262 R-----70 -.100000e+01 R-----72 -.100000e+01 + C----262 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----262 R-----77 -.100000e+01 R-----81 0.100000e+01 + C----262 R-----85 -.100000e+01 + C----263 R------8 0.100000e+01 R-----22 0.100000e+01 + C----263 R-----35 -.100000e+01 R-----39 0.100000e+01 + C----264 OBJ.FUNC 0.000000e+00 + C----265 R-----37 0.100000e+01 R-----45 0.100000e+01 + C----266 R------1 0.100000e+01 R------6 0.100000e+01 + C----266 R------7 0.100000e+01 R------9 0.100000e+01 + C----266 R-----11 0.100000e+01 R-----13 0.100000e+01 + C----266 R-----15 0.100000e+01 R-----16 -.100000e+01 + C----266 R-----17 -.100000e+01 R-----18 0.100000e+01 + C----266 R-----19 0.100000e+01 R-----21 0.100000e+01 + C----266 R-----22 -.100000e+01 R-----24 -.100000e+01 + C----266 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----266 R-----27 0.100000e+01 R-----28 -.100000e+01 + C----266 R-----30 0.100000e+01 R-----31 -.100000e+01 + C----266 R-----32 0.100000e+01 R-----34 0.100000e+01 + C----266 R-----36 0.100000e+01 R-----38 0.100000e+01 + C----266 R-----41 0.100000e+01 R-----46 0.100000e+01 + C----266 R-----47 -.100000e+01 R-----49 -.100000e+01 + C----266 R-----51 0.100000e+01 R-----54 0.100000e+01 + C----266 R-----56 -.100000e+01 R-----61 -.100000e+01 + C----266 R-----62 0.100000e+01 R-----66 -.100000e+01 + C----266 R-----68 -.100000e+01 R-----69 0.100000e+01 + C----266 R-----70 0.100000e+01 R-----71 -.100000e+01 + C----266 R-----74 -.100000e+01 R-----76 0.100000e+01 + C----266 R-----80 0.100000e+01 R-----81 -.100000e+01 + C----267 OBJ.FUNC 0.000000e+00 + C----268 OBJ.FUNC 0.000000e+00 + C----269 R------2 -.100000e+01 R------7 -.100000e+01 + C----269 R------8 -.100000e+01 R------9 -.100000e+01 + C----269 R-----11 -.100000e+01 R-----13 -.100000e+01 + C----269 R-----15 -.100000e+01 R-----16 0.100000e+01 + C----269 R-----17 0.100000e+01 R-----18 -.100000e+01 + C----269 R-----21 -.100000e+01 R-----22 0.100000e+01 + C----269 R-----24 0.100000e+01 R-----25 -.100000e+01 + C----269 R-----26 0.100000e+01 R-----27 -.100000e+01 + C----269 R-----30 -.100000e+01 R-----31 0.100000e+01 + C----269 R-----34 -.100000e+01 R-----36 -.100000e+01 + C----269 R-----37 0.100000e+01 R-----38 -.100000e+01 + C----269 R-----39 0.100000e+01 R-----41 -.100000e+01 + C----269 R-----43 -.100000e+01 R-----47 0.100000e+01 + C----269 R-----49 0.100000e+01 R-----51 -.100000e+01 + C----269 R-----54 -.100000e+01 R-----56 0.100000e+01 + C----269 R-----61 0.100000e+01 R-----62 -.100000e+01 + C----269 R-----63 -.100000e+01 R-----66 0.100000e+01 + C----269 R-----68 0.100000e+01 R-----69 -.100000e+01 + C----269 R-----71 0.100000e+01 R-----76 -.100000e+01 + C----269 R-----80 -.100000e+01 R-----81 0.100000e+01 + C----270 R------1 -.100000e+01 R------2 0.100000e+01 + C----270 R------6 -.100000e+01 R------8 0.100000e+01 + C----270 R-----19 -.100000e+01 R-----28 0.100000e+01 + C----270 R-----32 -.100000e+01 R-----37 -.100000e+01 + C----270 R-----39 -.100000e+01 R-----43 0.100000e+01 + C----270 R-----46 -.100000e+01 R-----63 0.100000e+01 + C----270 R-----70 -.100000e+01 R-----74 0.100000e+01 + C----271 OBJ.FUNC 0.000000e+00 + C----272 OBJ.FUNC 0.000000e+00 + C----273 OBJ.FUNC 0.000000e+00 + C----274 R------2 -.100000e+01 R------3 -.100000e+01 + C----274 R------4 0.100000e+01 R------5 -.100000e+01 + C----274 R------8 -.100000e+01 R------9 0.100000e+01 + C----274 R-----12 0.100000e+01 R-----14 -.100000e+01 + C----274 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----274 R-----22 -.100000e+01 R-----23 -.100000e+01 + C----274 R-----27 0.100000e+01 R-----28 -.100000e+01 + C----274 R-----30 0.100000e+01 R-----31 -.100000e+01 + C----274 R-----33 -.100000e+01 R-----34 0.100000e+01 + C----274 R-----37 -.100000e+01 R-----38 0.100000e+01 + C----274 R-----39 -.100000e+01 R-----41 0.100000e+01 + C----274 R-----43 -.100000e+01 R-----45 -.100000e+01 + C----274 R-----47 -.100000e+01 R-----51 0.100000e+01 + C----274 R-----53 0.100000e+01 R-----54 0.100000e+01 + C----274 R-----55 -.100000e+01 R-----56 -.100000e+01 + C----274 R-----57 0.100000e+01 R-----58 -.100000e+01 + C----274 R-----59 0.100000e+01 R-----60 -.100000e+01 + C----274 R-----67 -.100000e+01 R-----70 0.100000e+01 + C----274 R-----71 -.100000e+01 R-----72 0.100000e+01 + C----274 R-----73 -.100000e+01 R-----74 -.100000e+01 + C----274 R-----76 0.100000e+01 R-----77 0.100000e+01 + C----274 R-----78 -.100000e+01 R-----80 0.100000e+01 + C----274 R-----85 0.100000e+01 + C----275 OBJ.FUNC 0.000000e+00 + C----276 OBJ.FUNC 0.000000e+00 + C----277 R------2 0.100000e+01 R------3 0.100000e+01 + C----277 R------4 -.100000e+01 R------5 0.100000e+01 + C----277 R------8 0.100000e+01 R------9 -.100000e+01 + C----277 R-----12 -.100000e+01 R-----14 0.100000e+01 + C----277 R-----16 0.100000e+01 R-----17 0.100000e+01 + C----277 R-----22 0.100000e+01 R-----23 0.100000e+01 + C----277 R-----27 -.100000e+01 R-----28 0.100000e+01 + C----277 R-----30 -.100000e+01 R-----31 0.100000e+01 + C----277 R-----33 0.100000e+01 R-----34 -.100000e+01 + C----277 R-----37 0.100000e+01 R-----38 -.100000e+01 + C----277 R-----39 0.100000e+01 R-----41 -.100000e+01 + C----277 R-----43 0.100000e+01 R-----45 0.100000e+01 + C----277 R-----47 0.100000e+01 R-----51 -.100000e+01 + C----277 R-----53 -.100000e+01 R-----54 -.100000e+01 + C----277 R-----55 0.100000e+01 R-----56 0.100000e+01 + C----277 R-----57 -.100000e+01 R-----58 0.100000e+01 + C----277 R-----59 -.100000e+01 R-----60 0.100000e+01 + C----277 R-----67 0.100000e+01 R-----70 -.100000e+01 + C----277 R-----71 0.100000e+01 R-----72 -.100000e+01 + C----277 R-----73 0.100000e+01 R-----74 0.100000e+01 + C----277 R-----76 -.100000e+01 R-----77 -.100000e+01 + C----277 R-----78 0.100000e+01 R-----80 -.100000e+01 + C----277 R-----85 -.100000e+01 + C----278 OBJ.FUNC 0.000000e+00 + C----279 OBJ.FUNC 0.000000e+00 + C----280 R------1 0.100000e+01 R------2 0.100000e+01 + C----280 R------5 0.100000e+01 R------6 0.100000e+01 + C----280 R------8 0.100000e+01 R------9 0.100000e+01 + C----280 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----280 R-----18 0.100000e+01 R-----20 0.100000e+01 + C----280 R-----21 0.100000e+01 R-----22 -.100000e+01 + C----280 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----280 R-----27 0.100000e+01 R-----29 -.100000e+01 + C----280 R-----30 0.100000e+01 R-----33 0.100000e+01 + C----280 R-----34 0.100000e+01 R-----36 0.100000e+01 + C----280 R-----38 0.100000e+01 R-----41 0.100000e+01 + C----280 R-----42 0.100000e+01 R-----43 0.100000e+01 + C----280 R-----44 0.100000e+01 R-----49 -.100000e+01 + C----280 R-----50 -.100000e+01 R-----51 0.100000e+01 + C----280 R-----52 -.100000e+01 R-----53 -.100000e+01 + C----280 R-----54 0.100000e+01 R-----55 0.100000e+01 + C----280 R-----56 -.100000e+01 R-----57 -.100000e+01 + C----280 R-----63 0.100000e+01 R-----65 0.100000e+01 + C----280 R-----70 0.100000e+01 R-----71 -.100000e+01 + C----280 R-----72 -.100000e+01 R-----75 -.100000e+01 + C----280 R-----76 0.100000e+01 R-----79 -.100000e+01 + C----280 R-----81 -.100000e+01 R-----82 -.100000e+01 + C----280 R-----83 0.100000e+01 R-----85 -.100000e+01 + C----281 R------1 -.100000e+01 R-----23 0.100000e+01 + C----281 R-----46 -.100000e+01 R-----52 0.100000e+01 + C----281 R-----70 -.100000e+01 R-----72 0.100000e+01 + C----282 OBJ.FUNC 0.000000e+00 + C----283 R------2 -.100000e+01 R------5 -.100000e+01 + C----283 R------6 -.100000e+01 R------8 -.100000e+01 + C----283 R------9 -.100000e+01 R-----16 0.100000e+01 + C----283 R-----17 0.100000e+01 R-----18 -.100000e+01 + C----283 R-----20 -.100000e+01 R-----21 -.100000e+01 + C----283 R-----22 0.100000e+01 R-----23 -.100000e+01 + C----283 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----283 R-----27 -.100000e+01 R-----29 0.100000e+01 + C----283 R-----30 -.100000e+01 R-----33 -.100000e+01 + C----283 R-----34 -.100000e+01 R-----36 -.100000e+01 + C----283 R-----38 -.100000e+01 R-----41 -.100000e+01 + C----283 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----283 R-----44 -.100000e+01 R-----46 0.100000e+01 + C----283 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----283 R-----51 -.100000e+01 R-----53 0.100000e+01 + C----283 R-----54 -.100000e+01 R-----55 -.100000e+01 + C----283 R-----56 0.100000e+01 R-----57 0.100000e+01 + C----283 R-----63 -.100000e+01 R-----65 -.100000e+01 + C----283 R-----71 0.100000e+01 R-----75 0.100000e+01 + C----283 R-----76 -.100000e+01 R-----79 0.100000e+01 + C----283 R-----81 0.100000e+01 R-----82 0.100000e+01 + C----283 R-----83 -.100000e+01 R-----85 0.100000e+01 + C----284 R-----64 -.100000e+01 R-----67 0.100000e+01 + C----284 R-----85 -.100000e+01 + C----285 R------5 0.100000e+01 R-----12 0.100000e+01 + C----285 R-----16 -.100000e+01 R-----31 -.100000e+01 + C----285 R-----33 0.100000e+01 R-----40 -.100000e+01 + C----285 R-----41 0.100000e+01 R-----44 0.100000e+01 + C----285 R-----48 0.100000e+01 R-----49 -.100000e+01 + C----285 R-----59 0.100000e+01 R-----66 0.100000e+01 + C----285 R-----76 0.100000e+01 R-----77 -.100000e+01 + C----285 R-----82 -.100000e+01 + C----286 R------1 0.100000e+01 R------2 -.100000e+01 + C----286 R------3 -.100000e+01 R------4 0.100000e+01 + C----286 R------6 -.100000e+01 R------8 -.100000e+01 + C----286 R------9 0.100000e+01 R-----10 0.100000e+01 + C----286 R-----13 0.100000e+01 R-----14 -.100000e+01 + C----286 R-----21 0.100000e+01 R-----29 0.100000e+01 + C----286 R-----32 0.100000e+01 R-----34 0.100000e+01 + C----286 R-----35 0.100000e+01 R-----39 -.100000e+01 + C----286 R-----40 0.100000e+01 R-----41 -.100000e+01 + C----286 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----286 R-----50 0.100000e+01 R-----51 0.100000e+01 + C----286 R-----53 0.100000e+01 R-----57 0.100000e+01 + C----286 R-----58 -.100000e+01 R-----60 -.100000e+01 + C----286 R-----64 0.100000e+01 R-----65 -.100000e+01 + C----286 R-----67 -.100000e+01 R-----69 -.100000e+01 + C----286 R-----70 0.100000e+01 R-----75 0.100000e+01 + C----286 R-----76 -.100000e+01 R-----77 0.100000e+01 + C----286 R-----78 -.100000e+01 R-----79 0.100000e+01 + C----286 R-----83 -.100000e+01 R-----84 0.100000e+01 + C----286 R-----85 0.100000e+01 + C----287 R-----19 0.100000e+01 R-----63 -.100000e+01 + C----287 R-----72 0.100000e+01 + C----288 R------3 0.100000e+01 R------5 -.100000e+01 + C----288 R------6 0.100000e+01 R------9 -.100000e+01 + C----288 R-----13 -.100000e+01 R-----17 0.100000e+01 + C----288 R-----19 -.100000e+01 R-----20 -.100000e+01 + C----288 R-----21 -.100000e+01 R-----23 -.100000e+01 + C----288 R-----26 0.100000e+01 R-----32 -.100000e+01 + C----288 R-----33 -.100000e+01 R-----34 -.100000e+01 + C----288 R-----44 -.100000e+01 R-----46 0.100000e+01 + C----288 R-----51 -.100000e+01 R-----52 0.100000e+01 + C----288 R-----55 -.100000e+01 R-----58 0.100000e+01 + C----288 R-----60 0.100000e+01 R-----69 0.100000e+01 + C----288 R-----73 -.100000e+01 R-----82 0.100000e+01 + C----289 R------1 -.100000e+01 R------2 0.100000e+01 + C----289 R------4 -.100000e+01 R------8 0.100000e+01 + C----289 R-----10 -.100000e+01 R-----12 -.100000e+01 + C----289 R-----14 0.100000e+01 R-----16 0.100000e+01 + C----289 R-----17 -.100000e+01 R-----20 0.100000e+01 + C----289 R-----23 0.100000e+01 R-----26 -.100000e+01 + C----289 R-----29 -.100000e+01 R-----31 0.100000e+01 + C----289 R-----35 -.100000e+01 R-----39 0.100000e+01 + C----289 R-----42 0.100000e+01 R-----43 0.100000e+01 + C----289 R-----46 -.100000e+01 R-----48 -.100000e+01 + C----289 R-----49 0.100000e+01 R-----50 -.100000e+01 + C----289 R-----52 -.100000e+01 R-----53 -.100000e+01 + C----289 R-----55 0.100000e+01 R-----57 -.100000e+01 + C----289 R-----59 -.100000e+01 R-----63 0.100000e+01 + C----289 R-----65 0.100000e+01 R-----66 -.100000e+01 + C----289 R-----70 -.100000e+01 R-----72 -.100000e+01 + C----289 R-----73 0.100000e+01 R-----75 -.100000e+01 + C----289 R-----78 0.100000e+01 R-----79 -.100000e+01 + C----289 R-----83 0.100000e+01 R-----84 -.100000e+01 + C----290 OBJ.FUNC 0.000000e+00 + C----291 R------2 -.100000e+01 R------5 0.100000e+01 + C----291 R------7 -.100000e+01 R------8 -.100000e+01 + C----291 R------9 0.100000e+01 R-----10 -.100000e+01 + C----291 R-----11 -.100000e+01 R-----12 0.100000e+01 + C----291 R-----13 -.100000e+01 R-----16 -.100000e+01 + C----291 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----291 R-----21 -.100000e+01 R-----22 -.100000e+01 + C----291 R-----26 0.100000e+01 R-----28 0.100000e+01 + C----291 R-----29 -.100000e+01 R-----33 0.100000e+01 + C----291 R-----34 -.100000e+01 R-----35 -.100000e+01 + C----291 R-----37 -.100000e+01 R-----41 0.100000e+01 + C----291 R-----42 0.100000e+01 R-----43 0.100000e+01 + C----291 R-----44 0.100000e+01 R-----46 -.100000e+01 + C----291 R-----48 0.100000e+01 R-----49 -.100000e+01 + C----291 R-----50 -.100000e+01 R-----51 0.100000e+01 + C----291 R-----52 -.100000e+01 R-----57 -.100000e+01 + C----291 R-----58 -.100000e+01 R-----59 0.100000e+01 + C----291 R-----60 0.100000e+01 R-----66 0.100000e+01 + C----291 R-----67 0.100000e+01 R-----68 -.100000e+01 + C----291 R-----69 -.100000e+01 R-----74 0.100000e+01 + C----291 R-----75 -.100000e+01 R-----78 0.100000e+01 + C----291 R-----80 -.100000e+01 R-----81 0.100000e+01 + C----291 R-----84 -.100000e+01 R-----85 -.100000e+01 + C----292 R------1 -.100000e+01 R------2 0.100000e+01 + C----292 R------7 0.100000e+01 R-----10 0.100000e+01 + C----292 R-----14 0.100000e+01 R-----15 0.100000e+01 + C----292 R-----19 0.100000e+01 R-----23 0.100000e+01 + C----292 R-----26 -.100000e+01 R-----27 -.100000e+01 + C----292 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----292 R-----35 0.100000e+01 R-----47 -.100000e+01 + C----292 R-----48 -.100000e+01 R-----77 0.100000e+01 + C----292 R-----81 -.100000e+01 R-----85 0.100000e+01 + C----293 R-----37 0.100000e+01 R-----45 0.100000e+01 + C----293 R-----51 -.100000e+01 R-----59 -.100000e+01 + C----294 R------1 0.100000e+01 R------5 -.100000e+01 + C----294 R------8 0.100000e+01 R------9 -.100000e+01 + C----294 R-----11 0.100000e+01 R-----12 -.100000e+01 + C----294 R-----13 0.100000e+01 R-----14 -.100000e+01 + C----294 R-----15 -.100000e+01 R-----16 0.100000e+01 + C----294 R-----20 -.100000e+01 R-----21 0.100000e+01 + C----294 R-----22 0.100000e+01 R-----23 -.100000e+01 + C----294 R-----27 0.100000e+01 R-----33 -.100000e+01 + C----294 R-----34 0.100000e+01 R-----41 -.100000e+01 + C----294 R-----42 -.100000e+01 R-----43 -.100000e+01 + C----294 R-----44 -.100000e+01 R-----45 -.100000e+01 + C----294 R-----46 0.100000e+01 R-----47 0.100000e+01 + C----294 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----294 R-----52 0.100000e+01 R-----57 0.100000e+01 + C----294 R-----58 0.100000e+01 R-----60 -.100000e+01 + C----294 R-----66 -.100000e+01 R-----67 -.100000e+01 + C----294 R-----68 0.100000e+01 R-----69 0.100000e+01 + C----294 R-----74 -.100000e+01 R-----75 0.100000e+01 + C----294 R-----77 -.100000e+01 R-----78 -.100000e+01 + C----294 R-----80 0.100000e+01 R-----84 0.100000e+01 + C----295 OBJ.FUNC 0.000000e+00 + C----296 OBJ.FUNC 0.000000e+00 + C----297 R------3 0.100000e+01 R------4 -.100000e+01 + C----297 R------6 -.100000e+01 R------9 -.100000e+01 + C----297 R-----14 -.100000e+01 R-----18 -.100000e+01 + C----297 R-----20 -.100000e+01 R-----21 0.100000e+01 + C----297 R-----23 -.100000e+01 R-----24 0.100000e+01 + C----297 R-----28 -.100000e+01 R-----29 0.100000e+01 + C----297 R-----30 -.100000e+01 R-----32 -.100000e+01 + C----297 R-----37 0.100000e+01 R-----38 -.100000e+01 + C----297 R-----39 -.100000e+01 R-----40 0.100000e+01 + C----297 R-----45 -.100000e+01 R-----47 -.100000e+01 + C----297 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----297 R-----51 0.100000e+01 R-----52 0.100000e+01 + C----297 R-----53 -.100000e+01 R-----55 -.100000e+01 + C----297 R-----56 0.100000e+01 R-----59 0.100000e+01 + C----297 R-----64 -.100000e+01 R-----69 0.100000e+01 + C----297 R-----70 -.100000e+01 R-----71 0.100000e+01 + C----297 R-----75 0.100000e+01 R-----79 -.100000e+01 + C----297 R-----80 0.100000e+01 R-----82 -.100000e+01 + C----297 R-----83 -.100000e+01 R-----84 0.100000e+01 + C----298 R------2 -.100000e+01 R------3 -.100000e+01 + C----298 R------4 0.100000e+01 R------6 0.100000e+01 + C----298 R------8 -.100000e+01 R-----10 -.100000e+01 + C----298 R-----11 -.100000e+01 R-----13 -.100000e+01 + C----298 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----298 R-----18 0.100000e+01 R-----21 -.100000e+01 + C----298 R-----22 -.100000e+01 R-----29 -.100000e+01 + C----298 R-----31 -.100000e+01 R-----34 0.100000e+01 + C----298 R-----35 -.100000e+01 R-----37 -.100000e+01 + C----298 R-----40 -.100000e+01 R-----46 -.100000e+01 + C----298 R-----50 -.100000e+01 R-----52 -.100000e+01 + C----298 R-----56 -.100000e+01 R-----57 0.100000e+01 + C----298 R-----58 -.100000e+01 R-----64 0.100000e+01 + C----298 R-----69 -.100000e+01 R-----71 -.100000e+01 + C----298 R-----73 -.100000e+01 R-----75 -.100000e+01 + C----298 R-----76 0.100000e+01 R-----82 0.100000e+01 + C----298 R-----84 -.100000e+01 R-----85 0.100000e+01 + C----299 OBJ.FUNC 0.000000e+00 + C----300 R------2 0.100000e+01 R------8 0.100000e+01 + C----300 R------9 0.100000e+01 R-----10 0.100000e+01 + C----300 R-----11 0.100000e+01 R-----13 0.100000e+01 + C----300 R-----14 0.100000e+01 R-----16 0.100000e+01 + C----300 R-----17 0.100000e+01 R-----20 0.100000e+01 + C----300 R-----22 0.100000e+01 R-----23 0.100000e+01 + C----300 R-----24 -.100000e+01 R-----28 0.100000e+01 + C----300 R-----30 0.100000e+01 R-----31 0.100000e+01 + C----300 R-----32 0.100000e+01 R-----34 -.100000e+01 + C----300 R-----35 0.100000e+01 R-----38 0.100000e+01 + C----300 R-----39 0.100000e+01 R-----45 0.100000e+01 + C----300 R-----46 0.100000e+01 R-----47 0.100000e+01 + C----300 R-----49 -.100000e+01 R-----51 -.100000e+01 + C----300 R-----53 0.100000e+01 R-----55 0.100000e+01 + C----300 R-----57 -.100000e+01 R-----58 0.100000e+01 + C----300 R-----59 -.100000e+01 R-----70 0.100000e+01 + C----300 R-----73 0.100000e+01 R-----76 -.100000e+01 + C----300 R-----79 0.100000e+01 R-----80 -.100000e+01 + C----300 R-----83 0.100000e+01 R-----85 -.100000e+01 + C----301 OBJ.FUNC 0.000000e+00 + C----302 R-----15 -.100000e+01 R-----33 -.100000e+01 + C----302 R-----37 -.100000e+01 R-----38 0.100000e+01 + C----302 R-----39 -.100000e+01 R-----40 0.100000e+01 + C----302 R-----69 0.100000e+01 + C----303 OBJ.FUNC 0.000000e+00 + C----304 OBJ.FUNC 0.000000e+00 + C----305 OBJ.FUNC 0.000000e+00 + C----306 R------3 0.100000e+01 R------4 -.100000e+01 + C----306 R------6 0.100000e+01 R-----11 0.100000e+01 + C----306 R-----14 0.100000e+01 R-----16 0.100000e+01 + C----306 R-----17 0.100000e+01 R-----19 0.100000e+01 + C----306 R-----20 -.100000e+01 R-----21 0.100000e+01 + C----306 R-----22 0.100000e+01 R-----24 -.100000e+01 + C----306 R-----25 0.100000e+01 R-----27 -.100000e+01 + C----306 R-----28 0.100000e+01 R-----30 -.100000e+01 + C----306 R-----32 0.100000e+01 R-----34 -.100000e+01 + C----306 R-----37 0.100000e+01 R-----38 -.100000e+01 + C----306 R-----39 0.100000e+01 R-----41 -.100000e+01 + C----306 R-----45 0.100000e+01 R-----48 0.100000e+01 + C----306 R-----49 -.100000e+01 R-----51 -.100000e+01 + C----306 R-----53 -.100000e+01 R-----54 -.100000e+01 + C----306 R-----55 0.100000e+01 R-----56 0.100000e+01 + C----306 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----306 R-----60 0.100000e+01 R-----61 -.100000e+01 + C----306 R-----63 -.100000e+01 R-----64 0.100000e+01 + C----306 R-----65 -.100000e+01 R-----66 -.100000e+01 + C----306 R-----67 0.100000e+01 R-----68 -.100000e+01 + C----306 R-----71 0.100000e+01 R-----73 0.100000e+01 + C----306 R-----75 0.100000e+01 R-----76 -.100000e+01 + C----306 R-----78 0.100000e+01 R-----85 -.100000e+01 + C----307 R------3 -.100000e+01 R------4 0.100000e+01 + C----307 R------6 -.100000e+01 R-----11 -.100000e+01 + C----307 R-----14 -.100000e+01 R-----15 0.100000e+01 + C----307 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----307 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----307 R-----21 -.100000e+01 R-----22 -.100000e+01 + C----307 R-----24 0.100000e+01 R-----25 -.100000e+01 + C----307 R-----27 0.100000e+01 R-----28 -.100000e+01 + C----307 R-----30 0.100000e+01 R-----32 -.100000e+01 + C----307 R-----33 0.100000e+01 R-----34 0.100000e+01 + C----307 R-----40 -.100000e+01 R-----41 0.100000e+01 + C----307 R-----45 -.100000e+01 R-----48 -.100000e+01 + C----307 R-----49 0.100000e+01 R-----51 0.100000e+01 + C----307 R-----53 0.100000e+01 R-----54 0.100000e+01 + C----307 R-----55 -.100000e+01 R-----56 -.100000e+01 + C----307 R-----58 -.100000e+01 R-----59 0.100000e+01 + C----307 R-----60 -.100000e+01 R-----61 0.100000e+01 + C----307 R-----63 0.100000e+01 R-----64 -.100000e+01 + C----307 R-----65 0.100000e+01 R-----66 0.100000e+01 + C----307 R-----67 -.100000e+01 R-----68 0.100000e+01 + C----307 R-----69 -.100000e+01 R-----71 -.100000e+01 + C----307 R-----73 -.100000e+01 R-----75 -.100000e+01 + C----307 R-----76 0.100000e+01 R-----78 -.100000e+01 + C----307 R-----85 0.100000e+01 + C----308 OBJ.FUNC 0.000000e+00 + C----309 R-----33 -.100000e+01 + C----310 R------5 0.100000e+01 R-----14 -.100000e+01 + C----310 R-----32 -.100000e+01 R-----33 0.100000e+01 + C----310 R-----35 -.100000e+01 R-----43 -.100000e+01 + C----310 R-----44 0.100000e+01 R-----66 0.100000e+01 + C----311 R------1 -.100000e+01 R------5 -.100000e+01 + C----311 R------6 -.100000e+01 R------7 -.100000e+01 + C----311 R-----10 -.100000e+01 R-----11 -.100000e+01 + C----311 R-----13 -.100000e+01 R-----14 0.100000e+01 + C----311 R-----15 -.100000e+01 R-----18 -.100000e+01 + C----311 R-----19 -.100000e+01 R-----20 0.100000e+01 + C----311 R-----21 -.100000e+01 R-----24 0.100000e+01 + C----311 R-----25 -.100000e+01 R-----26 0.100000e+01 + C----311 R-----29 -.100000e+01 R-----36 -.100000e+01 + C----311 R-----40 -.100000e+01 R-----42 0.100000e+01 + C----311 R-----43 0.100000e+01 R-----46 -.100000e+01 + C----311 R-----48 -.100000e+01 R-----49 0.100000e+01 + C----311 R-----50 -.100000e+01 R-----52 -.100000e+01 + C----311 R-----61 0.100000e+01 R-----62 -.100000e+01 + C----311 R-----63 0.100000e+01 R-----64 -.100000e+01 + C----311 R-----65 0.100000e+01 R-----68 0.100000e+01 + C----311 R-----69 -.100000e+01 R-----75 -.100000e+01 + C----311 R-----79 -.100000e+01 R-----81 0.100000e+01 + C----311 R-----82 -.100000e+01 R-----83 0.100000e+01 + C----311 R-----84 -.100000e+01 + C----312 OBJ.FUNC 0.000000e+00 + C----313 R------1 0.100000e+01 R------6 0.100000e+01 + C----313 R------7 0.100000e+01 R-----10 0.100000e+01 + C----313 R-----11 0.100000e+01 R-----13 0.100000e+01 + C----313 R-----15 0.100000e+01 R-----18 0.100000e+01 + C----313 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----313 R-----21 0.100000e+01 R-----24 -.100000e+01 + C----313 R-----25 0.100000e+01 R-----26 -.100000e+01 + C----313 R-----29 0.100000e+01 R-----32 0.100000e+01 + C----313 R-----35 0.100000e+01 R-----36 0.100000e+01 + C----313 R-----40 0.100000e+01 R-----42 -.100000e+01 + C----313 R-----44 -.100000e+01 R-----46 0.100000e+01 + C----313 R-----48 0.100000e+01 R-----49 -.100000e+01 + C----313 R-----50 0.100000e+01 R-----52 0.100000e+01 + C----313 R-----61 -.100000e+01 R-----62 0.100000e+01 + C----313 R-----63 -.100000e+01 R-----64 0.100000e+01 + C----313 R-----65 -.100000e+01 R-----66 -.100000e+01 + C----313 R-----68 -.100000e+01 R-----69 0.100000e+01 + C----313 R-----75 0.100000e+01 R-----79 0.100000e+01 + C----313 R-----81 -.100000e+01 R-----82 0.100000e+01 + C----313 R-----83 -.100000e+01 R-----84 0.100000e+01 + C----314 R------1 0.100000e+01 R------2 0.100000e+01 + C----314 R------5 0.100000e+01 R------6 0.100000e+01 + C----314 R------8 0.100000e+01 R------9 0.100000e+01 + C----314 R-----16 -.100000e+01 R-----17 -.100000e+01 + C----314 R-----18 0.100000e+01 R-----20 0.100000e+01 + C----314 R-----21 0.100000e+01 R-----22 -.100000e+01 + C----314 R-----23 0.100000e+01 R-----25 0.100000e+01 + C----314 R-----26 -.100000e+01 R-----27 0.100000e+01 + C----314 R-----29 -.100000e+01 R-----30 0.100000e+01 + C----314 R-----33 0.100000e+01 R-----34 0.100000e+01 + C----314 R-----36 0.100000e+01 R-----38 0.100000e+01 + C----314 R-----41 0.100000e+01 R-----42 0.100000e+01 + C----314 R-----43 0.100000e+01 R-----44 0.100000e+01 + C----314 R-----49 -.100000e+01 R-----50 -.100000e+01 + C----314 R-----51 0.100000e+01 R-----53 -.100000e+01 + C----314 R-----54 0.100000e+01 R-----55 0.100000e+01 + C----314 R-----56 -.100000e+01 R-----57 -.100000e+01 + C----314 R-----63 0.100000e+01 R-----65 0.100000e+01 + C----314 R-----70 0.100000e+01 R-----71 -.100000e+01 + C----314 R-----75 -.100000e+01 R-----76 0.100000e+01 + C----314 R-----79 -.100000e+01 R-----81 -.100000e+01 + C----314 R-----82 -.100000e+01 R-----83 0.100000e+01 + C----314 R-----85 -.100000e+01 + C----315 OBJ.FUNC 0.000000e+00 + C----316 OBJ.FUNC 0.000000e+00 + C----317 R------1 -.100000e+01 R------2 -.100000e+01 + C----317 R------5 -.100000e+01 R------6 -.100000e+01 + C----317 R------8 -.100000e+01 R------9 -.100000e+01 + C----317 R-----16 0.100000e+01 R-----17 0.100000e+01 + C----317 R-----18 -.100000e+01 R-----20 -.100000e+01 + C----317 R-----21 -.100000e+01 R-----22 0.100000e+01 + C----317 R-----23 -.100000e+01 R-----25 -.100000e+01 + C----317 R-----26 0.100000e+01 R-----27 -.100000e+01 + C----317 R-----29 0.100000e+01 R-----30 -.100000e+01 + C----317 R-----33 -.100000e+01 R-----34 -.100000e+01 + C----317 R-----36 -.100000e+01 R-----38 -.100000e+01 + C----317 R-----41 -.100000e+01 R-----42 -.100000e+01 + C----317 R-----43 -.100000e+01 R-----44 -.100000e+01 + C----317 R-----49 0.100000e+01 R-----50 0.100000e+01 + C----317 R-----51 -.100000e+01 R-----53 0.100000e+01 + C----317 R-----54 -.100000e+01 R-----55 -.100000e+01 + C----317 R-----56 0.100000e+01 R-----57 0.100000e+01 + C----317 R-----63 -.100000e+01 R-----65 -.100000e+01 + C----317 R-----70 -.100000e+01 R-----71 0.100000e+01 + C----317 R-----75 0.100000e+01 R-----76 -.100000e+01 + C----317 R-----79 0.100000e+01 R-----81 0.100000e+01 + C----317 R-----82 0.100000e+01 R-----83 -.100000e+01 + C----317 R-----85 0.100000e+01 + C----318 OBJ.FUNC 0.000000e+00 + C----319 OBJ.FUNC 0.000000e+00 + C----320 OBJ.FUNC 0.000000e+00 + C----321 R-----10 -.100000e+01 R-----17 -.100000e+01 + C----321 R-----25 0.100000e+01 R-----29 -.100000e+01 + C----321 R-----32 0.100000e+01 R-----75 0.100000e+01 + C----321 R-----82 -.100000e+01 + C----322 OBJ.FUNC 0.000000e+00 + C----323 R------2 0.100000e+01 R------3 0.100000e+01 + C----323 R------4 -.100000e+01 R------6 0.100000e+01 + C----323 R------8 0.100000e+01 R------9 0.100000e+01 + C----323 R-----12 -.100000e+01 R-----15 0.100000e+01 + C----323 R-----17 0.100000e+01 R-----20 0.100000e+01 + C----323 R-----23 0.100000e+01 R-----24 -.100000e+01 + C----323 R-----35 -.100000e+01 R-----36 0.100000e+01 + C----323 R-----37 0.100000e+01 R-----39 0.100000e+01 + C----323 R-----43 0.100000e+01 R-----48 -.100000e+01 + C----323 R-----50 -.100000e+01 R-----51 0.100000e+01 + C----323 R-----52 -.100000e+01 R-----53 -.100000e+01 + C----323 R-----55 0.100000e+01 R-----57 -.100000e+01 + C----323 R-----58 0.100000e+01 R-----59 -.100000e+01 + C----323 R-----60 0.100000e+01 R-----63 0.100000e+01 + C----323 R-----64 -.100000e+01 R-----65 0.100000e+01 + C----323 R-----66 -.100000e+01 R-----67 0.100000e+01 + C----323 R-----68 -.100000e+01 R-----73 0.100000e+01 + C----323 R-----75 -.100000e+01 R-----77 -.100000e+01 + C----323 R-----78 0.100000e+01 R-----79 -.100000e+01 + C----323 R-----82 0.100000e+01 R-----83 0.100000e+01 + C----323 R-----84 -.100000e+01 R-----85 -.100000e+01 + C----324 R------2 -.100000e+01 R------3 -.100000e+01 + C----324 R------4 0.100000e+01 R------5 0.100000e+01 + C----324 R------7 -.100000e+01 R------9 -.100000e+01 + C----324 R-----10 0.100000e+01 R-----12 0.100000e+01 + C----324 R-----15 -.100000e+01 R-----18 -.100000e+01 + C----324 R-----19 0.100000e+01 R-----20 -.100000e+01 + C----324 R-----23 -.100000e+01 R-----24 0.100000e+01 + C----324 R-----25 -.100000e+01 R-----27 0.100000e+01 + C----324 R-----33 0.100000e+01 R-----34 -.100000e+01 + C----324 R-----38 -.100000e+01 R-----42 0.100000e+01 + C----324 R-----43 -.100000e+01 R-----46 0.100000e+01 + C----324 R-----48 0.100000e+01 R-----51 -.100000e+01 + C----324 R-----52 0.100000e+01 R-----55 -.100000e+01 + C----324 R-----57 0.100000e+01 R-----58 -.100000e+01 + C----324 R-----59 0.100000e+01 R-----60 -.100000e+01 + C----324 R-----63 -.100000e+01 R-----64 0.100000e+01 + C----324 R-----65 -.100000e+01 R-----66 0.100000e+01 + C----324 R-----67 -.100000e+01 R-----68 0.100000e+01 + C----324 R-----73 -.100000e+01 R-----77 0.100000e+01 + C----324 R-----78 -.100000e+01 R-----79 0.100000e+01 + C----324 R-----80 -.100000e+01 R-----83 -.100000e+01 + C----324 R-----84 0.100000e+01 R-----85 0.100000e+01 + C----325 R------5 -.100000e+01 R------6 -.100000e+01 + C----325 R------7 0.100000e+01 R------8 -.100000e+01 + C----325 R-----18 0.100000e+01 R-----19 -.100000e+01 + C----325 R-----27 -.100000e+01 R-----29 0.100000e+01 + C----325 R-----32 -.100000e+01 R-----33 -.100000e+01 + C----325 R-----34 0.100000e+01 R-----35 0.100000e+01 + C----325 R-----36 -.100000e+01 R-----37 -.100000e+01 + C----325 R-----38 0.100000e+01 R-----39 -.100000e+01 + C----325 R-----42 -.100000e+01 R-----46 -.100000e+01 + C----325 R-----50 0.100000e+01 R-----53 0.100000e+01 + C----325 R-----80 0.100000e+01 +RHS + RHS R------1 0.602220e-01 + RHS R------3 0.330486e-01 + RHS R------4 0.330486e-01 + RHS R------5 0.330486e-01 + RHS R------6 0.330486e-01 + RHS R------7 0.330486e-01 + RHS R------8 0.330486e-01 + RHS R------9 0.330486e-01 + RHS R-----10 0.330486e-01 + RHS R-----11 0.330486e-01 + RHS R-----12 0.330486e-01 + RHS R-----13 0.330486e-01 + RHS R-----14 0.330486e-01 + RHS R-----15 0.330486e-01 + RHS R-----16 0.330486e-01 + RHS R-----17 0.330486e-01 + RHS R-----18 0.330486e-01 + RHS R-----19 0.330486e-01 + RHS R-----20 0.330486e-01 + RHS R-----21 0.330486e-01 + RHS R-----22 0.442200e-01 + RHS R-----23 0.330486e-01 + RHS R-----24 0.330486e-01 + RHS R-----25 0.330486e-01 + RHS R-----26 0.330486e-01 + RHS R-----27 0.330486e-01 + RHS R-----28 0.330486e-01 + RHS R-----29 0.330486e-01 + RHS R-----30 0.330486e-01 + RHS R-----31 0.330486e-01 + RHS R-----32 0.640060e-01 + RHS R-----33 0.570294e-01 + RHS R-----34 0.432552e-01 + RHS R-----35 0.543495e-01 + RHS R-----36 0.330486e-01 + RHS R-----37 0.663423e-01 + RHS R-----38 0.330486e-01 + RHS R-----39 0.488474e-01 + RHS R-----40 0.427662e-01 + RHS R-----41 0.330486e-01 + RHS R-----42 0.330486e-01 + RHS R-----43 0.519145e-01 + RHS R-----44 0.392033e-01 + RHS R-----45 0.517633e-01 + RHS R-----46 0.472162e-01 + RHS R-----47 0.330486e-01 + RHS R-----48 0.498476e-01 + RHS R-----49 0.330486e-01 + RHS R-----50 0.440972e-01 + RHS R-----51 0.378391e-01 + RHS R-----52 0.338946e-01 + RHS R-----53 0.330486e-01 + RHS R-----54 0.344420e-01 + RHS R-----55 0.386269e-01 + RHS R-----56 0.330486e-01 + RHS R-----57 0.330486e-01 + RHS R-----58 0.449287e-01 + RHS R-----59 0.535838e-01 + RHS R-----60 0.330486e-01 + RHS R-----61 0.330486e-01 + RHS R-----62 0.417991e-01 + RHS R-----63 0.330486e-01 + RHS R-----64 0.330486e-01 + RHS R-----65 0.384921e-01 + RHS R-----66 0.330486e-01 + RHS R-----67 0.330486e-01 + RHS R-----68 0.330486e-01 + RHS R-----69 0.344993e-01 + RHS R-----70 0.330486e-01 + RHS R-----71 0.458672e-01 + RHS R-----72 0.330486e-01 + RHS R-----73 0.330486e-01 + RHS R-----74 0.401985e-01 + RHS R-----75 0.428269e-01 + RHS R-----76 0.330486e-01 + RHS R-----77 0.330486e-01 + RHS R-----78 0.330486e-01 + RHS R-----79 0.330486e-01 + RHS R-----80 0.371337e-01 + RHS R-----81 0.330486e-01 + RHS R-----82 0.381837e-01 + RHS R-----83 0.444273e-01 + RHS R-----84 0.330486e-01 + RHS R-----85 0.416797e-01 +RANGES +BOUNDS + FR BOUNDS C------2 + FR BOUNDS C------3 + FR BOUNDS C------4 + FR BOUNDS C------5 + FR BOUNDS C------6 + FR BOUNDS C------7 + FR BOUNDS C------8 + FR BOUNDS C------9 + FR BOUNDS C-----10 + FR BOUNDS C-----11 + FR BOUNDS C-----12 + FR BOUNDS C-----13 + FR BOUNDS C-----14 + FR BOUNDS C-----15 + FR BOUNDS C-----16 + FR BOUNDS C-----17 + FR BOUNDS C-----18 + FR BOUNDS C-----19 + FR BOUNDS C-----20 + FR BOUNDS C-----21 + FR BOUNDS C-----22 + FR BOUNDS C-----23 + FR BOUNDS C-----24 + FR BOUNDS C-----25 + FR BOUNDS C-----26 + FR BOUNDS C-----27 + FR BOUNDS C-----28 + FR BOUNDS C-----29 + FR BOUNDS C-----30 + FR BOUNDS C-----31 + FR BOUNDS C-----32 + FR BOUNDS C-----33 + FR BOUNDS C-----34 + FR BOUNDS C-----35 + FR BOUNDS C-----36 + FR BOUNDS C-----37 + FR BOUNDS C-----38 + FR BOUNDS C-----39 + FR BOUNDS C-----40 + FR BOUNDS C-----41 + FR BOUNDS C-----42 + FR BOUNDS C-----43 + FR BOUNDS C-----44 + FR BOUNDS C-----45 + FR BOUNDS C-----46 + FR BOUNDS C-----47 + FR BOUNDS C-----48 + FR BOUNDS C-----49 + FR BOUNDS C-----50 + FR BOUNDS C-----51 + FR BOUNDS C-----52 + FR BOUNDS C-----53 + FR BOUNDS C-----54 + FR BOUNDS C-----55 + FR BOUNDS C-----56 + FR BOUNDS C-----57 + FR BOUNDS C-----58 + FR BOUNDS C-----59 + FR BOUNDS C-----60 + FR BOUNDS C-----61 + FR BOUNDS C-----62 + FR BOUNDS C-----63 + FR BOUNDS C-----64 + FR BOUNDS C-----65 + FR BOUNDS C-----66 + FR BOUNDS C-----67 + FR BOUNDS C-----68 + FR BOUNDS C-----69 + FR BOUNDS C-----70 + FR BOUNDS C-----71 + FR BOUNDS C-----72 + FR BOUNDS C-----73 + FR BOUNDS C-----74 + FR BOUNDS C-----75 + FR BOUNDS C-----76 + FR BOUNDS C-----77 + FR BOUNDS C-----78 + FR BOUNDS C-----79 + FR BOUNDS C-----80 + FR BOUNDS C-----81 + FR BOUNDS C-----82 + FR BOUNDS C-----83 + FR BOUNDS C-----84 + FR BOUNDS C-----85 + FR BOUNDS C-----86 + FR BOUNDS C-----87 + FR BOUNDS C-----88 + FR BOUNDS C-----89 + FR BOUNDS C-----90 + FR BOUNDS C-----91 + FR BOUNDS C-----92 + FR BOUNDS C-----93 + FR BOUNDS C-----94 + FR BOUNDS C-----95 + FR BOUNDS C-----96 + FR BOUNDS C-----97 + FR BOUNDS C-----98 + FR BOUNDS C-----99 + FR BOUNDS C----100 + FR BOUNDS C----101 + FR BOUNDS C----102 + FR BOUNDS C----103 + FR BOUNDS C----104 + FR BOUNDS C----105 + FR BOUNDS C----106 + FR BOUNDS C----107 + FR BOUNDS C----108 + FR BOUNDS C----109 + FR BOUNDS C----110 + FR BOUNDS C----111 + FR BOUNDS C----112 + FR BOUNDS C----113 + FR BOUNDS C----114 + FR BOUNDS C----115 + FR BOUNDS C----116 + FR BOUNDS C----117 + FR BOUNDS C----118 + FR BOUNDS C----119 + FR BOUNDS C----120 + FR BOUNDS C----121 + FR BOUNDS C----122 + FR BOUNDS C----123 + FR BOUNDS C----124 + FR BOUNDS C----125 + FR BOUNDS C----126 + FR BOUNDS C----127 + FR BOUNDS C----128 + FR BOUNDS C----129 + FR BOUNDS C----130 + FR BOUNDS C----131 + FR BOUNDS C----132 + FR BOUNDS C----133 + FR BOUNDS C----134 + FR BOUNDS C----135 + FR BOUNDS C----136 + FR BOUNDS C----137 + FR BOUNDS C----138 + FR BOUNDS C----139 + FR BOUNDS C----140 + FR BOUNDS C----141 + FR BOUNDS C----142 + FR BOUNDS C----143 + FR BOUNDS C----144 + FR BOUNDS C----145 + FR BOUNDS C----146 + FR BOUNDS C----147 + FR BOUNDS C----148 + FR BOUNDS C----149 + FR BOUNDS C----150 + FR BOUNDS C----151 + FR BOUNDS C----152 + FR BOUNDS C----153 + FR BOUNDS C----154 + FR BOUNDS C----155 + FR BOUNDS C----156 + FR BOUNDS C----157 + FR BOUNDS C----158 + FR BOUNDS C----159 + FR BOUNDS C----160 + FR BOUNDS C----161 + FR BOUNDS C----162 + FR BOUNDS C----163 + FR BOUNDS C----164 + FR BOUNDS C----165 + FR BOUNDS C----166 + FR BOUNDS C----167 + FR BOUNDS C----168 + FR BOUNDS C----169 + FR BOUNDS C----170 + FR BOUNDS C----171 + FR BOUNDS C----172 + FR BOUNDS C----173 + FR BOUNDS C----174 + FR BOUNDS C----175 + FR BOUNDS C----176 + FR BOUNDS C----177 + FR BOUNDS C----178 + FR BOUNDS C----179 + FR BOUNDS C----180 + FR BOUNDS C----181 + FR BOUNDS C----182 + FR BOUNDS C----183 + FR BOUNDS C----184 + FR BOUNDS C----185 + FR BOUNDS C----186 + FR BOUNDS C----187 + FR BOUNDS C----188 + FR BOUNDS C----189 + FR BOUNDS C----190 + FR BOUNDS C----191 + FR BOUNDS C----192 + FR BOUNDS C----193 + FR BOUNDS C----194 + FR BOUNDS C----195 + FR BOUNDS C----196 + FR BOUNDS C----197 + FR BOUNDS C----198 + FR BOUNDS C----199 + FR BOUNDS C----200 + FR BOUNDS C----201 + FR BOUNDS C----202 + FR BOUNDS C----203 + FR BOUNDS C----204 + FR BOUNDS C----205 + FR BOUNDS C----206 + FR BOUNDS C----207 + FR BOUNDS C----208 + FR BOUNDS C----209 + FR BOUNDS C----210 + FR BOUNDS C----211 + FR BOUNDS C----212 + FR BOUNDS C----213 + FR BOUNDS C----214 + FR BOUNDS C----215 + FR BOUNDS C----216 + FR BOUNDS C----217 + FR BOUNDS C----218 + FR BOUNDS C----219 + FR BOUNDS C----220 + FR BOUNDS C----221 + FR BOUNDS C----222 + FR BOUNDS C----223 + FR BOUNDS C----224 + FR BOUNDS C----225 + FR BOUNDS C----226 + FR BOUNDS C----227 + FR BOUNDS C----228 + FR BOUNDS C----229 + FR BOUNDS C----230 + FR BOUNDS C----231 + FR BOUNDS C----232 + FR BOUNDS C----233 + FR BOUNDS C----234 + FR BOUNDS C----235 + FR BOUNDS C----236 + FR BOUNDS C----237 + FR BOUNDS C----238 + FR BOUNDS C----239 + FR BOUNDS C----240 + FR BOUNDS C----241 + FR BOUNDS C----242 + FR BOUNDS C----243 + FR BOUNDS C----244 + FR BOUNDS C----245 + FR BOUNDS C----246 + FR BOUNDS C----247 + FR BOUNDS C----248 + FR BOUNDS C----249 + FR BOUNDS C----250 + FR BOUNDS C----251 + FR BOUNDS C----252 + FR BOUNDS C----253 + FR BOUNDS C----254 + FR BOUNDS C----255 + FR BOUNDS C----256 + FR BOUNDS C----257 + FR BOUNDS C----258 + FR BOUNDS C----259 + FR BOUNDS C----260 + FR BOUNDS C----261 + FR BOUNDS C----262 + FR BOUNDS C----263 + FR BOUNDS C----264 + FR BOUNDS C----265 + FR BOUNDS C----266 + FR BOUNDS C----267 + FR BOUNDS C----268 + FR BOUNDS C----269 + FR BOUNDS C----270 + FR BOUNDS C----271 + FR BOUNDS C----272 + FR BOUNDS C----273 + FR BOUNDS C----274 + FR BOUNDS C----275 + FR BOUNDS C----276 + FR BOUNDS C----277 + FR BOUNDS C----278 + FR BOUNDS C----279 + FR BOUNDS C----280 + FR BOUNDS C----281 + FR BOUNDS C----282 + FR BOUNDS C----283 + FR BOUNDS C----284 + FR BOUNDS C----285 + FR BOUNDS C----286 + FR BOUNDS C----287 + FR BOUNDS C----288 + FR BOUNDS C----289 + FR BOUNDS C----290 + FR BOUNDS C----291 + FR BOUNDS C----292 + FR BOUNDS C----293 + FR BOUNDS C----294 + FR BOUNDS C----295 + FR BOUNDS C----296 + FR BOUNDS C----297 + FR BOUNDS C----298 + FR BOUNDS C----299 + FR BOUNDS C----300 + FR BOUNDS C----301 + FR BOUNDS C----302 + FR BOUNDS C----303 + FR BOUNDS C----304 + FR BOUNDS C----305 + FR BOUNDS C----306 + FR BOUNDS C----307 + FR BOUNDS C----308 + FR BOUNDS C----309 + FR BOUNDS C----310 + FR BOUNDS C----311 + FR BOUNDS C----312 + FR BOUNDS C----313 + FR BOUNDS C----314 + FR BOUNDS C----315 + FR BOUNDS C----316 + FR BOUNDS C----317 + FR BOUNDS C----318 + FR BOUNDS C----319 + FR BOUNDS C----320 + FR BOUNDS C----321 + FR BOUNDS C----322 + FR BOUNDS C----323 + FR BOUNDS C----324 + FR BOUNDS C----325 +QUADOBJ + C------2 C------2 0.100000e+01 + C------3 C------3 0.100000e+01 + C------4 C------4 0.100000e+01 + C------5 C------5 0.100000e+01 + C------6 C------6 0.100000e+01 + C------7 C------7 0.100000e+01 + C------8 C------8 0.100000e+01 + C------9 C------9 0.100000e+01 + C-----10 C-----10 0.100000e+01 + C-----11 C-----11 0.100000e+01 + C-----12 C-----12 0.100000e+01 + C-----13 C-----13 0.100000e+01 + C-----14 C-----14 0.100000e+01 + C-----15 C-----15 0.100000e+01 + C-----16 C-----16 0.100000e+01 + C-----17 C-----17 0.100000e+01 + C-----18 C-----18 0.100000e+01 + C-----19 C-----19 0.100000e+01 + C-----20 C-----20 0.100000e+01 + C-----21 C-----21 0.100000e+01 + C-----22 C-----22 0.100000e+01 + C-----23 C-----23 0.100000e+01 + C-----24 C-----24 0.100000e+01 + C-----25 C-----25 0.100000e+01 + C-----26 C-----26 0.100000e+01 + C-----27 C-----27 0.100000e+01 + C-----28 C-----28 0.100000e+01 + C-----29 C-----29 0.100000e+01 + C-----30 C-----30 0.100000e+01 + C-----31 C-----31 0.100000e+01 + C-----32 C-----32 0.100000e+01 + C-----33 C-----33 0.100000e+01 + C-----34 C-----34 0.100000e+01 + C-----35 C-----35 0.100000e+01 + C-----36 C-----36 0.100000e+01 + C-----37 C-----37 0.100000e+01 + C-----38 C-----38 0.100000e+01 + C-----39 C-----39 0.100000e+01 + C-----40 C-----40 0.100000e+01 + C-----41 C-----41 0.100000e+01 + C-----42 C-----42 0.100000e+01 + C-----43 C-----43 0.100000e+01 + C-----44 C-----44 0.100000e+01 + C-----45 C-----45 0.100000e+01 + C-----46 C-----46 0.100000e+01 + C-----47 C-----47 0.100000e+01 + C-----48 C-----48 0.100000e+01 + C-----49 C-----49 0.100000e+01 + C-----50 C-----50 0.100000e+01 + C-----51 C-----51 0.100000e+01 + C-----52 C-----52 0.100000e+01 + C-----53 C-----53 0.100000e+01 + C-----54 C-----54 0.100000e+01 + C-----55 C-----55 0.100000e+01 + C-----56 C-----56 0.100000e+01 + C-----57 C-----57 0.100000e+01 + C-----58 C-----58 0.100000e+01 + C-----59 C-----59 0.100000e+01 + C-----60 C-----60 0.100000e+01 + C-----61 C-----61 0.100000e+01 + C-----62 C-----62 0.100000e+01 + C-----63 C-----63 0.100000e+01 + C-----64 C-----64 0.100000e+01 + C-----65 C-----65 0.100000e+01 + C-----66 C-----66 0.100000e+01 + C-----67 C-----67 0.100000e+01 + C-----68 C-----68 0.100000e+01 + C-----69 C-----69 0.100000e+01 + C-----70 C-----70 0.100000e+01 + C-----71 C-----71 0.100000e+01 + C-----72 C-----72 0.100000e+01 + C-----73 C-----73 0.100000e+01 + C-----74 C-----74 0.100000e+01 + C-----75 C-----75 0.100000e+01 + C-----76 C-----76 0.100000e+01 + C-----77 C-----77 0.100000e+01 + C-----78 C-----78 0.100000e+01 + C-----79 C-----79 0.100000e+01 + C-----80 C-----80 0.100000e+01 + C-----81 C-----81 0.100000e+01 + C-----82 C-----82 0.100000e+01 + C-----83 C-----83 0.100000e+01 + C-----84 C-----84 0.100000e+01 + C-----85 C-----85 0.100000e+01 + C-----86 C-----86 0.100000e+01 + C-----87 C-----87 0.100000e+01 + C-----88 C-----88 0.100000e+01 + C-----89 C-----89 0.100000e+01 + C-----90 C-----90 0.100000e+01 + C-----91 C-----91 0.100000e+01 + C-----92 C-----92 0.100000e+01 + C-----93 C-----93 0.100000e+01 + C-----94 C-----94 0.100000e+01 + C-----95 C-----95 0.100000e+01 + C-----96 C-----96 0.100000e+01 + C-----97 C-----97 0.100000e+01 + C-----98 C-----98 0.100000e+01 + C-----99 C-----99 0.100000e+01 + C----100 C----100 0.100000e+01 + C----101 C----101 0.100000e+01 + C----102 C----102 0.100000e+01 + C----103 C----103 0.100000e+01 + C----104 C----104 0.100000e+01 + C----105 C----105 0.100000e+01 + C----106 C----106 0.100000e+01 + C----107 C----107 0.100000e+01 + C----108 C----108 0.100000e+01 + C----109 C----109 0.100000e+01 + C----110 C----110 0.100000e+01 + C----111 C----111 0.100000e+01 + C----112 C----112 0.100000e+01 + C----113 C----113 0.100000e+01 + C----114 C----114 0.100000e+01 + C----115 C----115 0.100000e+01 + C----116 C----116 0.100000e+01 + C----117 C----117 0.100000e+01 + C----118 C----118 0.100000e+01 + C----119 C----119 0.100000e+01 + C----120 C----120 0.100000e+01 + C----121 C----121 0.100000e+01 + C----122 C----122 0.100000e+01 + C----123 C----123 0.100000e+01 + C----124 C----124 0.100000e+01 + C----125 C----125 0.100000e+01 + C----126 C----126 0.100000e+01 + C----127 C----127 0.100000e+01 + C----128 C----128 0.100000e+01 + C----129 C----129 0.100000e+01 + C----130 C----130 0.100000e+01 + C----131 C----131 0.100000e+01 + C----132 C----132 0.100000e+01 + C----133 C----133 0.100000e+01 + C----134 C----134 0.100000e+01 + C----135 C----135 0.100000e+01 + C----136 C----136 0.100000e+01 + C----137 C----137 0.100000e+01 + C----138 C----138 0.100000e+01 + C----139 C----139 0.100000e+01 + C----140 C----140 0.100000e+01 + C----141 C----141 0.100000e+01 + C----142 C----142 0.100000e+01 + C----143 C----143 0.100000e+01 + C----144 C----144 0.100000e+01 + C----145 C----145 0.100000e+01 + C----146 C----146 0.100000e+01 + C----147 C----147 0.100000e+01 + C----148 C----148 0.100000e+01 + C----149 C----149 0.100000e+01 + C----150 C----150 0.100000e+01 + C----151 C----151 0.100000e+01 + C----152 C----152 0.100000e+01 + C----153 C----153 0.100000e+01 + C----154 C----154 0.100000e+01 + C----155 C----155 0.100000e+01 + C----156 C----156 0.100000e+01 + C----157 C----157 0.100000e+01 + C----158 C----158 0.100000e+01 + C----159 C----159 0.100000e+01 + C----160 C----160 0.100000e+01 + C----161 C----161 0.100000e+01 + C----162 C----162 0.100000e+01 + C----163 C----163 0.100000e+01 + C----164 C----164 0.100000e+01 + C----165 C----165 0.100000e+01 + C----166 C----166 0.100000e+01 + C----167 C----167 0.100000e+01 + C----168 C----168 0.100000e+01 + C----169 C----169 0.100000e+01 + C----170 C----170 0.100000e+01 + C----171 C----171 0.100000e+01 + C----172 C----172 0.100000e+01 + C----173 C----173 0.100000e+01 + C----174 C----174 0.100000e+01 + C----175 C----175 0.100000e+01 + C----176 C----176 0.100000e+01 + C----177 C----177 0.100000e+01 + C----178 C----178 0.100000e+01 + C----179 C----179 0.100000e+01 + C----180 C----180 0.100000e+01 + C----181 C----181 0.100000e+01 + C----182 C----182 0.100000e+01 + C----183 C----183 0.100000e+01 + C----184 C----184 0.100000e+01 + C----185 C----185 0.100000e+01 + C----186 C----186 0.100000e+01 + C----187 C----187 0.100000e+01 + C----188 C----188 0.100000e+01 + C----189 C----189 0.100000e+01 + C----190 C----190 0.100000e+01 + C----191 C----191 0.100000e+01 + C----192 C----192 0.100000e+01 + C----193 C----193 0.100000e+01 + C----194 C----194 0.100000e+01 + C----195 C----195 0.100000e+01 + C----196 C----196 0.100000e+01 + C----197 C----197 0.100000e+01 + C----198 C----198 0.100000e+01 + C----199 C----199 0.100000e+01 + C----200 C----200 0.100000e+01 + C----201 C----201 0.100000e+01 + C----202 C----202 0.100000e+01 + C----203 C----203 0.100000e+01 + C----204 C----204 0.100000e+01 + C----205 C----205 0.100000e+01 + C----206 C----206 0.100000e+01 + C----207 C----207 0.100000e+01 + C----208 C----208 0.100000e+01 + C----209 C----209 0.100000e+01 + C----210 C----210 0.100000e+01 + C----211 C----211 0.100000e+01 + C----212 C----212 0.100000e+01 + C----213 C----213 0.100000e+01 + C----214 C----214 0.100000e+01 + C----215 C----215 0.100000e+01 + C----216 C----216 0.100000e+01 + C----217 C----217 0.100000e+01 + C----218 C----218 0.100000e+01 + C----219 C----219 0.100000e+01 + C----220 C----220 0.100000e+01 + C----221 C----221 0.100000e+01 + C----222 C----222 0.100000e+01 + C----223 C----223 0.100000e+01 + C----224 C----224 0.100000e+01 + C----225 C----225 0.100000e+01 + C----226 C----226 0.100000e+01 + C----227 C----227 0.100000e+01 + C----228 C----228 0.100000e+01 + C----229 C----229 0.100000e+01 + C----230 C----230 0.100000e+01 + C----231 C----231 0.100000e+01 + C----232 C----232 0.100000e+01 + C----233 C----233 0.100000e+01 + C----234 C----234 0.100000e+01 + C----235 C----235 0.100000e+01 + C----236 C----236 0.100000e+01 + C----237 C----237 0.100000e+01 + C----238 C----238 0.100000e+01 + C----239 C----239 0.100000e+01 + C----240 C----240 0.100000e+01 + C----241 C----241 0.100000e+01 + C----242 C----242 0.100000e+01 + C----243 C----243 0.100000e+01 + C----244 C----244 0.100000e+01 + C----245 C----245 0.100000e+01 + C----246 C----246 0.100000e+01 + C----247 C----247 0.100000e+01 + C----248 C----248 0.100000e+01 + C----249 C----249 0.100000e+01 + C----250 C----250 0.100000e+01 + C----251 C----251 0.100000e+01 + C----252 C----252 0.100000e+01 + C----253 C----253 0.100000e+01 + C----254 C----254 0.100000e+01 + C----255 C----255 0.100000e+01 + C----256 C----256 0.100000e+01 + C----257 C----257 0.100000e+01 + C----258 C----258 0.100000e+01 + C----259 C----259 0.100000e+01 + C----260 C----260 0.100000e+01 + C----261 C----261 0.100000e+01 + C----262 C----262 0.100000e+01 + C----263 C----263 0.100000e+01 + C----264 C----264 0.100000e+01 + C----265 C----265 0.100000e+01 + C----266 C----266 0.100000e+01 + C----267 C----267 0.100000e+01 + C----268 C----268 0.100000e+01 + C----269 C----269 0.100000e+01 + C----270 C----270 0.100000e+01 + C----271 C----271 0.100000e+01 + C----272 C----272 0.100000e+01 + C----273 C----273 0.100000e+01 + C----274 C----274 0.100000e+01 + C----275 C----275 0.100000e+01 + C----276 C----276 0.100000e+01 + C----277 C----277 0.100000e+01 + C----278 C----278 0.100000e+01 + C----279 C----279 0.100000e+01 + C----280 C----280 0.100000e+01 + C----281 C----281 0.100000e+01 + C----282 C----282 0.100000e+01 + C----283 C----283 0.100000e+01 + C----284 C----284 0.100000e+01 + C----285 C----285 0.100000e+01 + C----286 C----286 0.100000e+01 + C----287 C----287 0.100000e+01 + C----288 C----288 0.100000e+01 + C----289 C----289 0.100000e+01 + C----290 C----290 0.100000e+01 + C----291 C----291 0.100000e+01 + C----292 C----292 0.100000e+01 + C----293 C----293 0.100000e+01 + C----294 C----294 0.100000e+01 + C----295 C----295 0.100000e+01 + C----296 C----296 0.100000e+01 + C----297 C----297 0.100000e+01 + C----298 C----298 0.100000e+01 + C----299 C----299 0.100000e+01 + C----300 C----300 0.100000e+01 + C----301 C----301 0.100000e+01 + C----302 C----302 0.100000e+01 + C----303 C----303 0.100000e+01 + C----304 C----304 0.100000e+01 + C----305 C----305 0.100000e+01 + C----306 C----306 0.100000e+01 + C----307 C----307 0.100000e+01 + C----308 C----308 0.100000e+01 + C----309 C----309 0.100000e+01 + C----310 C----310 0.100000e+01 + C----311 C----311 0.100000e+01 + C----312 C----312 0.100000e+01 + C----313 C----313 0.100000e+01 + C----314 C----314 0.100000e+01 + C----315 C----315 0.100000e+01 + C----316 C----316 0.100000e+01 + C----317 C----317 0.100000e+01 + C----318 C----318 0.100000e+01 + C----319 C----319 0.100000e+01 + C----320 C----320 0.100000e+01 + C----321 C----321 0.100000e+01 + C----322 C----322 0.100000e+01 + C----323 C----323 0.100000e+01 + C----324 C----324 0.100000e+01 + C----325 C----325 0.100000e+01 +ENDATA diff --git a/check/meson.build b/check/meson.build index f02b350f48..16dd5b41cf 100644 --- a/check/meson.build +++ b/check/meson.build @@ -60,17 +60,21 @@ foreach test : test_array executable(test.get(0), sources : ['TestMain.cpp', 'Avgas.cpp', - test.get(1)], + test.get(1), + highs_conf_file], dependencies : _deps, - link_with : _linkto , + link_with : highslib, cpp_args : _args, include_directories: _incdirs, ), + timeout: 300, ) endforeach test('test_capi', - executable('capi_unit_tests', 'TestCAPI.c', - include_directories: _incdirs, - link_with : _linkto , -)) + executable('capi_unit_tests', + sources: ['TestCAPI.c', highs_conf_file], + include_directories: _incdirs, + link_with : highslib, + ) + ) diff --git a/check/pythontest.py b/check/pythontest.py index e35ed993cc..fd7c32c8db 100644 --- a/check/pythontest.py +++ b/check/pythontest.py @@ -1,11 +1,11 @@ execfile("../src/interfaces/highs_lp_solver.py") -cc = (1.0,-2.0) -cl = (0.0,0.0) -cu = (10.0,10.0) -ru = (2.0,1.0) -rl = (0.0,0.0) -astart = (0,2,4) -aindex = (0,1,0,1) -avalue = (1.0,2.0,1.0,3.0) +cc = (1.0, -2.0) +cl = (0.0, 0.0) +cu = (10.0, 10.0) +ru = (2.0, 1.0) +rl = (0.0, 0.0) +astart = (0, 2, 4) +aindex = (0, 1, 0, 1) +avalue = (1.0, 2.0, 1.0, 3.0) call_highs(cc, cl, cu, rl, ru, astart, aindex, avalue) diff --git a/cmake/CheckAtomic.cmake b/cmake/CheckAtomic.cmake new file mode 100644 index 0000000000..38d70b3f0f --- /dev/null +++ b/cmake/CheckAtomic.cmake @@ -0,0 +1,74 @@ +# atomic builtins are required for threading support. +INCLUDE(CheckCXXSourceCompiles) +INCLUDE(CheckLibraryExists) + +function(check_working_cxx_atomics varname) + set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11") + CHECK_CXX_SOURCE_COMPILES(" +#include +std::atomic x; +std::atomic y; +std::atomic z; +int main() { + ++z; + ++y; + return ++x; +} +" ${varname}) + set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) +endfunction(check_working_cxx_atomics) + +function(check_working_cxx_atomics64 varname) + set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") + CHECK_CXX_SOURCE_COMPILES(" +#include +#include +std::atomic x (0); +int main() { + uint64_t i = x.load(std::memory_order_relaxed); + (void)i; + return 0; +} +" ${varname}) + set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) +endfunction(check_working_cxx_atomics64) + +# Check for (non-64-bit) atomic operations. +if(MSVC) + set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) +elseif(LLVM_COMPILER_IS_GCC_COMPATIBLE OR CMAKE_CXX_COMPILER_ID MATCHES "XL" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) + if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) + check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) + if(HAVE_LIBATOMIC) + list(APPEND CMAKE_REQUIRED_LIBRARIES atomic) + check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) + if(NOT HAVE_CXX_ATOMICS_WITH_LIB) + message(FATAL_ERROR "Host compiler must support std::atomic!") + endif() + else() + message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") + endif() + endif() +endif() + +# Check for 64-bit atomic operations. +if(MSVC) + set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) +elseif(LLVM_COMPILER_IS_GCC_COMPATIBLE OR CMAKE_CXX_COMPILER_ID MATCHES "XL" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) + if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) + if(HAVE_CXX_LIBATOMICS64) + list(APPEND CMAKE_REQUIRED_LIBRARIES atomic) + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) + if(NOT HAVE_CXX_ATOMICS64_WITH_LIB) + message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!") + endif() + else() + message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.") + endif() + endif() +endif() diff --git a/cmake/README.md b/cmake/README.md new file mode 100644 index 0000000000..5a24107a5a --- /dev/null +++ b/cmake/README.md @@ -0,0 +1,237 @@ +# HiGHS CMake Build Instructions + +| OS | C++ | Fortran | Python | CSharp Example | .NET | +|:-------- | :---: | :------: | :----: | :----: | :----: | +| Linux | [![Status][linux_cpp_svg]][linux_cpp_link] | [![Status][linux_fortran_svg]][linux_fortran_link] | [![Status][linux_python_svg]][linux_python_link] | *(1)* | [![Status][linux_dotnet_svg]][linux_dotnet_link] | +| MacOS | [![Status][macos_cpp_svg]][macos_cpp_link] | [![Status][macos_fortran_svg]][macos_fortran_link] | [![Status][macos_python_svg]][macos_python_link] | *(1)* |[![Status][macos_dotnet_svg]][macos_dotnet_link] | +| Windows | [![Status][windows_cpp_svg]][windows_cpp_link] | *(2)* | [![Status][windows_python_svg]][windows_python_link] | [![Status][windows_csharp_svg]][windows_csharp_link] | [![Status][windows_dotnet_svg]][windows_dotnet_link] | + +[linux_cpp_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/cmake-linux-cpp.yml/badge.svg +[linux_cpp_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/cmake-linux-cpp.yml +[macos_cpp_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/cmake-macos-cpp.yml/badge.svg +[macos_cpp_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/cmake-macos-cpp.yml +[windows_cpp_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/cmake-windows-cpp.yml/badge.svg +[windows_cpp_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/cmake-windows-cpp.yml + +[linux_python_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-python-ubuntu.yml/badge.svg +[linux_python_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-python-ubuntu.yml +[macos_python_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-python-macos.yml/badge.svg +[macos_python_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-python-macos.yml +[windows_python_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-python-win.yml/badge.svg +[windows_python_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-python-win.yml + +[windows_csharp_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-csharp-win.yml/badge.svg +[windows_csharp_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-csharp-win.yml + +[linux_dotnet_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-nuget-ubuntu.yml/badge.svg +[linux_dotnet_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-nuget-ubuntu.yml +[macos_dotnet_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-nuget-macos.yml/badge.svg +[macos_dotnet_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-nuget-macos.yml +[windows_dotnet_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-nuget-win.yml/badge.svg +[windows_dotnet_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-nuget-win.yml + +[linux_fortran_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-fortran-ubuntu.yml/badge.svg +[linux_fortran_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-fortran-ubuntu.yml +[macos_fortran_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-fortran-macos.yml/badge.svg +[macos_fortran_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-fortran-macos.yml +[windows_fortran_svg]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-fortran-win.yml/badge.svg +[windows_fortran_link]: https://github.com/ERGO-Code/HiGHS/actions/workflows/test-fortran-win.yml + +*(1)* CMake C# is currently only supported for Microsoft Visual Studio 11 2012 and + later. You can still build and run the HiGHS C# nuget package on Linux and MacOS with `dotnet`, see the workflows in the .NET column. It is only the CSharp example build with CMake that is not supported for Unix generators. + +*(2)* Not tested yet. + + +
+ +*Contents* + +- [HiGHS CMake Build Instructions](#highs-cmake-build-instructions) + - [Introduction](#introduction) + - [Requirement](#requirement) + - [Supported compilers](#supported-compilers) +- [Build](#build) + - [Install](#install) + - [Windows](#windows) +- [CMake Options](#cmake-options) +- [Integrating HiGHS in your CMake Project](#integrating-highs-in-your-cmake-project) + +## Introduction + +HiGHS can be built from source using CMake: . CMake works by generating native Makefiles or build projects that can be used in the compiler environment of your choice. + +HiGHS can be built as a standalone project or it could be incorporated into an existing CMake project. + +## Requirement +You'll need: + +* `CMake >= 3.15`. +* A C++11 compiler + +## Supported compilers + +Here is a list of the supported compilers: + +* Clang `clang` +* GNU `g++` +* Intel `icc` +* Microsoft `MSVC` + +# Build + +To build the C++ library and executable run + +``` bash +cd HiGHS +cmake -S. -B build +cmake --build build --parallel +``` + +This generates HiGHS in the `build` directory and creates the [executable](@ref Executable) `build/bin/highs`, or `build/bin/Release/highs.exe` on Windows. To perform a quick test to see whether the compilation was successful, run `ctest` from within the build folder. + +``` bash +ctest +``` + +On Windows, the configuration type must be specified: +``` bash +ctest -C Release +``` + +## Install + +The default installation location may need administrative +permissions. To install, after building and testing, run + +``` bash +cmake --install build +``` + +form the root directory. + +To install in a specified installation directory run CMake with the +`CMAKE_INSTALL_PREFIX` flag set: + +``` bash +cmake -S. -B build -DCMAKE_INSTALL_PREFIX=/path/to/highs_install +cmake --build build --parallel +cmake --install build +``` + +## 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` + +```shell + cmake -S . -B build + cmake --build build --config Release +``` + +It is also possible to specify a specific Visual studio version to build with: +```shell + 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: + +```shell + cmake -A Win32 -S . -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 +```shell + cmake -DSTDCALL=ON -S . -B build + cmake --build build +``` + + + + +# CMake Options + +There are several options that can be passed to CMake to modify how the code +is built.
+To set these options and parameters, use `-D=`. + +All CMake options are passed at configure time, i.e., by running
+`cmake -S. -B -DOPTION_ONE=ON -DOPTION_TWO=OFF ...`
+before running `cmake --build `
+ +For example, to generate build files in a new +subdirectory called 'build', run: + +```shell +cmake -S. -Bbuild +``` +and then build with: + +```shell +cmake --build build +``` + +Following is a list of available options: +| CMake Option | Default Value | Note | +|:-------------|:--------------|:-----| +| `CMAKE_BUILD_TYPE` | Release | see CMake documentation [here](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | +| `BUILD_SHARED_LIBS` | ON(*) | Build shared libraries (.so or .dyld). * OFF by default on Windows | +| `BUILD_CXX` | ON | Build C++ | +| `FORTRAN` | OFF | Build Fortran interface | +| `CSHARP` | OFF | Build CSharp wrapper | +| `BUILD_DOTNET` | OFF | Build .Net package | +| `PYTHON_BUILD_SETUP` | OFF | Build Python bindings. Called at `pip install` from pyproject.toml | +| `ZLIB` | ON | Use ZLIB if available | +| `ALL_TESTS` | OFF | Run unit tests and extended instance test set | + + + +HiGHS can be integrated into other CMake-based projects. + +# Integrating HiGHS in your CMake Project + +If you already have HiGHS installed on your system, you can use `find_package()` to include HiGHS in your C++ CMake project. + +``` +project(LOAD_HIGHS LANGUAGES CXX) + +set(HIGHS_DIR path_to_highs_install/lib/cmake/highs) + +find_package(HIGHS 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 +``` bash +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 "latest" +) + +FetchContent_MakeAvailable(highs) + +add_executable(main call_from_cpp.cc) +target_link_libraries(main highs::highs) +``` diff --git a/cmake/c-highs.cmake b/cmake/c-highs.cmake deleted file mode 100644 index 607eb0f738..0000000000 --- a/cmake/c-highs.cmake +++ /dev/null @@ -1,10 +0,0 @@ -# add_c_example() -function(add_c_example FILE_NAME) - message(STATUS "Configuring test ${FILE_NAME}: ...") - get_filename_component(TEST_NAME ${FILE_NAME} NAME_WE) - add_executable(${TEST_NAME} ${FILE_NAME}) - - target_include_directories(${TEST_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(${TEST_NAME} PRIVATE ${PROJECT_NAMESPACE}::highs) - message(STATUS "Configuring test ${FILE_NAME}: ...DONE") -endfunction() \ No newline at end of file diff --git a/cmake/cpp-highs.cmake b/cmake/cpp-highs.cmake index 478b57658c..3b31891f3b 100644 --- a/cmake/cpp-highs.cmake +++ b/cmake/cpp-highs.cmake @@ -1,15 +1,13 @@ if(NOT BUILD_CXX) return() endif() +# set(CMAKE_VERBOSE_MAKEFILE ON) # 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) +# add_library(${PROJECT_NAMESPACE}::highs ALIAS highs) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -22,6 +20,23 @@ target_include_directories(highs INTERFACE $ ) +# Properties +if(NOT APPLE) + set_target_properties(highs PROPERTIES VERSION ${PROJECT_VERSION}) +else() + # Clang don't support version x.y.z with z > 255 + set_target_properties(highs PROPERTIES + INSTALL_RPATH "@loader_path" + VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) +endif() +set_target_properties(highs PROPERTIES + SOVERSION ${PROJECT_VERSION_MAJOR} + POSITION_INDEPENDENT_CODE ON + INTERFACE_POSITION_INDEPENDENT_CODE ON + INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR} + COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION +) + ################### ## Install rules ## ################### @@ -32,9 +47,10 @@ install(FILES ${PROJECT_BINARY_DIR}/highs_export.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) string (TOLOWER ${PROJECT_NAME} lower) - install(TARGETS highs + +install(TARGETS highs EXPORT ${lower}-targets - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -42,13 +58,18 @@ string (TOLOWER ${PROJECT_NAME} lower) # Add library targets to the build-tree export set export(TARGETS highs - NAMESPACE ${PROJECT_NAMESPACE}:: - FILE "${HIGHS_BINARY_DIR}/highs-targets.cmake") - + NAMESPACE ${PROJECT_NAMESPACE}::highs + FILE "${HIGHS_BINARY_DIR}/highs-targets.cmake") install(EXPORT ${lower}-targets NAMESPACE ${PROJECT_NAMESPACE}:: + FILE highs-targets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${lower}) +# install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs-config.cmake" +# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/highs) +# install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs.pc" +# DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + include(CMakePackageConfigHelpers) string (TOLOWER "${PROJECT_NAME}" PACKAGE_PREFIX) @@ -58,8 +79,7 @@ string (TOLOWER "${PROJECT_NAME}" PACKAGE_PREFIX) # NO_CHECK_REQUIRED_COMPONENTS_MACRO) write_basic_package_version_file( "${PROJECT_BINARY_DIR}/${PACKAGE_PREFIX}-config-version.cmake" - COMPATIBILITY SameMajorVersion - ) + COMPATIBILITY SameMajorVersion) # add_cxx_test() # CMake function to generate and build C++ test. @@ -90,3 +110,39 @@ function(add_cxx_test FILE_NAME) endif() message(STATUS "Configuring test ${FILE_NAME}: ...DONE") endfunction() + +# set_target_properties(highs PROPERTIES INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR}) +# set_target_properties(highs PROPERTIES COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION) + +# add_c_test() +# CMake function to generate and build C++ test. +# Parameters: +# the C filename +# e.g.: +# add_c_test(foo.c) +function(add_c_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 c_${COMPONENT_NAME}_${TEST_NAME} COMMAND ${TEST_NAME}) + endif() + message(STATUS "Configuring test ${FILE_NAME}: ...DONE") +endfunction() + +# set_target_properties(highs PROPERTIES INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR}) +# set_target_properties(highs PROPERTIES COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION) diff --git a/cmake/dotnet.cmake b/cmake/dotnet.cmake new file mode 100644 index 0000000000..c3eb96f7a4 --- /dev/null +++ b/cmake/dotnet.cmake @@ -0,0 +1,94 @@ +if(NOT BUILD_DOTNET) + return() +endif() + +if(NOT TARGET ${PROJECT_NAMESPACE}::highs) + message(FATAL_ERROR ".Net: missing highs TARGET") +endif() + +set(DOTNET_PACKAGE Highs.Native) +set(DOTNET_PACKAGES_DIR "${PROJECT_BINARY_DIR}/dotnet") + +# Runtime IDentifier +# see: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)") + set(DOTNET_PLATFORM arm64) +else() + set(DOTNET_PLATFORM x64) +endif() + +if(APPLE) + set(DOTNET_RID osx-${DOTNET_PLATFORM}) +elseif(UNIX) + set(DOTNET_RID linux-${DOTNET_PLATFORM}) +elseif(WIN32) + set(DOTNET_RID win-${DOTNET_PLATFORM}) +else() + message(FATAL_ERROR "Unsupported system !") +endif() +message(STATUS ".Net RID: ${DOTNET_RID}") + +# Targeted Framework Moniker +# see: https://docs.microsoft.com/en-us/dotnet/standard/frameworks +# see: https://learn.microsoft.com/en-us/dotnet/standard/net-standard +# if(USE_DOTNET_46) +# list(APPEND TFM "net46") +# endif() +# if(USE_DOTNET_461) +# list(APPEND TFM "net461") +# endif() +# if(USE_DOTNET_462) +# list(APPEND TFM "net462") +# endif() +# if(USE_DOTNET_48) +# list(APPEND TFM "net48") +# endif() +if(USE_DOTNET_STD_21) + list(APPEND TFM "netstandard2.1") +endif() +# if(USE_DOTNET_CORE_31) +# list(APPEND TFM "netcoreapp3.1") +# endif() +if(USE_DOTNET_6) + list(APPEND TFM "net6.0") +endif() +# if(USE_DOTNET_7) +# list(APPEND TFM "net7.0") +# endif() + +list(LENGTH TFM TFM_LENGTH) +if(TFM_LENGTH EQUAL "0") + message(FATAL_ERROR "No .Net SDK selected !") +endif() + +string(JOIN ";" DOTNET_TFM ${TFM}) +message(STATUS ".Net TFM: ${DOTNET_TFM}") +if(TFM_LENGTH GREATER "1") + string(CONCAT DOTNET_TFM "" "${DOTNET_TFM}" "") +else() + string(CONCAT DOTNET_TFM "" "${DOTNET_TFM}" "") +endif() + +set(DOTNET_PROJECT ${DOTNET_PACKAGE}) +message(STATUS ".Net project: ${DOTNET_PROJECT}") +set(DOTNET_PROJECT_DIR ${DOTNET_PACKAGES_DIR}/${DOTNET_PROJECT}) +message(STATUS ".Net project build path: ${DOTNET_PROJECT_DIR}") + +file(MAKE_DIRECTORY ${DOTNET_PACKAGES_DIR}) + +configure_file( + ${PROJECT_SOURCE_DIR}/nuget/Highs.csproj.in + ${DOTNET_PROJECT_DIR}/${DOTNET_PROJECT}.csproj + @ONLY) + +file(COPY + ${PROJECT_SOURCE_DIR}/src/interfaces/highs_csharp_api.cs + DESTINATION ${DOTNET_PROJECT_DIR}) + +file(COPY + ${PROJECT_SOURCE_DIR}/README.md + DESTINATION ${DOTNET_PROJECT_DIR}) + +file(COPY + ${PROJECT_SOURCE_DIR}/nuget/HiGHS_Logo.png + DESTINATION ${DOTNET_PROJECT_DIR}) diff --git a/cmake/python-highs.cmake b/cmake/python-highs.cmake new file mode 100644 index 0000000000..4a429cb110 --- /dev/null +++ b/cmake/python-highs.cmake @@ -0,0 +1,70 @@ +if (NOT PYTHON_BUILD_SETUP) + return() +endif() + +# set(CMAKE_VERBOSE_MAKEFILE ON) + +include(sources-python) + +set(sources_python ${highs_sources_python} + ${cupdlp_sources_python} + ${ipx_sources_python} + ${basiclu_sources_python}) + +set(headers_python ${highs_headers_python} + ${cupdlp_headers_python} + ${ipx_headers_python} + ${basiclu_headers_python}) + +# Find Python 3 +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +find_package(pybind11 CONFIG) + +python_add_library(_core MODULE src/highs_bindings.cpp WITH_SOABI) + +# Pybind11 +# include(FetchContent) +# message(CHECK_START "Fetching pybind11") +# list(APPEND CMAKE_MESSAGE_INDENT " ") +# set(PYBIND11_INSTALL ON) +# set(PYBIND11_TEST OFF) +# FetchContent_Declare( +# pybind11 +# GIT_REPOSITORY "https://github.com/pybind/pybind11.git" +# GIT_TAG "v2.11.1" +# ) +# FetchContent_MakeAvailable(pybind11) +# list(POP_BACK CMAKE_MESSAGE_INDENT) +# message(CHECK_PASS "fetched") + +# add module +# pybind11_add_module(highspy highspy/highs_bindings.cpp) + +target_link_libraries(_core PRIVATE pybind11::headers) + +# sources for python +target_sources(_core PUBLIC ${sources_python} ${headers_python}) + +# include directories for python +target_include_directories(_core PUBLIC ${include_dirs_python}) + +# This is passing in the version as a define just as an example +target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION}) + +if(MSVC) + target_compile_options(_core PRIVATE "/bigobj") +endif() + +# if(MSVC) +# # Try to split large pdb files into objects. +# # https://github.com/tensorflow/tensorflow/issues/31610 +# add_compile_options("/Z7") +# add_link_options("/DEBUG:FASTLINK") +# if(STDCALL) +# # /Gz - stdcall calling convention +# add_definitions(/Gz) +# endif() +# endif() + +# The install directory is the output (wheel) directory +install(TARGETS _core DESTINATION highspy) diff --git a/cmake/utils-highs.cmake b/cmake/set-version.cmake similarity index 98% rename from cmake/utils-highs.cmake rename to cmake/set-version.cmake index d91720f155..7fe794b21d 100644 --- a/cmake/utils-highs.cmake +++ b/cmake/set-version.cmake @@ -23,4 +23,4 @@ function(set_version VERSION) endif() set(${VERSION} "${MAJOR}.${MINOR}.${PATCH}" PARENT_SCOPE) -endfunction() +endfunction() \ No newline at end of file diff --git a/cmake/sources-python.cmake b/cmake/sources-python.cmake new file mode 100644 index 0000000000..baff378e61 --- /dev/null +++ b/cmake/sources-python.cmake @@ -0,0 +1,435 @@ +set(include_dirs_python + ${CMAKE_SOURCE_DIR}/extern + ${CMAKE_SOURCE_DIR}/extern/filereader + ${CMAKE_SOURCE_DIR}/extern/pdqsort + ${CMAKE_SOURCE_DIR}/extern/zstr + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/interfaces + ${CMAKE_SOURCE_DIR}/src/io + ${CMAKE_SOURCE_DIR}/src/ipm + ${CMAKE_SOURCE_DIR}/src/ipm/ipx + ${CMAKE_SOURCE_DIR}/src/ipm/basiclu + ${CMAKE_SOURCE_DIR}/src/lp_data + ${CMAKE_SOURCE_DIR}/src/mip + ${CMAKE_SOURCE_DIR}/src/model + ${CMAKE_SOURCE_DIR}/src/parallel + ${CMAKE_SOURCE_DIR}/src/pdlp + ${CMAKE_SOURCE_DIR}/src/pdlp/cupdlp + ${CMAKE_SOURCE_DIR}/src/presolve + ${CMAKE_SOURCE_DIR}/src/qpsolver + ${CMAKE_SOURCE_DIR}/src/simplex + ${CMAKE_SOURCE_DIR}/src/test + ${CMAKE_SOURCE_DIR}/src/util + $) + +set(cupdlp_sources_python + src/pdlp/cupdlp/cupdlp_cs.c + src/pdlp/cupdlp/cupdlp_linalg.c + src/pdlp/cupdlp/cupdlp_proj.c + src/pdlp/cupdlp/cupdlp_restart.c + src/pdlp/cupdlp/cupdlp_scaling_cuda.c + src/pdlp/cupdlp/cupdlp_solver.c + src/pdlp/cupdlp/cupdlp_step.c + src/pdlp/cupdlp/cupdlp_utils.c) + +set(cupdlp_headers_python + src/pdlp/cupdlp/cupdlp_cs.h + src/pdlp/cupdlp/cupdlp_defs.h + src/pdlp/cupdlp/cupdlp_linalg.h + src/pdlp/cupdlp/cupdlp_proj.h + src/pdlp/cupdlp/cupdlp_restart.h + src/pdlp/cupdlp/cupdlp_scaling_cuda.h + src/pdlp/cupdlp/cupdlp_solver.h + src/pdlp/cupdlp/cupdlp_step.h + src/pdlp/cupdlp/cupdlp_utils.c) + +set(basiclu_sources_python + src/ipm/basiclu/basiclu_factorize.c + src/ipm/basiclu/basiclu_get_factors.c + src/ipm/basiclu/basiclu_initialize.c + src/ipm/basiclu/basiclu_object.c + src/ipm/basiclu/basiclu_solve_dense.c + src/ipm/basiclu/basiclu_solve_for_update.c + src/ipm/basiclu/basiclu_solve_sparse.c + src/ipm/basiclu/basiclu_update.c + src/ipm/basiclu/lu_build_factors.c + src/ipm/basiclu/lu_condest.c + src/ipm/basiclu/lu_dfs.c + src/ipm/basiclu/lu_factorize_bump.c + src/ipm/basiclu/lu_file.c + src/ipm/basiclu/lu_garbage_perm.c + src/ipm/basiclu/lu_initialize.c + src/ipm/basiclu/lu_internal.c + src/ipm/basiclu/lu_markowitz.c + src/ipm/basiclu/lu_matrix_norm.c + src/ipm/basiclu/lu_pivot.c + src/ipm/basiclu/lu_residual_test.c + src/ipm/basiclu/lu_setup_bump.c + src/ipm/basiclu/lu_singletons.c + src/ipm/basiclu/lu_solve_dense.c + src/ipm/basiclu/lu_solve_for_update.c + src/ipm/basiclu/lu_solve_sparse.c + src/ipm/basiclu/lu_solve_symbolic.c + src/ipm/basiclu/lu_solve_triangular.c + src/ipm/basiclu/lu_update.c) + +set(basiclu_headers_python + src/ipm/basiclu/basiclu_factorize.h + src/ipm/basiclu/basiclu_get_factors.h + src/ipm/basiclu/basiclu_initialize.h + src/ipm/basiclu/basiclu_obj_factorize.h + src/ipm/basiclu/basiclu_obj_free.h + src/ipm/basiclu/basiclu_obj_get_factors.h + src/ipm/basiclu/basiclu_obj_initialize.h + src/ipm/basiclu/basiclu_obj_solve_dense.h + src/ipm/basiclu/basiclu_obj_solve_for_update.h + src/ipm/basiclu/basiclu_obj_solve_sparse.h + src/ipm/basiclu/basiclu_obj_update.h + src/ipm/basiclu/basiclu_object.h + src/ipm/basiclu/basiclu_solve_dense.h + src/ipm/basiclu/basiclu_solve_for_update.h + src/ipm/basiclu/basiclu_solve_sparse.h + src/ipm/basiclu/basiclu_update.h + src/ipm/basiclu/basiclu.h + src/ipm/basiclu/lu_def.h + src/ipm/basiclu/lu_file.h + src/ipm/basiclu/lu_internal.h + src/ipm/basiclu/lu_list.h) + +set(ipx_sources_python + src/ipm/ipx/basiclu_kernel.cc + src/ipm/ipx/basiclu_wrapper.cc + src/ipm/ipx/basis.cc + src/ipm/ipx/conjugate_residuals.cc + src/ipm/ipx/control.cc + src/ipm/ipx/crossover.cc + src/ipm/ipx/diagonal_precond.cc + src/ipm/ipx/forrest_tomlin.cc + src/ipm/ipx/guess_basis.cc + src/ipm/ipx/indexed_vector.cc + src/ipm/ipx/info.cc + src/ipm/ipx/ipm.cc + src/ipm/ipx/ipx_c.cc + src/ipm/ipx/iterate.cc + src/ipm/ipx/kkt_solver_basis.cc + src/ipm/ipx/kkt_solver_diag.cc + src/ipm/ipx/kkt_solver.cc + src/ipm/ipx/linear_operator.cc + src/ipm/ipx/lp_solver.cc + src/ipm/ipx/lu_factorization.cc + src/ipm/ipx/lu_update.cc + src/ipm/ipx/maxvolume.cc + src/ipm/ipx/model.cc + src/ipm/ipx/normal_matrix.cc + src/ipm/ipx/sparse_matrix.cc + src/ipm/ipx/sparse_utils.cc + src/ipm/ipx/splitted_normal_matrix.cc + src/ipm/ipx/starting_basis.cc + src/ipm/ipx/symbolic_invert.cc + src/ipm/ipx/timer.cc + src/ipm/ipx/utils.cc) + + set(ipx_headers_python + src/ipm/ipx/basiclu_kernel.h + src/ipm/ipx/basiclu_wrapper.h + src/ipm/ipx/basis.h + src/ipm/ipx/conjugate_residuals.h + src/ipm/ipx/control.h + src/ipm/ipx/crossover.h + src/ipm/ipx/diagonal_precond.h + src/ipm/ipx/forrest_tomlin.h + src/ipm/ipx/guess_basis.h + src/ipm/ipx/indexed_vector.h + src/ipm/ipx/info.h + src/ipm/ipx/ipm.h + src/ipm/ipx/ipx_c.h + src/ipm/ipx/ipx_config.h + src/ipm/ipx/ipx_info.h + src/ipm/ipx/ipx_internal.h + src/ipm/ipx/ipx_parameters.h + src/ipm/ipx/ipx_status.h + src/ipm/ipx/iterate.h + src/ipm/ipx/kkt_solver_basis.h + src/ipm/ipx/kkt_solver_diag.h + src/ipm/ipx/kkt_solver.h + src/ipm/ipx/linear_operator.h + src/ipm/ipx/lp_solver.h + src/ipm/ipx/lu_factorization.h + src/ipm/ipx/lu_update.h + src/ipm/ipx/maxvolume.h + src/ipm/ipx/model.h + src/ipm/ipx/multistream.h + src/ipm/ipx/normal_matrix.h + src/ipm/ipx/power_method.h + src/ipm/ipx/sparse_matrix.h + src/ipm/ipx/sparse_utils.h + src/ipm/ipx/splitted_normal_matrix.h + src/ipm/ipx/starting_basis.h + src/ipm/ipx/symbolic_invert.h + src/ipm/ipx/timer.h + src/ipm/ipx/utils.h) + +set(highs_sources_python + extern/filereaderlp/reader.cpp + src/interfaces/highs_c_api.cpp + src/io/Filereader.cpp + src/io/FilereaderEms.cpp + src/io/FilereaderLp.cpp + src/io/FilereaderMps.cpp + src/io/HighsIO.cpp + src/io/HMpsFF.cpp + src/io/HMPSIO.cpp + src/io/LoadOptions.cpp + src/ipm/IpxWrapper.cpp + src/lp_data/Highs.cpp + src/lp_data/HighsCallback.cpp + src/lp_data/HighsDebug.cpp + src/lp_data/HighsInfo.cpp + src/lp_data/HighsInfoDebug.cpp + src/lp_data/HighsInterface.cpp + src/lp_data/HighsLp.cpp + src/lp_data/HighsLpUtils.cpp + src/lp_data/HighsModelUtils.cpp + src/lp_data/HighsOptions.cpp + src/lp_data/HighsRanging.cpp + src/lp_data/HighsSolution.cpp + src/lp_data/HighsSolutionDebug.cpp + src/lp_data/HighsSolve.cpp + src/lp_data/HighsStatus.cpp + src/mip/HighsCliqueTable.cpp + src/mip/HighsConflictPool.cpp + src/mip/HighsCutGeneration.cpp + src/mip/HighsCutPool.cpp + src/mip/HighsDebugSol.cpp + src/mip/HighsDomain.cpp + src/mip/HighsDynamicRowMatrix.cpp + src/mip/HighsGFkSolve.cpp + src/mip/HighsImplications.cpp + src/mip/HighsLpAggregator.cpp + src/mip/HighsLpRelaxation.cpp + src/mip/HighsMipSolver.cpp + src/mip/HighsMipSolverData.cpp + src/mip/HighsModkSeparator.cpp + src/mip/HighsNodeQueue.cpp + src/mip/HighsObjectiveFunction.cpp + src/mip/HighsPathSeparator.cpp + src/mip/HighsPrimalHeuristics.cpp + src/mip/HighsPseudocost.cpp + src/mip/HighsRedcostFixing.cpp + src/mip/HighsSearch.cpp + src/mip/HighsSeparation.cpp + src/mip/HighsSeparator.cpp + src/mip/HighsTableauSeparator.cpp + src/mip/HighsTransformedLp.cpp + src/model/HighsHessian.cpp + src/model/HighsHessianUtils.cpp + src/model/HighsModel.cpp + src/parallel/HighsTaskExecutor.cpp + src/pdlp/CupdlpWrapper.cpp + src/presolve/HighsPostsolveStack.cpp + src/presolve/HighsSymmetry.cpp + src/presolve/HPresolve.cpp + src/presolve/HPresolveAnalysis.cpp + src/presolve/ICrash.cpp + src/presolve/ICrashUtil.cpp + src/presolve/ICrashX.cpp + src/presolve/PresolveComponent.cpp + src/qpsolver/a_asm.cpp + src/qpsolver/a_quass.cpp + src/qpsolver/basis.cpp + src/qpsolver/perturbation.cpp + src/qpsolver/quass.cpp + src/qpsolver/ratiotest.cpp + src/qpsolver/scaling.cpp + src/simplex/HEkk.cpp + src/simplex/HEkkControl.cpp + src/simplex/HEkkDebug.cpp + src/simplex/HEkkDual.cpp + src/simplex/HEkkDualMulti.cpp + src/simplex/HEkkDualRHS.cpp + src/simplex/HEkkDualRow.cpp + src/simplex/HEkkInterface.cpp + src/simplex/HEkkPrimal.cpp + src/simplex/HighsSimplexAnalysis.cpp + src/simplex/HSimplex.cpp + src/simplex/HSimplexDebug.cpp + src/simplex/HSimplexNla.cpp + src/simplex/HSimplexNlaDebug.cpp + src/simplex/HSimplexNlaFreeze.cpp + src/simplex/HSimplexNlaProductForm.cpp + src/simplex/HSimplexReport.cpp + src/test/KktCh2.cpp + src/test/DevKkt.cpp + src/util/HFactor.cpp + src/util/HFactorDebug.cpp + src/util/HFactorExtend.cpp + src/util/HFactorRefactor.cpp + src/util/HFactorUtils.cpp + src/util/HighsHash.cpp + src/util/HighsLinearSumBounds.cpp + src/util/HighsMatrixPic.cpp + src/util/HighsMatrixUtils.cpp + src/util/HighsSort.cpp + src/util/HighsSparseMatrix.cpp + src/util/HighsUtils.cpp + src/util/HSet.cpp + src/util/HVectorBase.cpp + src/util/stringutil.cpp) + +set(highs_headers_python + extern/filereaderlp/builder.hpp + extern/filereaderlp/def.hpp + extern/filereaderlp/model.hpp + extern/filereaderlp/reader.hpp + extern/pdqsort/pdqsort.h + src/interfaces/highs_c_api.h + src/io/Filereader.h + src/io/FilereaderEms.h + src/io/FilereaderLp.h + src/io/FilereaderMps.h + src/io/HighsIO.h + src/io/HMpsFF.h + src/io/HMPSIO.h + src/io/LoadOptions.h + src/ipm/IpxSolution.h + src/ipm/IpxWrapper.h + src/lp_data/HConst.h + src/lp_data/HighsAnalysis.h + src/lp_data/HighsCallback.h + src/lp_data/HighsCallbackStruct.h + src/lp_data/HighsDebug.h + src/lp_data/HighsInfo.h + src/lp_data/HighsInfoDebug.h + src/lp_data/HighsLp.h + src/lp_data/HighsLpSolverObject.h + src/lp_data/HighsLpUtils.h + src/lp_data/HighsModelUtils.h + src/lp_data/HighsOptions.h + src/lp_data/HighsRanging.h + src/lp_data/HighsRuntimeOptions.h + src/lp_data/HighsSolution.h + src/lp_data/HighsSolutionDebug.h + src/lp_data/HighsSolve.h + src/lp_data/HighsStatus.h + src/lp_data/HStruct.h + src/mip/HighsCliqueTable.h + src/mip/HighsConflictPool.h + src/mip/HighsCutGeneration.h + src/mip/HighsCutPool.h + src/mip/HighsDebugSol.h + src/mip/HighsDomain.h + src/mip/HighsDomainChange.h + src/mip/HighsDynamicRowMatrix.h + src/mip/HighsGFkSolve.h + src/mip/HighsImplications.h + src/mip/HighsLpAggregator.h + src/mip/HighsLpRelaxation.h + src/mip/HighsMipSolver.h + src/mip/HighsMipSolverData.h + src/mip/HighsModkSeparator.h + src/mip/HighsNodeQueue.h + src/mip/HighsObjectiveFunction.h + src/mip/HighsPathSeparator.h + src/mip/HighsPrimalHeuristics.h + src/mip/HighsPseudocost.h + src/mip/HighsRedcostFixing.h + src/mip/HighsSearch.h + src/mip/HighsSeparation.h + src/mip/HighsSeparator.h + src/mip/HighsTableauSeparator.h + src/mip/HighsTransformedLp.h + src/model/HighsHessian.h + src/model/HighsHessianUtils.h + src/model/HighsModel.h + src/parallel/HighsBinarySemaphore.h + src/parallel/HighsCacheAlign.h + src/parallel/HighsCombinable.h + src/parallel/HighsMutex.h + src/parallel/HighsParallel.h + src/parallel/HighsRaceTimer.h + src/parallel/HighsSchedulerConstants.h + src/parallel/HighsSpinMutex.h + src/parallel/HighsSplitDeque.h + src/parallel/HighsTask.h + src/parallel/HighsTaskExecutor.h + src/pdlp/CupdlpWrapper.h + src/presolve/HighsPostsolveStack.h + src/presolve/HighsSymmetry.h + src/presolve/HPresolve.h + src/presolve/HPresolveAnalysis.h + src/presolve/ICrash.h + src/presolve/ICrashUtil.h + src/presolve/ICrashX.h + src/presolve/PresolveComponent.h + src/qpsolver/a_asm.hpp + src/qpsolver/a_quass.hpp + src/qpsolver/basis.hpp + src/qpsolver/crashsolution.hpp + src/qpsolver/dantzigpricing.hpp + src/qpsolver/devexpricing.hpp + src/qpsolver/eventhandler.hpp + src/qpsolver/factor.hpp + src/qpsolver/feasibility_bounded.hpp + src/qpsolver/feasibility_highs.hpp + src/qpsolver/gradient.hpp + src/qpsolver/instance.hpp + src/qpsolver/matrix.hpp + src/qpsolver/perturbation.hpp + src/qpsolver/pricing.hpp + src/qpsolver/qpconst.hpp + src/qpsolver/qpvector.hpp + src/qpsolver/quass.hpp + src/qpsolver/ratiotest.hpp + src/qpsolver/runtime.hpp + src/qpsolver/scaling.hpp + src/qpsolver/settings.hpp + src/qpsolver/snippets.hpp + src/qpsolver/statistics.hpp + src/qpsolver/steepestedgepricing.hpp + src/simplex/HApp.h + src/simplex/HEkk.h + src/simplex/HEkkDual.h + src/simplex/HEkkDualRHS.h + src/simplex/HEkkDualRow.h + src/simplex/HEkkPrimal.h + src/simplex/HighsSimplexAnalysis.h + src/simplex/HSimplex.h + src/simplex/HSimplexDebug.h + src/simplex/HSimplexNla.h + src/simplex/HSimplexReport.h + src/simplex/SimplexConst.h + src/simplex/SimplexStruct.h + src/simplex/SimplexTimer.h + src/test/DevKkt.h + src/test/KktCh2.h + src/util/FactorTimer.h + src/util/HFactor.h + src/util/HFactorConst.h + src/util/HFactorDebug.h + src/util/HighsCDouble.h + src/util/HighsComponent.h + src/util/HighsDataStack.h + src/util/HighsDisjointSets.h + src/util/HighsHash.h + src/util/HighsHashTree.h + src/util/HighsInt.h + src/util/HighsIntegers.h + src/util/HighsLinearSumBounds.h + src/util/HighsMatrixPic.h + src/util/HighsMatrixSlice.h + src/util/HighsMatrixUtils.h + src/util/HighsMemoryAllocation.h + src/util/HighsRandom.h + src/util/HighsRbTree.h + src/util/HighsSort.h + src/util/HighsSparseMatrix.h + src/util/HighsSparseVectorSum.h + src/util/HighsSplay.h + src/util/HighsTimer.h + src/util/HighsUtils.h + src/util/HSet.h + src/util/HVector.h + src/util/HVectorBase.h + src/util/stringutil.h + src/Highs.h + ) \ No newline at end of file diff --git a/cmake/sources.cmake b/cmake/sources.cmake new file mode 100644 index 0000000000..6c7e4647df --- /dev/null +++ b/cmake/sources.cmake @@ -0,0 +1,439 @@ +set(include_dirs + ${CMAKE_SOURCE_DIR}/extern + ${CMAKE_SOURCE_DIR}/extern/filereader + ${CMAKE_SOURCE_DIR}/extern/pdqsort + ${CMAKE_SOURCE_DIR}/extern/zstr + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/interfaces + ${CMAKE_SOURCE_DIR}/src/io + ${CMAKE_SOURCE_DIR}/src/ipm + ${CMAKE_SOURCE_DIR}/src/ipm/ipx + ${CMAKE_SOURCE_DIR}/src/ipm/basiclu + ${CMAKE_SOURCE_DIR}/src/lp_data + ${CMAKE_SOURCE_DIR}/src/mip + ${CMAKE_SOURCE_DIR}/src/model + ${CMAKE_SOURCE_DIR}/src/parallel + ${CMAKE_SOURCE_DIR}/src/pdlp + ${CMAKE_SOURCE_DIR}/src/pdlp/cupdlp + ${CMAKE_SOURCE_DIR}/src/presolve + ${CMAKE_SOURCE_DIR}/src/qpsolver + ${CMAKE_SOURCE_DIR}/src/simplex + ${CMAKE_SOURCE_DIR}/src/test + ${CMAKE_SOURCE_DIR}/src/util + $) + +set(cupdlp_sources + pdlp/cupdlp/cupdlp_cs.c + pdlp/cupdlp/cupdlp_linalg.c + pdlp/cupdlp/cupdlp_proj.c + pdlp/cupdlp/cupdlp_restart.c + pdlp/cupdlp/cupdlp_scaling_cuda.c + pdlp/cupdlp/cupdlp_solver.c + pdlp/cupdlp/cupdlp_step.c + pdlp/cupdlp/cupdlp_utils.c) + +set(cupdlp_headers + pdlp/cupdlp/cupdlp_cs.h + pdlp/cupdlp/cupdlp_defs.h + pdlp/cupdlp/cupdlp_linalg.h + pdlp/cupdlp/cupdlp_proj.h + pdlp/cupdlp/cupdlp_restart.h + pdlp/cupdlp/cupdlp_scaling_cuda.h + pdlp/cupdlp/cupdlp_solver.h + pdlp/cupdlp/cupdlp_step.h + pdlp/cupdlp/cupdlp_utils.c) + +set(basiclu_sources + ipm/basiclu/basiclu_factorize.c + ipm/basiclu/basiclu_get_factors.c + ipm/basiclu/basiclu_initialize.c + ipm/basiclu/basiclu_object.c + ipm/basiclu/basiclu_solve_dense.c + ipm/basiclu/basiclu_solve_for_update.c + ipm/basiclu/basiclu_solve_sparse.c + ipm/basiclu/basiclu_update.c + ipm/basiclu/lu_build_factors.c + ipm/basiclu/lu_condest.c + ipm/basiclu/lu_dfs.c + ipm/basiclu/lu_factorize_bump.c + ipm/basiclu/lu_file.c + ipm/basiclu/lu_garbage_perm.c + ipm/basiclu/lu_initialize.c + ipm/basiclu/lu_internal.c + ipm/basiclu/lu_markowitz.c + ipm/basiclu/lu_matrix_norm.c + ipm/basiclu/lu_pivot.c + ipm/basiclu/lu_residual_test.c + ipm/basiclu/lu_setup_bump.c + ipm/basiclu/lu_singletons.c + ipm/basiclu/lu_solve_dense.c + ipm/basiclu/lu_solve_for_update.c + ipm/basiclu/lu_solve_sparse.c + ipm/basiclu/lu_solve_symbolic.c + ipm/basiclu/lu_solve_triangular.c + ipm/basiclu/lu_update.c) + +set(basiclu_headers + ipm/basiclu/basiclu_factorize.h + ipm/basiclu/basiclu_get_factors.h + ipm/basiclu/basiclu_initialize.h + ipm/basiclu/basiclu_obj_factorize.h + ipm/basiclu/basiclu_obj_free.h + ipm/basiclu/basiclu_obj_get_factors.h + ipm/basiclu/basiclu_obj_initialize.h + ipm/basiclu/basiclu_obj_solve_dense.h + ipm/basiclu/basiclu_obj_solve_for_update.h + ipm/basiclu/basiclu_obj_solve_sparse.h + ipm/basiclu/basiclu_obj_update.h + ipm/basiclu/basiclu_object.h + ipm/basiclu/basiclu_solve_dense.h + ipm/basiclu/basiclu_solve_for_update.h + ipm/basiclu/basiclu_solve_sparse.h + ipm/basiclu/basiclu_update.h + ipm/basiclu/basiclu.h + ipm/basiclu/lu_def.h + ipm/basiclu/lu_file.h + ipm/basiclu/lu_internal.h + ipm/basiclu/lu_list.h) + +set(ipx_sources + ipm/ipx/basiclu_kernel.cc + ipm/ipx/basiclu_wrapper.cc + ipm/ipx/basis.cc + ipm/ipx/conjugate_residuals.cc + ipm/ipx/control.cc + ipm/ipx/crossover.cc + ipm/ipx/diagonal_precond.cc + ipm/ipx/forrest_tomlin.cc + ipm/ipx/guess_basis.cc + ipm/ipx/indexed_vector.cc + ipm/ipx/info.cc + ipm/ipx/ipm.cc + ipm/ipx/ipx_c.cc + ipm/ipx/iterate.cc + ipm/ipx/kkt_solver_basis.cc + ipm/ipx/kkt_solver_diag.cc + ipm/ipx/kkt_solver.cc + ipm/ipx/linear_operator.cc + ipm/ipx/lp_solver.cc + ipm/ipx/lu_factorization.cc + ipm/ipx/lu_update.cc + ipm/ipx/maxvolume.cc + ipm/ipx/model.cc + ipm/ipx/normal_matrix.cc + ipm/ipx/sparse_matrix.cc + ipm/ipx/sparse_utils.cc + ipm/ipx/splitted_normal_matrix.cc + ipm/ipx/starting_basis.cc + ipm/ipx/symbolic_invert.cc + ipm/ipx/timer.cc + ipm/ipx/utils.cc) + +set(ipx_headers + ipm/ipx/basiclu_kernel.h + ipm/ipx/basiclu_wrapper.h + ipm/ipx/basis.h + ipm/ipx/conjugate_residuals.h + ipm/ipx/control.h + ipm/ipx/crossover.h + ipm/ipx/diagonal_precond.h + ipm/ipx/forrest_tomlin.h + ipm/ipx/guess_basis.h + ipm/ipx/indexed_vector.h + ipm/ipx/info.h + ipm/ipx/ipm.h + ipm/ipx/ipx_c.h + ipm/ipx/ipx_config.h + ipm/ipx/ipx_info.h + ipm/ipx/ipx_internal.h + ipm/ipx/ipx_parameters.h + ipm/ipx/ipx_status.h + ipm/ipx/iterate.h + ipm/ipx/kkt_solver_basis.h + ipm/ipx/kkt_solver_diag.h + ipm/ipx/kkt_solver.h + ipm/ipx/linear_operator.h + ipm/ipx/lp_solver.h + ipm/ipx/lu_factorization.h + ipm/ipx/lu_update.h + ipm/ipx/maxvolume.h + ipm/ipx/model.h + ipm/ipx/multistream.h + ipm/ipx/normal_matrix.h + ipm/ipx/power_method.h + ipm/ipx/sparse_matrix.h + ipm/ipx/sparse_utils.h + ipm/ipx/splitted_normal_matrix.h + ipm/ipx/starting_basis.h + ipm/ipx/symbolic_invert.h + ipm/ipx/timer.h + ipm/ipx/utils.h) + +set(highs_sources + ../extern/filereaderlp/reader.cpp + interfaces/highs_c_api.cpp + io/Filereader.cpp + io/FilereaderEms.cpp + io/FilereaderLp.cpp + io/FilereaderMps.cpp + io/HighsIO.cpp + io/HMpsFF.cpp + io/HMPSIO.cpp + io/LoadOptions.cpp + ipm/IpxWrapper.cpp + lp_data/Highs.cpp + lp_data/HighsCallback.cpp + lp_data/HighsDebug.cpp + lp_data/HighsInfo.cpp + lp_data/HighsInfoDebug.cpp + lp_data/HighsDeprecated.cpp + lp_data/HighsInterface.cpp + lp_data/HighsLp.cpp + lp_data/HighsLpUtils.cpp + lp_data/HighsModelUtils.cpp + lp_data/HighsOptions.cpp + lp_data/HighsRanging.cpp + lp_data/HighsSolution.cpp + lp_data/HighsSolutionDebug.cpp + lp_data/HighsSolve.cpp + lp_data/HighsStatus.cpp + mip/HighsCliqueTable.cpp + mip/HighsConflictPool.cpp + mip/HighsCutGeneration.cpp + mip/HighsCutPool.cpp + mip/HighsDebugSol.cpp + mip/HighsDomain.cpp + mip/HighsDynamicRowMatrix.cpp + mip/HighsGFkSolve.cpp + mip/HighsImplications.cpp + mip/HighsLpAggregator.cpp + mip/HighsLpRelaxation.cpp + mip/HighsMipSolver.cpp + mip/HighsMipSolverData.cpp + mip/HighsModkSeparator.cpp + mip/HighsNodeQueue.cpp + mip/HighsObjectiveFunction.cpp + mip/HighsPathSeparator.cpp + mip/HighsPrimalHeuristics.cpp + mip/HighsPseudocost.cpp + mip/HighsRedcostFixing.cpp + mip/HighsSearch.cpp + mip/HighsSeparation.cpp + mip/HighsSeparator.cpp + mip/HighsTableauSeparator.cpp + mip/HighsTransformedLp.cpp + model/HighsHessian.cpp + model/HighsHessianUtils.cpp + model/HighsModel.cpp + parallel/HighsTaskExecutor.cpp + pdlp/CupdlpWrapper.cpp + presolve/HighsPostsolveStack.cpp + presolve/HighsSymmetry.cpp + presolve/HPresolve.cpp + presolve/HPresolveAnalysis.cpp + presolve/ICrash.cpp + presolve/ICrashUtil.cpp + presolve/ICrashX.cpp + presolve/PresolveComponent.cpp + qpsolver/a_asm.cpp + qpsolver/a_quass.cpp + qpsolver/basis.cpp + qpsolver/perturbation.cpp + qpsolver/quass.cpp + qpsolver/ratiotest.cpp + qpsolver/scaling.cpp + simplex/HEkk.cpp + simplex/HEkkControl.cpp + simplex/HEkkDebug.cpp + simplex/HEkkDual.cpp + simplex/HEkkDualMulti.cpp + simplex/HEkkDualRHS.cpp + simplex/HEkkDualRow.cpp + simplex/HEkkInterface.cpp + simplex/HEkkPrimal.cpp + simplex/HighsSimplexAnalysis.cpp + simplex/HSimplex.cpp + simplex/HSimplexDebug.cpp + simplex/HSimplexNla.cpp + simplex/HSimplexNlaDebug.cpp + simplex/HSimplexNlaFreeze.cpp + simplex/HSimplexNlaProductForm.cpp + simplex/HSimplexReport.cpp + test/KktCh2.cpp + test/DevKkt.cpp + util/HFactor.cpp + util/HFactorDebug.cpp + util/HFactorExtend.cpp + util/HFactorRefactor.cpp + util/HFactorUtils.cpp + util/HighsHash.cpp + util/HighsLinearSumBounds.cpp + util/HighsMatrixPic.cpp + util/HighsMatrixUtils.cpp + util/HighsSort.cpp + util/HighsSparseMatrix.cpp + util/HighsUtils.cpp + util/HSet.cpp + util/HVectorBase.cpp + util/stringutil.cpp) + +# add catch header? +set(highs_headers + ../extern/filereaderlp/builder.hpp + ../extern/filereaderlp/def.hpp + ../extern/filereaderlp/model.hpp + ../extern/filereaderlp/reader.hpp + ../extern/pdqsort/pdqsort.h + ../extern/zstr/strict_fstream.hpp + ../extern/zstr/zstr.hpp + interfaces/highs_c_api.h + io/Filereader.h + io/FilereaderEms.h + io/FilereaderLp.h + io/FilereaderMps.h + io/HighsIO.h + io/HMpsFF.h + io/HMPSIO.h + io/LoadOptions.h + ipm/IpxSolution.h + ipm/IpxWrapper.h + lp_data/HConst.h + lp_data/HighsAnalysis.h + lp_data/HighsCallback.h + lp_data/HighsCallbackStruct.h + lp_data/HighsDebug.h + lp_data/HighsInfo.h + lp_data/HighsInfoDebug.h + lp_data/HighsLp.h + lp_data/HighsLpSolverObject.h + lp_data/HighsLpUtils.h + lp_data/HighsModelUtils.h + lp_data/HighsOptions.h + lp_data/HighsRanging.h + lp_data/HighsRuntimeOptions.h + lp_data/HighsSolution.h + lp_data/HighsSolutionDebug.h + lp_data/HighsSolve.h + lp_data/HighsStatus.h + lp_data/HStruct.h + mip/HighsCliqueTable.h + mip/HighsConflictPool.h + mip/HighsCutGeneration.h + mip/HighsCutPool.h + mip/HighsDebugSol.h + mip/HighsDomain.h + mip/HighsDomainChange.h + mip/HighsDynamicRowMatrix.h + mip/HighsGFkSolve.h + mip/HighsImplications.h + mip/HighsLpAggregator.h + mip/HighsLpRelaxation.h + mip/HighsMipSolver.h + mip/HighsMipSolverData.h + mip/HighsModkSeparator.h + mip/HighsNodeQueue.h + mip/HighsObjectiveFunction.h + mip/HighsPathSeparator.h + mip/HighsPrimalHeuristics.h + mip/HighsPseudocost.h + mip/HighsRedcostFixing.h + mip/HighsSearch.h + mip/HighsSeparation.h + mip/HighsSeparator.h + mip/HighsTableauSeparator.h + mip/HighsTransformedLp.h + model/HighsHessian.h + model/HighsHessianUtils.h + model/HighsModel.h + parallel/HighsBinarySemaphore.h + parallel/HighsCacheAlign.h + parallel/HighsCombinable.h + parallel/HighsMutex.h + parallel/HighsParallel.h + parallel/HighsRaceTimer.h + parallel/HighsSchedulerConstants.h + parallel/HighsSpinMutex.h + parallel/HighsSplitDeque.h + parallel/HighsTask.h + parallel/HighsTaskExecutor.h + pdlp/CupdlpWrapper.h + presolve/HighsPostsolveStack.h + presolve/HighsSymmetry.h + presolve/HPresolve.h + presolve/HPresolveAnalysis.h + presolve/ICrash.h + presolve/ICrashUtil.h + presolve/ICrashX.h + presolve/PresolveComponent.h + qpsolver/a_asm.hpp + qpsolver/a_quass.hpp + qpsolver/basis.hpp + qpsolver/crashsolution.hpp + qpsolver/dantzigpricing.hpp + qpsolver/devexpricing.hpp + qpsolver/eventhandler.hpp + qpsolver/factor.hpp + qpsolver/feasibility_bounded.hpp + qpsolver/feasibility_highs.hpp + qpsolver/gradient.hpp + qpsolver/instance.hpp + qpsolver/matrix.hpp + qpsolver/perturbation.hpp + qpsolver/pricing.hpp + qpsolver/qpconst.hpp + qpsolver/qpvector.hpp + qpsolver/quass.hpp + qpsolver/ratiotest.hpp + qpsolver/runtime.hpp + qpsolver/scaling.hpp + qpsolver/settings.hpp + qpsolver/snippets.hpp + qpsolver/statistics.hpp + qpsolver/steepestedgepricing.hpp + simplex/HApp.h + simplex/HEkk.h + simplex/HEkkDual.h + simplex/HEkkDualRHS.h + simplex/HEkkDualRow.h + simplex/HEkkPrimal.h + simplex/HighsSimplexAnalysis.h + simplex/HSimplex.h + simplex/HSimplexDebug.h + simplex/HSimplexNla.h + simplex/HSimplexReport.h + simplex/SimplexConst.h + simplex/SimplexStruct.h + simplex/SimplexTimer.h + test/DevKkt.h + test/KktCh2.h + util/FactorTimer.h + util/HFactor.h + util/HFactorConst.h + util/HFactorDebug.h + util/HighsCDouble.h + util/HighsComponent.h + util/HighsDataStack.h + util/HighsDisjointSets.h + util/HighsHash.h + util/HighsHashTree.h + util/HighsInt.h + util/HighsIntegers.h + util/HighsLinearSumBounds.h + util/HighsMatrixPic.h + util/HighsMatrixSlice.h + util/HighsMatrixUtils.h + util/HighsMemoryAllocation.h + util/HighsRandom.h + util/HighsRbTree.h + util/HighsSort.h + util/HighsSparseMatrix.h + util/HighsSparseVectorSum.h + util/HighsSplay.h + util/HighsTimer.h + util/HighsUtils.h + util/HSet.h + util/HVector.h + util/HVectorBase.h + util/stringutil.h + Highs.h + ) diff --git a/docs/HiGHS_CopyrightHeader.pl b/docs/HiGHS_CopyrightHeader.pl index 0d5329877c..27f9e1a67f 100755 --- a/docs/HiGHS_CopyrightHeader.pl +++ b/docs/HiGHS_CopyrightHeader.pl @@ -5,7 +5,7 @@ $CopyrightHeaderLine0 = "/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */"; $CopyrightHeaderLine1 = "/* */"; $CopyrightHeaderLine2 = "/* This file is part of the HiGHS linear optimization suite */"; -$CopyrightHeaderLine3 = "/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */"; +$CopyrightHeaderLine3 = "/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */"; $CopyrightHeaderLine4 = "/* Leona Gottwald and Michael Feldmeier */"; $CopyrightHeaderLine5 = "/* Available as open-source under the MIT License */"; $RemoveCopyrightHeader = 0; diff --git a/docs/README.md b/docs/README.md index 61426e0037..86464838fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,7 @@ To edit the documentation, checkout a branch and edit the Markdown files in the To build locally, [install Julia](https://julialang.org/downloads/), then (from the `docs` directory) run: -``` +``` bash $ julia make.jl ``` diff --git a/docs/make.jl b/docs/make.jl index 378d543907..132d36c4de 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -68,17 +68,18 @@ Documenter.makedocs( ], "Callbacks" => "callbacks.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", + "C" => "interfaces/c_api.md", + "Fortran" => "interfaces/fortran.md", + "Python" => Any[ + "interfaces/python/index.md", + "interfaces/python/example-py.md", + ], + "CSharp" => "interfaces/csharp.md", "Julia" => "interfaces/julia/index.md", "Other" => "interfaces/other.md", ], diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index 8cf96e7e32..2c764e97c6 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -1,8 +1,13 @@ # Callbacks -The HiGHS callback allows user actions to be performed within HiGHS. There is one generic callback method that can be defined by a user, with specific callback scenarios communicated to the user via a parameter. Particular callbacks must be activated (and can be deactivated) as described below. The user callback can be given any name and, below, is called `userCallback`. Its definition is - -```bash +The HiGHS callback allows user actions to be performed within HiGHS. There is +one generic callback method that can be defined by a user, with specific +callback scenarios communicated to the user via a parameter. Particular +callbacks must be activated (and can be deactivated) as described below. The +user callback can be given any name and, below, is called `userCallback`. Its +definition is + +```cpp void userCallback(const int callback_type, const char* message, const HighsCallbackDataOut* data_out, @@ -22,12 +27,12 @@ The logging callback type is a cast of the relevant member of the C++ enum `HighsCallbackType`, and is available in C as a constant. The user's callback method is communicated to HiGHS via the method that in the HiGHS C++ class is -```bash +```cpp HighsStatus setCallback(void (*userCallback)(const int, const char*, const HighsCallbackDataOut*, HighsCallbackDataIn*, void*), void* user_callback_data); ``` and, in the HiGHS C API is -```bash +```cpp HighsInt Highs_setCallback( void* highs, void (*userCallback)(const int, const char*, @@ -35,21 +40,21 @@ HighsInt Highs_setCallback( struct HighsCallbackDataIn*, void*), void* user_callback_data) ``` -There current callback scenarios are set out below, and the particular callback is activated in C++ by calling +The current callback scenarios are set out below, and the particular callback is activated in C++ by calling -```bash +```cpp HighsStatus startCallback(const int callback_type); ``` and, in C, by calling -```bash +```cpp HighsInt Highs_startCallback(void* highs, const int callback_type); ``` , and de-activated in C++ by calling -```bash +```cpp HighsStatus stopCallback(const int callback_type); ``` and, in C, by calling -```bash +```cpp HighsInt Highs_stopCallback(void* highs, const int callback_type); ``` @@ -119,10 +124,10 @@ For each of the MIP callbacks, the following `HighsCallbackDataOut` struct membe * `running_time`: execution time of HiGHS * `objective_function_value`: the objective function value of the best integer feasible solution found -* `num_nodes`: the number of MIP nodes explored to date -* `primal_bound`: the primal bound -* `dual_bound`: the dual bound -* `mip_gap`: the (relative) difference between tht primal and dual bounds +* `mip_node_count`: the number of MIP nodes explored to date +* `mip_primal_bound`: the primal bound +* `mip_dual_bound`: the dual bound +* `mip_gap`: the (relative) difference between the primal and dual bounds diff --git a/docs/src/executable.md b/docs/src/executable.md index d18d46ab63..d9f1777555 100644 --- a/docs/src/executable.md +++ b/docs/src/executable.md @@ -6,7 +6,7 @@ For convenience, the executable is assumed to be `bin/highs`. The model given by the MPS file `model.mps` is solved by the command: -```bash +```shell $ bin/highs model.mps ``` @@ -19,7 +19,7 @@ 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 +```shell $ bin/highs --help HiGHS options Usage: diff --git a/docs/src/guide/basic.md b/docs/src/guide/basic.md index 764b99ecd4..1b674a84a6 100644 --- a/docs/src/guide/basic.md +++ b/docs/src/guide/basic.md @@ -20,14 +20,17 @@ 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. +Enums are scalar identifier types that can take only a limited range of values. + +#### [Classes](@id guide-basic-classes) + +The advantage of using the native `C++` classes in HiGHS is that many fewer parameters are needed +when passing data to and from HiGHS. The binding of the data members +of these classes to `highspy` structures allows them to be used when +calling HiGHS from Python, although they are 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 diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 3d93481564..bba05a2a6a 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -2,7 +2,7 @@ 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 +cpp-getting-started), [`C`](@ref c-api), [`C#`](@ref csharp) and [`Fortran`](@ref fortran-api). It is written in three sections: [basic](@ref guide-basic), [further](@ref guide-further) and [advanced](@ref guide-advanced). diff --git a/docs/src/index.md b/docs/src/index.md index 2f5bcd7b59..59e799a4da 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,7 +1,5 @@ # 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. @@ -59,7 +57,7 @@ The C interface cannot make use of the C++ structures and enums, and its methods ## 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. +point methods. MIPs are solved by branch-and-cut, and QPs by active set. ## Citing HiGHS diff --git a/docs/src/installation.md b/docs/src/installation.md index d9a875a145..e9a3e764d7 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -1,10 +1,14 @@ # Install HiGHS +## Compile from source + +HiGHS uses CMake as build system, and requires at least version +3.15. Details about building from source using cmake can be found in `HiGHS/cmake/README.md`. + ## 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). +[`Julia`](@ref HiGHS.jl), [`Python`](@ref python-getting-started), [`CSharp`](@ref nuget) and [`Rust`](@ref Rust). ## Precompiled Binaries @@ -16,92 +20,22 @@ 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 -``` +Multiple versions are available. Each version has the form `vX.Y.Z`. In +general, you should choose the most recent versinon. -HiGHS is installed using the command +To install a precompiled binary, download the appropriate `HiGHSstatic.vX.Y.Z.[platform-string].tar.gz` +file and extract the executable located at `/bin/highs`. -```bash -$ cmake --install . -``` +Do not download the file starting with `HiGHSstatic-logs`. These files contain +information from the automated compilation system. Click "Show all N assets" +to see more files. -This installs the library in `lib/`, as well as all header files in `include/highs/`. For a custom -installation in `install_folder` run +### Platform strings -```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. +The GitHub releases contain precompiled binaries for a number of different +platforms. These are indicated by the platform-specific string in each +filename. + * For Windows users: 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` diff --git a/docs/src/interfaces/c/index.md b/docs/src/interfaces/c_api.md similarity index 100% rename from docs/src/interfaces/c/index.md rename to docs/src/interfaces/c_api.md diff --git a/docs/src/interfaces/cpp/index.md b/docs/src/interfaces/cpp/index.md index f6151d2c56..23c43dd6d3 100644 --- a/docs/src/interfaces/cpp/index.md +++ b/docs/src/interfaces/cpp/index.md @@ -8,6 +8,7 @@ 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 ` @@ -15,43 +16,14 @@ HiGHS uses CMake (minimum version 3.15) as a build system, and can use the follo - Intel ` icc ` - Microsoft ` MSVC ` -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 +Instructions for building HiGHS from source code are in `HiGHS/cmake/README.md`. -The default installation location may need administrative -permissions. To install, after building and testing, run +The simplest setup is to build HiGHS in a build directory within the root direcory. The +name of the build folder is arbitrary but, assuming it is `build`, +the sequence of commands is as follows ``` 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 . -``` +cd HiGHS +cmake -S. -B build +cmake --build build --parallel +``` \ No newline at end of file diff --git a/docs/src/interfaces/cpp/library.md b/docs/src/interfaces/cpp/library.md index 53869789a3..27f8615300 100644 --- a/docs/src/interfaces/cpp/library.md +++ b/docs/src/interfaces/cpp/library.md @@ -5,22 +5,22 @@ The HiGHS library is defined in the [`src/Highs.h`](https://github.com/ERGO-Code 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 -``` +``` cpp Highs::passModel(const HighsModel& model) ``` or create and populate an instance of the `HighsLp` class, and call -``` +``` cpp Highs::passModel(const HighsLp& lp) ``` For reading models from a file, use -``` +``` cpp Highs::readModel(const std::string& filename) ``` Below is an example of building a `HighsModel` -``` +``` cpp // Create and populate a HighsModel instance for the LP // Min f = x_0 + x_1 + 3 @@ -56,7 +56,7 @@ Below is an example of building a `HighsModel` ## Solve model -``` +``` cpp // Create a Highs instance Highs highs; HighsStatus return_status; @@ -79,7 +79,7 @@ Below is an example of building a `HighsModel` Solution information: -``` +``` cpp const HighsInfo& info = highs.getInfo(); cout << "Simplex iteration count: " << info.simplex_iteration_count << endl; cout << "Objective function value: " << info.objective_function_value << endl; @@ -91,7 +91,7 @@ Solution information: ## Integrality variables To indicate that variables must take integer values use the `HighsLp::integrality` vector. -``` +``` cpp model.lp_.integrality_.resize(lp.num_col_); for (int col=0; col < lp.num_col_; col++) model.lp_.integrality_[col] = HighsVarType::kInteger; diff --git a/docs/src/interfaces/cpp/link.md b/docs/src/interfaces/cpp/link.md deleted file mode 100755 index d2414789a9..0000000000 --- a/docs/src/interfaces/cpp/link.md +++ /dev/null @@ -1,59 +0,0 @@ -## 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/csharp.md b/docs/src/interfaces/csharp.md new file mode 100644 index 0000000000..dceed6b00d --- /dev/null +++ b/docs/src/interfaces/csharp.md @@ -0,0 +1,55 @@ +#### [CSharp](@id csharp) + +#### Build from source + +There is a C# example code in `examples/call_highs_from_csharp.cs`. From the HiGHS root directory, run + +``` bash +cmake -S. -Bbuild -DCSHARP=ON +``` + +If a CSharp compiler is available, this builds the example using cmake and generates a binary in the build directory (`build/bin/csharpexample`). + +#### [NuGet](@id nuget) + +The nuget package Highs.Native is on https://www.nuget.org, at https://www.nuget.org/packages/Highs.Native/. + +It can be added to your C# project with `dotnet` + +```shell +dotnet add package Highs.Native --version 1.7.2 +``` + +The nuget package contains runtime libraries for + +* `win-x64` +* `win-x32` +* `linux-x64` +* `linux-arm64` +* `macos-x64` +* `macos-arm64` + +Details for building locally can be found in `nuget/README.md`. + +#### C# API + +The C# API can be called directly. Here are observations on calling the HiGHS C# API from C#: + + * The file `HiGHS/src/interfaces/highs_csharp_api.cs` contains all the PInvoke you need. + * 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. + diff --git a/docs/src/interfaces/fortran.md b/docs/src/interfaces/fortran.md new file mode 100644 index 0000000000..8d5786b46a --- /dev/null +++ b/docs/src/interfaces/fortran.md @@ -0,0 +1,11 @@ + +#### [Fortran](@id fortran-api) + +The interface is in +`HiGHS/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: +``` bash +cmake -DFORTRAN=ON .. +``` \ No newline at end of file diff --git a/docs/src/interfaces/other.md b/docs/src/interfaces/other.md index fd4bfdbf59..89bc34afe2 100644 --- a/docs/src/interfaces/other.md +++ b/docs/src/interfaces/other.md @@ -8,39 +8,6 @@ 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 diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md index d3188071fb..d75c67dd9e 100644 --- a/docs/src/interfaces/python/example-py.md +++ b/docs/src/interfaces/python/example-py.md @@ -40,8 +40,8 @@ subject to x1 <= 7 Using the simplified interface, the model can be built as follows: ```python -x0 = h.addVar(lb = 0, ub = 4) -x1 = h.addVar(lb = 1, ub = 7) +x0 = h.addVariable(lb = 0, ub = 4) +x1 = h.addVariable(lb = 1, ub = 7) h.addConstr(5 <= x0 + 2*x1 <= 15) h.addConstr(6 <= 3*x0 + 2*x1) @@ -210,7 +210,7 @@ print('Basis validity = ', h.basisValidityToString(info.basis_validity)) * `changeColCost` * `changeColBounds` * `changeRowBounds` - * `changeColsCosts` + * `changeColsCost` * `changeColsBounds` * `changeRowsBounds` * `changeCoeff` diff --git a/docs/src/interfaces/python/index.md b/docs/src/interfaces/python/index.md index ae9c3fc7e3..991b2cae32 100644 --- a/docs/src/interfaces/python/index.md +++ b/docs/src/interfaces/python/index.md @@ -6,7 +6,7 @@ HiGHS is available as `highspy` on [PyPi](https://pypi.org/project/highspy/). If `highspy` is not already installed, run: -```bash +```shell $ pip install highspy ``` @@ -33,6 +33,14 @@ HiGHS must be initialized before making calls to the HiGHS Python library: h = highspy.Highs() ``` +## Logging + +When called from C++, or via the C API, console logging is duplicated +to a file that, by default, is `Highs.log`. However, to channel +logging to a file from `highspy`, the name of the file needs to be +specified explicitly via a call to `setOptionValue('log_file', +'foo.bar')`. + ## Methods Detailed documentation of the methods and structures is given in the diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md index 5fd698a202..2505c4d3c3 100644 --- a/docs/src/options/definitions.md +++ b/docs/src/options/definitions.md @@ -6,7 +6,7 @@ - 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 +- Solver option: "simplex", "choose", "ipm" or "pdlp". If "simplex"/"ipm"/"pdlp" is chosen then, for a MIP (QP) the integrality constraint (quadratic term) will be ignored - Type: string - Default: "choose" @@ -32,25 +32,25 @@ - Default: "off" ## infinite\_cost -- Limit on cost coefficient: values larger than this will be treated as infinite +- Limit on |cost coefficient|: values greater than or equal to 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 +- Limit on |constraint bound|: values greater than or equal to 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 +- Lower limit on |matrix entries|: values less than or equal to 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 +- Upper limit on |matrix entries|: values greater than or equal to this will be treated as infinite - Type: double - Range: [1, inf] - Default: 1e+15 @@ -97,6 +97,18 @@ - Range: {0, 2147483647} - Default: 0 +## user\_bound\_scale +- Exponent of power-of-two bound scaling for model +- Type: integer +- Range: {-2147483647, 2147483647} +- Default: 0 + +## user\_cost\_scale +- Exponent of power-of-two cost scaling for model +- Type: integer +- Range: {-2147483647, 2147483647} +- Default: 0 + ## simplex\_strategy - Strategy for simplex solver 0 => Choose; 1 => Dual (serial); 2 => Dual (PAMI); 3 => Dual (SIP); 4 => Primal - Type: integer @@ -191,6 +203,11 @@ - Type: boolean - Default: "true" +## mip\_allow\_restart +- Whether MIP restart is permitted +- Type: boolean +- Default: "true" + ## mip\_max\_nodes - MIP solver max number of nodes - Type: integer @@ -284,9 +301,55 @@ - Range: [0, inf] - Default: 1e-06 +## mip\_min\_logging\_interval +- MIP minimum logging interval +- Type: double +- Range: [0, inf] +- Default: 5 + ## ipm\_iteration\_limit - Iteration limit for IPM solver - Type: integer - Range: {0, 2147483647} - Default: 2147483647 +## pdlp\_native\_termination +- Use native termination for PDLP solver: Default = false +- Type: boolean +- Default: "false" + +## pdlp\_scaling +- Scaling option for PDLP solver: Default = true +- Type: boolean +- Default: "true" + +## pdlp\_iteration\_limit +- Iteration limit for PDLP solver +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + +## pdlp\_e\_restart\_method +- Restart mode for PDLP solver: 0 => none; 1 => GPU (default); 2 => CPU +- Type: integer +- Range: {0, 2} +- Default: 1 + +## pdlp\_d\_gap\_tol +- Duality gap tolerance for PDLP solver: Default = 1e-4 +- Type: double +- Range: [1e-12, inf] +- Default: 0.0001 + +## qp\_iteration\_limit +- Iteration limit for QP solver +- Type: integer +- Range: {0, 2147483647} +- Default: 2147483647 + +## qp\_nullspace\_limit +- Nullspace limit for QP solver +- Type: integer +- Range: {0, 2147483647} +- Default: 4000 + diff --git a/docs/src/options/intro.md b/docs/src/options/intro.md index 7396072a87..ff33cae234 100644 --- a/docs/src/options/intro.md +++ b/docs/src/options/intro.md @@ -15,15 +15,15 @@ options file. A sample options file, giving documentation of all the options is written to the console by the command: -```bash +```shell $ bin/highs --options_file="" ``` ## Option methods -To set the value of option `name`, call: +The following code illustrates how to access the HiGHS options in Python. To set the value of option `name`, call: -``` +``` python status = h.setOptionValue(name, value) ``` @@ -32,13 +32,13 @@ explicit value. To get the value of option `name`, call: -``` +``` python [status, value] = h.getOptionValue(name) ``` To get the type of option `name`, call: -``` +``` python [status, type] = h.getOptionType(name) ``` diff --git a/docs/src/parallel.md b/docs/src/parallel.md index b14defc01e..3a857629e3 100644 --- a/docs/src/parallel.md +++ b/docs/src/parallel.md @@ -49,18 +49,10 @@ performed regardless of the value of the ## 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. +speeding 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/enums.md b/docs/src/structures/enums.md index af9601b7c2..7975e41fb0 100644 --- a/docs/src/structures/enums.md +++ b/docs/src/structures/enums.md @@ -73,6 +73,8 @@ This defines the status of the model after a call to `run` * `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 + * `kInterrupt`: The solver has been interrupted by the user + * `kMemoryLimit`: The solver has been unable to allocate sufficient memory * `kUnknown`: The model status is unknown ## HighsBasisStatus diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1382a4e34e..4427f4b487 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -17,8 +17,10 @@ endif() # endif() if(BUILD_CXX_EXAMPLE) - file(GLOB C_SRCS "*.c") + file(GLOB C_SRCS "*_minimal.c") + # set(C_SRCS "call_highs_from_c_minimal.c") foreach(FILE_NAME IN LISTS C_SRCS) - add_c_example(${FILE_NAME}) + message(${FILE_NAME}) + add_c_test(${FILE_NAME}) endforeach() -endif() \ No newline at end of file +endif() diff --git a/examples/branch-and-price.py b/examples/branch-and-price.py new file mode 100644 index 0000000000..4fe7c1d620 --- /dev/null +++ b/examples/branch-and-price.py @@ -0,0 +1,465 @@ +# An example branch-and-price algorithm for the bin packing problem +# +from collections import defaultdict +from operator import itemgetter +import highspy, random, time, math + +# Relative gap for column generation +CG_GAP_REL = 1e-4 + +# Random seeds for reproducibility +random.seed(100) +SEED = 100 + +# Instance parameters +NumberItems = 40 +ItemWeights = [round(random.uniform(1, 10), 1) for _ in range(NumberItems)] +BinCapacity = 15 + + +# +# Solve instance with greedy first fit decreasing (FFD) heuristic +# +def solveGreedyModel(): + bins = defaultdict(float) + solution = defaultdict(list) + + for item, w in sorted(enumerate(ItemWeights), reverse=True, key=itemgetter(1)): + index = next((i for i, W in bins.items() if W + w <= BinCapacity), len(bins)) + bins[index] += w + solution[index].append(item) + + return list(solution.values()) + +# +# The compact bin-packing model (for comparison with branch-and-price) +# +# min sum_{j} y_{j} +# s.t. +# sum_{j} x_{ij} == 1, \forall i // Each item i is assigned to exactly one bin +# sum_{i} w_{i} * x_{ij} <= c * y_{j}, \forall j // Bin capacity constraint +# +# x_{ij} = 1 if item i is assigned to bin j, 0 otherwise +# y_{j} = 1 if bin j is open, 0 otherwise +# +def solveCompactModel(): + # Estimate maximum number of bins with first fit decreasing (for smaller sized model) + greedy_bins = solveGreedyModel() + B = len(greedy_bins) + + m = highspy.Highs() + m.setOptionValue('output_flag', True) + m.setOptionValue('mip_abs_gap', 1-1e-5) # Since all variables in the objective are binary, it should stop when the absolute gap is below 1 + m.setOptionValue('random_seed', SEED) + + # add binary variables x_{ij} and y_{j} + x = {(i,j): i*B + j for i in range(NumberItems) for j in range(B) } + y = [len(x) + j for j in range(B)] + + m.addVars(len(x), [0]*len(x), [1]*len(x)); # x_{ij} \in {0,1}, \forall i,j + m.addVars(len(y), [0]*len(y), [1]*len(y)); # y_{j} \in {0,1}, \forall j + m.changeColsIntegrality( + len(x)+len(y), + list(range(len(x)+len(y))), + [highspy.HighsVarType.kInteger]*(len(x)+len(y)) + ) + + # min sum_{j} y_{j} + m.changeObjectiveSense(highspy.ObjSense.kMinimize) + m.changeColsCost(len(y), y, [1]*len(y)) + + # \sum_{j} x_{ij} == 1, \foreach i + # 1 <= \sum_{j} x_{ij} <= 1 + for i in range(NumberItems): + m.addRow( + 1, # lhs + 1, # rhs + B, # Number of non-zero variables + [x[i,j] for j in range(B)], # Indexes of variable + [1] * B # Coefficients of variables + ) + + # sum_{i} w_{i} * x_{ij} <= c * y_{j}, \foreach j + # -inf <= sum_{i} w_{i} * x_{ij} - c * y_{j} <= 0 + for j in range(B): + m.addRow( + -highspy.kHighsInf, # lhs + 0, # rhs + NumberItems+1, # Number of non-zero variables + [x[i,j] for i in range(NumberItems)] + [y[j]], # Indexes of variable + [ItemWeights[i] for i in range(NumberItems)] + [-BinCapacity] # Coefficients of variables + ) + + m.run() + vals = list(m.getSolution().col_value) + + bins = [ + [i for i in range(NumberItems) if vals[x[i,j]] > 0.9] + for j in range(B) + if vals[y[j]] > 0.9 + ] + + return bins + +# +# Master problem for column generation +# A column represents a set of items packed into a bin +# +# min \sum_{k} \lambda_{k} +# s.t. +# \sum_{k \in K_i} \lambda_{k} == 1, \foreach i (\mu_i duals) // Each item i is packed exactly once (but may be spread over multiple columns) +# // where K_i is the set of columns where i appears +# 0 <= \lambda_{k} <= 1, \foreach k // Can use each column at most once (can be fractional) +# +def createMasterProblem(columns: list): + m = highspy.Highs() + m.setOptionValue('output_flag', False) + m.setOptionValue('random_seed', SEED) + + m.addVars(len(columns), [0]*len(columns), [1]*len(columns)) + + # min \sum_{k} \lambda_{k} + m.changeObjectiveSense(highspy.ObjSense.kMinimize) + m.changeColsCost(len(columns), list(range(len(columns))), [1]*len(columns)) + + # \sum_{k \in K_i} \lambda_{k} == 1, \foreach i + # 1 <= \sum_{k \in K_i} \lambda_{k} <= 1 + for i in range(NumberItems): + K_i = [k for k, column in enumerate(columns) if i in column] + m.addRow(1, 1, len(K_i), K_i, [1]*len(K_i)) + + m.run() + + solution = m.getSolution() + vals = list(solution.col_value) + duals = list(solution.row_dual) + + return m, vals, duals + +# +# Solve the knapsack subproblem exactly with HiGHS +# +# 1 - max \sum_{i} \mu_{i} * z_{i} +# s.t. +# \sum_{i} w_{i} * z_{i} <= capacity +# +# z_{i} = 1 if item i is packed, 0 otherwise +# +def solveSubproblemExact(duals): + m = highspy.Highs() + m.setOptionValue('output_flag', False) + m.setOptionValue('random_seed', SEED) + + m.addVars(NumberItems, [0]*NumberItems, [1]*NumberItems) + m.changeColsIntegrality(NumberItems, list(range(NumberItems)), [highspy.HighsVarType.kInteger]*NumberItems) + + # max \sum_{i} \mu_{i} * z_{i} + # where \mu_{i} is the dual variable for the i-th row of the master problem + m.changeColsCost(NumberItems, list(range(NumberItems)), duals) + m.changeObjectiveSense(highspy.ObjSense.kMaximize) + + # sum_{i} w_{i} * z_{i} <= capacity, \foreach j + # -inf <= sum_{i} w_{i} * z_{i} <= capacity + m.addRow( + -highspy.kHighsInf, + BinCapacity, + NumberItems, + list(range(NumberItems)), + ItemWeights + ) + + m.run() + + vals = list(m.getSolution().col_value) + new_column = sorted([i for i in range(NumberItems) if vals[i] > 0.9]) + + return 1 - m.getObjectiveValue(), new_column + + +# Solve the knapsack subproblem with greedy heuristic +def solveSubproblemNotExact(duals): + total_weight = 0 + new_column = [] + + for i in sorted(range(NumberItems), key=lambda i: -duals[i]/ItemWeights[i]): + if duals[i] >= 0 and ItemWeights[i] + total_weight <= BinCapacity: + total_weight += ItemWeights[i] + new_column += [i] + + return 1 - sum(duals[i] for i in new_column), sorted(new_column) + + +# +# Generate columns for the master problem +# +def generateColumns(columns: list, m, msg=True, solve_exact = False, start_time = 0): + best_gap = math.inf + + iter = 0 + while True: + solution = m.getSolution() + duals = list(solution.row_dual) + ub = m.getObjectiveValue() + + # solve sub problem to generate new column + if not solve_exact: + obj_sub, new_column = solveSubproblemNotExact(duals) + else: + obj_sub, new_column = solveSubproblemExact(duals) + + iter += 1 + obj = ub + min(0, obj_sub) # we terminate if obj_sub >= 0 + gap = (ub-obj)/ub + + if gap < best_gap: + best_gap = gap + if msg: + row = [ + f"{len(columns)}", + f"{round(obj_sub, 3) :.4f}", + f'{round(obj, 3) :.4f}', + f"{round(ub, 3) :.4f}", + f"{max(0, gap) :.3%}", + f"{round(time.perf_counter()-start_time, 2) :.2f}" + ] + if iter == 1: + header = ["Columns", 'Pricing', 'Obj', 'UB', 'gap', 'Time'] + row_format = "".join([ + "{:>"+str(max(len(row_el), len(header_el))+3)+"}" + for row_el, header_el in zip(row, header) + ]) + print(row_format.format(*header)) + + print(row_format.format(*row)) + + # terminate if no column with good reduced cost is found + if obj_sub >= 0 or gap < CG_GAP_REL: + break + + # Adds a new column to the master problem + columns += [new_column] + m.addCol( + 1, # cost + 0, # lower bound + 1, # upper bound + len(new_column), # number of rows + new_column, # indexes of rows + [1]*len(new_column) # coefficients of rows + ) + + # Solves the master problem + m.run() + + return m, columns, list(solution.col_value) + + +# +# Branching helper structures/functions +# +class Node: + layer: int + assigned_columns: list[int] + + value: float + final_columns: list[int] + final_columns_vals: list[float] + final_fractional_columns: list[int] + + def __init__(self, layer: int, assigned_columns: list[int], parent = None): + self.layer = layer + self.parent = parent + self.assigned_columns = assigned_columns + + +def getFractionalColumns(vals: list, columns: list): + return sorted( + [k for k, val in enumerate(vals) if abs(round(val) - val) > 1e-6], + key=lambda k: -len(columns[k]) # order by most fractional (largest number of items) + ) + + +def solveNode(node: Node, columns: list[list[int]], m): + # "enforce" the branch constraints, i.e., selected columns in the current node + lower_bounds = [int(k in node.assigned_columns) for k in range(len(columns))] + + m.changeColsBounds( + len(columns), # Qty columns to change bounds + list(range(len(columns))), # Which columns + lower_bounds, # Lower bound (0 if not assigned else 1) + [1]*len(columns) # Upper bound (always 1) + ) + + # Making sure that there is no repeated index + assert len(node.assigned_columns) == len(set(node.assigned_columns)) + + m.run() + + if m.getModelStatus() == highspy.HighsModelStatus.kOptimal: + m, columns, vals = generateColumns(columns, m, msg=False, solve_exact=False) # Generate columns quickly + m, columns, vals = generateColumns(columns, m, msg=False, solve_exact=True) # Prove optimality on node + + node.value = sum(vals) + node.final_columns = columns + node.final_columns_vals = vals + node.final_fractional_columns = getFractionalColumns(vals, columns) + + # ensure no column already assigned to 1 in the node is returned back with a fractional value + assert len(set(node.final_fractional_columns).intersection(set(node.assigned_columns))) == 0 + return True, m, node + + return False, m, node + + +# +# Branch-and-price algorithm +# +# Note: Branch selection has been chosen for simple implementation and it's ability to avoid symmetry. +# This comes at the cost of an unbalanced search tree (other approaches may solve the problem with fewer branches), +# and it is less likely to be generalizable to other problems. +# +# Specifically, the code "up-branches" columns with fractional value in the RMP solution, i.e., forces specific columns +# to be selected in RMP. The subproblems don't need to explicitly enforce this constraint (unlike other branching strategies). +# +def branchAndPrice(m, vals, columns, start_time=0): + header = ["NodesExpl", "TreeSize", "CurrCols", "FracCols", "UB", "Time"] + row_format ="{:>12}" * (len(header)) + print(row_format.format(*header)) + + root_node = Node(-1, []) + root_node.value = sum(vals) + root_node.final_columns = columns + root_node.final_columns_vals = vals + root_node.final_fractional_columns = getFractionalColumns(vals, columns) + + # create initial branches for each column with fractional value in RMP solution + branch_tree: list[Node] = [Node(0, [column_idx]) for column_idx in root_node.final_fractional_columns] + branch_tree_dict = {frozenset(node.assigned_columns): node for node in branch_tree} + + # RMP lower bound can be rounded up to nearest integer (avoiding floating point precision issues) + # so, take integer floor, add one if fractional part greater than tolerance + rmp_LB = int(root_node.value) + int(root_node.value % 1 > 1e-6) + best_obj = math.inf + best_node = None + best_count_frac_columns = math.inf + count_nodes_visited = 0 + + # if no fractional columns, then root node is an optimal integer solution + if len(root_node.final_fractional_columns) == 0: + best_node = root_node + print(row_format.format(*[0, 0, len(columns), 0, best_node.value, f"{round(time.perf_counter()-start_time, 2) :.2f}" ])) + + # explore branches, looking for optimal integer solution + while len(branch_tree) > 0: + # Choose next node in branch to evaluate. Prioritize nodes that are likely to be + # integer (DFS with fewer fractional columns), and likely to be optimal (larger value). + # + # i.e., maximize branch depth (layer), minimize number of fractional columns of its parent, + # and maximize value of its original fractional columns. + node = min(branch_tree, key=lambda node: ( + -node.layer, + len(node.parent.final_fractional_columns) if node.parent is not None else math.inf, + -node.parent.final_columns_vals[node.assigned_columns[-1]] if node.parent is not None else 0 + )) + branch_tree.remove(node) + + # Solves the node with column generation + mp_is_feasible, m, node = solveNode(node, columns, m) + count_nodes_visited += 1 + + # Only add columns if the master problem is feasible + # Prune nodes that cannot lead to optimal solution + if mp_is_feasible and node.value < best_obj: + columns = node.final_columns + count_fractional_columns = len(node.final_fractional_columns) + + # if no fractional columns, then this is an integer solution + # update the best solution here for console output + if count_fractional_columns == 0 and node.value < best_obj: + best_obj = int(round(node.value, 0)) # avoid float precision issues + best_node = node + + # found a less fractional solution or enough time has elapsed + if count_fractional_columns < best_count_frac_columns or time.perf_counter()-start_time > 5: + best_count_frac_columns = count_fractional_columns + print(row_format.format(*[ + count_nodes_visited, + len(branch_tree), + len(columns), + best_count_frac_columns, + best_obj if best_obj < math.inf else "-", + f"{round(time.perf_counter()-start_time, 2) :.2f}" + ])) + + # add all fractional columns as new branches + if count_fractional_columns > 0: + for column_idx in node.final_fractional_columns: + new_assigned_columns = node.assigned_columns + [column_idx] + key = frozenset(new_assigned_columns) + + if key not in branch_tree_dict: + new_node = Node(node.layer + 1, new_assigned_columns, node) + branch_tree = [new_node] + branch_tree + branch_tree_dict[key] = new_node + + # if no fractional columns, then this is an integer solution + # stop if we've found a provably optimal solution (using lower bound from RMP root node) + elif rmp_LB == best_obj: + break + + # explored the entire tree, so best found solution is optimal + if best_node is not None: + return [column for idx, column in enumerate(best_node.final_columns) if best_node.final_columns_vals[idx] > 0.9] + + raise Exception("It should not get to this point") + + +if __name__ == '__main__': + if max(ItemWeights) > BinCapacity: + print(f"Instance is infeasible: item {max(enumerate(ItemWeights), key=itemgetter(1))[0]} has weight {max(ItemWeights)} > {BinCapacity} (bin capacity).") + exit() + + start_time = time.perf_counter() + greedy_bins = solveGreedyModel() + greedy_time = time.perf_counter()-start_time + print(f"Greedy estimate: {len(greedy_bins)} bins") + print(f"Finished in {greedy_time: .2f}s\n") + + start_time = time.perf_counter() + compact_bins = solveCompactModel() + compact_time = time.perf_counter()-start_time + + print(f"\nSolution by compact model: {len(compact_bins)} bins") + for bin, items in enumerate(compact_bins): + tt_weight = round(sum(ItemWeights[i] for i in items)) + print(f"Bin {bin+1} ({tt_weight} <= {BinCapacity}): {items}") + assert tt_weight <= BinCapacity + + print(f"Finished in {compact_time: .2f}s\n") + + # start with initial set of columns for feasible master problem + start_time = time.perf_counter() + columns = [[i] for i in range(NumberItems)] + greedy_bins + m, vals, duals = createMasterProblem(columns) + + print("\nSolving root node:") + m, columns, vals = generateColumns(columns, m, solve_exact=False, start_time=start_time) + + print("\nProving optimality on root node:") + m, columns, vals = generateColumns(columns, m, solve_exact=True, start_time=start_time) + + print("\nBranch-and-price:") + cg_bins = branchAndPrice(m, vals, columns, start_time) + cg_time = time.perf_counter()-start_time + + print(f"\nSolution by column generation: {len(cg_bins)} bins") + for bin, items in enumerate(cg_bins): + tt_weight = round(sum(ItemWeights[i] for i in items)) + print(f"Bin {bin+1} ({tt_weight} <= {BinCapacity}): {items}") + assert tt_weight <= BinCapacity + + print(f"Finished in {cg_time: .2f}s\n") + + print(f"Greedy : {len(greedy_bins)} bins, {greedy_time:6.2f}s") + print(f"Compact: {len(compact_bins)} bins, {compact_time:6.2f}s") + print(f"ColGen : {len(cg_bins)} bins, {cg_time:6.2f}s\n") + diff --git a/examples/call_highs_from_c.c b/examples/call_highs_from_c.c index 8167c0811c..6b89169f03 100644 --- a/examples/call_highs_from_c.c +++ b/examples/call_highs_from_c.c @@ -63,11 +63,12 @@ void minimal_api() { // bound on x_1, it serves to illustrate a non-trivial packed // column-wise matrix. // - const int num_col = 2; - const int num_row = 3; - const int num_nz = 5; + // The HighsInt type is either int or long, depending on the HiGHS build + const HighsInt num_col = 2; + const HighsInt num_row = 3; + const HighsInt num_nz = 5; // Define the optimization sense and objective offset - int sense = kHighsObjSenseMinimize; + HighsInt sense = kHighsObjSenseMinimize; const double offset = 3; // Define the column costs, lower bounds and upper bounds @@ -78,9 +79,9 @@ void minimal_api() { const double row_lower[3] = {-1.0e30, 5.0, 6.0}; const double row_upper[3] = {7.0, 15.0, 1.0e30}; // Define the constraint matrix column-wise - const int a_format = kHighsMatrixFormatColwise; - const int a_start[2] = {0, 2}; - const int a_index[5] = {1, 2, 0, 1, 2}; + const HighsInt a_format = kHighsMatrixFormatColwise; + const HighsInt a_start[2] = {0, 2}; + const HighsInt a_index[5] = {1, 2, 0, 1, 2}; const double a_value[5] = {1.0, 3.0, 1.0, 2.0, 2.0}; double objective_value; @@ -89,11 +90,11 @@ void minimal_api() { double* row_value = (double*)malloc(sizeof(double) * num_row); double* row_dual = (double*)malloc(sizeof(double) * num_row); - int* col_basis_status = (int*)malloc(sizeof(int) * num_col); - int* row_basis_status = (int*)malloc(sizeof(int) * num_row); + HighsInt* col_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* row_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_row); - int model_status; - int run_status; + HighsInt model_status; + HighsInt run_status; run_status = Highs_lpCall(num_col, num_row, num_nz, a_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, @@ -105,17 +106,17 @@ void minimal_api() { assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %d; Model status = %d\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); objective_value = offset; // Report the column primal and dual values, and basis status - for (int i = 0; i < num_col; i++) { - printf("Col%d = %lf; dual = %lf; status = %d\n", i, col_value[i], col_dual[i], col_basis_status[i]); + for (HighsInt i = 0; i < num_col; i++) { + printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); objective_value += col_value[i]*col_cost[i]; } // Report the row primal and dual values, and basis status - for (int i = 0; i < num_row; i++) { - printf("Row%d = %lf; dual = %lf; status = %d\n", i, row_value[i], row_dual[i], row_basis_status[i]); + for (HighsInt i = 0; i < num_row; i++) { + printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); @@ -131,24 +132,24 @@ void minimal_api() { assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %d; Model status = %d\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); // Compute the objective value objective_value = offset; - for (int i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; // Report the column primal and dual values, and basis status - for (int i = 0; i < num_col; i++) { - printf("Col%d = %lf; dual = %lf; status = %d\n", i, col_value[i], col_dual[i], col_basis_status[i]); + for (HighsInt i = 0; i < num_col; i++) { + printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); } // Report the row primal and dual values, and basis status - for (int i = 0; i < num_row; i++) { - printf("Row%d = %lf; dual = %lf; status = %d\n", i, row_value[i], row_dual[i], row_basis_status[i]); + for (HighsInt i = 0; i < num_row; i++) { + printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); // // Indicate that the optimal solution for both columns must be // integer valued and solve the model as a MIP - int integrality[2] = {1, 1}; + HighsInt integrality[2] = {1, 1}; run_status = Highs_mipCall(num_col, num_row, num_nz, a_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value, @@ -159,18 +160,18 @@ void minimal_api() { assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %d; Model status = %d\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); // Compute the objective value objective_value = offset; - for (int i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; // Report the column primal values - for (int i = 0; i < num_col; i++) { - printf("Col%d = %lf\n", i, col_value[i]); + for (HighsInt i = 0; i < num_col; i++) { + printf("Col%" HIGHSINT_FORMAT " = %lf\n", i, col_value[i]); } // Report the row primal values - for (int i = 0; i < num_row; i++) { - printf("Row%d = %lf\n", i, row_value[i]); + for (HighsInt i = 0; i < num_row; i++) { + printf("Row%" HIGHSINT_FORMAT " = %lf\n", i, row_value[i]); } printf("Optimal objective value = %g\n", objective_value); @@ -189,11 +190,11 @@ void minimal_api_qp() { // // subject to x_1 + x_2 + x_3 >= 1; x>=0 - const int num_col = 3; - const int num_row = 1; - const int num_nz = 3; + const HighsInt num_col = 3; + const HighsInt num_row = 1; + const HighsInt num_nz = 3; // Define the optimization sense and objective offset - int sense = kHighsObjSenseMinimize; + HighsInt sense = kHighsObjSenseMinimize; const double offset = 0; // Define the column costs, lower bounds and upper bounds @@ -204,15 +205,15 @@ void minimal_api_qp() { const double row_lower[1] = {1}; const double row_upper[1] = {1.0e30}; // Define the constraint matrix row-wise - const int a_format = kHighsMatrixFormatRowwise; - const int a_start[2] = {0, 3}; - const int a_index[3] = {0, 1, 2}; + const HighsInt a_format = kHighsMatrixFormatRowwise; + const HighsInt a_start[2] = {0, 3}; + const HighsInt a_index[3] = {0, 1, 2}; const double a_value[3] = {1.0, 1.0, 1.0}; - const int q_format = kHighsHessianFormatTriangular; - const int q_num_nz = 4; - const int q_start[3] = {0, 2, 3}; - const int q_index[4] = {0, 2, 1, 2}; + const HighsInt q_format = kHighsHessianFormatTriangular; + const HighsInt q_num_nz = 4; + const HighsInt q_start[3] = {0, 2, 3}; + const HighsInt q_index[4] = {0, 2, 1, 2}; const double q_value[4] = {2.0, -1.0, 0.2, 2.0}; double objective_value; @@ -221,11 +222,11 @@ void minimal_api_qp() { double* row_value = (double*)malloc(sizeof(double) * num_row); double* row_dual = (double*)malloc(sizeof(double) * num_row); - int* col_basis_status = (int*)malloc(sizeof(int) * num_col); - int* row_basis_status = (int*)malloc(sizeof(int) * num_row); + HighsInt* col_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* row_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_row); - int model_status; - int run_status; + HighsInt model_status; + HighsInt run_status; run_status = Highs_qpCall(num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, @@ -238,34 +239,34 @@ void minimal_api_qp() { assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %d; Model status = %d\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); // Compute the objective value objective_value = offset; - for (int i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; - for (int i = 0; i < num_col; i++) { - int from_el = q_start[i]; - int to_el; + for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) { + HighsInt from_el = q_start[i]; + HighsInt to_el; if (i+1 +#include +#include +#include + +// gcc call_highs_from_c.c -o highstest -I install_folder/include/ -L install_folder/lib/ -lhighs + +void minimal_api() { + // This illustrates the use of Highs_lpCall, the simple C interface to + // HiGHS. It's designed to solve the general LP problem + // + // Min c^Tx + d subject to L <= Ax <= U; l <= x <= u + // + // where A is a matrix with m rows and n columns + // + // The scalar n is num_col + // The scalar m is num_row + // + // The vector c is col_cost + // The scalar d is offset + // The vector l is col_lower + // The vector u is col_upper + // The vector L is row_lower + // The vector U is row_upper + // + // The matrix A is represented in packed vector form, either + // row-wise or column-wise: only its nonzeros are stored + // + // * The number of nonzeros in A is num_nz + // + // * The indices of the nonnzeros in the vectors of A are stored in a_index + // + // * The values of the nonnzeros in the vectors of A are stored in a_value + // + // * The position in a_index/a_value of the index/value of the first + // nonzero in each vector is stored in a_start + // + // Note that a_start[0] must be zero + // + // After a successful call to Highs_lpCall, the primal and dual + // solution, and the simplex basis are returned as follows + // + // The vector x is col_value + // The vector Ax is row_value + // The vector of dual values for the variables x is col_dual + // The vector of dual values for the variables Ax is row_dual + // The basic/nonbasic status of the variables x is col_basis_status + // The basic/nonbasic status of the variables Ax is row_basis_status + // + // The status of the solution obtained is model_status + // + // The use of Highs_lpCall is illustrated 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. + // + // The HighsInt type is either int or long, depending on the HiGHS build + const HighsInt num_col = 2; + const HighsInt num_row = 3; + const HighsInt num_nz = 5; + // Define the optimization sense and objective offset + HighsInt sense = kHighsObjSenseMinimize; + const double offset = 3; + + // Define the column costs, lower bounds and upper bounds + const double col_cost[2] = {1.0, 1.0}; + const double col_lower[2] = {0.0, 1.0}; + const double col_upper[2] = {4.0, 1.0e30}; + // Define the row lower bounds and upper bounds + const double row_lower[3] = {-1.0e30, 5.0, 6.0}; + const double row_upper[3] = {7.0, 15.0, 1.0e30}; + // Define the constraint matrix column-wise + const HighsInt a_format = kHighsMatrixFormatColwise; + const HighsInt a_start[2] = {0, 2}; + const HighsInt a_index[5] = {1, 2, 0, 1, 2}; + const double a_value[5] = {1.0, 3.0, 1.0, 2.0, 2.0}; + + double objective_value; + double* col_value = (double*)malloc(sizeof(double) * num_col); + double* col_dual = (double*)malloc(sizeof(double) * num_col); + double* row_value = (double*)malloc(sizeof(double) * num_row); + double* row_dual = (double*)malloc(sizeof(double) * num_row); + + HighsInt* col_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* row_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_row); + + HighsInt model_status; + HighsInt run_status; + + run_status = Highs_lpCall(num_col, num_row, num_nz, a_format, + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, + &model_status); + // The run must be successful, and the model status optimal + assert(run_status == kHighsStatusOk); + assert(model_status == kHighsModelStatusOptimal); + + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + + objective_value = offset; + // Report the column primal and dual values, and basis status + for (HighsInt i = 0; i < num_col; i++) { + printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); + objective_value += col_value[i]*col_cost[i]; + } + // Report the row primal and dual values, and basis status + for (HighsInt i = 0; i < num_row; i++) { + printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); + } + printf("Optimal objective value = %g\n", objective_value); + + // Switch the sense to maximization and solve the LP again + sense = kHighsObjSenseMaximize; + run_status = Highs_lpCall(num_col, num_row, num_nz, a_format, + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, + &model_status); + // The run must be successful, and the model status optimal + assert(run_status == kHighsStatusOk); + assert(model_status == kHighsModelStatusOptimal); + + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + + // Compute the objective value + objective_value = offset; + for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + // Report the column primal and dual values, and basis status + for (HighsInt i = 0; i < num_col; i++) { + printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); + } + // Report the row primal and dual values, and basis status + for (HighsInt i = 0; i < num_row; i++) { + printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); + } + printf("Optimal objective value = %g\n", objective_value); + // + // Indicate that the optimal solution for both columns must be + // integer valued and solve the model as a MIP + HighsInt integrality[2] = {1, 1}; + run_status = Highs_mipCall(num_col, num_row, num_nz, a_format, + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + integrality, + col_value, row_value, + &model_status); + // The run must be successful, and the model status optimal + assert(run_status == kHighsStatusOk); + assert(model_status == kHighsModelStatusOptimal); + + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + + // Compute the objective value + objective_value = offset; + for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + // Report the column primal values + for (HighsInt i = 0; i < num_col; i++) { + printf("Col%" HIGHSINT_FORMAT " = %lf\n", i, col_value[i]); + } + // Report the row primal values + for (HighsInt i = 0; i < num_row; i++) { + printf("Row%" HIGHSINT_FORMAT " = %lf\n", i, row_value[i]); + } + printf("Optimal objective value = %g\n", objective_value); + + free(col_value); + free(col_dual); + free(row_value); + free(row_dual); + free(col_basis_status); + free(row_basis_status); +} + +void minimal_api_qp() { + // Illustrate the solution of a QP + // + // minimize -x_2 + (1/2)(2x_1^2 - 2x_1x_3 + 0.2x_2^2 + 2x_3^2) + // + // subject to x_1 + x_2 + x_3 >= 1; x>=0 + + const HighsInt num_col = 3; + const HighsInt num_row = 1; + const HighsInt num_nz = 3; + // Define the optimization sense and objective offset + HighsInt sense = kHighsObjSenseMinimize; + const double offset = 0; + + // Define the column costs, lower bounds and upper bounds + const double col_cost[3] = {0.0, -1.0, 0.0}; + const double col_lower[3] = {0.0, 0.0, 0.0}; + const double col_upper[3] = {1.0e30, 1.0e30, 1.0e30}; + // Define the row lower bounds and upper bounds + const double row_lower[1] = {1}; + const double row_upper[1] = {1.0e30}; + // Define the constraint matrix row-wise + const HighsInt a_format = kHighsMatrixFormatRowwise; + const HighsInt a_start[2] = {0, 3}; + const HighsInt a_index[3] = {0, 1, 2}; + const double a_value[3] = {1.0, 1.0, 1.0}; + + const HighsInt q_format = kHighsHessianFormatTriangular; + const HighsInt q_num_nz = 4; + const HighsInt q_start[3] = {0, 2, 3}; + const HighsInt q_index[4] = {0, 2, 1, 2}; + const double q_value[4] = {2.0, -1.0, 0.2, 2.0}; + + double objective_value; + double* col_value = (double*)malloc(sizeof(double) * num_col); + double* col_dual = (double*)malloc(sizeof(double) * num_col); + double* row_value = (double*)malloc(sizeof(double) * num_row); + double* row_dual = (double*)malloc(sizeof(double) * num_row); + + HighsInt* col_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_col); + HighsInt* row_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_row); + + HighsInt model_status; + HighsInt run_status; + + run_status = Highs_qpCall(num_col, num_row, num_nz, q_num_nz, a_format, q_format, + sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, + q_start, q_index, q_value, + col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, + &model_status); + // The run must be successful, and the model status optimal + assert(run_status == kHighsStatusOk); + assert(model_status == kHighsModelStatusOptimal); + + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + + // Compute the objective value + objective_value = offset; + for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) { + HighsInt from_el = q_start[i]; + HighsInt to_el; + if (i+1= data_out.objective_function_value + ), "Objective function value is invalid!" + user_callback_data[0] = data_out.objective_function_value + + else: + # Various other callback types + if callback_type == hscb.HighsCallbackType.kCallbackLogging: + if dev_run: + print(f"userInterruptCallback(type {callback_type}): {message}") + + elif callback_type == hscb.HighsCallbackType.kCallbackSimplexInterrupt: + if dev_run: + print(f"userInterruptCallback(type {callback_type}): {message}") + print("with iteration count = ", + data_out.simplex_iteration_count) + + data_in.user_interrupt = ( + data_out.simplex_iteration_count > SIMPLEX_ITERATION_LIMIT + ) + + elif callback_type == hscb.HighsCallbackType.kCallbackIpmInterrupt: + if dev_run: + print(f"userInterruptCallback(type {callback_type}): {message}") + print(f"with iteration count = {data_out.ipm_iteration_count}") + + data_in.user_interrupt = ( + data_out.ipm_iteration_count > IPM_ITERATION_LIMIT + ) + + elif callback_type == hscb.HighsCallbackType.kCallbackMipInterrupt: + if dev_run: + print(f"userInterruptCallback(type {callback_type}): {message}") + print(f"Dual bound = {data_out.mip_dual_bound:.4g}") + print(f"Primal bound = {data_out.mip_primal_bound:.4g}") + print(f"Gap = {data_out.mip_gap:.4g}") + print(f"Objective = {data_out.objective_function_value:.4g}") + + data_in.user_interrupt = ( + data_out.objective_function_value < EGOUT_OBJECTIVE_TARGET + ) + + +# Define model +h.addVar(-inf, inf) +h.addVar(-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) + + +# Set callback and run +h.setCallback(user_interrupt_callback, None) +h.startCallback(hscb.HighsCallbackType.kCallbackLogging) + +h.run() +h.stopCallback(hscb.HighsCallbackType.kCallbackLogging) + +# Get solution +num_var = h.getNumCol() +solution = h.getSolution() +basis = h.getBasis() +info = h.getInfo() +model_status = h.getModelStatus() +print("Model status = ", h.modelStatusToString(model_status)) +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)) +print("Variables:") +for icol in range(0, 5): + print(icol, solution.col_value[icol], + h.basisStatusToString(basis.col_status[icol])) +print("...") +for icol in range(num_var-2, num_var): + print(icol, solution.col_value[icol], + h.basisStatusToString(basis.col_status[icol])) diff --git a/examples/call_highs_from_python_highspy.py b/examples/call_highs_from_python_highspy.py new file mode 100644 index 0000000000..1cf6302a4a --- /dev/null +++ b/examples/call_highs_from_python_highspy.py @@ -0,0 +1,52 @@ + +import highspy +import numpy as np + +# Highs h +h = highspy.Highs() + +# Set up problem +inf = highspy.kHighsInf +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) + +# Access LP +lp = h.getLp() +num_nz = h.getNumNz() +print('LP has ', lp.num_col_, ' columns', + lp.num_row_, ' rows and ', num_nz, ' nonzeros') + +print('Solving...') +# Disable output from HiGHS for very small LP +h.setOptionValue("log_to_console", False) + +# Solve problem +h.run() + +print('Problem solved.') +print() + +# Print solution information +# solution = h.getSolution() +# basis = h.getBasis() +info = h.getInfo() +model_status = h.getModelStatus() + +print('Model status = ', h.modelStatusToString(model_status)) +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)) + +h.clear() diff --git a/examples/call_highs_from_python_mps.py b/examples/call_highs_from_python_mps.py new file mode 100644 index 0000000000..69a74549b2 --- /dev/null +++ b/examples/call_highs_from_python_mps.py @@ -0,0 +1,39 @@ + +# The path to the MPS file instance assumes that this is run +# from the root directory. + +import highspy +import numpy as np + +# Highs h +h = highspy.Highs() + +# Solve from mps file +# Initialize an instance of Highs +# h = highspy.Highs() +# Here we are re-using the one from above. +h.readModel('check/instances/25fv47.mps') + +# Print +lp = h.getLp() +num_nz = h.getNumNz() +print('LP has ', lp.num_col_, ' columns', + lp.num_row_, ' rows and ', num_nz, ' nonzeros') + +# Solve the problem +h.run() + +# Print solution information +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)) diff --git a/examples/chip.py b/examples/chip.py new file mode 100644 index 0000000000..dae469cd7d --- /dev/null +++ b/examples/chip.py @@ -0,0 +1,53 @@ +# model and solve the LP +# +# maximize 10 Tables + 25 SetsOfChairs +# s. t. Tables + 2 SetsOfChairs <= 80: Assembly +# Tables + 4 SetsOfChairs <= 120: Finishing +# Tables >= 0; SetsOfChairs >= 0 +import highspy + +h = highspy.Highs() +h.silent() + +varNames = list() +varNames.append('Tables') +varNames.append('Sets of chairs') + +x1 = h.addVariable(obj = 10, name = varNames[0]) +x2 = h.addVariable(obj = 25, name = varNames[1]) + +vars = list() +vars.append(x1) +vars.append(x2) + +constrNames = list() +constrNames.append('Assembly') +constrNames.append('Finishing') + +h.addConstr(x1 + 2*x2 <= 80, name = constrNames[0]) +h.addConstr(x1 + 4*x2 <= 120, name = constrNames[1]) + +h.setMaximize() + +status = h.writeModel('Chip.lp') +print('writeModel(\'Chip.lp\') status =', status) +status = h.writeModel('Chip.mps') +print('writeModel(\'Chip.mps\') status =', status) + +h.solve() + +for var in vars: + print('Make', h.variableValue(var), h.variableName(var), ': Reduced cost', h.variableDual(var)) +print('Make', h.variableValues(vars), 'of', h.variableNames(vars)) +print('Make', h.allVariableValues(), 'of', h.allVariableNames()) + +for name in constrNames: + print('Constraint', name, 'has value', h.constrValue(name), 'and dual', h.constrDual(name)) + +print('Constraints have values', h.constrValues(constrNames), 'and duals', h.constrDuals(constrNames)) + +print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals()) + +print('Optimal objective value is', h.getObjectiveValue()) + + diff --git a/examples/distillation.py b/examples/distillation.py new file mode 100644 index 0000000000..5e375ab496 --- /dev/null +++ b/examples/distillation.py @@ -0,0 +1,92 @@ +# model and solve the problem, first as an LP, then as a MIP +# +# minimize 8 TypeA + 10 TypeB +# s. t. 2 TypeA + 2 TypeB >= 7: Product1 +# 3 TypeA + 4 TypeB >= 12: Product2 +# 2 TypeA + 1 TypeB >= 6: Product3 +# TypeA >= 0; TypeB >= 0 + +import highspy + +# For printing +width = 4 +precision = 3 + +h = highspy.Highs() +h.silent() + +variableNames = list() +variableNames.append('TypeA') +variableNames.append('TypeB') + +useTypeA = h.addVariable(obj = 8, name = variableNames[0]) +useTypeB = h.addVariable(obj = 10, name = variableNames[1]) + +vars = list() +vars.append(useTypeA) +vars.append(useTypeB) + +constrNames = list() +constrNames.append('Product1') +constrNames.append('Product2') +constrNames.append('Product3') + +h.addConstr(2*useTypeA + 2*useTypeB >= 7, name = constrNames[0]) +h.addConstr(3*useTypeA + 4*useTypeB >= 12, name = constrNames[1]) +h.addConstr(2*useTypeA + useTypeB >= 6, name = constrNames[2]) + +h.setMinimize() + +status = h.writeModel('Distillation.lp') +print('writeModel(\'Distillation.lp\') status =', status) +status = h.writeModel('Distillation.mps') +print('writeModel(\'Distillation.mps\') status =', status) + +print() +print('Solve as LP') + +h.solve() + +for var in vars: + print('Use {0:.1f} of {1:s}: reduced cost {2:.6f}'.format(h.variableValue(var), h.variableName(var), h.variableDual(var))) +print('Use', h.variableValues(vars), 'of', h.variableNames(vars)) +print('Use', h.allVariableValues(), 'of', h.allVariableNames()) + +for name in constrNames: + print(f"Constraint {name} has value {h.constrValue(name):{width}.{precision}} and dual {h.constrDual(name):{width}.{precision}}") + +print('Constraints have values', h.constrValues(constrNames), 'and duals', h.constrDuals(constrNames)) + +print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals()) + +for var in vars: + print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}") +print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}") + +print() +print('Solve as MIP') + +for var in vars: + h.setInteger(var) + +h.solve() + +for var in vars: + print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}") +print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}") + +print() +print('Solve as LP with Gomory cut') + +# Make the variables continuous +for var in vars: + h.setContinuous(var) + +# Add Gomory cut +h.addConstr(useTypeA + useTypeB >= 4, name = "Gomory") + +h.solve() + +for var in vars: + print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}") +print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}") diff --git a/examples/minimal.py b/examples/minimal.py index b2a5935fbf..943e231105 100644 --- a/examples/minimal.py +++ b/examples/minimal.py @@ -2,8 +2,8 @@ h = highspy.Highs() -x1 = h.addVar(lb=-h.inf) -x2 = h.addVar(lb=-h.inf) +x1 = h.addVariable(lb = -h.inf) +x2 = h.addVariable(lb = -h.inf) h.addConstr(x2 - x1 >= 2) h.addConstr(x1 + x2 >= 0) diff --git a/extern/doctest.h b/extern/doctest.h deleted file mode 100644 index 54cbd5aa5b..0000000000 --- a/extern/doctest.h +++ /dev/null @@ -1,6205 +0,0 @@ -// ====================================================================== lgtm [cpp/missing-header-guard] -// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == -// ====================================================================== -// -// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD -// -// Copyright (c) 2016-2019 Viktor Kirilov -// -// Distributed under the MIT Software License -// See accompanying file LICENSE.txt or copy at -// https://opensource.org/licenses/MIT -// -// The documentation can be found at the library's page: -// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md -// -// ================================================================================================= -// ================================================================================================= -// ================================================================================================= -// -// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 -// which uses the Boost Software License - Version 1.0 -// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt -// -// The concept of subcases (sections in Catch) and expression decomposition are from there. -// Some parts of the code are taken directly: -// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> -// - the Approx() helper class for floating point comparison -// - colors in the console -// - breaking into a debugger -// - signal / SEH handling -// - timer -// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) -// -// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest -// which uses the Boost Software License - Version 1.0 -// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt -// -// ================================================================================================= -// ================================================================================================= -// ================================================================================================= - -#ifndef DOCTEST_LIBRARY_INCLUDED -#define DOCTEST_LIBRARY_INCLUDED - -// ================================================================================================= -// == VERSION ====================================================================================== -// ================================================================================================= - -#define DOCTEST_VERSION_MAJOR 2 -#define DOCTEST_VERSION_MINOR 4 -#define DOCTEST_VERSION_PATCH 0 -#define DOCTEST_VERSION_STR "2.4.0" - -#define DOCTEST_VERSION \ - (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) - -// ================================================================================================= -// == COMPILER VERSION ============================================================================= -// ================================================================================================= - -// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect - -#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) - -// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... -#if defined(_MSC_VER) && defined(_MSC_FULL_VER) -#if _MSC_VER == _MSC_FULL_VER / 10000 -#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) -#else // MSVC -#define DOCTEST_MSVC \ - DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) -#endif // MSVC -#endif // MSVC -#if defined(__clang__) && defined(__clang_minor__) -#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) -#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ - !defined(__INTEL_COMPILER) -#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#endif // GCC - -#ifndef DOCTEST_MSVC -#define DOCTEST_MSVC 0 -#endif // DOCTEST_MSVC -#ifndef DOCTEST_CLANG -#define DOCTEST_CLANG 0 -#endif // DOCTEST_CLANG -#ifndef DOCTEST_GCC -#define DOCTEST_GCC 0 -#endif // DOCTEST_GCC - -// ================================================================================================= -// == COMPILER WARNINGS HELPERS ==================================================================== -// ================================================================================================= - -#if DOCTEST_CLANG -#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) -#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") -#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) -#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") -#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ - DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) -#else // DOCTEST_CLANG -#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -#define DOCTEST_CLANG_SUPPRESS_WARNING(w) -#define DOCTEST_CLANG_SUPPRESS_WARNING_POP -#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) -#endif // DOCTEST_CLANG - -#if DOCTEST_GCC -#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) -#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") -#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) -#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") -#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ - DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) -#else // DOCTEST_GCC -#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH -#define DOCTEST_GCC_SUPPRESS_WARNING(w) -#define DOCTEST_GCC_SUPPRESS_WARNING_POP -#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) -#endif // DOCTEST_GCC - -#if DOCTEST_MSVC -#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) -#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) -#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) -#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) -#else // DOCTEST_MSVC -#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -#define DOCTEST_MSVC_SUPPRESS_WARNING(w) -#define DOCTEST_MSVC_SUPPRESS_WARNING_POP -#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) -#endif // DOCTEST_MSVC - -// ================================================================================================= -// == COMPILER WARNINGS ============================================================================ -// ================================================================================================= - -DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") - -DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") -DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") -DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") - -DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... -DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' - -// 4548 - expression before comma has no effect; expected expression with side - effect -// 4265 - class has virtual functions, but destructor is not virtual -// 4986 - exception specification does not match previous declaration -// 4350 - behavior change: 'member1' called instead of 'member2' -// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' -// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch -// 4774 - format string expected in argument 'x' is not a string literal -// 4820 - padding in structs - -// only 4 should be disabled globally: -// - 4514 # unreferenced inline function has been removed -// - 4571 # SEH related -// - 4710 # function not inlined -// - 4711 # function 'x' selected for automatic inline expansion - -#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ - DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5105) - -#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP - -// ================================================================================================= -// == FEATURE DETECTION ============================================================================ -// ================================================================================================= - -// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support -// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx -// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html -// MSVC version table: -// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering -// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) -// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) -// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) -// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) -// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) -// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) -// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) -// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) - -#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) -#define DOCTEST_CONFIG_WINDOWS_SEH -#endif // MSVC -#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) -#undef DOCTEST_CONFIG_WINDOWS_SEH -#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH - -#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ - !defined(__EMSCRIPTEN__) -#define DOCTEST_CONFIG_POSIX_SIGNALS -#endif // _WIN32 -#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) -#undef DOCTEST_CONFIG_POSIX_SIGNALS -#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) -#define DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // no exceptions -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#define DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) -#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS - -#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) -#define DOCTEST_CONFIG_IMPLEMENT -#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -#if defined(_WIN32) || defined(__CYGWIN__) -#if DOCTEST_MSVC -#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) -#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) -#else // MSVC -#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) -#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) -#endif // MSVC -#else // _WIN32 -#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) -#define DOCTEST_SYMBOL_IMPORT -#endif // _WIN32 - -#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL -#ifdef DOCTEST_CONFIG_IMPLEMENT -#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT -#else // DOCTEST_CONFIG_IMPLEMENT -#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT -#endif // DOCTEST_CONFIG_IMPLEMENT -#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL -#define DOCTEST_INTERFACE -#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL - -#define DOCTEST_EMPTY - -#if DOCTEST_MSVC -#define DOCTEST_NOINLINE __declspec(noinline) -#define DOCTEST_UNUSED -#define DOCTEST_ALIGNMENT(x) -#else // MSVC -#define DOCTEST_NOINLINE __attribute__((noinline)) -#define DOCTEST_UNUSED __attribute__((unused)) -#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) -#endif // MSVC - -#ifndef DOCTEST_NORETURN -#define DOCTEST_NORETURN [[noreturn]] -#endif // DOCTEST_NORETURN - -#ifndef DOCTEST_NOEXCEPT -#define DOCTEST_NOEXCEPT noexcept -#endif // DOCTEST_NOEXCEPT - -// ================================================================================================= -// == FEATURE DETECTION END ======================================================================== -// ================================================================================================= - -// internal macros for string concatenation and anonymous variable name generation -#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 -#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) -#ifdef __COUNTER__ // not standard and may be missing for some compilers -#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) -#else // __COUNTER__ -#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) -#endif // __COUNTER__ - -#define DOCTEST_TOSTR(x) #x - -#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE -#define DOCTEST_REF_WRAP(x) x& -#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE -#define DOCTEST_REF_WRAP(x) x -#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE - -// not using __APPLE__ because... this is how Catch does it -#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED -#define DOCTEST_PLATFORM_MAC -#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#define DOCTEST_PLATFORM_IPHONE -#elif defined(_WIN32) -#define DOCTEST_PLATFORM_WINDOWS -#else // DOCTEST_PLATFORM -#define DOCTEST_PLATFORM_LINUX -#endif // DOCTEST_PLATFORM - -#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ - static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) -#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP - -#ifndef DOCTEST_BREAK_INTO_DEBUGGER -// should probably take a look at https://github.com/scottt/debugbreak -#ifdef DOCTEST_PLATFORM_MAC -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) -#elif DOCTEST_MSVC -#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() -#elif defined(__MINGW32__) -DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") -extern "C" __declspec(dllimport) void __stdcall DebugBreak(); -DOCTEST_GCC_SUPPRESS_WARNING_POP -#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() -#else // linux -#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) -#endif // linux -#endif // DOCTEST_BREAK_INTO_DEBUGGER - -// this is kept here for backwards compatibility since the config option was changed -#ifdef DOCTEST_CONFIG_USE_IOSFWD -#define DOCTEST_CONFIG_USE_STD_HEADERS -#endif // DOCTEST_CONFIG_USE_IOSFWD - -#ifdef DOCTEST_CONFIG_USE_STD_HEADERS -#include -#include -#include -#else // DOCTEST_CONFIG_USE_STD_HEADERS - -#if DOCTEST_CLANG -// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) -#include -#endif // clang - -#ifdef _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD -#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD -#else // _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { -#define DOCTEST_STD_NAMESPACE_END } -#endif // _LIBCPP_VERSION - -// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) - -DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) -typedef decltype(nullptr) nullptr_t; -template -struct char_traits; -template <> -struct char_traits; -template -class basic_ostream; -typedef basic_ostream> ostream; -template -class tuple; -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -template -class allocator; -template -class basic_string; -using string = basic_string, allocator>; -#endif // VS 2019 -DOCTEST_STD_NAMESPACE_END - -DOCTEST_MSVC_SUPPRESS_WARNING_POP - -#endif // DOCTEST_CONFIG_USE_STD_HEADERS - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#include -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - -namespace doctest { - -DOCTEST_INTERFACE extern bool is_running_in_test; - -// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length -// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: -// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) -// - if small - capacity left before going on the heap - using the lowest 5 bits -// - if small - 2 bits are left unused - the second and third highest ones -// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) -// and the "is small" bit remains "0" ("as well as the capacity left") so its OK -// Idea taken from this lecture about the string implementation of facebook/folly - fbstring -// https://www.youtube.com/watch?v=kPR8h4-qZdk -// TODO: -// - optimizations - like not deleting memory unnecessarily in operator= and etc. -// - resize/reserve/clear -// - substr -// - replace -// - back/front -// - iterator stuff -// - find & friends -// - push_back/pop_back -// - assign/insert/erase -// - relational operators as free functions - taking const char* as one of the params -class DOCTEST_INTERFACE String -{ - static const unsigned len = 24; //!OCLINT avoid private static members - static const unsigned last = len - 1; //!OCLINT avoid private static members - - struct view // len should be more than sizeof(view) - because of the final byte for flags - { - char* ptr; - unsigned size; - unsigned capacity; - }; - - union - { - char buf[len]; - view data; - }; - - bool isOnStack() const { return (buf[last] & 128) == 0; } - void setOnHeap(); - void setLast(unsigned in = last); - - void copy(const String& other); - -public: - String(); - ~String(); - - // cppcheck-suppress noExplicitConstructor - String(const char* in); - String(const char* in, unsigned in_size); - - String(const String& other); - String& operator=(const String& other); - - String& operator+=(const String& other); - String operator+(const String& other) const; - - String(String&& other); - String& operator=(String&& other); - - char operator[](unsigned i) const; - char& operator[](unsigned i); - - // the only functions I'm willing to leave in the interface - available for inlining - const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT - char* c_str() { - if(isOnStack()) - return reinterpret_cast(buf); - return data.ptr; - } - - unsigned size() const; - unsigned capacity() const; - - int compare(const char* other, bool no_case = false) const; - int compare(const String& other, bool no_case = false) const; -}; - -DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); -DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); - -DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); - -namespace Color { - enum Enum - { - None = 0, - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White - }; - - DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); -} // namespace Color - -namespace assertType { - enum Enum - { - // macro traits - - is_warn = 1, - is_check = 2 * is_warn, - is_require = 2 * is_check, - - is_normal = 2 * is_require, - is_throws = 2 * is_normal, - is_throws_as = 2 * is_throws, - is_throws_with = 2 * is_throws_as, - is_nothrow = 2 * is_throws_with, - - is_false = 2 * is_nothrow, - is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types - - is_eq = 2 * is_unary, - is_ne = 2 * is_eq, - - is_lt = 2 * is_ne, - is_gt = 2 * is_lt, - - is_ge = 2 * is_gt, - is_le = 2 * is_ge, - - // macro types - - DT_WARN = is_normal | is_warn, - DT_CHECK = is_normal | is_check, - DT_REQUIRE = is_normal | is_require, - - DT_WARN_FALSE = is_normal | is_false | is_warn, - DT_CHECK_FALSE = is_normal | is_false | is_check, - DT_REQUIRE_FALSE = is_normal | is_false | is_require, - - DT_WARN_THROWS = is_throws | is_warn, - DT_CHECK_THROWS = is_throws | is_check, - DT_REQUIRE_THROWS = is_throws | is_require, - - DT_WARN_THROWS_AS = is_throws_as | is_warn, - DT_CHECK_THROWS_AS = is_throws_as | is_check, - DT_REQUIRE_THROWS_AS = is_throws_as | is_require, - - DT_WARN_THROWS_WITH = is_throws_with | is_warn, - DT_CHECK_THROWS_WITH = is_throws_with | is_check, - DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, - - DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, - DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, - DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, - - DT_WARN_NOTHROW = is_nothrow | is_warn, - DT_CHECK_NOTHROW = is_nothrow | is_check, - DT_REQUIRE_NOTHROW = is_nothrow | is_require, - - DT_WARN_EQ = is_normal | is_eq | is_warn, - DT_CHECK_EQ = is_normal | is_eq | is_check, - DT_REQUIRE_EQ = is_normal | is_eq | is_require, - - DT_WARN_NE = is_normal | is_ne | is_warn, - DT_CHECK_NE = is_normal | is_ne | is_check, - DT_REQUIRE_NE = is_normal | is_ne | is_require, - - DT_WARN_GT = is_normal | is_gt | is_warn, - DT_CHECK_GT = is_normal | is_gt | is_check, - DT_REQUIRE_GT = is_normal | is_gt | is_require, - - DT_WARN_LT = is_normal | is_lt | is_warn, - DT_CHECK_LT = is_normal | is_lt | is_check, - DT_REQUIRE_LT = is_normal | is_lt | is_require, - - DT_WARN_GE = is_normal | is_ge | is_warn, - DT_CHECK_GE = is_normal | is_ge | is_check, - DT_REQUIRE_GE = is_normal | is_ge | is_require, - - DT_WARN_LE = is_normal | is_le | is_warn, - DT_CHECK_LE = is_normal | is_le | is_check, - DT_REQUIRE_LE = is_normal | is_le | is_require, - - DT_WARN_UNARY = is_normal | is_unary | is_warn, - DT_CHECK_UNARY = is_normal | is_unary | is_check, - DT_REQUIRE_UNARY = is_normal | is_unary | is_require, - - DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, - DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, - DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, - }; -} // namespace assertType - -DOCTEST_INTERFACE const char* assertString(assertType::Enum at); -DOCTEST_INTERFACE const char* failureString(assertType::Enum at); -DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); - -struct DOCTEST_INTERFACE TestCaseData -{ - String m_file; // the file in which the test was registered - unsigned m_line; // the line where the test was registered - const char* m_name; // name of the test case - const char* m_test_suite; // the test suite in which the test was added - const char* m_description; - bool m_skip; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; -}; - -struct DOCTEST_INTERFACE AssertData -{ - // common - for all asserts - const TestCaseData* m_test_case; - assertType::Enum m_at; - const char* m_file; - int m_line; - const char* m_expr; - bool m_failed; - - // exception-related - for all asserts - bool m_threw; - String m_exception; - - // for normal asserts - String m_decomp; - - // for specific exception-related asserts - bool m_threw_as; - const char* m_exception_type; - const char* m_exception_string; -}; - -struct DOCTEST_INTERFACE MessageData -{ - String m_string; - const char* m_file; - int m_line; - assertType::Enum m_severity; -}; - -struct DOCTEST_INTERFACE SubcaseSignature -{ - String m_name; - const char* m_file; - int m_line; - - bool operator<(const SubcaseSignature& other) const; -}; - -struct DOCTEST_INTERFACE IContextScope -{ - IContextScope(); - virtual ~IContextScope(); - virtual void stringify(std::ostream*) const = 0; -}; - -struct ContextOptions //!OCLINT too many fields -{ - std::ostream* cout; // stdout stream - std::cout by default - std::ostream* cerr; // stderr stream - std::cerr by default - String binary_name; // the test binary name - - // == parameters from the command line - String out; // output filename - String order_by; // how tests should be ordered - unsigned rand_seed; // the seed for rand ordering - - unsigned first; // the first (matching) test to be executed - unsigned last; // the last (matching) test to be executed - - int abort_after; // stop tests after this many failed assertions - int subcase_filter_levels; // apply the subcase filters for the first N levels - - bool success; // include successful assertions in output - bool case_sensitive; // if filtering should be case sensitive - bool exit; // if the program should be exited after the tests are ran/whatever - bool duration; // print the time duration of each test case - bool no_throw; // to skip exceptions-related assertion macros - bool no_exitcode; // if the framework should return 0 as the exitcode - bool no_run; // to not run the tests at all (can be done with an "*" exclude) - bool no_version; // to not print the version of the framework - bool no_colors; // if output to the console should be colorized - bool force_colors; // forces the use of colors even when a tty cannot be detected - bool no_breaks; // to not break into the debugger - bool no_skip; // don't skip test cases which are marked to be skipped - bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): - bool no_path_in_filenames; // if the path to files should be removed from the output - bool no_line_numbers; // if source code line numbers should be omitted from the output - bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! - bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! - - bool help; // to print the help - bool version; // to print the version - bool count; // if only the count of matching tests is to be retrieved - bool list_test_cases; // to list all tests matching the filters - bool list_test_suites; // to list all suites matching the filters - bool list_reporters; // lists all registered reporters -}; - -namespace detail { -#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) - template - struct enable_if - {}; - - template - struct enable_if - { typedef TYPE type; }; -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - // clang-format off - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - - template struct remove_const { typedef T type; }; - template struct remove_const { typedef T type; }; - // clang-format on - - template - struct deferred_false - // cppcheck-suppress unusedStructMember - { static const bool value = false; }; - - namespace has_insertion_operator_impl { - std::ostream &os(); - template - DOCTEST_REF_WRAP(T) val(); - - template - struct check { - static constexpr auto value = false; - }; - - template - struct check(), void())> { - static constexpr auto value = true; - }; - } // namespace has_insertion_operator_impl - - template - using has_insertion_operator = has_insertion_operator_impl::check; - - DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); - - DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream - DOCTEST_INTERFACE String getTlsOssResult(); - - template - struct StringMakerBase - { - template - static String convert(const DOCTEST_REF_WRAP(T)) { - return "{?}"; - } - }; - - template <> - struct StringMakerBase - { - template - static String convert(const DOCTEST_REF_WRAP(T) in) { - *getTlsOss() << in; - return getTlsOssResult(); - } - }; - - DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); - - template - String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { - return rawMemoryToString(&object, sizeof(object)); - } - - template - const char* type_to_string() { - return "<>"; - } -} // namespace detail - -template -struct StringMaker : public detail::StringMakerBase::value> -{}; - -template -struct StringMaker -{ - template - static String convert(U* p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; - -template -struct StringMaker -{ - static String convert(R C::*p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; - -template -String toString(const DOCTEST_REF_WRAP(T) value) { - return StringMaker::convert(value); -} - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(char* in); -DOCTEST_INTERFACE String toString(const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(bool in); -DOCTEST_INTERFACE String toString(float in); -DOCTEST_INTERFACE String toString(double in); -DOCTEST_INTERFACE String toString(double long in); - -DOCTEST_INTERFACE String toString(char in); -DOCTEST_INTERFACE String toString(char signed in); -DOCTEST_INTERFACE String toString(char unsigned in); -DOCTEST_INTERFACE String toString(int short in); -DOCTEST_INTERFACE String toString(int short unsigned in); -DOCTEST_INTERFACE String toString(int in); -DOCTEST_INTERFACE String toString(int unsigned in); -DOCTEST_INTERFACE String toString(int long in); -DOCTEST_INTERFACE String toString(int long unsigned in); -DOCTEST_INTERFACE String toString(int long long in); -DOCTEST_INTERFACE String toString(int long long unsigned in); -DOCTEST_INTERFACE String toString(std::nullptr_t in); - -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -DOCTEST_INTERFACE String toString(const std::string& in); -#endif // VS 2019 - -class DOCTEST_INTERFACE Approx -{ -public: - explicit Approx(double value); - - Approx operator()(double value) const; - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template - explicit Approx(const T& value, - typename detail::enable_if::value>::type* = - static_cast(nullptr)) { - *this = Approx(static_cast(value)); - } -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - Approx& epsilon(double newEpsilon); - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template - typename detail::enable_if::value, Approx&>::type epsilon( - const T& newEpsilon) { - m_epsilon = static_cast(newEpsilon); - return *this; - } -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - Approx& scale(double newScale); - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - template - typename detail::enable_if::value, Approx&>::type scale( - const T& newScale) { - m_scale = static_cast(newScale); - return *this; - } -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - // clang-format off - DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); - DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); - DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); - - DOCTEST_INTERFACE friend String toString(const Approx& in); - -#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS -#define DOCTEST_APPROX_PREFIX \ - template friend typename detail::enable_if::value, bool>::type - - DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } - DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } - DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } -#undef DOCTEST_APPROX_PREFIX -#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS - - // clang-format on - -private: - double m_epsilon; - double m_scale; - double m_value; -}; - -DOCTEST_INTERFACE String toString(const Approx& in); - -DOCTEST_INTERFACE const ContextOptions* getContextOptions(); - -#if !defined(DOCTEST_CONFIG_DISABLE) - -namespace detail { - // clang-format off -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - template struct decay_array { typedef T type; }; - template struct decay_array { typedef T* type; }; - template struct decay_array { typedef T* type; }; - - template struct not_char_pointer { enum { value = 1 }; }; - template<> struct not_char_pointer { enum { value = 0 }; }; - template<> struct not_char_pointer { enum { value = 0 }; }; - - template struct can_use_op : public not_char_pointer::type> {}; -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - // clang-format on - - struct DOCTEST_INTERFACE TestFailureException - { - }; - - DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - DOCTEST_NORETURN -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - DOCTEST_INTERFACE void throwException(); - - struct DOCTEST_INTERFACE Subcase - { - SubcaseSignature m_signature; - bool m_entered = false; - - Subcase(const String& name, const char* file, int line); - ~Subcase(); - - operator bool() const; - }; - - template - String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, - const DOCTEST_REF_WRAP(R) rhs) { - return toString(lhs) + op + toString(rhs); - } - -#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ - template \ - DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ - bool res = op_macro(lhs, rhs); \ - if(m_at & assertType::is_false) \ - res = !res; \ - if(!res || doctest::getContextOptions()->success) \ - return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ - return Result(res); \ - } - - // more checks could be added - like in Catch: - // https://github.com/catchorg/Catch2/pull/1480/files - // https://github.com/catchorg/Catch2/pull/1481/files -#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ - template \ - rt& operator op(const R&) { \ - static_assert(deferred_false::value, \ - "Expression Too Complex Please Rewrite As Binary Comparison!"); \ - return *this; \ - } - - struct DOCTEST_INTERFACE Result - { - bool m_passed; - String m_decomp; - - Result(bool passed, const String& decomposition = String()); - - // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence - DOCTEST_FORBIT_EXPRESSION(Result, &) - DOCTEST_FORBIT_EXPRESSION(Result, ^) - DOCTEST_FORBIT_EXPRESSION(Result, |) - DOCTEST_FORBIT_EXPRESSION(Result, &&) - DOCTEST_FORBIT_EXPRESSION(Result, ||) - DOCTEST_FORBIT_EXPRESSION(Result, ==) - DOCTEST_FORBIT_EXPRESSION(Result, !=) - DOCTEST_FORBIT_EXPRESSION(Result, <) - DOCTEST_FORBIT_EXPRESSION(Result, >) - DOCTEST_FORBIT_EXPRESSION(Result, <=) - DOCTEST_FORBIT_EXPRESSION(Result, >=) - DOCTEST_FORBIT_EXPRESSION(Result, =) - DOCTEST_FORBIT_EXPRESSION(Result, +=) - DOCTEST_FORBIT_EXPRESSION(Result, -=) - DOCTEST_FORBIT_EXPRESSION(Result, *=) - DOCTEST_FORBIT_EXPRESSION(Result, /=) - DOCTEST_FORBIT_EXPRESSION(Result, %=) - DOCTEST_FORBIT_EXPRESSION(Result, <<=) - DOCTEST_FORBIT_EXPRESSION(Result, >>=) - DOCTEST_FORBIT_EXPRESSION(Result, &=) - DOCTEST_FORBIT_EXPRESSION(Result, ^=) - DOCTEST_FORBIT_EXPRESSION(Result, |=) - }; - -#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - DOCTEST_CLANG_SUPPRESS_WARNING_PUSH - DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") - //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") - - DOCTEST_GCC_SUPPRESS_WARNING_PUSH - DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") - //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") - - DOCTEST_MSVC_SUPPRESS_WARNING_PUSH - // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 - DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch - DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch - DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch - //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation - -#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - // clang-format off -#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE bool -#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type - inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } - inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } - inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } - inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } - inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } - inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - // clang-format on - -#define DOCTEST_RELATIONAL_OP(name, op) \ - template \ - DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ - const DOCTEST_REF_WRAP(R) rhs) { \ - return lhs op rhs; \ - } - - DOCTEST_RELATIONAL_OP(eq, ==) - DOCTEST_RELATIONAL_OP(ne, !=) - DOCTEST_RELATIONAL_OP(lt, <) - DOCTEST_RELATIONAL_OP(gt, >) - DOCTEST_RELATIONAL_OP(le, <=) - DOCTEST_RELATIONAL_OP(ge, >=) - -#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_CMP_EQ(l, r) l == r -#define DOCTEST_CMP_NE(l, r) l != r -#define DOCTEST_CMP_GT(l, r) l > r -#define DOCTEST_CMP_LT(l, r) l < r -#define DOCTEST_CMP_GE(l, r) l >= r -#define DOCTEST_CMP_LE(l, r) l <= r -#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_CMP_EQ(l, r) eq(l, r) -#define DOCTEST_CMP_NE(l, r) ne(l, r) -#define DOCTEST_CMP_GT(l, r) gt(l, r) -#define DOCTEST_CMP_LT(l, r) lt(l, r) -#define DOCTEST_CMP_GE(l, r) ge(l, r) -#define DOCTEST_CMP_LE(l, r) le(l, r) -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - - template - // cppcheck-suppress copyCtorAndEqOperator - struct Expression_lhs - { - L lhs; - assertType::Enum m_at; - - explicit Expression_lhs(L in, assertType::Enum at) - : lhs(in) - , m_at(at) {} - - DOCTEST_NOINLINE operator Result() { - bool res = !!lhs; - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional - res = !res; - - if(!res || getContextOptions()->success) - return Result(res, toString(lhs)); - return Result(res); - } - - // clang-format off - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional - // clang-format on - - // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) - // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the - // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) - DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) - }; - -#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_MSVC_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP - -#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - struct DOCTEST_INTERFACE ExpressionDecomposer - { - assertType::Enum m_at; - - ExpressionDecomposer(assertType::Enum at); - - // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) - // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... - // https://github.com/catchorg/Catch2/issues/870 - // https://github.com/catchorg/Catch2/issues/565 - template - Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { - return Expression_lhs(operand, m_at); - } - }; - - struct DOCTEST_INTERFACE TestSuite - { - const char* m_test_suite; - const char* m_description; - bool m_skip; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; - - TestSuite& operator*(const char* in); - - template - TestSuite& operator*(const T& in) { - in.fill(*this); - return *this; - } - }; - - typedef void (*funcType)(); - - struct DOCTEST_INTERFACE TestCase : public TestCaseData - { - funcType m_test; // a function pointer to the test case - - const char* m_type; // for templated test cases - gets appended to the real name - int m_template_id; // an ID used to distinguish between the different versions of a templated test case - String m_full_name; // contains the name (only for templated test cases!) + the template type - - TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type = "", int template_id = -1); - - TestCase(const TestCase& other); - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function - TestCase& operator=(const TestCase& other); - DOCTEST_MSVC_SUPPRESS_WARNING_POP - - TestCase& operator*(const char* in); - - template - TestCase& operator*(const T& in) { - in.fill(*this); - return *this; - } - - bool operator<(const TestCase& other) const; - }; - - // forward declarations of functions used by the macros - DOCTEST_INTERFACE int regTest(const TestCase& tc); - DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); - DOCTEST_INTERFACE bool isDebuggerActive(); - - template - int instantiationHelper(const T&) { return 0; } - - namespace binaryAssertComparison { - enum Enum - { - eq = 0, - ne, - gt, - lt, - ge, - le - }; - } // namespace binaryAssertComparison - - // clang-format off - template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; - -#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ - template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; - // clang-format on - - DOCTEST_BINARY_RELATIONAL_OP(0, eq) - DOCTEST_BINARY_RELATIONAL_OP(1, ne) - DOCTEST_BINARY_RELATIONAL_OP(2, gt) - DOCTEST_BINARY_RELATIONAL_OP(3, lt) - DOCTEST_BINARY_RELATIONAL_OP(4, ge) - DOCTEST_BINARY_RELATIONAL_OP(5, le) - - struct DOCTEST_INTERFACE ResultBuilder : public AssertData - { - ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type = "", const char* exception_string = ""); - - void setResult(const Result& res); - - template - DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, - const DOCTEST_REF_WRAP(R) rhs) { - m_failed = !RelationalComparator()(lhs, rhs); - if(m_failed || getContextOptions()->success) - m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); - } - - template - DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { - m_failed = !val; - - if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional - m_failed = !m_failed; - - if(m_failed || getContextOptions()->success) - m_decomp = toString(val); - } - - void translateException(); - - bool log(); - void react() const; - }; - - namespace assertAction { - enum Enum - { - nothing = 0, - dbgbreak = 1, - shouldthrow = 2 - }; - } // namespace assertAction - - DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); - - DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, - const char* expr, Result result); - -#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ - do { \ - if(!is_running_in_test) { \ - if(failed) { \ - ResultBuilder rb(at, file, line, expr); \ - rb.m_failed = failed; \ - rb.m_decomp = decomp; \ - failed_out_of_a_testing_context(rb); \ - if(isDebuggerActive() && !getContextOptions()->no_breaks) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - if(checkIfShouldThrow(at)) \ - throwException(); \ - } \ - return; \ - } \ - } while(false) - -#define DOCTEST_ASSERT_IN_TESTS(decomp) \ - ResultBuilder rb(at, file, line, expr); \ - rb.m_failed = failed; \ - if(rb.m_failed || getContextOptions()->success) \ - rb.m_decomp = decomp; \ - if(rb.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - if(rb.m_failed && checkIfShouldThrow(at)) \ - throwException() - - template - DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, - const char* expr, const DOCTEST_REF_WRAP(L) lhs, - const DOCTEST_REF_WRAP(R) rhs) { - bool failed = !RelationalComparator()(lhs, rhs); - - // ################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); - DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); - } - - template - DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, - const char* expr, const DOCTEST_REF_WRAP(L) val) { - bool failed = !val; - - if(at & assertType::is_false) //!OCLINT bitwise operator in conditional - failed = !failed; - - // ################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); - DOCTEST_ASSERT_IN_TESTS(toString(val)); - } - - struct DOCTEST_INTERFACE IExceptionTranslator - { - IExceptionTranslator(); - virtual ~IExceptionTranslator(); - virtual bool translate(String&) const = 0; - }; - - template - class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class - { - public: - explicit ExceptionTranslator(String (*translateFunction)(T)) - : m_translateFunction(translateFunction) {} - - bool translate(String& res) const override { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - try { - throw; // lgtm [cpp/rethrow-no-exception] - // cppcheck-suppress catchExceptionByValue - } catch(T ex) { // NOLINT - res = m_translateFunction(ex); //!OCLINT parameter reassignment - return true; - } catch(...) {} //!OCLINT - empty catch statement -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - ((void)res); // to silence -Wunused-parameter - return false; - } - - private: - String (*m_translateFunction)(T); - }; - - DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); - - template - struct StringStreamBase - { - template - static void convert(std::ostream* s, const T& in) { - *s << toString(in); - } - - // always treat char* as a string in this context - no matter - // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined - static void convert(std::ostream* s, const char* in) { *s << String(in); } - }; - - template <> - struct StringStreamBase - { - template - static void convert(std::ostream* s, const T& in) { - *s << in; - } - }; - - template - struct StringStream : public StringStreamBase::value> - {}; - - template - void toStream(std::ostream* s, const T& value) { - StringStream::convert(s, value); - } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); - DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); - DOCTEST_INTERFACE void toStream(std::ostream* s, float in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double in); - DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); - - DOCTEST_INTERFACE void toStream(std::ostream* s, char in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); - DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); - DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); - - // ContextScope base class used to allow implementing methods of ContextScope - // that don't depend on the template parameter in doctest.cpp. - class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { - protected: - ContextScopeBase(); - - void destroy(); - }; - - template class ContextScope : public ContextScopeBase - { - const L &lambda_; - - public: - explicit ContextScope(const L &lambda) : lambda_(lambda) {} - - ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} - - void stringify(std::ostream* s) const override { lambda_(s); } - - ~ContextScope() override { destroy(); } - }; - - struct DOCTEST_INTERFACE MessageBuilder : public MessageData - { - std::ostream* m_stream; - - MessageBuilder(const char* file, int line, assertType::Enum severity); - MessageBuilder() = delete; - ~MessageBuilder(); - - template - MessageBuilder& operator<<(const T& in) { - toStream(m_stream, in); - return *this; - } - - bool log(); - void react(); - }; - - template - ContextScope MakeContextScope(const L &lambda) { - return ContextScope(lambda); - } -} // namespace detail - -#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ - struct name \ - { \ - type data; \ - name(type in = def) \ - : data(in) {} \ - void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ - void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ - } - -DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); -DOCTEST_DEFINE_DECORATOR(description, const char*, ""); -DOCTEST_DEFINE_DECORATOR(skip, bool, true); -DOCTEST_DEFINE_DECORATOR(timeout, double, 0); -DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); -DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); -DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); - -template -int registerExceptionTranslator(String (*translateFunction)(T)) { - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") - static detail::ExceptionTranslator exceptionTranslator(translateFunction); - DOCTEST_CLANG_SUPPRESS_WARNING_POP - detail::registerExceptionTranslatorImpl(&exceptionTranslator); - return 0; -} - -} // namespace doctest - -// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro -// introduces an anonymous namespace in which getCurrentTestSuite gets overridden -namespace doctest_detail_test_suite_ns { -DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); -} // namespace doctest_detail_test_suite_ns - -namespace doctest { -#else // DOCTEST_CONFIG_DISABLE -template -int registerExceptionTranslator(String (*)(T)) { - return 0; -} -#endif // DOCTEST_CONFIG_DISABLE - -namespace detail { - typedef void (*assert_handler)(const AssertData&); - struct ContextState; -} // namespace detail - -class DOCTEST_INTERFACE Context -{ - detail::ContextState* p; - - void parseArgs(int argc, const char* const* argv, bool withDefaults = false); - -public: - explicit Context(int argc = 0, const char* const* argv = nullptr); - - ~Context(); - - void applyCommandLine(int argc, const char* const* argv); - - void addFilter(const char* filter, const char* value); - void clearFilters(); - void setOption(const char* option, int value); - void setOption(const char* option, const char* value); - - bool shouldExit(); - - void setAsDefaultForAssertsOutOfTestCases(); - - void setAssertHandler(detail::assert_handler ah); - - int run(); -}; - -namespace TestCaseFailureReason { - enum Enum - { - None = 0, - AssertFailure = 1, // an assertion has failed in the test case - Exception = 2, // test case threw an exception - Crash = 4, // a crash... - TooManyFailedAsserts = 8, // the abort-after option - Timeout = 16, // see the timeout decorator - ShouldHaveFailedButDidnt = 32, // see the should_fail decorator - ShouldHaveFailedAndDid = 64, // see the should_fail decorator - DidntFailExactlyNumTimes = 128, // see the expected_failures decorator - FailedExactlyNumTimes = 256, // see the expected_failures decorator - CouldHaveFailedAndDid = 512 // see the may_fail decorator - }; -} // namespace TestCaseFailureReason - -struct DOCTEST_INTERFACE CurrentTestCaseStats -{ - int numAssertsCurrentTest; - int numAssertsFailedCurrentTest; - double seconds; - int failure_flags; // use TestCaseFailureReason::Enum -}; - -struct DOCTEST_INTERFACE TestCaseException -{ - String error_string; - bool is_crash; -}; - -struct DOCTEST_INTERFACE TestRunStats -{ - unsigned numTestCases; - unsigned numTestCasesPassingFilters; - unsigned numTestSuitesPassingFilters; - unsigned numTestCasesFailed; - int numAsserts; - int numAssertsFailed; -}; - -struct QueryData -{ - const TestRunStats* run_stats = nullptr; - const TestCaseData** data = nullptr; - unsigned num_data = 0; -}; - -struct DOCTEST_INTERFACE IReporter -{ - // The constructor has to accept "const ContextOptions&" as a single argument - // which has most of the options for the run + a pointer to the stdout stream - // Reporter(const ContextOptions& in) - - // called when a query should be reported (listing test cases, printing the version, etc.) - virtual void report_query(const QueryData&) = 0; - - // called when the whole test run starts - virtual void test_run_start() = 0; - // called when the whole test run ends (caching a pointer to the input doesn't make sense here) - virtual void test_run_end(const TestRunStats&) = 0; - - // called when a test case is started (safe to cache a pointer to the input) - virtual void test_case_start(const TestCaseData&) = 0; - // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) - virtual void test_case_reenter(const TestCaseData&) = 0; - // called when a test case has ended - virtual void test_case_end(const CurrentTestCaseStats&) = 0; - - // called when an exception is thrown from the test case (or it crashes) - virtual void test_case_exception(const TestCaseException&) = 0; - - // called whenever a subcase is entered (don't cache pointers to the input) - virtual void subcase_start(const SubcaseSignature&) = 0; - // called whenever a subcase is exited (don't cache pointers to the input) - virtual void subcase_end() = 0; - - // called for each assert (don't cache pointers to the input) - virtual void log_assert(const AssertData&) = 0; - // called for each message (don't cache pointers to the input) - virtual void log_message(const MessageData&) = 0; - - // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator - // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) - virtual void test_case_skipped(const TestCaseData&) = 0; - - // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have - virtual ~IReporter(); - - // can obtain all currently active contexts and stringify them if one wishes to do so - static int get_num_active_contexts(); - static const IContextScope* const* get_active_contexts(); - - // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown - static int get_num_stringified_contexts(); - static const String* get_stringified_contexts(); -}; - -namespace detail { - typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); - - DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); - - template - IReporter* reporterCreator(const ContextOptions& o) { - return new Reporter(o); - } -} // namespace detail - -template -int registerReporter(const char* name, int priority, bool isReporter) { - detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); - return 0; -} -} // namespace doctest - -// if registering is not disabled -#if !defined(DOCTEST_CONFIG_DISABLE) - -// common code in asserts - for convenience -#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ - if(b.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - b.react() - -#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#define DOCTEST_WRAP_IN_TRY(x) x; -#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#define DOCTEST_WRAP_IN_TRY(x) \ - try { \ - x; \ - } catch(...) { _DOCTEST_RB.translateException(); } -#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS - -#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(...) \ - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ - static_cast(__VA_ARGS__); \ - DOCTEST_GCC_SUPPRESS_WARNING_POP -#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; -#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS - -// registers the test by initializing a dummy var with a function -#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ - global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::regTest( \ - doctest::detail::TestCase( \ - f, __FILE__, __LINE__, \ - doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ - decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() - -#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ - namespace { \ - struct der : public base \ - { \ - void f(); \ - }; \ - static void func() { \ - der v; \ - v.f(); \ - } \ - DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ - } \ - inline DOCTEST_NOINLINE void der::f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ - static void f(); \ - DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ - static void f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ - static doctest::detail::funcType proxy() { return f; } \ - DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ - static void f() - -// for registering tests -#define DOCTEST_TEST_CASE(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) - -// for registering tests in classes - requires C++17 for inline variables! -#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) -#define DOCTEST_TEST_CASE_CLASS(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ - decorators) -#else // DOCTEST_TEST_CASE_CLASS -#define DOCTEST_TEST_CASE_CLASS(...) \ - TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER -#endif // DOCTEST_TEST_CASE_CLASS - -// for registering tests with a fixture -#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) - -// for converting types to strings without the header and demangling -#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ - template <> \ - inline const char* type_to_string<__VA_ARGS__>() { \ - return "<" #__VA_ARGS__ ">"; \ - } -#define DOCTEST_TYPE_TO_STRING(...) \ - namespace doctest { namespace detail { \ - DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ - } \ - } \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ - template \ - static void func(); \ - namespace { \ - template \ - struct iter; \ - template \ - struct iter> \ - { \ - iter(const char* file, unsigned line, int index) { \ - doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ - doctest_detail_test_suite_ns::getCurrentTestSuite(), \ - doctest::detail::type_to_string(), \ - int(line) * 1000 + index) \ - * dec); \ - iter>(file, line, index + 1); \ - } \ - }; \ - template <> \ - struct iter> \ - { \ - iter(const char*, unsigned, int) {} \ - }; \ - } \ - template \ - static void func() - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ - DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) - -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ - doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ - DOCTEST_GLOBAL_NO_WARNINGS_END() - -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ - template \ - static void anon() - -#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) - -// for subcases -#define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ - doctest::detail::Subcase(name, __FILE__, __LINE__)) - -// for grouping tests in test suites by using code blocks -#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ - namespace ns_name { namespace doctest_detail_test_suite_ns { \ - static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ - static doctest::detail::TestSuite data; \ - static bool inited = false; \ - DOCTEST_MSVC_SUPPRESS_WARNING_POP \ - DOCTEST_CLANG_SUPPRESS_WARNING_POP \ - if(!inited) { \ - data* decorators; \ - inited = true; \ - } \ - return data; \ - } \ - } \ - } \ - namespace ns_name - -#define DOCTEST_TEST_SUITE(decorators) \ - DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) - -// for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for ending a testsuite block -#define DOCTEST_TEST_SUITE_END \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for registering exception translators -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ - inline doctest::String translatorName(signature); \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ - doctest::registerExceptionTranslator(translatorName); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - doctest::String translatorName(signature) - -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ - signature) - -// for registering reporters -#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter(name, priority, true); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for registering listeners -#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter(name, priority, false); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for logging -#define DOCTEST_INFO(expression) \ - DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ - DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression) - -#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression) \ - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \ - auto lambda_name = [&](std::ostream* s_name) { \ - doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ - mb_name.m_stream = s_name; \ - mb_name << expression; \ - }; \ - DOCTEST_MSVC_SUPPRESS_WARNING_POP \ - auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name) - -#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) - -#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ - do { \ - doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ - mb << x; \ - DOCTEST_ASSERT_LOG_AND_REACT(mb); \ - } while(false) - -// clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -// clang-format on - -#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) - -#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. - -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ - doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ - << __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ - DOCTEST_CLANG_SUPPRESS_WARNING_POP - -#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - do { \ - DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ - } while(false) - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -// necessary for _MESSAGE -#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 - -#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::decomp_assert( \ - doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ - doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ - << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) -#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) -#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) -#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) -#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) -#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) - -// clang-format off -#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false) -#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false) -// clang-format on - -#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ - do { \ - if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #expr, #__VA_ARGS__, message); \ - try { \ - DOCTEST_CAST_TO_VOID(expr) \ - } catch(const doctest::detail::remove_const< \ - doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ - _DOCTEST_RB.translateException(); \ - _DOCTEST_RB.m_threw_as = true; \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } \ - } while(false) - -#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ - do { \ - if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, expr_str, "", __VA_ARGS__); \ - try { \ - DOCTEST_CAST_TO_VOID(expr) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } \ - } while(false) - -#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - try { \ - DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -// clang-format off -#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") -#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") -#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") - -#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) - -#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) - -#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) - -#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) -#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) -#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while(false) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while(false) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while(false) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while(false) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while(false) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while(false) -// clang-format on - -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY( \ - _DOCTEST_RB.binary_assert( \ - __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while(false) - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ - doctest::detail::binary_assert( \ - doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) - -#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ - #__VA_ARGS__, __VA_ARGS__) - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) -#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) -#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) -#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) -#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) -#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) -#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) -#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) -#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) -#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) -#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) -#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) -#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) -#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) -#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) -#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) -#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) -#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) - -#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) -#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS - -#undef DOCTEST_WARN_THROWS -#undef DOCTEST_CHECK_THROWS -#undef DOCTEST_REQUIRE_THROWS -#undef DOCTEST_WARN_THROWS_AS -#undef DOCTEST_CHECK_THROWS_AS -#undef DOCTEST_REQUIRE_THROWS_AS -#undef DOCTEST_WARN_THROWS_WITH -#undef DOCTEST_CHECK_THROWS_WITH -#undef DOCTEST_REQUIRE_THROWS_WITH -#undef DOCTEST_WARN_THROWS_WITH_AS -#undef DOCTEST_CHECK_THROWS_WITH_AS -#undef DOCTEST_REQUIRE_THROWS_WITH_AS -#undef DOCTEST_WARN_NOTHROW -#undef DOCTEST_CHECK_NOTHROW -#undef DOCTEST_REQUIRE_NOTHROW - -#undef DOCTEST_WARN_THROWS_MESSAGE -#undef DOCTEST_CHECK_THROWS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_MESSAGE -#undef DOCTEST_WARN_THROWS_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#undef DOCTEST_WARN_NOTHROW_MESSAGE -#undef DOCTEST_CHECK_NOTHROW_MESSAGE -#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#define DOCTEST_WARN_THROWS(...) ((void)0) -#define DOCTEST_CHECK_THROWS(...) ((void)0) -#define DOCTEST_REQUIRE_THROWS(...) ((void)0) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_WARN_NOTHROW(...) ((void)0) -#define DOCTEST_CHECK_NOTHROW(...) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) - -#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#undef DOCTEST_REQUIRE -#undef DOCTEST_REQUIRE_FALSE -#undef DOCTEST_REQUIRE_MESSAGE -#undef DOCTEST_REQUIRE_FALSE_MESSAGE -#undef DOCTEST_REQUIRE_EQ -#undef DOCTEST_REQUIRE_NE -#undef DOCTEST_REQUIRE_GT -#undef DOCTEST_REQUIRE_LT -#undef DOCTEST_REQUIRE_GE -#undef DOCTEST_REQUIRE_LE -#undef DOCTEST_REQUIRE_UNARY -#undef DOCTEST_REQUIRE_UNARY_FALSE - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - -// ================================================================================================= -// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == -// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == -// ================================================================================================= -#else // DOCTEST_CONFIG_DISABLE - -#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ - namespace { \ - template \ - struct der : public base \ - { void f(); }; \ - } \ - template \ - inline void der::f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ - template \ - static inline void f() - -// for registering tests -#define DOCTEST_TEST_CASE(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) - -// for registering tests in classes -#define DOCTEST_TEST_CASE_CLASS(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) - -// for registering tests with a fixture -#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) - -// for converting types to strings without the header and demangling -#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#define DOCTEST_TYPE_TO_STRING_IMPL(...) - -// for typed tests -#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ - template \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ - template \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() - -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for subcases -#define DOCTEST_SUBCASE(name) - -// for a testsuite block -#define DOCTEST_TEST_SUITE(name) namespace - -// for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for ending a testsuite block -#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - template \ - static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) - -#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) -#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) - -#define DOCTEST_INFO(x) ((void)0) -#define DOCTEST_CAPTURE(x) ((void)0) -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) -#define DOCTEST_MESSAGE(x) ((void)0) -#define DOCTEST_FAIL_CHECK(x) ((void)0) -#define DOCTEST_FAIL(x) ((void)0) - -#define DOCTEST_WARN(...) ((void)0) -#define DOCTEST_CHECK(...) ((void)0) -#define DOCTEST_REQUIRE(...) ((void)0) -#define DOCTEST_WARN_FALSE(...) ((void)0) -#define DOCTEST_CHECK_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_FALSE(...) ((void)0) - -#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) - -#define DOCTEST_WARN_THROWS(...) ((void)0) -#define DOCTEST_CHECK_THROWS(...) ((void)0) -#define DOCTEST_REQUIRE_THROWS(...) ((void)0) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_WARN_NOTHROW(...) ((void)0) -#define DOCTEST_CHECK_NOTHROW(...) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) - -#define DOCTEST_WARN_EQ(...) ((void)0) -#define DOCTEST_CHECK_EQ(...) ((void)0) -#define DOCTEST_REQUIRE_EQ(...) ((void)0) -#define DOCTEST_WARN_NE(...) ((void)0) -#define DOCTEST_CHECK_NE(...) ((void)0) -#define DOCTEST_REQUIRE_NE(...) ((void)0) -#define DOCTEST_WARN_GT(...) ((void)0) -#define DOCTEST_CHECK_GT(...) ((void)0) -#define DOCTEST_REQUIRE_GT(...) ((void)0) -#define DOCTEST_WARN_LT(...) ((void)0) -#define DOCTEST_CHECK_LT(...) ((void)0) -#define DOCTEST_REQUIRE_LT(...) ((void)0) -#define DOCTEST_WARN_GE(...) ((void)0) -#define DOCTEST_CHECK_GE(...) ((void)0) -#define DOCTEST_REQUIRE_GE(...) ((void)0) -#define DOCTEST_WARN_LE(...) ((void)0) -#define DOCTEST_CHECK_LE(...) ((void)0) -#define DOCTEST_REQUIRE_LE(...) ((void)0) - -#define DOCTEST_WARN_UNARY(...) ((void)0) -#define DOCTEST_CHECK_UNARY(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY(...) ((void)0) -#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) -#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) - -#endif // DOCTEST_CONFIG_DISABLE - -// clang-format off -// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS -#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ -#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ -#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ -#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE -#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE -#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE -#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT -#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT -#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT -#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT -#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT -#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT -#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE -#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE -#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE -#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE -#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE -#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE - -#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY -#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY -#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY -#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE -#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE -#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE - -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE -// clang-format on - -// BDD style macros -// clang-format off -#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) -#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) -#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) -#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) - -#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) -#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) -#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) -#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) -#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) -// clang-format on - -// == SHORT VERSIONS OF THE MACROS -#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) - -#define TEST_CASE DOCTEST_TEST_CASE -#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS -#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE -#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING -#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE -#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE -#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE -#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY -#define SUBCASE DOCTEST_SUBCASE -#define TEST_SUITE DOCTEST_TEST_SUITE -#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN -#define TEST_SUITE_END DOCTEST_TEST_SUITE_END -#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR -#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER -#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER -#define INFO DOCTEST_INFO -#define CAPTURE DOCTEST_CAPTURE -#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT -#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT -#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT -#define MESSAGE DOCTEST_MESSAGE -#define FAIL_CHECK DOCTEST_FAIL_CHECK -#define FAIL DOCTEST_FAIL -#define TO_LVALUE DOCTEST_TO_LVALUE - -#define WARN DOCTEST_WARN -#define WARN_FALSE DOCTEST_WARN_FALSE -#define WARN_THROWS DOCTEST_WARN_THROWS -#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS -#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH -#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS -#define WARN_NOTHROW DOCTEST_WARN_NOTHROW -#define CHECK DOCTEST_CHECK -#define CHECK_FALSE DOCTEST_CHECK_FALSE -#define CHECK_THROWS DOCTEST_CHECK_THROWS -#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS -#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH -#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS -#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW -#define REQUIRE DOCTEST_REQUIRE -#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE -#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS -#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS -#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH -#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS -#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW - -#define WARN_MESSAGE DOCTEST_WARN_MESSAGE -#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE -#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE -#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE -#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE -#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE -#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE -#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE -#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE -#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE -#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE -#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE -#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE -#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE -#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE -#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#define SCENARIO DOCTEST_SCENARIO -#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS -#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE -#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE -#define GIVEN DOCTEST_GIVEN -#define WHEN DOCTEST_WHEN -#define AND_WHEN DOCTEST_AND_WHEN -#define THEN DOCTEST_THEN -#define AND_THEN DOCTEST_AND_THEN - -#define WARN_EQ DOCTEST_WARN_EQ -#define CHECK_EQ DOCTEST_CHECK_EQ -#define REQUIRE_EQ DOCTEST_REQUIRE_EQ -#define WARN_NE DOCTEST_WARN_NE -#define CHECK_NE DOCTEST_CHECK_NE -#define REQUIRE_NE DOCTEST_REQUIRE_NE -#define WARN_GT DOCTEST_WARN_GT -#define CHECK_GT DOCTEST_CHECK_GT -#define REQUIRE_GT DOCTEST_REQUIRE_GT -#define WARN_LT DOCTEST_WARN_LT -#define CHECK_LT DOCTEST_CHECK_LT -#define REQUIRE_LT DOCTEST_REQUIRE_LT -#define WARN_GE DOCTEST_WARN_GE -#define CHECK_GE DOCTEST_CHECK_GE -#define REQUIRE_GE DOCTEST_REQUIRE_GE -#define WARN_LE DOCTEST_WARN_LE -#define CHECK_LE DOCTEST_CHECK_LE -#define REQUIRE_LE DOCTEST_REQUIRE_LE -#define WARN_UNARY DOCTEST_WARN_UNARY -#define CHECK_UNARY DOCTEST_CHECK_UNARY -#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY -#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE -#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE -#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE - -// KEPT FOR BACKWARDS COMPATIBILITY -#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ -#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ -#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ -#define FAST_WARN_NE DOCTEST_FAST_WARN_NE -#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE -#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE -#define FAST_WARN_GT DOCTEST_FAST_WARN_GT -#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT -#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT -#define FAST_WARN_LT DOCTEST_FAST_WARN_LT -#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT -#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT -#define FAST_WARN_GE DOCTEST_FAST_WARN_GE -#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE -#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE -#define FAST_WARN_LE DOCTEST_FAST_WARN_LE -#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE -#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE - -#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY -#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY -#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY -#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE -#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE -#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE - -#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE - -#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES - -#if !defined(DOCTEST_CONFIG_DISABLE) - -// this is here to clear the 'current test suite' for the current translation unit - at the top -DOCTEST_TEST_SUITE_END(); - -// add stringification for primitive/fundamental types -namespace doctest { namespace detail { - DOCTEST_TYPE_TO_STRING_IMPL(bool) - DOCTEST_TYPE_TO_STRING_IMPL(float) - DOCTEST_TYPE_TO_STRING_IMPL(double) - DOCTEST_TYPE_TO_STRING_IMPL(long double) - DOCTEST_TYPE_TO_STRING_IMPL(char) - DOCTEST_TYPE_TO_STRING_IMPL(signed char) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) -#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) - DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) -#endif // not MSVC or wchar_t support enabled - DOCTEST_TYPE_TO_STRING_IMPL(short int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) - DOCTEST_TYPE_TO_STRING_IMPL(int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) - DOCTEST_TYPE_TO_STRING_IMPL(long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) - DOCTEST_TYPE_TO_STRING_IMPL(long long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) -}} // namespace doctest::detail - -#endif // DOCTEST_CONFIG_DISABLE - -DOCTEST_CLANG_SUPPRESS_WARNING_POP -DOCTEST_MSVC_SUPPRESS_WARNING_POP -DOCTEST_GCC_SUPPRESS_WARNING_POP - -#endif // DOCTEST_LIBRARY_INCLUDED - -#ifndef DOCTEST_SINGLE_HEADER -#define DOCTEST_SINGLE_HEADER -#endif // DOCTEST_SINGLE_HEADER - -#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) - -#ifndef DOCTEST_SINGLE_HEADER -#include "doctest_fwd.h" -#endif // DOCTEST_SINGLE_HEADER - -DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") - -#ifndef DOCTEST_LIBRARY_IMPLEMENTATION -#define DOCTEST_LIBRARY_IMPLEMENTATION - -DOCTEST_CLANG_SUPPRESS_WARNING_POP - -DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") - -DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") -DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") -DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") -DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") -DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") -DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") -DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") - -DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration -DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant -DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled -DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified -DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal -DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe -DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C -DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... -DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' - -DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN - -// required includes - will go only in one translation unit! -#include -#include -#include -// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 -#ifdef __BORLANDC__ -#include -#endif // __BORLANDC__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef DOCTEST_CONFIG_POSIX_SIGNALS -#include -#endif // DOCTEST_CONFIG_POSIX_SIGNALS -#include -#include -#include - -#ifdef DOCTEST_PLATFORM_MAC -#include -#include -#include -#endif // DOCTEST_PLATFORM_MAC - -#ifdef DOCTEST_PLATFORM_WINDOWS - -// defines for a leaner windows.h -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// not sure what AfxWin.h is for - here I do what Catch does -#ifdef __AFXDLL -#include -#else -#if defined(__MINGW32__) || defined(__MINGW64__) -#include -#else // MINGW -#include -#endif // MINGW -#endif -#include - -#else // DOCTEST_PLATFORM_WINDOWS - -#include -#include - -#endif // DOCTEST_PLATFORM_WINDOWS - -// this is a fix for https://github.com/onqtam/doctest/issues/348 -// https://mail.gnome.org/archives/xml/2012-January/msg00000.html -#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) -#define STDOUT_FILENO fileno(stdout) -#endif // HAVE_UNISTD_H - -DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END - -// counts the number of elements in a C array -#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) - -#ifdef DOCTEST_CONFIG_DISABLE -#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled -#else // DOCTEST_CONFIG_DISABLE -#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled -#endif // DOCTEST_CONFIG_DISABLE - -#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX -#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" -#endif - -#ifndef DOCTEST_THREAD_LOCAL -#define DOCTEST_THREAD_LOCAL thread_local -#endif - -#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS -#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX -#else -#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" -#endif - -namespace doctest { - -bool is_running_in_test = false; - -namespace { - using namespace detail; - // case insensitive strcmp - int stricmp(const char* a, const char* b) { - for(;; a++, b++) { - const int d = tolower(*a) - tolower(*b); - if(d != 0 || !*a) - return d; - } - } - - template - String fpToString(T value, int precision) { - std::ostringstream oss; - oss << std::setprecision(precision) << std::fixed << value; - std::string d = oss.str(); - size_t i = d.find_last_not_of('0'); - if(i != std::string::npos && i != d.size() - 1) { - if(d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d.c_str(); - } - - struct Endianness - { - enum Arch - { - Big, - Little - }; - - static Arch which() { - int x = 1; - // casting any data pointer to char* is allowed - auto ptr = reinterpret_cast(&x); - if(*ptr) - return Little; - return Big; - } - }; -} // namespace - -namespace detail { - void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } - - String rawMemoryToString(const void* object, unsigned size) { - // Reverse order for little endian architectures - int i = 0, end = static_cast(size), inc = 1; - if(Endianness::which() == Endianness::Little) { - i = end - 1; - end = inc = -1; - } - - unsigned const char* bytes = static_cast(object); - std::ostringstream oss; - oss << "0x" << std::setfill('0') << std::hex; - for(; i != end; i += inc) - oss << std::setw(2) << static_cast(bytes[i]); - return oss.str().c_str(); - } - - DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) - - std::ostream* getTlsOss() { - g_oss.clear(); // there shouldn't be anything worth clearing in the flags - g_oss.str(""); // the slow way of resetting a string stream - //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 - return &g_oss; - } - - String getTlsOssResult() { - //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 - return g_oss.str().c_str(); - } - -#ifndef DOCTEST_CONFIG_DISABLE - -namespace timer_large_integer -{ - -#if defined(DOCTEST_PLATFORM_WINDOWS) - typedef ULONGLONG type; -#else // DOCTEST_PLATFORM_WINDOWS - using namespace std; - typedef uint64_t type; -#endif // DOCTEST_PLATFORM_WINDOWS -} - -typedef timer_large_integer::type ticks_t; - -#ifdef DOCTEST_CONFIG_GETCURRENTTICKS - ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } -#elif defined(DOCTEST_PLATFORM_WINDOWS) - ticks_t getCurrentTicks() { - static LARGE_INTEGER hz = {0}, hzo = {0}; - if(!hz.QuadPart) { - QueryPerformanceFrequency(&hz); - QueryPerformanceCounter(&hzo); - } - LARGE_INTEGER t; - QueryPerformanceCounter(&t); - return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; - } -#else // DOCTEST_PLATFORM_WINDOWS - ticks_t getCurrentTicks() { - timeval t; - gettimeofday(&t, nullptr); - return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); - } -#endif // DOCTEST_PLATFORM_WINDOWS - - struct Timer - { - void start() { m_ticks = getCurrentTicks(); } - unsigned int getElapsedMicroseconds() const { - return static_cast(getCurrentTicks() - m_ticks); - } - //unsigned int getElapsedMilliseconds() const { - // return static_cast(getElapsedMicroseconds() / 1000); - //} - double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } - - private: - ticks_t m_ticks = 0; - }; - - // this holds both parameters from the command line and runtime data for tests - struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats - { - std::atomic numAssertsCurrentTest_atomic; - std::atomic numAssertsFailedCurrentTest_atomic; - - std::vector> filters = decltype(filters)(9); // 9 different filters - - std::vector reporters_currently_used; - - const TestCase* currentTest = nullptr; - - assert_handler ah = nullptr; - - Timer timer; - - std::vector stringifiedContexts; // logging from INFO() due to an exception - - // stuff for subcases - std::vector subcasesStack; - std::set subcasesPassed; - int subcasesCurrentMaxLevel; - bool should_reenter; - std::atomic shouldLogCurrentException; - - void resetRunData() { - numTestCases = 0; - numTestCasesPassingFilters = 0; - numTestSuitesPassingFilters = 0; - numTestCasesFailed = 0; - numAsserts = 0; - numAssertsFailed = 0; - numAssertsCurrentTest = 0; - numAssertsFailedCurrentTest = 0; - } - - void finalizeTestCaseData() { - seconds = timer.getElapsedSeconds(); - - // update the non-atomic counters - numAsserts += numAssertsCurrentTest_atomic; - numAssertsFailed += numAssertsFailedCurrentTest_atomic; - numAssertsCurrentTest = numAssertsCurrentTest_atomic; - numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; - - if(numAssertsFailedCurrentTest) - failure_flags |= TestCaseFailureReason::AssertFailure; - - if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && - Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) - failure_flags |= TestCaseFailureReason::Timeout; - - if(currentTest->m_should_fail) { - if(failure_flags) { - failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; - } else { - failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; - } - } else if(failure_flags && currentTest->m_may_fail) { - failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; - } else if(currentTest->m_expected_failures > 0) { - if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { - failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; - } else { - failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; - } - } - - bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || - (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || - (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); - - // if any subcase has failed - the whole test case has failed - if(failure_flags && !ok_to_fail) - numTestCasesFailed++; - } - }; - - ContextState* g_cs = nullptr; - - // used to avoid locks for the debug output - // TODO: figure out if this is indeed necessary/correct - seems like either there still - // could be a race or that there wouldn't be a race even if using the context directly - DOCTEST_THREAD_LOCAL bool g_no_colors; - -#endif // DOCTEST_CONFIG_DISABLE -} // namespace detail - -void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } -void String::setLast(unsigned in) { buf[last] = char(in); } - -void String::copy(const String& other) { - using namespace std; - if(other.isOnStack()) { - memcpy(buf, other.buf, len); - } else { - setOnHeap(); - data.size = other.data.size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, other.data.ptr, data.size + 1); - } -} - -String::String() { - buf[0] = '\0'; - setLast(); -} - -String::~String() { - if(!isOnStack()) - delete[] data.ptr; -} - -String::String(const char* in) - : String(in, strlen(in)) {} - -String::String(const char* in, unsigned in_size) { - using namespace std; - if(in_size <= last) { - memcpy(buf, in, in_size + 1); - setLast(last - in_size); - } else { - setOnHeap(); - data.size = in_size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, in, in_size + 1); - } -} - -String::String(const String& other) { copy(other); } - -String& String::operator=(const String& other) { - if(this != &other) { - if(!isOnStack()) - delete[] data.ptr; - - copy(other); - } - - return *this; -} - -String& String::operator+=(const String& other) { - const unsigned my_old_size = size(); - const unsigned other_size = other.size(); - const unsigned total_size = my_old_size + other_size; - using namespace std; - if(isOnStack()) { - if(total_size < len) { - // append to the current stack space - memcpy(buf + my_old_size, other.c_str(), other_size + 1); - setLast(last - total_size); - } else { - // alloc new chunk - char* temp = new char[total_size + 1]; - // copy current data to new location before writing in the union - memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed - // update data in union - setOnHeap(); - data.size = total_size; - data.capacity = data.size + 1; - data.ptr = temp; - // transfer the rest of the data - memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } - } else { - if(data.capacity > total_size) { - // append to the current heap block - data.size = total_size; - memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } else { - // resize - data.capacity *= 2; - if(data.capacity <= total_size) - data.capacity = total_size + 1; - // alloc new chunk - char* temp = new char[data.capacity]; - // copy current data to new location before releasing it - memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed - // release old chunk - delete[] data.ptr; - // update the rest of the union members - data.size = total_size; - data.ptr = temp; - // transfer the rest of the data - memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } - } - - return *this; -} - -String String::operator+(const String& other) const { return String(*this) += other; } - -String::String(String&& other) { - using namespace std; - memcpy(buf, other.buf, len); - other.buf[0] = '\0'; - other.setLast(); -} - -String& String::operator=(String&& other) { - using namespace std; - if(this != &other) { - if(!isOnStack()) - delete[] data.ptr; - memcpy(buf, other.buf, len); - other.buf[0] = '\0'; - other.setLast(); - } - return *this; -} - -char String::operator[](unsigned i) const { - return const_cast(this)->operator[](i); // NOLINT -} - -char& String::operator[](unsigned i) { - if(isOnStack()) - return reinterpret_cast(buf)[i]; - return data.ptr[i]; -} - -DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") -unsigned String::size() const { - if(isOnStack()) - return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 - return data.size; -} -DOCTEST_GCC_SUPPRESS_WARNING_POP - -unsigned String::capacity() const { - if(isOnStack()) - return len; - return data.capacity; -} - -int String::compare(const char* other, bool no_case) const { - if(no_case) - return doctest::stricmp(c_str(), other); - return std::strcmp(c_str(), other); -} - -int String::compare(const String& other, bool no_case) const { - return compare(other.c_str(), no_case); -} - -// clang-format off -bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } -bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } -bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } -bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } -bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } -bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } -// clang-format on - -std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } - -namespace { - void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) -} // namespace - -namespace Color { - std::ostream& operator<<(std::ostream& s, Color::Enum code) { - color_to_stream(s, code); - return s; - } -} // namespace Color - -// clang-format off -const char* assertString(assertType::Enum at) { - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled - switch(at) { //!OCLINT missing default in switch statements - case assertType::DT_WARN : return "WARN"; - case assertType::DT_CHECK : return "CHECK"; - case assertType::DT_REQUIRE : return "REQUIRE"; - - case assertType::DT_WARN_FALSE : return "WARN_FALSE"; - case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; - case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; - - case assertType::DT_WARN_THROWS : return "WARN_THROWS"; - case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; - case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; - - case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; - case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; - case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; - - case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; - case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; - case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; - - case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; - case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; - case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; - - case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; - case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; - case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; - - case assertType::DT_WARN_EQ : return "WARN_EQ"; - case assertType::DT_CHECK_EQ : return "CHECK_EQ"; - case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; - case assertType::DT_WARN_NE : return "WARN_NE"; - case assertType::DT_CHECK_NE : return "CHECK_NE"; - case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; - case assertType::DT_WARN_GT : return "WARN_GT"; - case assertType::DT_CHECK_GT : return "CHECK_GT"; - case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; - case assertType::DT_WARN_LT : return "WARN_LT"; - case assertType::DT_CHECK_LT : return "CHECK_LT"; - case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; - case assertType::DT_WARN_GE : return "WARN_GE"; - case assertType::DT_CHECK_GE : return "CHECK_GE"; - case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; - case assertType::DT_WARN_LE : return "WARN_LE"; - case assertType::DT_CHECK_LE : return "CHECK_LE"; - case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; - - case assertType::DT_WARN_UNARY : return "WARN_UNARY"; - case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; - case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; - case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; - case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; - case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; - } - DOCTEST_MSVC_SUPPRESS_WARNING_POP - return ""; -} -// clang-format on - -const char* failureString(assertType::Enum at) { - if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional - return "WARNING"; - if(at & assertType::is_check) //!OCLINT bitwise operator in conditional - return "ERROR"; - if(at & assertType::is_require) //!OCLINT bitwise operator in conditional - return "FATAL ERROR"; - return ""; -} - -DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") -DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") -// depending on the current options this will remove the path of filenames -const char* skipPathFromFilename(const char* file) { - if(getContextOptions()->no_path_in_filenames) { - auto back = std::strrchr(file, '\\'); - auto forward = std::strrchr(file, '/'); - if(back || forward) { - if(back > forward) - forward = back; - return forward + 1; - } - } - return file; -} -DOCTEST_CLANG_SUPPRESS_WARNING_POP -DOCTEST_GCC_SUPPRESS_WARNING_POP - -bool SubcaseSignature::operator<(const SubcaseSignature& other) const { - if(m_line != other.m_line) - return m_line < other.m_line; - if(std::strcmp(m_file, other.m_file) != 0) - return std::strcmp(m_file, other.m_file) < 0; - return m_name.compare(other.m_name) < 0; -} - -IContextScope::IContextScope() = default; -IContextScope::~IContextScope() = default; - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(char* in) { return toString(static_cast(in)); } -String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(bool in) { return in ? "true" : "false"; } -String toString(float in) { return fpToString(in, 5) + "f"; } -String toString(double in) { return fpToString(in, 10); } -String toString(double long in) { return fpToString(in, 15); } - -#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ - String toString(type in) { \ - char buf[64]; \ - std::sprintf(buf, fmt, in); \ - return buf; \ - } - -DOCTEST_TO_STRING_OVERLOAD(char, "%d") -DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") -DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int short, "%d") -DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int, "%d") -DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") -DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") -DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") -DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") -DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") - -String toString(std::nullptr_t) { return "NULL"; } - -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -String toString(const std::string& in) { return in.c_str(); } -#endif // VS 2019 - -Approx::Approx(double value) - : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) - , m_scale(1.0) - , m_value(value) {} - -Approx Approx::operator()(double value) const { - Approx approx(value); - approx.epsilon(m_epsilon); - approx.scale(m_scale); - return approx; -} - -Approx& Approx::epsilon(double newEpsilon) { - m_epsilon = newEpsilon; - return *this; -} -Approx& Approx::scale(double newScale) { - m_scale = newScale; - return *this; -} - -bool operator==(double lhs, const Approx& rhs) { - // Thanks to Richard Harris for his help refining this formula - return std::fabs(lhs - rhs.m_value) < - rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); -} -bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } -bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } -bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } -bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } -bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } -bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } -bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } -bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } -bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } -bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } -bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } - -String toString(const Approx& in) { - return String("Approx( ") + doctest::toString(in.m_value) + " )"; -} -const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } - -} // namespace doctest - -#ifdef DOCTEST_CONFIG_DISABLE -namespace doctest { -Context::Context(int, const char* const*) {} -Context::~Context() = default; -void Context::applyCommandLine(int, const char* const*) {} -void Context::addFilter(const char*, const char*) {} -void Context::clearFilters() {} -void Context::setOption(const char*, int) {} -void Context::setOption(const char*, const char*) {} -bool Context::shouldExit() { return false; } -void Context::setAsDefaultForAssertsOutOfTestCases() {} -void Context::setAssertHandler(detail::assert_handler) {} -int Context::run() { return 0; } - -IReporter::~IReporter() = default; - -int IReporter::get_num_active_contexts() { return 0; } -const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } -int IReporter::get_num_stringified_contexts() { return 0; } -const String* IReporter::get_stringified_contexts() { return nullptr; } - -int registerReporter(const char*, int, IReporter*) { return 0; } - -} // namespace doctest -#else // DOCTEST_CONFIG_DISABLE - -#if !defined(DOCTEST_CONFIG_COLORS_NONE) -#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) -#ifdef DOCTEST_PLATFORM_WINDOWS -#define DOCTEST_CONFIG_COLORS_WINDOWS -#else // linux -#define DOCTEST_CONFIG_COLORS_ANSI -#endif // platform -#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI -#endif // DOCTEST_CONFIG_COLORS_NONE - -namespace doctest_detail_test_suite_ns { -// holds the current test suite -doctest::detail::TestSuite& getCurrentTestSuite() { - static doctest::detail::TestSuite data; - return data; -} -} // namespace doctest_detail_test_suite_ns - -namespace doctest { -namespace { - // the int (priority) is part of the key for automatic sorting - sadly one can register a - // reporter with a duplicate name and a different priority but hopefully that won't happen often :| - typedef std::map, reporterCreatorFunc> reporterMap; - - reporterMap& getReporters() { - static reporterMap data; - return data; - } - reporterMap& getListeners() { - static reporterMap data; - return data; - } -} // namespace -namespace detail { -#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ - for(auto& curr_rep : g_cs->reporters_currently_used) \ - curr_rep->function(__VA_ARGS__) - - bool checkIfShouldThrow(assertType::Enum at) { - if(at & assertType::is_require) //!OCLINT bitwise operator in conditional - return true; - - if((at & assertType::is_check) //!OCLINT bitwise operator in conditional - && getContextOptions()->abort_after > 0 && - (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= - getContextOptions()->abort_after) - return true; - - return false; - } - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - DOCTEST_NORETURN void throwException() { - g_cs->shouldLogCurrentException = false; - throw TestFailureException(); - } // NOLINT(cert-err60-cpp) -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - void throwException() {} -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS -} // namespace detail - -namespace { - using namespace detail; - // matching of a string against a wildcard mask (case sensitivity configurable) taken from - // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing - int wildcmp(const char* str, const char* wild, bool caseSensitive) { - const char* cp = str; - const char* mp = wild; - - while((*str) && (*wild != '*')) { - if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && - (*wild != '?')) { - return 0; - } - wild++; - str++; - } - - while(*str) { - if(*wild == '*') { - if(!*++wild) { - return 1; - } - mp = wild; - cp = str + 1; - } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || - (*wild == '?')) { - wild++; - str++; - } else { - wild = mp; //!OCLINT parameter reassignment - str = cp++; //!OCLINT parameter reassignment - } - } - - while(*wild == '*') { - wild++; - } - return !*wild; - } - - //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html - //unsigned hashStr(unsigned const char* str) { - // unsigned long hash = 5381; - // char c; - // while((c = *str++)) - // hash = ((hash << 5) + hash) + c; // hash * 33 + c - // return hash; - //} - - // checks if the name matches any of the filters (and can be configured what to do when empty) - bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, - bool caseSensitive) { - if(filters.empty() && matchEmpty) - return true; - for(auto& curr : filters) - if(wildcmp(name, curr.c_str(), caseSensitive)) - return true; - return false; - } -} // namespace -namespace detail { - - Subcase::Subcase(const String& name, const char* file, int line) - : m_signature({name, file, line}) { - ContextState* s = g_cs; - - // check subcase filters - if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { - if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) - return; - if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) - return; - } - - // if a Subcase on the same level has already been entered - if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { - s->should_reenter = true; - return; - } - - // push the current signature to the stack so we can check if the - // current stack + the current new subcase have been traversed - s->subcasesStack.push_back(m_signature); - if(s->subcasesPassed.count(s->subcasesStack) != 0) { - // pop - revert to previous stack since we've already passed this - s->subcasesStack.pop_back(); - return; - } - - s->subcasesCurrentMaxLevel = s->subcasesStack.size(); - m_entered = true; - - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); - } - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - - Subcase::~Subcase() { - if(m_entered) { - // only mark the subcase stack as passed if no subcases have been skipped - if(g_cs->should_reenter == false) - g_cs->subcasesPassed.insert(g_cs->subcasesStack); - g_cs->subcasesStack.pop_back(); - -#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L - if(std::uncaught_exceptions() > 0 -#else - if(std::uncaught_exception() -#endif - && g_cs->shouldLogCurrentException) { - DOCTEST_ITERATE_THROUGH_REPORTERS( - test_case_exception, {"exception thrown in subcase - will translate later " - "when the whole test case has been exited (cannot " - "translate while there is an active exception)", - false}); - g_cs->shouldLogCurrentException = false; - } - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); - } - } - - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP - DOCTEST_MSVC_SUPPRESS_WARNING_POP - - Subcase::operator bool() const { return m_entered; } - - Result::Result(bool passed, const String& decomposition) - : m_passed(passed) - , m_decomp(decomposition) {} - - ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) - : m_at(at) {} - - TestSuite& TestSuite::operator*(const char* in) { - m_test_suite = in; - // clear state - m_description = nullptr; - m_skip = false; - m_may_fail = false; - m_should_fail = false; - m_expected_failures = 0; - m_timeout = 0; - return *this; - } - - TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type, int template_id) { - m_file = file; - m_line = line; - m_name = nullptr; // will be later overridden in operator* - m_test_suite = test_suite.m_test_suite; - m_description = test_suite.m_description; - m_skip = test_suite.m_skip; - m_may_fail = test_suite.m_may_fail; - m_should_fail = test_suite.m_should_fail; - m_expected_failures = test_suite.m_expected_failures; - m_timeout = test_suite.m_timeout; - - m_test = test; - m_type = type; - m_template_id = template_id; - } - - TestCase::TestCase(const TestCase& other) - : TestCaseData() { - *this = other; - } - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function - DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice - TestCase& TestCase::operator=(const TestCase& other) { - static_cast(*this) = static_cast(other); - - m_test = other.m_test; - m_type = other.m_type; - m_template_id = other.m_template_id; - m_full_name = other.m_full_name; - - if(m_template_id != -1) - m_name = m_full_name.c_str(); - return *this; - } - DOCTEST_MSVC_SUPPRESS_WARNING_POP - - TestCase& TestCase::operator*(const char* in) { - m_name = in; - // make a new name with an appended type for templated test case - if(m_template_id != -1) { - m_full_name = String(m_name) + m_type; - // redirect the name to point to the newly constructed full name - m_name = m_full_name.c_str(); - } - return *this; - } - - bool TestCase::operator<(const TestCase& other) const { - if(m_line != other.m_line) - return m_line < other.m_line; - const int file_cmp = m_file.compare(other.m_file); - if(file_cmp != 0) - return file_cmp < 0; - return m_template_id < other.m_template_id; - } -} // namespace detail -namespace { - using namespace detail; - // for sorting tests by file/line - bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { - // this is needed because MSVC gives different case for drive letters - // for __FILE__ when evaluated in a header and a source file - const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); - if(res != 0) - return res < 0; - if(lhs->m_line != rhs->m_line) - return lhs->m_line < rhs->m_line; - return lhs->m_template_id < rhs->m_template_id; - } - - // for sorting tests by suite/file/line - bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { - const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); - if(res != 0) - return res < 0; - return fileOrderComparator(lhs, rhs); - } - - // for sorting tests by name/suite/file/line - bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { - const int res = std::strcmp(lhs->m_name, rhs->m_name); - if(res != 0) - return res < 0; - return suiteOrderComparator(lhs, rhs); - } - - // all the registered tests - std::set& getRegisteredTests() { - static std::set data; - return data; - } - -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - HANDLE g_stdoutHandle; - WORD g_origFgAttrs; - WORD g_origBgAttrs; - bool g_attrsInitted = false; - - int colors_init() { - if(!g_attrsInitted) { - g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - g_attrsInitted = true; - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); - g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | - BACKGROUND_BLUE | BACKGROUND_INTENSITY); - g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | - FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } - return 0; - } - - int dumy_init_console_colors = colors_init(); -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - void color_to_stream(std::ostream& s, Color::Enum code) { - ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS - ((void)code); // for DOCTEST_CONFIG_COLORS_NONE -#ifdef DOCTEST_CONFIG_COLORS_ANSI - if(g_no_colors || - (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) - return; - - auto col = ""; - // clang-format off - switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement - case Color::Red: col = "[0;31m"; break; - case Color::Green: col = "[0;32m"; break; - case Color::Blue: col = "[0;34m"; break; - case Color::Cyan: col = "[0;36m"; break; - case Color::Yellow: col = "[0;33m"; break; - case Color::Grey: col = "[1;30m"; break; - case Color::LightGrey: col = "[0;37m"; break; - case Color::BrightRed: col = "[1;31m"; break; - case Color::BrightGreen: col = "[1;32m"; break; - case Color::BrightWhite: col = "[1;37m"; break; - case Color::Bright: // invalid - case Color::None: - case Color::White: - default: col = "[0m"; - } - // clang-format on - s << "\033" << col; -#endif // DOCTEST_CONFIG_COLORS_ANSI - -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - if(g_no_colors || - (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) - return; - -#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) - - // clang-format off - switch (code) { - case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; - case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; - case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; - case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; - case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; - case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; - case Color::Grey: DOCTEST_SET_ATTR(0); break; - case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; - case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; - case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; - case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; - case Color::None: - case Color::Bright: // invalid - default: DOCTEST_SET_ATTR(g_origFgAttrs); - } - // clang-format on -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - - std::vector& getExceptionTranslators() { - static std::vector data; - return data; - } - - String translateActiveException() { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - String res; - auto& translators = getExceptionTranslators(); - for(auto& curr : translators) - if(curr->translate(res)) - return res; - // clang-format off - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") - try { - throw; - } catch(std::exception& ex) { - return ex.what(); - } catch(std::string& msg) { - return msg.c_str(); - } catch(const char* msg) { - return msg; - } catch(...) { - return "unknown exception"; - } - DOCTEST_GCC_SUPPRESS_WARNING_POP -// clang-format on -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - return ""; -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } -} // namespace - -namespace detail { - // used by the macros for registering tests - int regTest(const TestCase& tc) { - getRegisteredTests().insert(tc); - return 0; - } - - // sets the current test suite - int setTestSuite(const TestSuite& ts) { - doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; - return 0; - } - -#ifdef DOCTEST_IS_DEBUGGER_ACTIVE - bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } -#else // DOCTEST_IS_DEBUGGER_ACTIVE -#ifdef DOCTEST_PLATFORM_MAC - // The following function is taken directly from the following technical note: - // https://developer.apple.com/library/archive/qa/qa1361/_index.html - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive() { - int mib[4]; - kinfo_proc info; - size_t size; - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - info.kp_proc.p_flag = 0; - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); - // Call sysctl. - size = sizeof(info); - if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { - std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; - return false; - } - // We're being debugged if the P_TRACED flag is set. - return ((info.kp_proc.p_flag & P_TRACED) != 0); - } -#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) - bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } -#else - bool isDebuggerActive() { return false; } -#endif // Platform -#endif // DOCTEST_IS_DEBUGGER_ACTIVE - - void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { - if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == - getExceptionTranslators().end()) - getExceptionTranslators().push_back(et); - } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, char* in) { *s << in; } - void toStream(std::ostream* s, const char* in) { *s << in; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } - void toStream(std::ostream* s, float in) { *s << in; } - void toStream(std::ostream* s, double in) { *s << in; } - void toStream(std::ostream* s, double long in) { *s << in; } - - void toStream(std::ostream* s, char in) { *s << in; } - void toStream(std::ostream* s, char signed in) { *s << in; } - void toStream(std::ostream* s, char unsigned in) { *s << in; } - void toStream(std::ostream* s, int short in) { *s << in; } - void toStream(std::ostream* s, int short unsigned in) { *s << in; } - void toStream(std::ostream* s, int in) { *s << in; } - void toStream(std::ostream* s, int unsigned in) { *s << in; } - void toStream(std::ostream* s, int long in) { *s << in; } - void toStream(std::ostream* s, int long unsigned in) { *s << in; } - void toStream(std::ostream* s, int long long in) { *s << in; } - void toStream(std::ostream* s, int long long unsigned in) { *s << in; } - - DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() - - ContextScopeBase::ContextScopeBase() { - g_infoContexts.push_back(this); - } - - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 - DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - - // destroy cannot be inlined into the destructor because that would mean calling stringify after - // ContextScope has been destroyed (base class destructors run after derived class destructors). - // Instead, ContextScope calls this method directly from its destructor. - void ContextScopeBase::destroy() { -#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L - if(std::uncaught_exceptions() > 0) { -#else - if(std::uncaught_exception()) { -#endif - std::ostringstream s; - this->stringify(&s); - g_cs->stringifiedContexts.push_back(s.str().c_str()); - } - g_infoContexts.pop_back(); - } - - DOCTEST_CLANG_SUPPRESS_WARNING_POP - DOCTEST_GCC_SUPPRESS_WARNING_POP - DOCTEST_MSVC_SUPPRESS_WARNING_POP -} // namespace detail -namespace { - using namespace detail; - -#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) - struct FatalConditionHandler - { - void reset() {} - }; -#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH - - void reportFatal(const std::string&); - -#ifdef DOCTEST_PLATFORM_WINDOWS - - struct SignalDefs - { - DWORD id; - const char* name; - }; - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - SignalDefs signalDefs[] = { - {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, - {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, - {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, - {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, - }; - - struct FatalConditionHandler - { - static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { - for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); - break; - } - } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; - } - - FatalConditionHandler() { - isSet = true; - // 32k seems enough for doctest to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - // Register an unhandled exception filter - previousTop = SetUnhandledExceptionFilter(handleException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } - - static void reset() { - if(isSet) { - // Unregister handler and restore the old guarantee - SetUnhandledExceptionFilter(previousTop); - SetThreadStackGuarantee(&guaranteeSize); - previousTop = nullptr; - isSet = false; - } - } - - ~FatalConditionHandler() { reset(); } - - private: - static bool isSet; - static ULONG guaranteeSize; - static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; - }; - - bool FatalConditionHandler::isSet = false; - ULONG FatalConditionHandler::guaranteeSize = 0; - LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; - -#else // DOCTEST_PLATFORM_WINDOWS - - struct SignalDefs - { - int id; - const char* name; - }; - SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, - {SIGILL, "SIGILL - Illegal instruction signal"}, - {SIGFPE, "SIGFPE - Floating point error signal"}, - {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, - {SIGTERM, "SIGTERM - Termination request signal"}, - {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; - - struct FatalConditionHandler - { - static bool isSet; - static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; - static stack_t oldSigStack; - static char altStackMem[4 * SIGSTKSZ]; - - static void handleSignal(int sig) { - const char* name = ""; - for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - SignalDefs& def = signalDefs[i]; - if(sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise(sig); - } - - FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = sizeof(altStackMem); - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = {}; - sa.sa_handler = handleSignal; // NOLINT - sa.sa_flags = SA_ONSTACK; - for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - - ~FatalConditionHandler() { reset(); } - static void reset() { - if(isSet) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - }; - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[] = {}; - -#endif // DOCTEST_PLATFORM_WINDOWS -#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH - -} // namespace - -namespace { - using namespace detail; - -#ifdef DOCTEST_PLATFORM_WINDOWS -#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) -#else - // TODO: integration with XCode and other IDEs -#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) -#endif // Platform - - void addAssert(assertType::Enum at) { - if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional - g_cs->numAssertsCurrentTest_atomic++; - } - - void addFailedAssert(assertType::Enum at) { - if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional - g_cs->numAssertsFailedCurrentTest_atomic++; - } - -#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) - void reportFatal(const std::string& message) { - g_cs->failure_flags |= TestCaseFailureReason::Crash; - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); - - while(g_cs->subcasesStack.size()) { - g_cs->subcasesStack.pop_back(); - DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); - } - - g_cs->finalizeTestCaseData(); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); - } -#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH -} // namespace -namespace detail { - - ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, - const char* exception_type, const char* exception_string) { - m_test_case = g_cs->currentTest; - m_at = at; - m_file = file; - m_line = line; - m_expr = expr; - m_failed = true; - m_threw = false; - m_threw_as = false; - m_exception_type = exception_type; - m_exception_string = exception_string; -#if DOCTEST_MSVC - if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC - ++m_expr; -#endif // MSVC - } - - void ResultBuilder::setResult(const Result& res) { - m_decomp = res.m_decomp; - m_failed = !res.m_passed; - } - - void ResultBuilder::translateException() { - m_threw = true; - m_exception = translateActiveException(); - } - - bool ResultBuilder::log() { - if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional - m_failed = !m_threw; - } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT - m_failed = !m_threw_as || (m_exception != m_exception_string); - } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional - m_failed = !m_threw_as; - } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - m_failed = m_exception != m_exception_string; - } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional - m_failed = m_threw; - } - - if(m_exception.size()) - m_exception = String("\"") + m_exception + "\""; - - if(is_running_in_test) { - addAssert(m_at); - DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); - - if(m_failed) - addFailedAssert(m_at); - } else if(m_failed) { - failed_out_of_a_testing_context(*this); - } - - return m_failed && isDebuggerActive() && - !getContextOptions()->no_breaks; // break into debugger - } - - void ResultBuilder::react() const { - if(m_failed && checkIfShouldThrow(m_at)) - throwException(); - } - - void failed_out_of_a_testing_context(const AssertData& ad) { - if(g_cs->ah) - g_cs->ah(ad); - else - std::abort(); - } - - void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, - Result result) { - bool failed = !result.m_passed; - - // ################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ################################################################################### - DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); - DOCTEST_ASSERT_IN_TESTS(result.m_decomp); - } - - MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { - m_stream = getTlsOss(); - m_file = file; - m_line = line; - m_severity = severity; - } - - IExceptionTranslator::IExceptionTranslator() = default; - IExceptionTranslator::~IExceptionTranslator() = default; - - bool MessageBuilder::log() { - m_string = getTlsOssResult(); - DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); - - const bool isWarn = m_severity & assertType::is_warn; - - // warn is just a message in this context so we don't treat it as an assert - if(!isWarn) { - addAssert(m_severity); - addFailedAssert(m_severity); - } - - return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break - } - - void MessageBuilder::react() { - if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional - throwException(); - } - - MessageBuilder::~MessageBuilder() = default; -} // namespace detail -namespace { - using namespace detail; - - template - DOCTEST_NORETURN void throw_exception(Ex const& e) { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - throw e; -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - std::cerr << "doctest will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - -#ifndef DOCTEST_INTERNAL_ERROR -#define DOCTEST_INTERNAL_ERROR(msg) \ - throw_exception(std::logic_error( \ - __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) -#endif // DOCTEST_INTERNAL_ERROR - - // clang-format off - -// ================================================================================================= -// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp -// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. -// ================================================================================================= - - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - - void encodeTo( std::ostream& os ) const; - - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); - - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ); - - ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; - ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; - - ~ScopedElement(); - - ScopedElement& writeText( std::string const& text, bool indent = true ); - - template - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - private: - mutable XmlWriter* m_writer = nullptr; - }; - - XmlWriter( std::ostream& os = std::cout ); - ~XmlWriter(); - - XmlWriter( XmlWriter const& ) = delete; - XmlWriter& operator=( XmlWriter const& ) = delete; - - XmlWriter& startElement( std::string const& name ); - - ScopedElement scopedElement( std::string const& name ); - - XmlWriter& endElement(); - - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, const char* attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - std::stringstream rss; - rss << attribute; - return writeAttribute( name, rss.str() ); - } - - XmlWriter& writeText( std::string const& text, bool indent = true ); - - //XmlWriter& writeComment( std::string const& text ); - - //void writeStylesheetRef( std::string const& url ); - - //XmlWriter& writeBlankLine(); - - void ensureTagClosed(); - - private: - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector m_tags; - std::string m_indent; - std::ostream& m_os; - }; - -// ================================================================================================= -// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp -// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. -// ================================================================================================= - -using uchar = unsigned char; - -namespace { - - size_t trailingBytes(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return 2; - } - if ((c & 0xF0) == 0xE0) { - return 3; - } - if ((c & 0xF8) == 0xF0) { - return 4; - } - DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - uint32_t headerValue(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return c & 0x1F; - } - if ((c & 0xF0) == 0xE0) { - return c & 0x0F; - } - if ((c & 0xF8) == 0xF0) { - return c & 0x07; - } - DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - void hexEscapeChar(std::ostream& os, unsigned char c) { - std::ios_base::fmtflags f(os.flags()); - os << "\\x" - << std::uppercase << std::hex << std::setfill('0') << std::setw(2) - << static_cast(c); - os.flags(f); - } - -} // anonymous namespace - - XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) - : m_str( str ), - m_forWhat( forWhat ) - {} - - void XmlEncode::encodeTo( std::ostream& os ) const { - // Apostrophe escaping not necessary if we always use " to write attributes - // (see: https://www.w3.org/TR/xml/#syntax) - - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - uchar c = m_str[idx]; - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; - - case '>': - // See: https://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; - break; - - case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; - break; - - default: - // Check for control characters and invalid utf-8 - - // Escape control characters in standard ascii - // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); - break; - } - - // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; - break; - } - - // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX - // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); - break; - } - - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); - break; - } - // The header is valid, check data - // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) - bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - uchar nc = m_str[idx + n]; - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); - } - - if ( - // Wrong bit pattern of following bytes - (!valid) || - // Overlong encodings - (value < 0x80) || - ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant - (0x800 < value && value < 0x10000 && encBytes > 3) || - // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); - break; - } - - // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } - idx += encBytes - 1; - break; - } - } - } - - std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; - } - - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} - - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT - : m_writer( other.m_writer ){ - other.m_writer = nullptr; - } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { - if ( m_writer ) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - return *this; - } - - - XmlWriter::ScopedElement::~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } - - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { - m_writer->writeText( text, indent ); - return *this; - } - - XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) - { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while( !m_tags.empty() ) - endElement(); - } - - XmlWriter& XmlWriter::startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - m_os << m_indent << '<' << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; - } - - XmlWriter& XmlWriter::endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } - else { - m_os << m_indent << ""; - } - m_os << std::endl; - m_tags.pop_back(); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { - if( !name.empty() && attribute && attribute[0] != '\0' ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - m_os << m_indent; - m_os << XmlEncode( text ); - m_needsNewline = true; - } - return *this; - } - - //XmlWriter& XmlWriter::writeComment( std::string const& text ) { - // ensureTagClosed(); - // m_os << m_indent << ""; - // m_needsNewline = true; - // return *this; - //} - - //void XmlWriter::writeStylesheetRef( std::string const& url ) { - // m_os << "\n"; - //} - - //XmlWriter& XmlWriter::writeBlankLine() { - // ensureTagClosed(); - // m_os << '\n'; - // return *this; - //} - - void XmlWriter::ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << ">" << std::endl; - m_tagIsOpen = false; - } - } - - void XmlWriter::writeDeclaration() { - m_os << "\n"; - } - - void XmlWriter::newlineIfNecessary() { - if( m_needsNewline ) { - m_os << std::endl; - m_needsNewline = false; - } - } - -// ================================================================================================= -// End of copy-pasted code from Catch -// ================================================================================================= - - // clang-format on - - struct XmlReporter : public IReporter - { - XmlWriter xml; - std::mutex mutex; - - // caching pointers/references to objects of these types - safe to do - const ContextOptions& opt; - const TestCaseData* tc = nullptr; - - XmlReporter(const ContextOptions& co) - : xml(*co.cout) - , opt(co) {} - - void log_contexts() { - int num_contexts = get_num_active_contexts(); - if(num_contexts) { - auto contexts = get_active_contexts(); - std::stringstream ss; - for(int i = 0; i < num_contexts; ++i) { - contexts[i]->stringify(&ss); - xml.scopedElement("Info").writeText(ss.str()); - ss.str(""); - } - } - } - - unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } - - void test_case_start_impl(const TestCaseData& in) { - bool open_ts_tag = false; - if(tc != nullptr) { // we have already opened a test suite - if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { - xml.endElement(); - open_ts_tag = true; - } - } - else { - open_ts_tag = true; // first test case ==> first test suite - } - - if(open_ts_tag) { - xml.startElement("TestSuite"); - xml.writeAttribute("name", in.m_test_suite); - } - - tc = ∈ - xml.startElement("TestCase") - .writeAttribute("name", in.m_name) - .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) - .writeAttribute("line", line(in.m_line)) - .writeAttribute("description", in.m_description); - - if(Approx(in.m_timeout) != 0) - xml.writeAttribute("timeout", in.m_timeout); - if(in.m_may_fail) - xml.writeAttribute("may_fail", true); - if(in.m_should_fail) - xml.writeAttribute("should_fail", true); - } - - // ========================================================================================= - // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE - // ========================================================================================= - - void report_query(const QueryData& in) override { - test_run_start(); - if(opt.list_reporters) { - for(auto& curr : getListeners()) - xml.scopedElement("Listener") - .writeAttribute("priority", curr.first.first) - .writeAttribute("name", curr.first.second); - for(auto& curr : getReporters()) - xml.scopedElement("Reporter") - .writeAttribute("priority", curr.first.first) - .writeAttribute("name", curr.first.second); - } else if(opt.count || opt.list_test_cases) { - for(unsigned i = 0; i < in.num_data; ++i) { - xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) - .writeAttribute("testsuite", in.data[i]->m_test_suite) - .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) - .writeAttribute("line", line(in.data[i]->m_line)); - } - xml.scopedElement("OverallResultsTestCases") - .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); - } else if(opt.list_test_suites) { - for(unsigned i = 0; i < in.num_data; ++i) - xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); - xml.scopedElement("OverallResultsTestCases") - .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); - xml.scopedElement("OverallResultsTestSuites") - .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); - } - xml.endElement(); - } - - void test_run_start() override { - // remove .exe extension - mainly to have the same output on UNIX and Windows - std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); -#ifdef DOCTEST_PLATFORM_WINDOWS - if(binary_name.rfind(".exe") != std::string::npos) - binary_name = binary_name.substr(0, binary_name.length() - 4); -#endif // DOCTEST_PLATFORM_WINDOWS - - xml.startElement("doctest").writeAttribute("binary", binary_name); - if(opt.no_version == false) - xml.writeAttribute("version", DOCTEST_VERSION_STR); - - // only the consequential ones (TODO: filters) - xml.scopedElement("Options") - .writeAttribute("order_by", opt.order_by.c_str()) - .writeAttribute("rand_seed", opt.rand_seed) - .writeAttribute("first", opt.first) - .writeAttribute("last", opt.last) - .writeAttribute("abort_after", opt.abort_after) - .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) - .writeAttribute("case_sensitive", opt.case_sensitive) - .writeAttribute("no_throw", opt.no_throw) - .writeAttribute("no_skip", opt.no_skip); - } - - void test_run_end(const TestRunStats& p) override { - if(tc) // the TestSuite tag - only if there has been at least 1 test case - xml.endElement(); - - xml.scopedElement("OverallResultsAsserts") - .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) - .writeAttribute("failures", p.numAssertsFailed); - - xml.startElement("OverallResultsTestCases") - .writeAttribute("successes", - p.numTestCasesPassingFilters - p.numTestCasesFailed) - .writeAttribute("failures", p.numTestCasesFailed); - if(opt.no_skipped_summary == false) - xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); - xml.endElement(); - - xml.endElement(); - } - - void test_case_start(const TestCaseData& in) override { - test_case_start_impl(in); - xml.ensureTagClosed(); - } - - void test_case_reenter(const TestCaseData&) override {} - - void test_case_end(const CurrentTestCaseStats& st) override { - xml.startElement("OverallResultsAsserts") - .writeAttribute("successes", - st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) - .writeAttribute("failures", st.numAssertsFailedCurrentTest); - if(opt.duration) - xml.writeAttribute("duration", st.seconds); - if(tc->m_expected_failures) - xml.writeAttribute("expected_failures", tc->m_expected_failures); - xml.endElement(); - - xml.endElement(); - } - - void test_case_exception(const TestCaseException& e) override { - std::lock_guard lock(mutex); - - xml.scopedElement("Exception") - .writeAttribute("crash", e.is_crash) - .writeText(e.error_string.c_str()); - } - - void subcase_start(const SubcaseSignature& in) override { - std::lock_guard lock(mutex); - - xml.startElement("SubCase") - .writeAttribute("name", in.m_name) - .writeAttribute("filename", skipPathFromFilename(in.m_file)) - .writeAttribute("line", line(in.m_line)); - xml.ensureTagClosed(); - } - - void subcase_end() override { xml.endElement(); } - - void log_assert(const AssertData& rb) override { - if(!rb.m_failed && !opt.success) - return; - - std::lock_guard lock(mutex); - - xml.startElement("Expression") - .writeAttribute("success", !rb.m_failed) - .writeAttribute("type", assertString(rb.m_at)) - .writeAttribute("filename", skipPathFromFilename(rb.m_file)) - .writeAttribute("line", line(rb.m_line)); - - xml.scopedElement("Original").writeText(rb.m_expr); - - if(rb.m_threw) - xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); - - if(rb.m_at & assertType::is_throws_as) - xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); - if(rb.m_at & assertType::is_throws_with) - xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); - if((rb.m_at & assertType::is_normal) && !rb.m_threw) - xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); - - log_contexts(); - - xml.endElement(); - } - - void log_message(const MessageData& mb) override { - std::lock_guard lock(mutex); - - xml.startElement("Message") - .writeAttribute("type", failureString(mb.m_severity)) - .writeAttribute("filename", skipPathFromFilename(mb.m_file)) - .writeAttribute("line", line(mb.m_line)); - - xml.scopedElement("Text").writeText(mb.m_string.c_str()); - - log_contexts(); - - xml.endElement(); - } - - void test_case_skipped(const TestCaseData& in) override { - if(opt.no_skipped_summary == false) { - test_case_start_impl(in); - xml.writeAttribute("skipped", "true"); - xml.endElement(); - } - } - }; - - DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); - - void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { - if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == - 0) //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " - << Color::None; - - if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; - } else if((rb.m_at & assertType::is_throws_as) && - (rb.m_at & assertType::is_throws_with)) { //!OCLINT - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; - if(rb.m_threw) { - if(!rb.m_failed) { - s << "threw as expected!\n"; - } else { - s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; - } - } else { - s << "did NOT throw at all!\n"; - } - } else if(rb.m_at & - assertType::is_throws_as) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " - << rb.m_exception_type << " ) " << Color::None - << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & - assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\" ) " << Color::None - << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan - << rb.m_exception << "\n"; - } else { - s << (rb.m_threw ? "THREW exception: " : - (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); - if(rb.m_threw) - s << rb.m_exception << "\n"; - else - s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; - } - } - - // TODO: - // - log_contexts() - // - log_message() - // - respond to queries - // - honor remaining options - // - more attributes in tags - struct JUnitReporter : public IReporter - { - XmlWriter xml; - std::mutex mutex; - Timer timer; - std::vector deepestSubcaseStackNames; - - struct JUnitTestCaseData - { -DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") // gmtime - static std::string getCurrentTimestamp() { - // Beware, this is not reentrant because of backward compatibility issues - // Also, UTC only, again because of backward compatibility (%z is C++11) - time_t rawtime; - std::time(&rawtime); - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - - std::tm* timeInfo; - timeInfo = std::gmtime(&rawtime); - - char timeStamp[timeStampSize]; - const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; - - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); - return std::string(timeStamp); - } -DOCTEST_CLANG_SUPPRESS_WARNING_POP - - struct JUnitTestMessage - { - JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) - : message(_message), type(_type), details(_details) {} - - JUnitTestMessage(const std::string& _message, const std::string& _details) - : message(_message), type(), details(_details) {} - - std::string message, type, details; - }; - - struct JUnitTestCase - { - JUnitTestCase(const std::string& _classname, const std::string& _name) - : classname(_classname), name(_name), time(0), failures() {} - - std::string classname, name; - double time; - std::vector failures, errors; - }; - - void add(const std::string& classname, const std::string& name) { - testcases.emplace_back(classname, name); - } - - void appendSubcaseNamesToLastTestcase(std::vector nameStack) { - for(auto& curr: nameStack) - if(curr.size()) - testcases.back().name += std::string("/") + curr.c_str(); - } - - void addTime(double time) { - if(time < 1e-4) - time = 0; - testcases.back().time = time; - totalSeconds += time; - } - - void addFailure(const std::string& message, const std::string& type, const std::string& details) { - testcases.back().failures.emplace_back(message, type, details); - ++totalFailures; - } - - void addError(const std::string& message, const std::string& details) { - testcases.back().errors.emplace_back(message, details); - ++totalErrors; - } - - std::vector testcases; - double totalSeconds = 0; - int totalErrors = 0, totalFailures = 0; - }; - - JUnitTestCaseData testCaseData; - - // caching pointers/references to objects of these types - safe to do - const ContextOptions& opt; - const TestCaseData* tc = nullptr; - - JUnitReporter(const ContextOptions& co) - : xml(*co.cout) - , opt(co) {} - - unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } - - // ========================================================================================= - // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE - // ========================================================================================= - - void report_query(const QueryData&) override {} - - void test_run_start() override {} - - void test_run_end(const TestRunStats& p) override { - // remove .exe extension - mainly to have the same output on UNIX and Windows - std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); -#ifdef DOCTEST_PLATFORM_WINDOWS - if(binary_name.rfind(".exe") != std::string::npos) - binary_name = binary_name.substr(0, binary_name.length() - 4); -#endif // DOCTEST_PLATFORM_WINDOWS - xml.startElement("testsuites"); - xml.startElement("testsuite").writeAttribute("name", binary_name) - .writeAttribute("errors", testCaseData.totalErrors) - .writeAttribute("failures", testCaseData.totalFailures) - .writeAttribute("tests", p.numAsserts); - if(opt.no_time_in_output == false) { - xml.writeAttribute("time", testCaseData.totalSeconds); - xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); - } - if(opt.no_version == false) - xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); - - for(const auto& testCase : testCaseData.testcases) { - xml.startElement("testcase") - .writeAttribute("classname", testCase.classname) - .writeAttribute("name", testCase.name); - if(opt.no_time_in_output == false) - xml.writeAttribute("time", testCase.time); - // This is not ideal, but it should be enough to mimic gtest's junit output. - xml.writeAttribute("status", "run"); - - for(const auto& failure : testCase.failures) { - xml.scopedElement("failure") - .writeAttribute("message", failure.message) - .writeAttribute("type", failure.type) - .writeText(failure.details, false); - } - - for(const auto& error : testCase.errors) { - xml.scopedElement("error") - .writeAttribute("message", error.message) - .writeText(error.details); - } - - xml.endElement(); - } - xml.endElement(); - xml.endElement(); - } - - void test_case_start(const TestCaseData& in) override { - testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); - timer.start(); - } - - void test_case_reenter(const TestCaseData& in) override { - testCaseData.addTime(timer.getElapsedSeconds()); - testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); - deepestSubcaseStackNames.clear(); - - timer.start(); - testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); - } - - void test_case_end(const CurrentTestCaseStats&) override { - testCaseData.addTime(timer.getElapsedSeconds()); - testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); - deepestSubcaseStackNames.clear(); - } - - void test_case_exception(const TestCaseException& e) override { - std::lock_guard lock(mutex); - testCaseData.addError("exception", e.error_string.c_str()); - } - - void subcase_start(const SubcaseSignature& in) override { - std::lock_guard lock(mutex); - deepestSubcaseStackNames.push_back(in.m_name); - } - - void subcase_end() override {} - - void log_assert(const AssertData& rb) override { - if(!rb.m_failed) // report only failures & ignore the `success` option - return; - - std::lock_guard lock(mutex); - - std::ostringstream os; - os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") - << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; - - fulltext_log_assert_to_stream(os, rb); - testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); - } - - void log_message(const MessageData&) override {} - - void test_case_skipped(const TestCaseData&) override {} - }; - - DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); - - struct Whitespace - { - int nrSpaces; - explicit Whitespace(int nr) - : nrSpaces(nr) {} - }; - - std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { - if(ws.nrSpaces != 0) - out << std::setw(ws.nrSpaces) << ' '; - return out; - } - - struct ConsoleReporter : public IReporter - { - std::ostream& s; - bool hasLoggedCurrentTestStart; - std::vector subcasesStack; - size_t currentSubcaseLevel; - std::mutex mutex; - - // caching pointers/references to objects of these types - safe to do - const ContextOptions& opt; - const TestCaseData* tc; - - ConsoleReporter(const ContextOptions& co) - : s(*co.cout) - , opt(co) {} - - ConsoleReporter(const ContextOptions& co, std::ostream& ostr) - : s(ostr) - , opt(co) {} - - // ========================================================================================= - // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE - // ========================================================================================= - - void separator_to_stream() { - s << Color::Yellow - << "===============================================================================" - "\n"; - } - - const char* getSuccessOrFailString(bool success, assertType::Enum at, - const char* success_str) { - if(success) - return success_str; - return failureString(at); - } - - Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { - return success ? Color::BrightGreen : - (at & assertType::is_warn) ? Color::Yellow : Color::Red; - } - - void successOrFailColoredStringToStream(bool success, assertType::Enum at, - const char* success_str = "SUCCESS") { - s << getSuccessOrFailColor(success, at) - << getSuccessOrFailString(success, at, success_str) << ": "; - } - - void log_contexts() { - int num_contexts = get_num_active_contexts(); - if(num_contexts) { - auto contexts = get_active_contexts(); - - s << Color::None << " logged: "; - for(int i = 0; i < num_contexts; ++i) { - s << (i == 0 ? "" : " "); - contexts[i]->stringify(&s); - s << "\n"; - } - } - - s << "\n"; - } - - // this was requested to be made virtual so users could override it - virtual void file_line_to_stream(const char* file, int line, - const char* tail = "") { - s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") - << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option - << (opt.gnu_file_line ? ":" : "):") << tail; - } - - void logTestStart() { - if(hasLoggedCurrentTestStart) - return; - - separator_to_stream(); - file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); - if(tc->m_description) - s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; - if(tc->m_test_suite && tc->m_test_suite[0] != '\0') - s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; - if(strncmp(tc->m_name, " Scenario:", 11) != 0) - s << Color::Yellow << "TEST CASE: "; - s << Color::None << tc->m_name << "\n"; - - for(size_t i = 0; i < currentSubcaseLevel; ++i) { - if(subcasesStack[i].m_name[0] != '\0') - s << " " << subcasesStack[i].m_name << "\n"; - } - - if(currentSubcaseLevel != subcasesStack.size()) { - s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; - for(size_t i = 0; i < subcasesStack.size(); ++i) { - if(subcasesStack[i].m_name[0] != '\0') - s << " " << subcasesStack[i].m_name << "\n"; - } - } - - s << "\n"; - - hasLoggedCurrentTestStart = true; - } - - void printVersion() { - if(opt.no_version == false) - s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" - << DOCTEST_VERSION_STR << "\"\n"; - } - - void printIntro() { - printVersion(); - s << Color::Cyan << "[doctest] " << Color::None - << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; - } - - void printHelp() { - int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); - printVersion(); - // clang-format off - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; - s << Color::Cyan << "[doctest] " << Color::None; - s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "filters use wildcards for matching strings\n"; - s << Color::Cyan << "[doctest] " << Color::None; - s << "something passes a filter if any of the strings in a filter matches\n"; -#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; -#endif - s << Color::Cyan << "[doctest]\n" << Color::None; - s << Color::Cyan << "[doctest] " << Color::None; - s << "Query flags - the program quits after them. Available:\n\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " - << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " - << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " - << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " - << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " - << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " - << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; - // ================================================================================== << 79 - s << Color::Cyan << "[doctest] " << Color::None; - s << "The available / options/filters are:\n\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " - << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " - << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " - << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " - << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " - << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " - << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " - << Whitespace(sizePrefixDisplay*1) << "output filename\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " - << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; - s << Whitespace(sizePrefixDisplay*3) << " - by [file/suite/name/rand]\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " - << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " - << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; - s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " - << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; - s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " - << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " - << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; - s << Color::Cyan << "\n[doctest] " << Color::None; - s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " - << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " - << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " - << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " - << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " - << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " - << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " - << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " - << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " - << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " - << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " - << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " - << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " - << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " - << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; - s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " - << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; - // ================================================================================== << 79 - // clang-format on - - s << Color::Cyan << "\n[doctest] " << Color::None; - s << "for more information visit the project documentation\n\n"; - } - - void printRegisteredReporters() { - printVersion(); - auto printReporters = [this] (const reporterMap& reporters, const char* type) { - if(reporters.size()) { - s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; - for(auto& curr : reporters) - s << "priority: " << std::setw(5) << curr.first.first - << " name: " << curr.first.second << "\n"; - } - }; - printReporters(getListeners(), "listeners"); - printReporters(getReporters(), "reporters"); - } - - void list_query_results() { - separator_to_stream(); - if(opt.count || opt.list_test_cases) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - } else if(opt.list_test_suites) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "test suites with unskipped test cases passing the current filters: " - << g_cs->numTestSuitesPassingFilters << "\n"; - } - } - - // ========================================================================================= - // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE - // ========================================================================================= - - void report_query(const QueryData& in) override { - if(opt.version) { - printVersion(); - } else if(opt.help) { - printHelp(); - } else if(opt.list_reporters) { - printRegisteredReporters(); - } else if(opt.count || opt.list_test_cases) { - if(opt.list_test_cases) { - s << Color::Cyan << "[doctest] " << Color::None - << "listing all test case names\n"; - separator_to_stream(); - } - - for(unsigned i = 0; i < in.num_data; ++i) - s << Color::None << in.data[i]->m_name << "\n"; - - separator_to_stream(); - - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - - } else if(opt.list_test_suites) { - s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; - separator_to_stream(); - - for(unsigned i = 0; i < in.num_data; ++i) - s << Color::None << in.data[i]->m_test_suite << "\n"; - - separator_to_stream(); - - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "test suites with unskipped test cases passing the current filters: " - << g_cs->numTestSuitesPassingFilters << "\n"; - } - } - - void test_run_start() override { printIntro(); } - - void test_run_end(const TestRunStats& p) override { - separator_to_stream(); - s << std::dec; - - const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; - s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) - << p.numTestCasesPassingFilters << " | " - << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : - Color::Green) - << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" - << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) - << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; - if(opt.no_skipped_summary == false) { - const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; - s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped - << " skipped" << Color::None; - } - s << "\n"; - s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) - << p.numAsserts << " | " - << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) - << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None - << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) - << p.numAssertsFailed << " failed" << Color::None << " |\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) - << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; - } - - void test_case_start(const TestCaseData& in) override { - hasLoggedCurrentTestStart = false; - tc = ∈ - subcasesStack.clear(); - currentSubcaseLevel = 0; - } - - void test_case_reenter(const TestCaseData&) override { - subcasesStack.clear(); - } - - void test_case_end(const CurrentTestCaseStats& st) override { - // log the preamble of the test case only if there is something - // else to print - something other than that an assert has failed - if(opt.duration || - (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) - logTestStart(); - - if(opt.duration) - s << Color::None << std::setprecision(6) << std::fixed << st.seconds - << " s: " << tc->m_name << "\n"; - - if(st.failure_flags & TestCaseFailureReason::Timeout) - s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) - << std::fixed << tc->m_timeout << "!\n"; - - if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { - s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; - } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { - s << Color::Yellow << "Failed as expected so marking it as not failed\n"; - } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { - s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; - } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { - s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures - << " times so marking it as failed!\n"; - } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { - s << Color::Yellow << "Failed exactly " << tc->m_expected_failures - << " times as expected so marking it as not failed!\n"; - } - if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { - s << Color::Red << "Aborting - too many failed asserts!\n"; - } - s << Color::None; // lgtm [cpp/useless-expression] - } - - void test_case_exception(const TestCaseException& e) override { - logTestStart(); - - file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); - successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : - assertType::is_check); - s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") - << Color::Cyan << e.error_string << "\n"; - - int num_stringified_contexts = get_num_stringified_contexts(); - if(num_stringified_contexts) { - auto stringified_contexts = get_stringified_contexts(); - s << Color::None << " logged: "; - for(int i = num_stringified_contexts; i > 0; --i) { - s << (i == num_stringified_contexts ? "" : " ") - << stringified_contexts[i - 1] << "\n"; - } - } - s << "\n" << Color::None; - } - - void subcase_start(const SubcaseSignature& subc) override { - std::lock_guard lock(mutex); - subcasesStack.push_back(subc); - ++currentSubcaseLevel; - hasLoggedCurrentTestStart = false; - } - - void subcase_end() override { - std::lock_guard lock(mutex); - --currentSubcaseLevel; - hasLoggedCurrentTestStart = false; - } - - void log_assert(const AssertData& rb) override { - if(!rb.m_failed && !opt.success) - return; - - std::lock_guard lock(mutex); - - logTestStart(); - - file_line_to_stream(rb.m_file, rb.m_line, " "); - successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); - - fulltext_log_assert_to_stream(s, rb); - - log_contexts(); - } - - void log_message(const MessageData& mb) override { - std::lock_guard lock(mutex); - - logTestStart(); - - file_line_to_stream(mb.m_file, mb.m_line, " "); - s << getSuccessOrFailColor(false, mb.m_severity) - << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, - "MESSAGE") << ": "; - s << Color::None << mb.m_string << "\n"; - log_contexts(); - } - - void test_case_skipped(const TestCaseData&) override {} - }; - - DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); - -#ifdef DOCTEST_PLATFORM_WINDOWS - struct DebugOutputWindowReporter : public ConsoleReporter - { - DOCTEST_THREAD_LOCAL static std::ostringstream oss; - - DebugOutputWindowReporter(const ContextOptions& co) - : ConsoleReporter(co, oss) {} - -#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ - void func(type arg) override { \ - bool with_col = g_no_colors; \ - g_no_colors = false; \ - ConsoleReporter::func(arg); \ - DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ - oss.str(""); \ - g_no_colors = with_col; \ - } - - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) - DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) - }; - - DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; -#endif // DOCTEST_PLATFORM_WINDOWS - - // the implementation of parseOption() - bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { - // going from the end to the beginning and stopping on the first occurrence from the end - for(int i = argc; i > 0; --i) { - auto index = i - 1; - auto temp = std::strstr(argv[index], pattern); - if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue - // eliminate matches in which the chars before the option are not '-' - bool noBadCharsFound = true; - auto curr = argv[index]; - while(curr != temp) { - if(*curr++ != '-') { - noBadCharsFound = false; - break; - } - } - if(noBadCharsFound && argv[index][0] == '-') { - if(value) { - // parsing the value of an option - temp += strlen(pattern); - const unsigned len = strlen(temp); - if(len) { - *value = temp; - return true; - } - } else { - // just a flag - no value - return true; - } - } - } - } - return false; - } - - // parses an option and returns the string after the '=' character - bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, - const String& defaultVal = String()) { - if(value) - *value = defaultVal; -#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - // offset (normally 3 for "dt-") to skip prefix - if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) - return true; -#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - return parseOptionImpl(argc, argv, pattern, value); - } - - // locates a flag on the command line - bool parseFlag(int argc, const char* const* argv, const char* pattern) { - return parseOption(argc, argv, pattern); - } - - // parses a comma separated list of words after a pattern in one of the arguments in argv - bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, - std::vector& res) { - String filtersString; - if(parseOption(argc, argv, pattern, &filtersString)) { - // tokenize with "," as a separator - // cppcheck-suppress strtokCalled - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string - while(pch != nullptr) { - if(strlen(pch)) - res.push_back(pch); - // uses the strtok() internal state to go to the next token - // cppcheck-suppress strtokCalled - pch = std::strtok(nullptr, ","); - } - DOCTEST_CLANG_SUPPRESS_WARNING_POP - return true; - } - return false; - } - - enum optionType - { - option_bool, - option_int - }; - - // parses an int/bool option from the command line - bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, - int& res) { - String parsedValue; - if(!parseOption(argc, argv, pattern, &parsedValue)) - return false; - - if(type == 0) { - // boolean - const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 - const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 - - // if the value matches any of the positive/negative possibilities - for(unsigned i = 0; i < 4; i++) { - if(parsedValue.compare(positive[i], true) == 0) { - res = 1; //!OCLINT parameter reassignment - return true; - } - if(parsedValue.compare(negative[i], true) == 0) { - res = 0; //!OCLINT parameter reassignment - return true; - } - } - } else { - // integer - // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... - int theInt = std::atoi(parsedValue.c_str()); // NOLINT - if(theInt != 0) { - res = theInt; //!OCLINT parameter reassignment - return true; - } - } - return false; - } -} // namespace - -Context::Context(int argc, const char* const* argv) - : p(new detail::ContextState) { - parseArgs(argc, argv, true); - if(argc) - p->binary_name = argv[0]; -} - -Context::~Context() { - if(g_cs == p) - g_cs = nullptr; - delete p; -} - -void Context::applyCommandLine(int argc, const char* const* argv) { - parseArgs(argc, argv); - if(argc) - p->binary_name = argv[0]; -} - -// parses args -void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { - using namespace detail; - - // clang-format off - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); - parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); - // clang-format on - - int intRes = 0; - String strRes; - -#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ - if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ - parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ - p->var = !!intRes; \ - else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ - p->var = true; \ - else if(withDefaults) \ - p->var = default - -#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ - if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ - parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ - p->var = intRes; \ - else if(withDefaults) \ - p->var = default - -#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ - if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ - parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ - withDefaults) \ - p->var = strRes - - // clang-format off - DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); - DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); - DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); - - DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); - DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); - - DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); - DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); - - DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); - // clang-format on - - if(withDefaults) { - p->help = false; - p->version = false; - p->count = false; - p->list_test_cases = false; - p->list_test_suites = false; - p->list_reporters = false; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { - p->help = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { - p->version = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { - p->count = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { - p->list_test_cases = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { - p->list_test_suites = true; - p->exit = true; - } - if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || - parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { - p->list_reporters = true; - p->exit = true; - } -} - -// allows the user to add procedurally to the filters from the command line -void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } - -// allows the user to clear all filters from the command line -void Context::clearFilters() { - for(auto& curr : p->filters) - curr.clear(); -} - -// allows the user to override procedurally the int/bool options from the command line -void Context::setOption(const char* option, int value) { - setOption(option, toString(value).c_str()); -} - -// allows the user to override procedurally the string options from the command line -void Context::setOption(const char* option, const char* value) { - auto argv = String("-") + option + "=" + value; - auto lvalue = argv.c_str(); - parseArgs(1, &lvalue); -} - -// users should query this in their main() and exit the program if true -bool Context::shouldExit() { return p->exit; } - -void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } - -void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } - -// the main function that does all the filtering and test running -int Context::run() { - using namespace detail; - - // save the old context state in case such was setup - for using asserts out of a testing context - auto old_cs = g_cs; - // this is the current contest - g_cs = p; - is_running_in_test = true; - - g_no_colors = p->no_colors; - p->resetRunData(); - - // stdout by default - p->cout = &std::cout; - p->cerr = &std::cerr; - - // or to a file if specified - std::fstream fstr; - if(p->out.size()) { - fstr.open(p->out.c_str(), std::fstream::out); - p->cout = &fstr; - } - - auto cleanup_and_return = [&]() { - if(fstr.is_open()) - fstr.close(); - - // restore context - g_cs = old_cs; - is_running_in_test = false; - - // we have to free the reporters which were allocated when the run started - for(auto& curr : p->reporters_currently_used) - delete curr; - p->reporters_currently_used.clear(); - - if(p->numTestCasesFailed && !p->no_exitcode) - return EXIT_FAILURE; - return EXIT_SUCCESS; - }; - - // setup default reporter if none is given through the command line - if(p->filters[8].empty()) - p->filters[8].push_back("console"); - - // check to see if any of the registered reporters has been selected - for(auto& curr : getReporters()) { - if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) - p->reporters_currently_used.push_back(curr.second(*g_cs)); - } - - // TODO: check if there is nothing in reporters_currently_used - - // prepend all listeners - for(auto& curr : getListeners()) - p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); - -#ifdef DOCTEST_PLATFORM_WINDOWS - if(isDebuggerActive()) - p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); -#endif // DOCTEST_PLATFORM_WINDOWS - - // handle version, help and no_run - if(p->no_run || p->version || p->help || p->list_reporters) { - DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); - - return cleanup_and_return(); - } - - std::vector testArray; - for(auto& curr : getRegisteredTests()) - testArray.push_back(&curr); - p->numTestCases = testArray.size(); - - // sort the collected records - if(!testArray.empty()) { - if(p->order_by.compare("file", true) == 0) { - std::sort(testArray.begin(), testArray.end(), fileOrderComparator); - } else if(p->order_by.compare("suite", true) == 0) { - std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); - } else if(p->order_by.compare("name", true) == 0) { - std::sort(testArray.begin(), testArray.end(), nameOrderComparator); - } else if(p->order_by.compare("rand", true) == 0) { - std::srand(p->rand_seed); - - // random_shuffle implementation - const auto first = &testArray[0]; - for(size_t i = testArray.size() - 1; i > 0; --i) { - int idxToSwap = std::rand() % (i + 1); // NOLINT - - const auto temp = first[i]; - - first[i] = first[idxToSwap]; - first[idxToSwap] = temp; - } - } - } - - std::set testSuitesPassingFilt; - - bool query_mode = p->count || p->list_test_cases || p->list_test_suites; - std::vector queryResults; - - if(!query_mode) - DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); - - // invoke the registered functions if they match the filter criteria (or just count them) - for(auto& curr : testArray) { - const auto& tc = *curr; - - bool skip_me = false; - if(tc.m_skip && !p->no_skip) - skip_me = true; - - if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) - skip_me = true; - if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) - skip_me = true; - if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) - skip_me = true; - if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) - skip_me = true; - if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) - skip_me = true; - if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) - skip_me = true; - - if(!skip_me) - p->numTestCasesPassingFilters++; - - // skip the test if it is not in the execution range - if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || - (p->first > p->numTestCasesPassingFilters)) - skip_me = true; - - if(skip_me) { - if(!query_mode) - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); - continue; - } - - // do not execute the test if we are to only count the number of filter passing tests - if(p->count) - continue; - - // print the name of the test and don't execute it - if(p->list_test_cases) { - queryResults.push_back(&tc); - continue; - } - - // print the name of the test suite if not done already and don't execute it - if(p->list_test_suites) { - if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { - queryResults.push_back(&tc); - testSuitesPassingFilt.insert(tc.m_test_suite); - p->numTestSuitesPassingFilters++; - } - continue; - } - - // execute the test if it passes all the filtering - { - p->currentTest = &tc; - - p->failure_flags = TestCaseFailureReason::None; - p->seconds = 0; - - // reset atomic counters - p->numAssertsFailedCurrentTest_atomic = 0; - p->numAssertsCurrentTest_atomic = 0; - - p->subcasesPassed.clear(); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); - - p->timer.start(); - - bool run_test = true; - - do { - // reset some of the fields for subcases (except for the set of fully passed ones) - p->should_reenter = false; - p->subcasesCurrentMaxLevel = 0; - p->subcasesStack.clear(); - - p->shouldLogCurrentException = true; - - // reset stuff for logging with INFO() - p->stringifiedContexts.clear(); - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - try { -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - FatalConditionHandler fatalConditionHandler; // Handle signals - // execute the test - tc.m_test(); - fatalConditionHandler.reset(); -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - } catch(const TestFailureException&) { - p->failure_flags |= TestCaseFailureReason::AssertFailure; - } catch(...) { - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, - {translateActiveException(), false}); - p->failure_flags |= TestCaseFailureReason::Exception; - } -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - - // exit this loop if enough assertions have failed - even if there are more subcases - if(p->abort_after > 0 && - p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { - run_test = false; - p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; - } - - if(p->should_reenter && run_test) - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); - if(!p->should_reenter) - run_test = false; - } while(run_test); - - p->finalizeTestCaseData(); - - DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); - - p->currentTest = nullptr; - - // stop executing tests if enough assertions have failed - if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) - break; - } - } - - if(!query_mode) { - DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); - } else { - QueryData qdata; - qdata.run_stats = g_cs; - qdata.data = queryResults.data(); - qdata.num_data = unsigned(queryResults.size()); - DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); - } - - // see these issues on the reasoning for this: - // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 - // - https://github.com/onqtam/doctest/issues/126 - auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE - { std::cout << std::string(); }; - DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); - - return cleanup_and_return(); -} - -IReporter::~IReporter() = default; - -int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } -const IContextScope* const* IReporter::get_active_contexts() { - return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; -} - -int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } -const String* IReporter::get_stringified_contexts() { - return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; -} - -namespace detail { - void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { - if(isReporter) - getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); - else - getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); - } -} // namespace detail - -} // namespace doctest - -#endif // DOCTEST_CONFIG_DISABLE - -#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 -int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } -DOCTEST_MSVC_SUPPRESS_WARNING_POP -#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -DOCTEST_CLANG_SUPPRESS_WARNING_POP -DOCTEST_MSVC_SUPPRESS_WARNING_POP -DOCTEST_GCC_SUPPRESS_WARNING_POP - -#endif // DOCTEST_LIBRARY_IMPLEMENTATION -#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/extern/filereaderlp/reader.cpp b/extern/filereaderlp/reader.cpp index f995d6f3d0..0a289c0ea8 100644 --- a/extern/filereaderlp/reader.cpp +++ b/extern/filereaderlp/reader.cpp @@ -17,7 +17,7 @@ #include "builder.hpp" #include "def.hpp" #ifdef ZLIB_FOUND -#include "zstr/zstr.hpp" +#include "../extern/zstr/zstr.hpp" #endif // Cygwin doesn't come with an implementation for strdup if compiled with @@ -874,7 +874,7 @@ void Reader::splittokens() { if (new_section_type) { // Section type change currentsection = it->keyword; - // Make sure the new section type has not occured previously + // Make sure the new section type has not occurred previously lpassert(sectiontokens.count(currentsection) == 0); // Remember the beginning of the new section: its the token // following the current one diff --git a/extern/zstr/strict_fstream.hpp b/extern/zstr/strict_fstream.hpp index 7d03ea6648..953311a907 100644 --- a/extern/zstr/strict_fstream.hpp +++ b/extern/zstr/strict_fstream.hpp @@ -17,7 +17,7 @@ namespace strict_fstream { -// Help people out a bit, it seems like this is a common recommenation since +// Help people out a bit, it seems like this is a common recommendation since // musl breaks all over the place. #if defined(__NEED_size_t) && !defined(__MUSL__) #warning "It seems to be recommended to patch in a define for __MUSL__ if you use musl globally: https://www.openwall.com/lists/musl/2013/02/10/5" @@ -64,7 +64,7 @@ static std::string strerror() } else { return "Unknown error (" + std::to_string(err_num) + ")"; } -#elif ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || defined(__APPLE__) || defined(__FreeBSD__)) && ! _GNU_SOURCE) || defined(__MUSL__) +#elif ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)) && ! _GNU_SOURCE) || defined(__MUSL__) // XSI-compliant strerror_r() const int err_num = errno; // See above if (strerror_r(err_num, buff.data(), buff.size()) == 0) { diff --git a/extern/zstr/zstr.hpp b/extern/zstr/zstr.hpp index a1bd587c3b..438e620565 100644 --- a/extern/zstr/zstr.hpp +++ b/extern/zstr/zstr.hpp @@ -8,494 +8,465 @@ #pragma once +#include + #include #include -#include -#include -#include #include -#include "zstr/strict_fstream.hpp" +#include +#include -namespace zstr -{ +#include "../extern/zstr/strict_fstream.hpp" + +namespace zstr { static const std::size_t default_buff_size = static_cast(1 << 20); /// Exception class thrown by failed zlib operations. -class Exception - : public std::ios_base::failure -{ -public: - static std::string error_to_message(z_stream * zstrm_p, int ret) - { - std::string msg = "zlib: "; - switch (ret) - { - case Z_STREAM_ERROR: - msg += "Z_STREAM_ERROR: "; - break; - case Z_DATA_ERROR: - msg += "Z_DATA_ERROR: "; - break; - case Z_MEM_ERROR: - msg += "Z_MEM_ERROR: "; - break; - case Z_VERSION_ERROR: - msg += "Z_VERSION_ERROR: "; - break; - case Z_BUF_ERROR: - msg += "Z_BUF_ERROR: "; - break; - default: - std::ostringstream oss; - oss << ret; - msg += "[" + oss.str() + "]: "; - break; - } - if (zstrm_p->msg) { - msg += zstrm_p->msg; - } - msg += " (" - "next_in: " + - std::to_string(uintptr_t(zstrm_p->next_in)) + - ", avail_in: " + - std::to_string(uintptr_t(zstrm_p->avail_in)) + - ", next_out: " + - std::to_string(uintptr_t(zstrm_p->next_out)) + - ", avail_out: " + - std::to_string(uintptr_t(zstrm_p->avail_out)) + - ")"; - return msg; +class Exception : public std::ios_base::failure { + public: + static std::string error_to_message(z_stream* zstrm_p, int ret) { + std::string msg = "zlib: "; + switch (ret) { + case Z_STREAM_ERROR: + msg += "Z_STREAM_ERROR: "; + break; + case Z_DATA_ERROR: + msg += "Z_DATA_ERROR: "; + break; + case Z_MEM_ERROR: + msg += "Z_MEM_ERROR: "; + break; + case Z_VERSION_ERROR: + msg += "Z_VERSION_ERROR: "; + break; + case Z_BUF_ERROR: + msg += "Z_BUF_ERROR: "; + break; + default: + std::ostringstream oss; + oss << ret; + msg += "[" + oss.str() + "]: "; + break; } - - Exception(z_stream * zstrm_p, int ret) - : std::ios_base::failure(error_to_message(zstrm_p, ret)) - { + if (zstrm_p->msg) { + msg += zstrm_p->msg; } -}; // class Exception - -namespace detail -{ - -class z_stream_wrapper - : public z_stream -{ -public: - z_stream_wrapper(bool _is_input, int _level, int _window_bits) - : is_input(_is_input) - { - this->zalloc = nullptr;//Z_NULL - this->zfree = nullptr;//Z_NULL - this->opaque = nullptr;//Z_NULL - int ret; - if (is_input) - { - this->avail_in = 0; - this->next_in = nullptr;//Z_NULL - ret = inflateInit2(this, _window_bits ? _window_bits : 15+32); - } - else - { - ret = deflateInit2(this, _level, Z_DEFLATED, _window_bits ? _window_bits : 15+16, 8, Z_DEFAULT_STRATEGY); - } - if (ret != Z_OK) throw Exception(this, ret); + msg += + " (" + "next_in: " + + std::to_string(uintptr_t(zstrm_p->next_in)) + + ", avail_in: " + std::to_string(uintptr_t(zstrm_p->avail_in)) + + ", next_out: " + std::to_string(uintptr_t(zstrm_p->next_out)) + + ", avail_out: " + std::to_string(uintptr_t(zstrm_p->avail_out)) + ")"; + return msg; + } + + Exception(z_stream* zstrm_p, int ret) + : std::ios_base::failure(error_to_message(zstrm_p, ret)) {} +}; // class Exception + +namespace detail { + +class z_stream_wrapper : public z_stream { + public: + z_stream_wrapper(bool _is_input, int _level, int _window_bits) + : is_input(_is_input) { + this->zalloc = nullptr; // Z_NULL + this->zfree = nullptr; // Z_NULL + this->opaque = nullptr; // Z_NULL + int ret; + if (is_input) { + this->avail_in = 0; + this->next_in = nullptr; // Z_NULL + ret = inflateInit2(this, _window_bits ? _window_bits : 15 + 32); + } else { + ret = deflateInit2(this, _level, Z_DEFLATED, + _window_bits ? _window_bits : 15 + 16, 8, + Z_DEFAULT_STRATEGY); } - ~z_stream_wrapper() - { - if (is_input) - { - inflateEnd(this); - } - else - { - deflateEnd(this); - } + if (ret != Z_OK) throw Exception(this, ret); + } + ~z_stream_wrapper() { + if (is_input) { + inflateEnd(this); + } else { + deflateEnd(this); } -private: - bool is_input; -}; // class z_stream_wrapper - -} // namespace detail - -class istreambuf - : public std::streambuf -{ -public: - istreambuf(std::streambuf * _sbuf_p, - std::size_t _buff_size = default_buff_size, bool _auto_detect = true, int _window_bits = 0) - : sbuf_p(_sbuf_p), - in_buff(), - in_buff_start(nullptr), - in_buff_end(nullptr), - out_buff(), - zstrm_p(nullptr), - buff_size(_buff_size), - auto_detect(_auto_detect), - auto_detect_run(false), - is_text(false), - window_bits(_window_bits) - { - assert(sbuf_p); - in_buff = std::unique_ptr(new char[buff_size]); - in_buff_start = in_buff.get(); - in_buff_end = in_buff.get(); - out_buff = std::unique_ptr(new char[buff_size]); - setg(out_buff.get(), out_buff.get(), out_buff.get()); + } + + private: + bool is_input; +}; // class z_stream_wrapper + +} // namespace detail + +class istreambuf : public std::streambuf { + public: + istreambuf(std::streambuf* _sbuf_p, + std::size_t _buff_size = default_buff_size, + bool _auto_detect = true, int _window_bits = 0) + : sbuf_p(_sbuf_p), + in_buff(), + in_buff_start(nullptr), + in_buff_end(nullptr), + out_buff(), + zstrm_p(nullptr), + buff_size(_buff_size), + auto_detect(_auto_detect), + auto_detect_run(false), + is_text(false), + window_bits(_window_bits) { + assert(sbuf_p); + in_buff = std::unique_ptr(new char[buff_size]); + in_buff_start = in_buff.get(); + in_buff_end = in_buff.get(); + out_buff = std::unique_ptr(new char[buff_size]); + setg(out_buff.get(), out_buff.get(), out_buff.get()); + } + + istreambuf(const istreambuf&) = delete; + istreambuf& operator=(const istreambuf&) = delete; + + pos_type seekoff(off_type off, std::ios_base::seekdir dir, + std::ios_base::openmode which) override { + if (off != 0 || dir != std::ios_base::cur) { + return std::streambuf::seekoff(off, dir, which); } - istreambuf(const istreambuf &) = delete; - istreambuf & operator = (const istreambuf &) = delete; - - pos_type seekoff(off_type off, std::ios_base::seekdir dir, - std::ios_base::openmode which) override - { - if (off != 0 || dir != std::ios_base::cur) { - return std::streambuf::seekoff(off, dir, which); - } - - if (!zstrm_p) { - return 0; - } - - return static_cast(zstrm_p->total_out - static_cast(in_avail())); + if (!zstrm_p) { + return 0; } - std::streambuf::int_type underflow() override - { - if (this->gptr() == this->egptr()) - { - // pointers for free region in output buffer - char * out_buff_free_start = out_buff.get(); - int tries = 0; - do - { - if (++tries > 1000) { - throw std::ios_base::failure("Failed to fill buffer after 1000 tries"); - } - - // read more input if none available - if (in_buff_start == in_buff_end) - { - // empty input buffer: refill from the start - in_buff_start = in_buff.get(); - std::streamsize sz = sbuf_p->sgetn(in_buff.get(), static_cast(buff_size)); - in_buff_end = in_buff_start + sz; - if (in_buff_end == in_buff_start) break; // end of input - } - // auto detect if the stream contains text or deflate data - if (auto_detect && ! auto_detect_run) - { - auto_detect_run = true; - unsigned char b0 = *reinterpret_cast< unsigned char * >(in_buff_start); - unsigned char b1 = *reinterpret_cast< unsigned char * >(in_buff_start + 1); - // Ref: - // http://en.wikipedia.org/wiki/Gzip - // http://stackoverflow.com/questions/9050260/what-does-a-zlib-header-look-like - is_text = ! (in_buff_start + 2 <= in_buff_end - && ((b0 == 0x1F && b1 == 0x8B) // gzip header - || (b0 == 0x78 && (b1 == 0x01 // zlib header - || b1 == 0x9C - || b1 == 0xDA)))); - } - if (is_text) - { - // simply swap in_buff and out_buff, and adjust pointers - assert(in_buff_start == in_buff.get()); - std::swap(in_buff, out_buff); - out_buff_free_start = in_buff_end; - in_buff_start = in_buff.get(); - in_buff_end = in_buff.get(); - } - else - { - // run inflate() on input - if (! zstrm_p) zstrm_p = std::unique_ptr(new detail::z_stream_wrapper(true, Z_DEFAULT_COMPRESSION, window_bits)); - zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(in_buff_start); - zstrm_p->avail_in = uint32_t(in_buff_end - in_buff_start); - zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff_free_start); - zstrm_p->avail_out = uint32_t((out_buff.get() + buff_size) - out_buff_free_start); - int ret = inflate(zstrm_p.get(), Z_NO_FLUSH); - // process return code - if (ret != Z_OK && ret != Z_STREAM_END) throw Exception(zstrm_p.get(), ret); - // update in&out pointers following inflate() - in_buff_start = reinterpret_cast< decltype(in_buff_start) >(zstrm_p->next_in); - in_buff_end = in_buff_start + zstrm_p->avail_in; - out_buff_free_start = reinterpret_cast< decltype(out_buff_free_start) >(zstrm_p->next_out); - assert(out_buff_free_start + zstrm_p->avail_out == out_buff.get() + buff_size); - - if (ret == Z_STREAM_END) { - // if stream ended, deallocate inflator - zstrm_p.reset(); - } - } - } while (out_buff_free_start == out_buff.get()); - // 2 exit conditions: - // - end of input: there might or might not be output available - // - out_buff_free_start != out_buff: output available - this->setg(out_buff.get(), out_buff.get(), out_buff_free_start); + return static_cast(zstrm_p->total_out - + static_cast(in_avail())); + } + + std::streambuf::int_type underflow() override { + if (this->gptr() == this->egptr()) { + // pointers for free region in output buffer + char* out_buff_free_start = out_buff.get(); + int tries = 0; + do { + if (++tries > 1000) { + throw std::ios_base::failure( + "Failed to fill buffer after 1000 tries"); } - return this->gptr() == this->egptr() - ? traits_type::eof() - : traits_type::to_int_type(*this->gptr()); - } -private: - std::streambuf * sbuf_p; - std::unique_ptr in_buff; - char * in_buff_start; - char * in_buff_end; - std::unique_ptr out_buff; - std::unique_ptr zstrm_p; - std::size_t buff_size; - bool auto_detect; - bool auto_detect_run; - bool is_text; - int window_bits; - -}; // class istreambuf - -class ostreambuf - : public std::streambuf -{ -public: - ostreambuf(std::streambuf * _sbuf_p, - std::size_t _buff_size = default_buff_size, int _level = Z_DEFAULT_COMPRESSION, int _window_bits = 0) - : sbuf_p(_sbuf_p), - in_buff(), - out_buff(), - zstrm_p(new detail::z_stream_wrapper(false, _level, _window_bits)), - buff_size(_buff_size) - { - assert(sbuf_p); - in_buff = std::unique_ptr(new char[buff_size]); - out_buff = std::unique_ptr(new char[buff_size]); - setp(in_buff.get(), in_buff.get() + buff_size); - } - ostreambuf(const ostreambuf &) = delete; - ostreambuf & operator = (const ostreambuf &) = delete; - - int deflate_loop(int flush) - { - while (true) - { - zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff.get()); - zstrm_p->avail_out = uint32_t(buff_size); - int ret = deflate(zstrm_p.get(), flush); - if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { - failed = true; - throw Exception(zstrm_p.get(), ret); - } - std::streamsize sz = sbuf_p->sputn(out_buff.get(), reinterpret_cast< decltype(out_buff.get()) >(zstrm_p->next_out) - out_buff.get()); - if (sz != reinterpret_cast< decltype(out_buff.get()) >(zstrm_p->next_out) - out_buff.get()) - { - // there was an error in the sink stream - return -1; - } - if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || sz == 0) - { - break; - } + // read more input if none available + if (in_buff_start == in_buff_end) { + // empty input buffer: refill from the start + in_buff_start = in_buff.get(); + std::streamsize sz = sbuf_p->sgetn( + in_buff.get(), static_cast(buff_size)); + in_buff_end = in_buff_start + sz; + if (in_buff_end == in_buff_start) break; // end of input } - return 0; - } - - virtual ~ostreambuf() - { - // flush the zlib stream - // - // NOTE: Errors here (sync() return value not 0) are ignored, because we - // cannot throw in a destructor. This mirrors the behaviour of - // std::basic_filebuf::~basic_filebuf(). To see an exception on error, - // close the ofstream with an explicit call to close(), and do not rely - // on the implicit call in the destructor. - // - if (!failed) try { - sync(); - } catch (...) {} - } - std::streambuf::int_type overflow(std::streambuf::int_type c = traits_type::eof()) override - { - zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(pbase()); - zstrm_p->avail_in = uint32_t(pptr() - pbase()); - while (zstrm_p->avail_in > 0) - { - int r = deflate_loop(Z_NO_FLUSH); - if (r != 0) - { - setp(nullptr, nullptr); - return traits_type::eof(); - } + // auto detect if the stream contains text or deflate data + if (auto_detect && !auto_detect_run) { + auto_detect_run = true; + unsigned char b0 = *reinterpret_cast(in_buff_start); + unsigned char b1 = + *reinterpret_cast(in_buff_start + 1); + // Ref: + // http://en.wikipedia.org/wiki/Gzip + // http://stackoverflow.com/questions/9050260/what-does-a-zlib-header-look-like + is_text = !(in_buff_start + 2 <= in_buff_end && + ((b0 == 0x1F && b1 == 0x8B) // gzip header + || (b0 == 0x78 && (b1 == 0x01 // zlib header + || b1 == 0x9C || b1 == 0xDA)))); } - setp(in_buff.get(), in_buff.get() + buff_size); - return traits_type::eq_int_type(c, traits_type::eof()) ? traits_type::eof() : sputc(char_type(c)); - } - int sync() override - { - // first, call overflow to clear in_buff - overflow(); - if (! pptr()) return -1; - // then, call deflate asking to finish the zlib stream - zstrm_p->next_in = nullptr; - zstrm_p->avail_in = 0; - if (deflate_loop(Z_FINISH) != 0) return -1; - deflateReset(zstrm_p.get()); - return 0; - } -private: - std::streambuf * sbuf_p = nullptr; - std::unique_ptr in_buff; - std::unique_ptr out_buff; - std::unique_ptr zstrm_p; - std::size_t buff_size; - bool failed = false; - -}; // class ostreambuf - -class istream - : public std::istream -{ -public: - istream(std::istream & is, - std::size_t _buff_size = default_buff_size, bool _auto_detect = true, int _window_bits = 0) - : std::istream(new istreambuf(is.rdbuf(), _buff_size, _auto_detect, _window_bits)) - { - exceptions(std::ios_base::badbit); - } - explicit istream(std::streambuf * sbuf_p) - : std::istream(new istreambuf(sbuf_p)) - { - exceptions(std::ios_base::badbit); - } - virtual ~istream() - { - delete rdbuf(); - } -}; // class istream - -class ostream - : public std::ostream -{ -public: - ostream(std::ostream & os, - std::size_t _buff_size = default_buff_size, int _level = Z_DEFAULT_COMPRESSION, int _window_bits = 0) - : std::ostream(new ostreambuf(os.rdbuf(), _buff_size, _level, _window_bits)) - { - exceptions(std::ios_base::badbit); + if (is_text) { + // simply swap in_buff and out_buff, and adjust pointers + assert(in_buff_start == in_buff.get()); + std::swap(in_buff, out_buff); + out_buff_free_start = in_buff_end; + in_buff_start = in_buff.get(); + in_buff_end = in_buff.get(); + } else { + // run inflate() on input + if (!zstrm_p) + zstrm_p = std::unique_ptr( + new detail::z_stream_wrapper(true, Z_DEFAULT_COMPRESSION, + window_bits)); + zstrm_p->next_in = + reinterpret_castnext_in)>(in_buff_start); + zstrm_p->avail_in = uint32_t(in_buff_end - in_buff_start); + zstrm_p->next_out = reinterpret_castnext_out)>( + out_buff_free_start); + zstrm_p->avail_out = + uint32_t((out_buff.get() + buff_size) - out_buff_free_start); + int ret = inflate(zstrm_p.get(), Z_NO_FLUSH); + // process return code + if (ret != Z_OK && ret != Z_STREAM_END) + throw Exception(zstrm_p.get(), ret); + // update in&out pointers following inflate() + in_buff_start = + reinterpret_cast(zstrm_p->next_in); + in_buff_end = in_buff_start + zstrm_p->avail_in; + out_buff_free_start = reinterpret_cast( + zstrm_p->next_out); + assert(out_buff_free_start + zstrm_p->avail_out == + out_buff.get() + buff_size); + + if (ret == Z_STREAM_END) { + // if stream ended, deallocate inflator + zstrm_p.reset(); + } + } + } while (out_buff_free_start == out_buff.get()); + // 2 exit conditions: + // - end of input: there might or might not be output available + // - out_buff_free_start != out_buff: output available + this->setg(out_buff.get(), out_buff.get(), out_buff_free_start); } - explicit ostream(std::streambuf * sbuf_p) - : std::ostream(new ostreambuf(sbuf_p)) - { - exceptions(std::ios_base::badbit); + return this->gptr() == this->egptr() + ? traits_type::eof() + : traits_type::to_int_type(*this->gptr()); + } + + private: + std::streambuf* sbuf_p; + std::unique_ptr in_buff; + char* in_buff_start; + char* in_buff_end; + std::unique_ptr out_buff; + std::unique_ptr zstrm_p; + std::size_t buff_size; + bool auto_detect; + bool auto_detect_run; + bool is_text; + int window_bits; + +}; // class istreambuf + +class ostreambuf : public std::streambuf { + public: + ostreambuf(std::streambuf* _sbuf_p, + std::size_t _buff_size = default_buff_size, + int _level = Z_DEFAULT_COMPRESSION, int _window_bits = 0) + : sbuf_p(_sbuf_p), + in_buff(), + out_buff(), + zstrm_p(new detail::z_stream_wrapper(false, _level, _window_bits)), + buff_size(_buff_size) { + assert(sbuf_p); + in_buff = std::unique_ptr(new char[buff_size]); + out_buff = std::unique_ptr(new char[buff_size]); + setp(in_buff.get(), in_buff.get() + buff_size); + } + + ostreambuf(const ostreambuf&) = delete; + ostreambuf& operator=(const ostreambuf&) = delete; + + int deflate_loop(int flush) { + while (true) { + zstrm_p->next_out = + reinterpret_castnext_out)>(out_buff.get()); + zstrm_p->avail_out = uint32_t(buff_size); + int ret = deflate(zstrm_p.get(), flush); + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { + failed = true; + throw Exception(zstrm_p.get(), ret); + } + std::streamsize sz = sbuf_p->sputn( + out_buff.get(), + reinterpret_cast(zstrm_p->next_out) - + out_buff.get()); + if (sz != reinterpret_cast(zstrm_p->next_out) - + out_buff.get()) { + // there was an error in the sink stream + return -1; + } + if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || sz == 0) { + break; + } } - virtual ~ostream() - { - delete rdbuf(); + return 0; + } + + virtual ~ostreambuf() { + // flush the zlib stream + // + // NOTE: Errors here (sync() return value not 0) are ignored, because we + // cannot throw in a destructor. This mirrors the behaviour of + // std::basic_filebuf::~basic_filebuf(). To see an exception on error, + // close the ofstream with an explicit call to close(), and do not rely + // on the implicit call in the destructor. + // + if (!failed) try { + sync(); + } catch (...) { + } + } + std::streambuf::int_type overflow( + std::streambuf::int_type c = traits_type::eof()) override { + zstrm_p->next_in = reinterpret_castnext_in)>(pbase()); + zstrm_p->avail_in = uint32_t(pptr() - pbase()); + while (zstrm_p->avail_in > 0) { + int r = deflate_loop(Z_NO_FLUSH); + if (r != 0) { + setp(nullptr, nullptr); + return traits_type::eof(); + } } -}; // class ostream - -namespace detail -{ - -template < typename FStream_Type > -struct strict_fstream_holder -{ - strict_fstream_holder(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) - : _fs(filename, mode) - {} - strict_fstream_holder() = default; - FStream_Type _fs {}; -}; // class strict_fstream_holder - -} // namespace detail + setp(in_buff.get(), in_buff.get() + buff_size); + return traits_type::eq_int_type(c, traits_type::eof()) + ? traits_type::eof() + : sputc(char_type(c)); + } + int sync() override { + // first, call overflow to clear in_buff + overflow(); + if (!pptr()) return -1; + // then, call deflate asking to finish the zlib stream + zstrm_p->next_in = nullptr; + zstrm_p->avail_in = 0; + if (deflate_loop(Z_FINISH) != 0) return -1; + deflateReset(zstrm_p.get()); + return 0; + } + + private: + std::streambuf* sbuf_p = nullptr; + std::unique_ptr in_buff; + std::unique_ptr out_buff; + std::unique_ptr zstrm_p; + std::size_t buff_size; + bool failed = false; + +}; // class ostreambuf + +class istream : public std::istream { + public: + istream(std::istream& is, std::size_t _buff_size = default_buff_size, + bool _auto_detect = true, int _window_bits = 0) + : std::istream(new istreambuf(is.rdbuf(), _buff_size, _auto_detect, + _window_bits)) { + exceptions(std::ios_base::badbit); + } + explicit istream(std::streambuf* sbuf_p) + : std::istream(new istreambuf(sbuf_p)) { + exceptions(std::ios_base::badbit); + } + virtual ~istream() { delete rdbuf(); } +}; // class istream + +class ostream : public std::ostream { + public: + ostream(std::ostream& os, std::size_t _buff_size = default_buff_size, + int _level = Z_DEFAULT_COMPRESSION, int _window_bits = 0) + : std::ostream( + new ostreambuf(os.rdbuf(), _buff_size, _level, _window_bits)) { + exceptions(std::ios_base::badbit); + } + explicit ostream(std::streambuf* sbuf_p) + : std::ostream(new ostreambuf(sbuf_p)) { + exceptions(std::ios_base::badbit); + } + virtual ~ostream() { delete rdbuf(); } +}; // class ostream + +namespace detail { + +template +struct strict_fstream_holder { + strict_fstream_holder(const std::string& filename, + std::ios_base::openmode mode = std::ios_base::in) + : _fs(filename, mode) {} + strict_fstream_holder() = default; + FStream_Type _fs{}; +}; // class strict_fstream_holder + +} // namespace detail class ifstream - : private detail::strict_fstream_holder< strict_fstream::ifstream >, - public std::istream -{ -public: - explicit ifstream(const std::string filename, std::ios_base::openmode mode = std::ios_base::in, size_t buff_size = default_buff_size) - : detail::strict_fstream_holder< strict_fstream::ifstream >(filename, mode -#ifdef _WIN32 // to avoid problems with conversion of \r\n, only windows as otherwise there are problems on mac - | std::ios_base::binary + : private detail::strict_fstream_holder, + public std::istream { + public: + explicit ifstream(const std::string filename, + std::ios_base::openmode mode = std::ios_base::in, + size_t buff_size = default_buff_size) + : detail::strict_fstream_holder( + filename, mode +#ifdef _WIN32 // to avoid problems with conversion of \r\n, only windows as + // otherwise there are problems on mac + | std::ios_base::binary #endif - ), - std::istream(new istreambuf(_fs.rdbuf(), buff_size)) - { - exceptions(std::ios_base::badbit); - } - explicit ifstream(): detail::strict_fstream_holder< strict_fstream::ifstream >(), std::istream(new istreambuf(_fs.rdbuf())){} - void close() { - _fs.close(); - } - void open(const std::string filename, std::ios_base::openmode mode = std::ios_base::in) { - _fs.open(filename, mode -#ifdef _WIN32 // to avoid problems with conversion of \r\n, only windows as otherwise there are problems on mac - | std::ios_base::binary + ), + std::istream(new istreambuf(_fs.rdbuf(), buff_size)) { + exceptions(std::ios_base::badbit); + } + explicit ifstream() + : detail::strict_fstream_holder(), + std::istream(new istreambuf(_fs.rdbuf())) {} + void close() { _fs.close(); } + void open(const std::string filename, + std::ios_base::openmode mode = std::ios_base::in) { + _fs.open(filename, mode +#ifdef _WIN32 // to avoid problems with conversion of \r\n, only windows as + // otherwise there are problems on mac + | std::ios_base::binary #endif - ); - // make sure the previous buffer is deleted by putting it into a unique_ptr and set a new one after opening file - std::unique_ptr oldbuf(rdbuf(new istreambuf(_fs.rdbuf()))); - // call move assignment operator on istream which does not alter the stream buffer - std::istream::operator=(std::istream(rdbuf())); - } - bool is_open() const { - return _fs.is_open(); - } - virtual ~ifstream() - { - if (_fs.is_open()) close(); - if (rdbuf()) delete rdbuf(); - } - - /// Return the position within the compressed file (wrapped filestream) - std::streampos compressed_tellg() - { - return _fs.tellg(); - } -}; // class ifstream + ); + // make sure the previous buffer is deleted by putting it into a unique_ptr + // and set a new one after opening file + std::unique_ptr oldbuf(rdbuf(new istreambuf(_fs.rdbuf()))); + // call move assignment operator on istream which does not alter the stream + // buffer + std::istream::operator=(std::istream(rdbuf())); + } + bool is_open() const { return _fs.is_open(); } + virtual ~ifstream() { + if (_fs.is_open()) close(); + if (rdbuf()) delete rdbuf(); + } + + /// Return the position within the compressed file (wrapped filestream) + std::streampos compressed_tellg() { return _fs.tellg(); } +}; // class ifstream class ofstream - : private detail::strict_fstream_holder< strict_fstream::ofstream >, - public std::ostream -{ -public: - explicit ofstream(const std::string filename, std::ios_base::openmode mode = std::ios_base::out, - int level = Z_DEFAULT_COMPRESSION, size_t buff_size = default_buff_size) - : detail::strict_fstream_holder< strict_fstream::ofstream >(filename, mode | std::ios_base::binary), - std::ostream(new ostreambuf(_fs.rdbuf(), buff_size, level)) - { - exceptions(std::ios_base::badbit); - } - explicit ofstream(): detail::strict_fstream_holder< strict_fstream::ofstream >(), std::ostream(new ostreambuf(_fs.rdbuf())){} - void close() { - std::ostream::flush(); - _fs.close(); - } - void open(const std::string filename, std::ios_base::openmode mode = std::ios_base::out, int level = Z_DEFAULT_COMPRESSION) { - flush(); - _fs.open(filename, mode | std::ios_base::binary); - std::ostream::operator=(std::ostream(new ostreambuf(_fs.rdbuf(), default_buff_size, level))); - } - bool is_open() const { - return _fs.is_open(); - } - ofstream& flush() { - std::ostream::flush(); - _fs.flush(); - return *this; - } - virtual ~ofstream() - { - if (_fs.is_open()) close(); - if (rdbuf()) delete rdbuf(); - } - - // Return the position within the compressed file (wrapped filestream) - std::streampos compressed_tellp() - { - return _fs.tellp(); - } -}; // class ofstream - -} // namespace zstr - + : private detail::strict_fstream_holder, + public std::ostream { + public: + explicit ofstream(const std::string filename, + std::ios_base::openmode mode = std::ios_base::out, + int level = Z_DEFAULT_COMPRESSION, + size_t buff_size = default_buff_size) + : detail::strict_fstream_holder( + filename, mode | std::ios_base::binary), + std::ostream(new ostreambuf(_fs.rdbuf(), buff_size, level)) { + exceptions(std::ios_base::badbit); + } + explicit ofstream() + : detail::strict_fstream_holder(), + std::ostream(new ostreambuf(_fs.rdbuf())) {} + void close() { + std::ostream::flush(); + _fs.close(); + } + void open(const std::string filename, + std::ios_base::openmode mode = std::ios_base::out, + int level = Z_DEFAULT_COMPRESSION) { + flush(); + _fs.open(filename, mode | std::ios_base::binary); + std::ostream::operator=( + std::ostream(new ostreambuf(_fs.rdbuf(), default_buff_size, level))); + } + bool is_open() const { return _fs.is_open(); } + ofstream& flush() { + std::ostream::flush(); + _fs.flush(); + return *this; + } + virtual ~ofstream() { + if (_fs.is_open()) close(); + if (rdbuf()) delete rdbuf(); + } + + // Return the position within the compressed file (wrapped filestream) + std::streampos compressed_tellp() { return _fs.tellp(); } +}; // class ofstream + +} // namespace zstr diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..35cfdb9f79 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1709961763, + "narHash": "sha256-6H95HGJHhEZtyYA3rIQpvamMKAGoa8Yh2rFV29QnuGw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..5568ea308d --- /dev/null +++ b/flake.nix @@ -0,0 +1,73 @@ +{ + inputs = { + nixpkgs = { + url = "github:nixos/nixpkgs/nixos-unstable"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + }; + outputs = { nixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + }; + + version = with pkgs.lib; + # Read the version. Note: We assume the version numbers are in + # order in the file; i.e. Major, Minor, Patch. + let f = builtins.readFile ./Version.txt; + l = strings.splitString "\n" f; + # Drop the last term; it just says if it's alpha or not. + t = lists.take 3 l; + # Get the numbers on the other side of the equals + vs = lists.forEach t (v: lists.drop 1 (strings.splitString "=" v)); + # That's it! + in concatStrings (intersperse "." (lists.flatten vs)); + + # Build the binary + highs-binary = with pkgs; stdenv.mkDerivation { + pname = "highs"; + inherit version; + src = pkgs.lib.cleanSource ./.; + nativeBuildInputs = [ + clang + cmake + ]; + }; + + # Build the python package + highspy = pkgs.python3Packages.buildPythonPackage { + inherit version; + pname = "highspy"; + src = pkgs.lib.cleanSource ./.; + format = "pyproject"; + dontUseCmakeConfigure = true; + nativeBuildInputs = with pkgs.python3Packages; [ + numpy + pathspec + pybind11 + pyproject-metadata + scikit-build-core + pkgs.cmake + pkgs.ninja + ]; + buildInputs = [ + pkgs.zlib + ]; + }; + + in rec { + defaultApp = flake-utils.lib.mkApp { + drv = highs-binary; + }; + defaultPackage = highs-binary; + packages.highspy = highspy; + devShells.highspy = pkgs.mkShell { + buildInputs = [ + (pkgs.python3.withPackages (ps: [ highspy ]) ) + ]; + }; + } + ); +} diff --git a/highs-config.cmake.in b/highs-config.cmake.in index 5eadc3a856..85709dea10 100644 --- a/highs-config.cmake.in +++ b/highs-config.cmake.in @@ -2,19 +2,11 @@ set(HIGHS_DIR "@HIGHS_INSTALL_DIR@") -if (FAST_BUILD) - if(NOT TARGET highs) - include("${CMAKE_CURRENT_LIST_DIR}/highs-targets.cmake") - endif() +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 highs) set(HIGHS_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") diff --git a/highspy/meson.build b/highspy/meson.build deleted file mode 100644 index 3a65f001de..0000000000 --- a/highspy/meson.build +++ /dev/null @@ -1,26 +0,0 @@ -py_mod = import('python') -py = py_mod.find_installation(pure: false) -pyb11_dep = [ - py.dependency(), - dependency('pybind11') -] - -py.extension_module( - '_highs', - sources : highspy_cpp, - dependencies: [pyb11_dep, highs_dep], - cpp_args: _args, - install: true, - subdir: 'highspy', - include_directories: _incdirs, -) - -py.extension_module( - '_highs_options', - sources : highsoptions_cpp, - dependencies: [pyb11_dep, highs_dep], - cpp_args: _args, - install: true, - subdir: 'highspy', - include_directories: _incdirs, -) diff --git a/highspy/tests/__init__.py b/highspy/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/manifest.in b/manifest.in deleted file mode 100644 index 0d3fedf3ab..0000000000 --- a/manifest.in +++ /dev/null @@ -1,3 +0,0 @@ -include LICENSE -include highspy/highs_bindings.cpp -include src/Highs.h diff --git a/meson.build b/meson.build index 9dbe0c1680..3c02480b0a 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ -project('highs', 'cpp', - version : '1.6.0', - meson_version: '>= 1.2.0', +project('highs', 'cpp', 'c', + version : '1.7.2', + meson_version: '>= 1.1.0', default_options : ['warning_level=1', 'cpp_std=c++17', 'wrap_mode=forcefallback', @@ -10,10 +10,8 @@ project('highs', 'cpp', # Add C++ compiler options _args = [] # Extra arguments _deps = [] # Dependencies -_linkto = [] # All the sub-libraries _incdirs = [] # All the includes -add_languages('c', required: true) cc = meson.get_compiler('c') cppc = meson.get_compiler('cpp') @@ -23,16 +21,14 @@ is_windows = host_system == 'windows' is_mingw = is_windows and cc.get_id() == 'gcc' # Conditional arguments -if host_system == 'linux' - _args += '-Wno-return-type' - _args += '-Wno-switch' - _args += '-Wno-unused-variable' - _args += '-Wno-unused-but-set-variable' - _args += '-Wno-unused-const-variable' -endif +_args += cppc.get_supported_arguments([ + '-Wno-unused-variable', + '-Wno-unused-but-set-variable', + '-Wno-unused-label', +]) if cppc.get_id() == 'msvc' - add_project_arguments( +_args += cppc.get_supported_arguments([ '/wd4018', # Disable warning: 'expression' : signed/unsigned mismatch '/wd4061', # Disable warning: enumerator 'identifier' in switch of enum 'enumeration' is not explicitly handled by a case label '/wd4100', # Disable warning: 'identifier' : unreferenced formal parameter @@ -52,39 +48,25 @@ if cppc.get_id() == 'msvc' '/wd4514', # Disable warning: 'function' : unreferenced inline function has been removed '/wd4701', # Disable warning: potentially uninitialized local variable 'name' used '/wd4820', # Disable warning: 'bytes' bytes padding added after construct 'member_name' - language: 'cpp', - ) - _args += '-D_CRT_SECURE_NO_WARNINGS' + ]) +_args += '-D_CRT_SECURE_NO_WARNINGS' endif cpu_family = host_machine.cpu_family() if cpu_family in ['x86_64', 'i686'] and not is_windows - add_project_arguments('-mpopcnt', language: 'cpp') + add_project_arguments(cppc.get_supported_arguments('-mpopcnt'), language: 'cpp') endif if cpu_family in ['ppc64', 'powerpc64'] and not meson.is_cross_build() - add_project_arguments('-mpopcntd', language: 'cpp') + add_project_arguments(cppc.get_supported_arguments('-mpopcntd'), language: 'cpp') endif if is_mingw # For mingw-w64, don't use LTO - add_project_arguments('-fno-use-linker-plugin', language: ['c', 'cpp']) + add_project_arguments(cppc.get_supported_arguments('-fno-use-linker-plugin'), language: ['c', 'cpp']) endif # --------------------- Dependencies -if not is_windows - # libm for Unix systems - m_dep = cppc.find_library('m', required: false) - _deps += m_dep - # For building with clang - _deps += [declare_dependency(link_args: '-lstdc++')] -endif - -# Required -threads_dep = dependency('threads', - required: true) -_deps += threads_dep - # Determine whether it is necessary to link libatomic. This could be the case # e.g. on 32-bit platforms when atomic operations are used on 64-bit types. # The check is copied from SciPy which in turn came from @@ -124,6 +106,11 @@ endif _deps += atomic_dep +threads_dep = null_dep +if not is_windows + threads_dep = dependency('threads', required: false) +endif +_deps += threads_dep # Optional zlib_dep = dependency('zlib', required: get_option('use_zlib')) @@ -160,12 +147,6 @@ endif # --------------------- Library subdir('src') # defines highslib -_linkto += highslib - -highs_dep = declare_dependency(link_with: _linkto, - dependencies: _deps, - include_directories: _incdirs, - ) # --------------------- Tests @@ -175,12 +156,4 @@ if get_option('with_tests') endif # --------------------- Bindings -highspy_cpp = files([ - 'highspy/highs_bindings.cpp' -]) -highsoptions_cpp = files([ - 'highspy/highs_options.cpp' -]) -if get_option('with_pybind11') - subdir('highspy') -endif +# now in src/ diff --git a/nuget/HiGHS_Logo.png b/nuget/HiGHS_Logo.png new file mode 100644 index 0000000000..41f54dd1fc Binary files /dev/null and b/nuget/HiGHS_Logo.png differ diff --git a/nuget/Highs.csproj b/nuget/Highs.csproj new file mode 100644 index 0000000000..229df40970 --- /dev/null +++ b/nuget/Highs.csproj @@ -0,0 +1,25 @@ + + + netstandard2.0 + HiGHS + 1.0.0 + ERGO-Code + Wrapper for the HiGHS solver, see https://github.com/ERGO-Code/HiGHS.git + latest + README.md + LICENSE.txt + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nuget/Highs.csproj.in b/nuget/Highs.csproj.in new file mode 100644 index 0000000000..5baa6ffc7d --- /dev/null +++ b/nuget/Highs.csproj.in @@ -0,0 +1,36 @@ + + + netstandard2.0 + Highs.Native + @PROJECT_VERSION@ + + + + + .NET native wrapper for the HiGHS solver https://github.com/ERGO-Code/HiGHS.git + ERGO-Code + latest + README.md + MIT + https://www.highs.dev + https://github.com/ERGO-Code/HiGHS + + HiGHS_Logo.png + + + + + + + + + + + + + + + + + + diff --git a/nuget/HowToAlternative.md b/nuget/HowToAlternative.md new file mode 100644 index 0000000000..afa5272948 --- /dev/null +++ b/nuget/HowToAlternative.md @@ -0,0 +1,77 @@ +How To for nuget +=== + +This document describes one way of how to create a nuget package for .NET containing the highs solver for different runtimes. + +Steps +--- + +### Build shared libraries +For all desired targets, build a **shared library** of the c++ highs solver, e.g. + - win-x64 + - linux-x64 + - linux-arm64 + +### Move the files to the runtimes subfolder of the nuget folder +Copy the resulting libraries into the **runtimes** sub folder + - nuget/runtimes/win-x64/highs.dll + - nuget/runtimes/linux-x64/libhighs.so + - nuget/runtimes/linux-arm64/libhighs.so + +### Create the package +Run the dotet command to build and pack the package in the nuget directory. The required csproj. file is already present in the nuget folder +- `dotnet pack -c Release /p:Version=$version` + +### Checking the package +In order to check if the runtimes are contained in the nuget package, one can open the nupkg file in a tool like 7zip and check the runtimes folder. + +## nuget structure for native libraries +The nuget package is required to look like this for the native libraries +``` bash +package/ +|-- lib/ +| |-- netstandard2.0/ +| |-- highs_csharp_api.dll +|-- runtimes/ +| |-- linux-x64/ +| | |-- native/ +| | ! [linux-x64 native libraries] +| |-- linux-arm64/ +| | |-- native/ +| | ! [linux-arm64 native libraries] +| |-- win-x64/ +| | |-- native/ +| | ! [win-x64 native libraries] +``` + +## Examples for the builds +These are examples, how one might run the builds on an ubuntu system, e.g. as a wsl +Compiler flags should be adjusted to provide the best performance +### linux-x64 +This should run on a linux system +```shell +mkdir build_linux +cd build_linux +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="-O3 -march=native" -DCMAKE_C_FLAGS="-O3 -march=native" +make +``` + +### linux-arm64 +This might run on a linux-x64 system (or use a linux-arm64 system and skipp the toolchain part) +```shell +mkdir build_arm +cd build_arm +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake +make + +``` + +## win-x64 +This should run on a windows system +```shell +mkdir build_windows +cd build_windows +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_SYSTEM_NAME=Windows -A x64 +make + +``` diff --git a/nuget/README.md b/nuget/README.md new file mode 100644 index 0000000000..5a6198d228 --- /dev/null +++ b/nuget/README.md @@ -0,0 +1,36 @@ +This is the documentation page for the .NET wrapper of HiGHS. + +## NuGet + +The nuget package Highs.Native is on https://www.nuget.org, at https://www.nuget.org/packages/HiGHS/. + +It can be added to your C# project with `dotnet` + +```shell +dotnet add package Highs.Native --version 1.7.2 +``` + +The nuget package contains runtime libraries for + +* `win-x64` +* `win-x32` +* `linux-x64` +* `linux-arm64` +* `macos-x64` +* `macos-arm64` + +#### Local build + +To build the wrapper locally, you would need `cmake` and `dotnet`. CMake can be configured to generate the files required for the dotnet package locally, wtih the `BUILD_DOTNET` cmake variable. Assuming the build directory is called `build`, the package is generated in `build/dotnet/Highs.Native`, with a single runtime library, depending on the platform. From the HiGHS root directory, run + +``` bash +cmake -S. -Bbuild -DCSHARP=ON -DBUILD_DOTNET=ON +``` + +Then, from `build/dotnet/Highs.Native`, run + +```shell +dotnet pack -c Release /p:Version=$version +``` + +At the moment version is set manually. diff --git a/nuget/arm-toolchain.cmake b/nuget/arm-toolchain.cmake new file mode 100644 index 0000000000..6316c65ff8 --- /dev/null +++ b/nuget/arm-toolchain.cmake @@ -0,0 +1,15 @@ +# Toolchain file for cross-compiling for ARM + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +# Specify the cross-compiler paths +find_program(CMAKE_C_COMPILER NAMES aarch64-linux-gnu-gcc) +find_program(CMAKE_CXX_COMPILER NAMES aarch64-linux-gnu-g++) + +# Compiler flags +set(CMAKE_C_FLAGS_INIT "-O3" CACHE STRING "") +set(CMAKE_CXX_FLAGS_INIT "-O3" CACHE STRING "") + +# Set this to true so CMake knows it's cross-compiling +set(CMAKE_CROSSCOMPILING TRUE) \ No newline at end of file diff --git a/nuget/build_linux-arm.sh b/nuget/build_linux-arm.sh new file mode 100644 index 0000000000..c86f7c54f6 --- /dev/null +++ b/nuget/build_linux-arm.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +sudo apt update +sudo apt upgrade -y +sudo apt install build-essential -y +sudo apt install cmake -y +sudo apt install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu -y + +mkdir build_arm +cd build_arm +cmake ../.. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake +make +make -j8 \ No newline at end of file diff --git a/nuget/build_linux.sh b/nuget/build_linux.sh new file mode 100644 index 0000000000..f2d5befbed --- /dev/null +++ b/nuget/build_linux.sh @@ -0,0 +1,10 @@ +#!/bin/bash +sudo apt update +sudo apt upgrade -y +sudo apt install build-essential -y +sudo apt install cmake -y + +mkdir build_linux +cd build_linux +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="-O3 -march=native" -DCMAKE_C_FLAGS="-O3 -march=native" +make -j8 \ No newline at end of file diff --git a/nuget/build_windows.ps1 b/nuget/build_windows.ps1 new file mode 100644 index 0000000000..f0450329d8 --- /dev/null +++ b/nuget/build_windows.ps1 @@ -0,0 +1,27 @@ +# use cmake to build the HiGHS windows .dll +try { + if (-not (Test-Path build_windows)) { + mkdir build_windows + } + cd build_windows + + # Define a list to store arguments + $arguments = @() + + $arguments += "-A x64" + $arguments += "-DCMAKE_SYSTEM_NAME=Windows" + $arguments += "-DCMAKE_BUILD_TYPE=Release" + $arguments += "-DBUILD_SHARED_LIBS=ON" + + # Compiler flags as a single string + # $flags = "/arch:AVX512 /Ox /Ot /Oi /O2" - Intel stopped supporting AVX-512 on some CPUs, me might run into compatibility issues + $flags = "/Ox /Ot /Oi /O2" + $arguments += "-DCMAKE_CXX_FLAGS='$flags'" + + # Pass the argument list to cmake + & cmake $arguments ../.. + cmake --build . --config Release + } +catch { + Write-Error "Failed to build for Windows: $($_.Exception.Message)" +} diff --git a/nuget/generatePackage.ps1 b/nuget/generatePackage.ps1 new file mode 100644 index 0000000000..59c2d1b80e --- /dev/null +++ b/nuget/generatePackage.ps1 @@ -0,0 +1,28 @@ +try { + Set-Location "$PSScriptRoot\.." + # Use regex to match and extract version numbers with any prefix + $content = Get-Content Version.txt -Raw + $major = ($content | Select-String -Pattern ".*_MAJOR=(\d+)").Matches.Groups[1].Value + $minor = ($content | Select-String -Pattern ".*_MINOR=(\d+)").Matches.Groups[1].Value + $patch = ($content | Select-String -Pattern ".*_PATCH=(\d+)").Matches.Groups[1].Value + # check if a prereleas is set, i.e. the line with PRE_RELEASE doesn't start with # and has the value YES + $preReleaseMatch = $content | Select-String -Pattern "[^#]PRE_RELEASE=(.+)" -AllMatches + if ($preReleaseMatch -ne $null -and $preReleaseMatch.Matches.Count -gt 0 -and $preReleaseMatch.Matches[0].Groups[1].Success) { + $preRelease = $preReleaseMatch.Matches[0].Groups[1].Value.Trim() + } else { + $preRelease = "" + } + # Combine and output the version + $version = "$major.$minor.$patch" + if ($preRelease -eq "YES") { + # Append a pre-release suffix if needed + $version = "$version-alpha" # Adjust this line as needed for your pre-release naming convention + } + + Set-Location "$PSScriptRoot" + # Now pack + dotnet pack -c Release /p:Version=$version +} +catch { + Write-Error $_.Exception.Message +} diff --git a/pyproject.toml b/pyproject.toml index a442d61d76..e3173dfedf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,55 +1,198 @@ +[project.urls] +"Source Code" = "https://github.com/ERGO-Code/HiGHS" +"Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" + +[build-system] +requires = ["scikit-build-core>=0.3.3", "pybind11", "numpy"] +build-backend = "scikit_build_core.build" + [project] name = "highspy" -version = "1.6.0.dev0" +version = "1.7.2" description = "A thin set of pybind11 wrappers to HiGHS" -authors = [ - {name = "HiGHS developers", email = "highsopt@gmail.com"}, +authors = [{ name = "HiGHS developers", email = "highsopt@gmail.com" }] +readme = "README.md" + +requires-python = ">=3.8" +dependencies = ["numpy"] + +classifiers = [ + # "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +[project.optional-dependencies] +test = ["pytest", "numpy"] + +[tool.scikit-build] +cmake.args = ["-DPYTHON_BUILD_SETUP=ON"] +wheel.expand-macos-universal-tags = true + +# A list of packages to auto-copy into the wheel. If this is not set, it will +# default to the first of ``src/``, ``python/``, or +# ```` if they exist. The prefix(s) will be stripped from the package +# name inside the wheel. +wheel.packages = ["src/highspy"] + +# Files to include in the SDist even if they are skipped by default. Supports +# gitignore syntax. +sdist.include = [ + "src/highspy/highs.py", + "tests/test_highspy.py", + "Version.txt", + "LICENSE", + "README.md", + "src/HConfig.h.in", + "src", + "external", + "cmake", ] -dependencies = [ - # pybind11/numpy runtime dep - # https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html - "numpy>=1.7.0", + +sdist.exclude = [ + ".github", + ".gitattributes", + ".gitignore", + ".github", + "app", + "build", + "check", + "docs", + "subprojects", + ".coin-or", + "build_webdemo.sh", + ".clang-format", + "__setup.py", + "BUILD.bazel", + "meson*", + "MODS.md", + "WORKSPACE", ] -requires-python = ">=3.8" -readme = "README.md" -license = {text = "MIT"} -[project.urls] -"Source Code" = "https://github.com/ERGO-Code/HiGHS" -"Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" -[build-system] -requires = ["meson-python<0.14.0", "meson>=1.2.0"] -build-backend = "mesonpy" - -[tool.meson-python.args] -setup = ['-Dwith_pybind11=True', - '-Dhighsint64=False', - '-Dwrap_mode=forcefallback', - # ^-- collects pybind11, see https://github.com/ERGO-Code/HiGHS/pull/1343#discussion_r1252446966 - ] -dist = ['--include-subprojects'] -install = ['--skip-subprojects'] +# # Verbose printout when building. +# cmake.verbose = false + +# # The build type to use when building the project. Valid options are: "Debug", +# # "Release", "RelWithDebInfo", "MinSizeRel", "", etc. +# cmake.build-type = "Release" + +# # The versions of Ninja to allow. If Ninja is not present on the system or does +# # not pass this specifier, it will be downloaded via PyPI if possible. An empty +# # string will disable this check. +# ninja.version = ">=1.5" + +# # If CMake is not present on the system or is older required, it will be +# # downloaded via PyPI if possible. An empty string will disable this check. +# ninja.make-fallback = true + +# # The logging level to display, "DEBUG", "INFO", "WARNING", and "ERROR" are +# # possible options. +# logging.level = "WARNING" + + +# # If set to True, try to build a reproducible distribution (Unix and Python 3.9+ +# # recommended). ``SOURCE_DATE_EPOCH`` will be used for timestamps, or a fixed +# # value if not set. +# sdist.reproducible = true + +# # If set to True, CMake will be run before building the SDist. +# sdist.cmake = false + +# # The Python tags. The default (empty string) will use the default Python +# # version. You can also set this to "cp37" to enable the CPython 3.7+ Stable ABI +# # / Limited API (only on CPython and if the version is sufficient, otherwise +# # this has no effect). Or you can set it to "py3" or "py2.py3" to ignore Python +# # ABI compatibility. The ABI tag is inferred from this tag. +# wheel.py-api = "" + +# # Fill out extra tags that are not required. This adds "x86_64" and "arm64" to +# # the list of platforms when "universal2" is used, which helps older Pip's +# # (before 21.0.1) find the correct wheel. +# # wheel.expand-macos-universal-tags = false + +# # The install directory for the wheel. This is relative to the platlib root. You +# # might set this to the package name. The original dir is still at +# # SKBUILD_PLATLIB_DIR (also SKBUILD_DATA_DIR, etc. are available). EXPERIMENTAL: +# # An absolute path will be one level higher than the platlib root, giving access +# # to "/platlib", "/data", "/headers", and "/scripts". +# # wheel.install-dir = "" + +# # A list of license files to include in the wheel. Supports glob patterns. +# wheel.license-files = ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"] + +# # If set to True (the default), CMake will be run before building the wheel. +# wheel.cmake = true + +# # Target the platlib or the purelib. If not set, the default is to target the +# # platlib if wheel.cmake is true, and the purelib otherwise. +# # wheel.platlib = "" + +# # A set of patterns to exclude from the wheel. This is additive to the SDist +# # exclude patterns. This applies to the final paths in the wheel, and can +# # exclude files from CMake output as well. Editable installs may not respect +# # this exclusion. +# wheel.exclude = [] + +# # The build tag to use for the wheel. If empty, no build tag is used. +# wheel.build-tag = "" + +# # If CMake is less than this value, backport a copy of FindPython. Set to 0 +# # disable this, or the empty string. +# backport.find-python = "3.26.1" + +# # Select the editable mode to use. Can be "redirect" (default) or "inplace". +# editable.mode = "redirect" + +# # Turn on verbose output for the editable mode rebuilds. +# editable.verbose = true + +# # Rebuild the project when the package is imported. The build-directory must be +# # set. +# editable.rebuild = false + +# # The components to install. If empty, all default components are installed. +# install.components = [] + +# # Whether to strip the binaries. True for scikit-build-core 0.5+. +# install.strip = false + +# # List dynamic metadata fields and hook locations in this table. +# metadata = {} + +# # Strictly check all config options. If False, warnings will be printed for +# # unknown options. If True, an error will be raised. +# strict-config = true + +# # Enable early previews of features not finalized yet. +# experimental = false + +# # If set, this will provide a method for backward compatibility. +# # minimum-version = "0.8" # current version + +# # The build directory. Defaults to a temporary directory, but can be set. +# # build-dir = "" + + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +xfail_strict = true +log_cli_level = "INFO" +filterwarnings = ["error"] +testpaths = ["tests"] + [tool.cibuildwheel] build = "*" -skip = "cp36-*" -test-skip = "" - -[tool.cibuildwheel.linux] -manylinux-x86_64-image = "manylinux2014" -manylinux-i686-image = "manylinux2014" -repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" - -[tool.cibuildwheel.macos] -archs = ["x86_64 arm64"] -environment = { RUNNER_OS="macOS" } -repair-wheel-command = [ - "delocate-listdeps {wheel}", - "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}", -] +skip = "cp3{6,7}-*" -[tool.cibuildwheel.windows] -# Use delvewheel on windows, and install the project so delvewheel can find it -before-build = "pip install delvewheel meson ninja && meson setup bbdir && meson install -C bbdir" -repair-wheel-command = "delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w {dest_dir} {wheel}" +test-command = "pytest {project}/tests" +test-extras = ["test", "numpy"] +test-skip = ["*universal2:arm64"] +build-verbosity = 1 diff --git a/scripts/post_install_win.bat b/scripts/post_install_win.bat new file mode 100644 index 0000000000..35ae5c49dd --- /dev/null +++ b/scripts/post_install_win.bat @@ -0,0 +1,10 @@ +pip install delvewheel meson ninja + +meson setup bbdir +meson compile -C bbdir + +REM Repair the wheel using delvewheel +set destDir=%1 +set wheel=%2 + +delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w %destDir% %wheel% diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc24e57850..69a78869db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,318 +1,27 @@ +if (NOT BUILD_CXX) + return() +endif() + # Define library. -# Outdated CMake approach: update in progress - -set(basiclu_sources - ipm/basiclu/basiclu_factorize.c - ipm/basiclu/basiclu_solve_dense.c - ipm/basiclu/lu_build_factors.c - ipm/basiclu/lu_factorize_bump.c - ipm/basiclu/lu_initialize.c - ipm/basiclu/lu_markowitz.c - ipm/basiclu/lu_setup_bump.c - ipm/basiclu/lu_solve_sparse.c - ipm/basiclu/basiclu_get_factors.c - ipm/basiclu/basiclu_solve_for_update.c - ipm/basiclu/lu_condest.c - ipm/basiclu/lu_file.c - ipm/basiclu/lu_internal.c - ipm/basiclu/lu_matrix_norm.c - ipm/basiclu/lu_singletons.c - ipm/basiclu/lu_solve_symbolic.c - ipm/basiclu/lu_update.c - ipm/basiclu/basiclu_initialize.c - ipm/basiclu/basiclu_solve_sparse.c - ipm/basiclu/lu_pivot.c - ipm/basiclu/lu_solve_dense.c - ipm/basiclu/lu_solve_triangular.c - ipm/basiclu/basiclu_object.c - ipm/basiclu/basiclu_update.c - ipm/basiclu/lu_dfs.c - ipm/basiclu/lu_garbage_perm.c - ipm/basiclu/lu_residual_test.c - ipm/basiclu/lu_solve_for_update.c) - -set(ipx_sources - ipm/ipx/basiclu_kernel.cc - ipm/ipx/basiclu_wrapper.cc - ipm/ipx/basis.cc - ipm/ipx/conjugate_residuals.cc - ipm/ipx/control.cc - ipm/ipx/crossover.cc - ipm/ipx/diagonal_precond.cc - ipm/ipx/forrest_tomlin.cc - ipm/ipx/guess_basis.cc - ipm/ipx/indexed_vector.cc - ipm/ipx/info.cc - ipm/ipx/ipm.cc - ipm/ipx/ipx_c.cc - ipm/ipx/iterate.cc - ipm/ipx/kkt_solver.cc - ipm/ipx/kkt_solver_basis.cc - ipm/ipx/kkt_solver_diag.cc - ipm/ipx/linear_operator.cc - ipm/ipx/lp_solver.cc - ipm/ipx/lu_factorization.cc - ipm/ipx/lu_update.cc - ipm/ipx/maxvolume.cc - ipm/ipx/model.cc - ipm/ipx/normal_matrix.cc - ipm/ipx/sparse_matrix.cc - ipm/ipx/sparse_utils.cc - ipm/ipx/splitted_normal_matrix.cc - ipm/ipx/starting_basis.cc - ipm/ipx/symbolic_invert.cc - ipm/ipx/timer.cc - ipm/ipx/utils.cc) +include(sources) +set(sources ${highs_sources} ${cupdlp_sources} ${ipx_sources} ${basiclu_sources}) +set(headers ${highs_headers} ${cupdlp_headers} ${ipx_headers} ${basiclu_headers}) + +# Configure the config windows version file +if(MSVC) + string(REPLACE "." "," PROJECT_RC_VERSION "${PROJECT_VERSION}") + configure_file(${HIGHS_SOURCE_DIR}/version.rc.in + "${HIGHS_BINARY_DIR}/version.rc" @ONLY) + set(win_version_file ${HIGHS_BINARY_DIR}/version.rc) +else() + set(win_version_file) +endif() # Outdated CMake approach: update in progress if(NOT FAST_BUILD) - include_directories(ipm/ipx) - include_directories(ipm/basiclu) - - set(sources - ../extern/filereaderlp/reader.cpp - io/Filereader.cpp - io/FilereaderLp.cpp - io/FilereaderEms.cpp - io/FilereaderMps.cpp - io/HighsIO.cpp - io/HMPSIO.cpp - io/HMpsFF.cpp - io/LoadOptions.cpp - lp_data/Highs.cpp - lp_data/HighsCallback.cpp - lp_data/HighsDebug.cpp - lp_data/HighsInfo.cpp - lp_data/HighsInfoDebug.cpp - lp_data/HighsDeprecated.cpp - lp_data/HighsInterface.cpp - lp_data/HighsLp.cpp - lp_data/HighsLpUtils.cpp - lp_data/HighsModelUtils.cpp - lp_data/HighsRanging.cpp - lp_data/HighsSolution.cpp - lp_data/HighsSolutionDebug.cpp - lp_data/HighsSolve.cpp - lp_data/HighsStatus.cpp - lp_data/HighsOptions.cpp - mip/HighsMipSolver.cpp - mip/HighsMipSolverData.cpp - mip/HighsDomain.cpp - mip/HighsDynamicRowMatrix.cpp - mip/HighsLpRelaxation.cpp - mip/HighsSeparation.cpp - mip/HighsSeparator.cpp - mip/HighsTableauSeparator.cpp - mip/HighsModkSeparator.cpp - mip/HighsPathSeparator.cpp - mip/HighsCutGeneration.cpp - mip/HighsSearch.cpp - mip/HighsConflictPool.cpp - mip/HighsCutPool.cpp - mip/HighsCliqueTable.cpp - mip/HighsGFkSolve.cpp - mip/HighsTransformedLp.cpp - mip/HighsLpAggregator.cpp - mip/HighsDebugSol.cpp - mip/HighsImplications.cpp - mip/HighsPrimalHeuristics.cpp - mip/HighsPseudocost.cpp - mip/HighsRedcostFixing.cpp - mip/HighsNodeQueue.cpp - mip/HighsObjectiveFunction.cpp - model/HighsHessian.cpp - model/HighsHessianUtils.cpp - model/HighsModel.cpp - parallel/HighsTaskExecutor.cpp - presolve/ICrash.cpp - presolve/ICrashUtil.cpp - presolve/ICrashX.cpp - presolve/HighsPostsolveStack.cpp - presolve/HighsSymmetry.cpp - presolve/HPresolve.cpp - presolve/HPresolveAnalysis.cpp - presolve/PresolveComponent.cpp - qpsolver/a_asm.cpp - qpsolver/a_quass.cpp - qpsolver/basis.cpp - qpsolver/quass.cpp - qpsolver/ratiotest.cpp - qpsolver/scaling.cpp - qpsolver/perturbation.cpp - simplex/HEkk.cpp - simplex/HEkkControl.cpp - simplex/HEkkDebug.cpp - simplex/HEkkPrimal.cpp - simplex/HEkkDual.cpp - simplex/HEkkDualRHS.cpp - simplex/HEkkDualRow.cpp - simplex/HEkkDualMulti.cpp - simplex/HEkkInterface.cpp - simplex/HighsSimplexAnalysis.cpp - simplex/HSimplex.cpp - simplex/HSimplexDebug.cpp - simplex/HSimplexNla.cpp - simplex/HSimplexNlaDebug.cpp - simplex/HSimplexNlaFreeze.cpp - simplex/HSimplexNlaProductForm.cpp - simplex/HSimplexReport.cpp - test/DevKkt.cpp - test/KktCh2.cpp - util/HFactor.cpp - util/HFactorDebug.cpp - util/HFactorExtend.cpp - util/HFactorRefactor.cpp - util/HFactorUtils.cpp - util/HighsHash.cpp - util/HighsLinearSumBounds.cpp - util/HighsMatrixPic.cpp - util/HighsMatrixUtils.cpp - util/HighsSort.cpp - util/HighsSparseMatrix.cpp - util/HighsUtils.cpp - util/HSet.cpp - util/HVectorBase.cpp - util/stringutil.cpp - interfaces/highs_c_api.cpp) - - set(headers - ../extern/filereaderlp/builder.hpp - ../extern/filereaderlp/model.hpp - ../extern/filereaderlp/reader.hpp - io/Filereader.h - io/FilereaderLp.h - io/FilereaderEms.h - io/FilereaderMps.h - io/HMpsFF.h - io/HMPSIO.h - io/HighsIO.h - io/LoadOptions.h - lp_data/HConst.h - lp_data/HStruct.h - lp_data/HighsAnalysis.h - lp_data/HighsCallback.h - lp_data/HighsCallbackStruct.h - lp_data/HighsDebug.h - lp_data/HighsInfo.h - lp_data/HighsInfoDebug.h - lp_data/HighsLp.h - lp_data/HighsLpSolverObject.h - lp_data/HighsLpUtils.h - lp_data/HighsModelUtils.h - lp_data/HighsOptions.h - lp_data/HighsRanging.h - lp_data/HighsRuntimeOptions.h - lp_data/HighsSolution.h - lp_data/HighsSolutionDebug.h - lp_data/HighsSolve.h - lp_data/HighsStatus.h - mip/HighsCliqueTable.h - mip/HighsCutGeneration.h - mip/HighsConflictPool.h - mip/HighsCutPool.h - mip/HighsDebugSol.h - mip/HighsDomainChange.h - mip/HighsDomain.h - mip/HighsDynamicRowMatrix.h - mip/HighsGFkSolve.h - mip/HighsImplications.h - mip/HighsLpAggregator.h - mip/HighsLpRelaxation.h - mip/HighsMipSolverData.h - mip/HighsMipSolver.h - mip/HighsModkSeparator.h - mip/HighsNodeQueue.h - mip/HighsObjectiveFunction.h - mip/HighsPathSeparator.h - mip/HighsPrimalHeuristics.h - mip/HighsPseudocost.h - mip/HighsRedcostFixing.h - mip/HighsSearch.h - mip/HighsSeparation.h - mip/HighsSeparator.h - mip/HighsTableauSeparator.h - mip/HighsTransformedLp.h - model/HighsHessian.h - model/HighsHessianUtils.h - model/HighsModel.h - parallel/HighsBinarySemaphore.h - parallel/HighsCacheAlign.h - parallel/HighsCombinable.h - parallel/HighsMutex.h - parallel/HighsParallel.h - parallel/HighsRaceTimer.h - parallel/HighsSchedulerConstants.h - parallel/HighsSpinMutex.h - parallel/HighsSplitDeque.h - parallel/HighsTaskExecutor.h - parallel/HighsTask.h - qpsolver/quass.hpp - qpsolver/vector.hpp - qpsolver/scaling.hpp - qpsolver/perturbation.hpp - qpsolver/a_quass.hpp - qpsolver/a_asm.hpp - simplex/HApp.h - simplex/HEkk.h - simplex/HEkkDual.h - simplex/HEkkDualRHS.h - simplex/HEkkDualRow.h - simplex/HEkkPrimal.h - simplex/HighsSimplexAnalysis.h - simplex/HSimplex.h - simplex/HSimplexReport.h - simplex/HSimplexDebug.h - simplex/HSimplexNla.h - simplex/SimplexConst.h - simplex/SimplexStruct.h - simplex/SimplexTimer.h - presolve/ICrash.h - presolve/ICrashUtil.h - presolve/ICrashX.h - presolve/HighsPostsolveStack.h - presolve/HighsSymmetry.h - presolve/HPresolve.h - presolve/HPresolveAnalysis.h - presolve/PresolveComponent.h - test/DevKkt.h - test/KktCh2.h - util/FactorTimer.h - util/HFactor.h - util/HFactorConst.h - util/HFactorDebug.h - util/HighsCDouble.h - util/HighsComponent.h - util/HighsDataStack.h - util/HighsDisjointSets.h - util/HighsHash.h - util/HighsHashTree.h - util/HighsInt.h - util/HighsIntegers.h - util/HighsLinearSumBounds.h - util/HighsMatrixPic.h - util/HighsMatrixSlice.h - util/HighsMatrixUtils.h - util/HighsRandom.h - util/HighsRbTree.h - util/HighsSort.h - util/HighsSparseMatrix.h - util/HighsSparseVectorSum.h - util/HighsSplay.h - util/HighsTimer.h - util/HighsUtils.h - util/HSet.h - util/HVector.h - util/HVectorBase.h - util/stringutil.h - Highs.h - interfaces/highs_c_api.h - ) - - set(headers ${headers} ipm/IpxWrapper.h ${basiclu_headers} - ${ipx_headers}) - set(sources ${sources} ipm/IpxWrapper.cpp ${basiclu_sources} ${ipx_sources}) - add_library(libhighs ${sources}) + add_library(libhighs ${sources} ${headers} ${win_version_file}) + target_include_directories(libhighs PRIVATE ${include_dirs}) if(${BUILD_SHARED_LIBS}) # put version information into shared library file @@ -333,6 +42,7 @@ if(NOT FAST_BUILD) # on UNIX system the 'lib' prefix is automatically added set_target_properties(libhighs PROPERTIES OUTPUT_NAME "highs" + PDB_NAME "libhighs" MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") if(ZLIB AND ZLIB_FOUND) @@ -371,11 +81,10 @@ if(NOT FAST_BUILD) # target_compile_options(libipx PRIVATE "-Wno-return-type-c-linkage") # target_compile_options(libipx PRIVATE "-Wno-return-type" "-Wno-switch") - # target_compile_options(libipx PRIVATE "-Wno-unused-variable") # target_compile_options(libipx PRIVATE "-Wno-sign-compare") # target_compile_options(libipx PRIVATE "-Wno-logical-op-parentheses") endif() - + install(TARGETS libhighs EXPORT highs-targets LIBRARY ARCHIVE @@ -420,9 +129,7 @@ else() # Define library in modern CMake using target_*() # No interfaces (apart from c); No ipx; New (short) ctest instances. add_library(highs) - - set_target_properties(highs PROPERTIES POSITION_INDEPENDENT_CODE ON) - target_compile_definitions(highs PUBLIC LIBHIGHS_STATIC_DEFINE) + add_library(${PROJECT_NAMESPACE}::highs ALIAS highs) if(${BUILD_SHARED_LIBS}) # put version information into shared library file @@ -432,149 +139,102 @@ else() SOVERSION ${HIGHS_VERSION_MAJOR}.${HIGHS_VERSION_MINOR}) endif() - target_sources(highs PRIVATE - ../extern/filereaderlp/reader.cpp - io/Filereader.cpp - io/FilereaderLp.cpp - io/FilereaderEms.cpp - io/FilereaderMps.cpp - io/HighsIO.cpp - io/HMPSIO.cpp - io/HMpsFF.cpp - io/LoadOptions.cpp - lp_data/Highs.cpp - lp_data/HighsCallback.cpp - lp_data/HighsDebug.cpp - lp_data/HighsDeprecated.cpp - lp_data/HighsInfo.cpp - lp_data/HighsInfoDebug.cpp - lp_data/HighsInterface.cpp - lp_data/HighsLp.cpp - lp_data/HighsLpUtils.cpp - lp_data/HighsModelUtils.cpp - lp_data/HighsRanging.cpp - lp_data/HighsSolution.cpp - lp_data/HighsSolutionDebug.cpp - lp_data/HighsSolve.cpp - lp_data/HighsStatus.cpp - lp_data/HighsOptions.cpp - presolve/ICrash.cpp - presolve/ICrashUtil.cpp - presolve/ICrashX.cpp - mip/HighsMipSolver.cpp - mip/HighsMipSolverData.cpp - mip/HighsDomain.cpp - mip/HighsDynamicRowMatrix.cpp - mip/HighsLpRelaxation.cpp - mip/HighsSeparation.cpp - mip/HighsSeparator.cpp - mip/HighsTableauSeparator.cpp - mip/HighsModkSeparator.cpp - mip/HighsPathSeparator.cpp - mip/HighsCutGeneration.cpp - mip/HighsSearch.cpp - mip/HighsConflictPool.cpp - mip/HighsCutPool.cpp - mip/HighsCliqueTable.cpp - mip/HighsGFkSolve.cpp - mip/HighsTransformedLp.cpp - mip/HighsLpAggregator.cpp - mip/HighsDebugSol.cpp - mip/HighsImplications.cpp - mip/HighsPrimalHeuristics.cpp - mip/HighsPseudocost.cpp - mip/HighsNodeQueue.cpp - mip/HighsObjectiveFunction.cpp - mip/HighsRedcostFixing.cpp - model/HighsHessian.cpp - model/HighsHessianUtils.cpp - model/HighsModel.cpp - parallel/HighsTaskExecutor.cpp - presolve/ICrashX.cpp - presolve/HighsPostsolveStack.cpp - presolve/HighsSymmetry.cpp - presolve/HPresolve.cpp - presolve/HPresolveAnalysis.cpp - presolve/PresolveComponent.cpp - qpsolver/a_asm.cpp - qpsolver/a_quass.cpp - qpsolver/basis.cpp - qpsolver/quass.cpp - qpsolver/ratiotest.cpp - qpsolver/scaling.cpp - qpsolver/perturbation.cpp - simplex/HEkk.cpp - simplex/HEkkControl.cpp - simplex/HEkkDebug.cpp - simplex/HEkkPrimal.cpp - simplex/HEkkDual.cpp - simplex/HEkkDualRHS.cpp - simplex/HEkkDualRow.cpp - simplex/HEkkDualMulti.cpp - simplex/HEkkInterface.cpp - simplex/HighsSimplexAnalysis.cpp - simplex/HSimplex.cpp - simplex/HSimplexDebug.cpp - simplex/HSimplexNla.cpp - simplex/HSimplexNlaDebug.cpp - simplex/HSimplexNlaFreeze.cpp - simplex/HSimplexNlaProductForm.cpp - simplex/HSimplexReport.cpp - test/KktCh2.cpp - test/DevKkt.cpp - util/HFactor.cpp - util/HFactorDebug.cpp - util/HFactorExtend.cpp - util/HFactorRefactor.cpp - util/HFactorUtils.cpp - util/HighsHash.cpp - util/HighsLinearSumBounds.cpp - util/HighsMatrixPic.cpp - util/HighsMatrixUtils.cpp - util/HighsSort.cpp - util/HighsSparseMatrix.cpp - util/HighsUtils.cpp - util/HSet.cpp - util/HVectorBase.cpp - util/stringutil.cpp - interfaces/highs_c_api.cpp) + set_target_properties(highs PROPERTIES POSITION_INDEPENDENT_CODE ON) + + if(APPLE) + set_target_properties(highs PROPERTIES INSTALL_RPATH "@loader_path") + elseif (UNIX) + set_target_properties(highs PROPERTIES INSTALL_RPATH "$ORIGIN") + endif() target_include_directories(highs PUBLIC $ $ - $ + # $ ) - target_include_directories(highs PRIVATE - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - ) + # target_include_directories(highs PRIVATE + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $ + # $) + + # target_include_directories(highs PRIVATE + # $ + # $ + # $) + + target_sources(highs PRIVATE ${sources} ${headers} ${win_version_file}) + target_include_directories(highs PRIVATE ${include_dirs}) + + if(MSVC) + list(APPEND highs_compile_opts + "/bigobj" # Allow big object + "/DNOMINMAX" + "/DWIN32_LEAN_AND_MEAN=1" + "/D_CRT_SECURE_NO_WARNINGS" + "/D_CRT_SECURE_NO_DEPRECATE" + "/MP" # Build with multiple processes + "/Zc:preprocessor" # Enable preprocessor conformance mode + "/fp:precise" + ) + if (CMAKE_BUILD_TYPE STREQUAL Release) + list(APPEND highs_compile_opts + "/DNDEBUG") + endif() + # MSVC warning suppressions + list(APPEND highs_compile_opts + "/wd4005" # 'macro-redefinition' + "/wd4018" # 'expression' : signed/unsigned mismatch + "/wd4065" # switch statement contains 'default' but no 'case' labels + "/wd4068" # 'unknown pragma' + "/wd4101" # 'identifier' : unreferenced local variable + "/wd4146" # unary minus operator applied to unsigned type, result still unsigned + "/wd4200" # nonstandard extension used : zero-sized array in struct/union + "/wd4244" # 'conversion' conversion from 'type1' to 'type2', possible loss of data + "/wd4251" # 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + "/wd4267" # 'var' : conversion from 'size_t' to 'type', possible loss of data + "/wd4305" # 'identifier' : truncation from 'type1' to 'type2' + "/wd4307" # 'operator' : integral constant overflow + "/wd4309" # 'conversion' : truncation of constant value + "/wd4334" # 'operator' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) + "/wd4355" # 'this' : used in base member initializer list + "/wd4477" # 'fwprintf' : format string '%s' requires an argument of type 'wchar_t *' + "/wd4506" # no definition for inline function 'function' + "/wd4715" # function' : not all control paths return a value + "/wd4800" # 'type' : forcing value to bool 'true' or 'false' (performance warning) + "/wd4996" # The compiler encountered a deprecated declaration. + ) + target_compile_options(highs PRIVATE ${highs_compile_opts}) + # else() + # list(APPEND highs_compile_opts "-fwrapv") + endif() - target_include_directories(highs PRIVATE - $ - $ - $ - ) if(ZLIB AND ZLIB_FOUND) target_include_directories(highs PRIVATE $ ) target_link_libraries(highs ZLIB::ZLIB) - set(CONF_DEPENDENCIES "include(CMakeFindDependencyMacro)\nfind_dependency(ZLIB)") + set(CONF_DEPS + "include(CMakeFindDependencyMacro)\nfind_dependency(Threads)\nfind_dependency(ZLIB)") + set(CONF_DEPENDENCIES ${CONF_DEPS}) + else() + set(CONF_DEPENDENCIES "include(CMakeFindDependencyMacro)\nfind_dependency(Threads)") endif() + # # on UNIX system the 'lib' prefix is automatically added # set_target_properties(highs PROPERTIES @@ -585,150 +245,11 @@ else() # set_target_properties(highs PROPERTIES # LIBRARY_OUTPUT_DIRECTORY "${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") # endif() - set(headers_fast_build_ - ../extern/filereaderlp/builder.hpp - ../extern/filereaderlp/model.hpp - ../extern/filereaderlp/reader.hpp - io/Filereader.h - io/FilereaderLp.h - io/FilereaderEms.h - io/FilereaderMps.h - io/HMpsFF.h - io/HMPSIO.h - io/HighsIO.h - io/LoadOptions.h - lp_data/HConst.h - lp_data/HStruct.h - lp_data/HighsAnalysis.h - lp_data/HighsCallback.h - lp_data/HighsCallbackStruct.h - lp_data/HighsDebug.h - lp_data/HighsInfo.h - lp_data/HighsInfoDebug.h - lp_data/HighsLp.h - lp_data/HighsLpSolverObject.h - lp_data/HighsLpUtils.h - lp_data/HighsModelUtils.h - lp_data/HighsOptions.h - lp_data/HighsRanging.h - lp_data/HighsRuntimeOptions.h - lp_data/HighsSolution.h - lp_data/HighsSolutionDebug.h - lp_data/HighsSolve.h - lp_data/HighsStatus.h - mip/HighsCliqueTable.h - mip/HighsCutGeneration.h - mip/HighsConflictPool.h - mip/HighsCutPool.h - mip/HighsDebugSol.h - mip/HighsDomainChange.h - mip/HighsDomain.h - mip/HighsDynamicRowMatrix.h - mip/HighsGFkSolve.h - mip/HighsImplications.h - mip/HighsLpAggregator.h - mip/HighsLpRelaxation.h - mip/HighsMipSolverData.h - mip/HighsMipSolver.h - mip/HighsModkSeparator.h - mip/HighsNodeQueue.h - mip/HighsObjectiveFunction.h - mip/HighsPathSeparator.h - mip/HighsPrimalHeuristics.h - mip/HighsPseudocost.h - mip/HighsRedcostFixing.h - mip/HighsSearch.h - mip/HighsSeparation.h - mip/HighsSeparator.h - mip/HighsTableauSeparator.h - mip/HighsTransformedLp.h - model/HighsHessian.h - model/HighsHessianUtils.h - model/HighsModel.h - parallel/HighsBinarySemaphore.h - parallel/HighsCacheAlign.h - parallel/HighsCombinable.h - parallel/HighsMutex.h - parallel/HighsParallel.h - parallel/HighsRaceTimer.h - parallel/HighsSchedulerConstants.h - parallel/HighsSpinMutex.h - parallel/HighsSplitDeque.h - parallel/HighsTaskExecutor.h - parallel/HighsTask.h - qpsolver/a_asm.hpp - qpsolver/a_quass.hpp - qpsolver/quass.hpp - qpsolver/vector.hpp - qpsolver/scaling.hpp - qpsolver/perturbation.hpp - simplex/HApp.h - simplex/HEkk.h - simplex/HEkkDual.h - simplex/HEkkDualRHS.h - simplex/HEkkDualRow.h - simplex/HEkkPrimal.h - simplex/HighsSimplexAnalysis.h - simplex/HSimplex.h - simplex/HSimplexReport.h - simplex/HSimplexDebug.h - simplex/HSimplexNla.h - simplex/SimplexConst.h - simplex/SimplexStruct.h - simplex/SimplexTimer.h - presolve/ICrash.h - presolve/ICrashUtil.h - presolve/ICrashX.h - presolve/HighsPostsolveStack.h - presolve/HighsSymmetry.h - presolve/HPresolve.h - presolve/HPresolveAnalysis.h - presolve/PresolveComponent.h - test/DevKkt.h - test/KktCh2.h - util/FactorTimer.h - util/HFactor.h - util/HFactorConst.h - util/HFactorDebug.h - util/HighsCDouble.h - util/HighsComponent.h - util/HighsDataStack.h - util/HighsDisjointSets.h - util/HighsHash.h - util/HighsHashTree.h - util/HighsInt.h - util/HighsIntegers.h - util/HighsLinearSumBounds.h - util/HighsMatrixPic.h - util/HighsMatrixSlice.h - util/HighsMatrixUtils.h - util/HighsRandom.h - util/HighsRbTree.h - util/HighsSort.h - util/HighsSparseMatrix.h - util/HighsSparseVectorSum.h - util/HighsSplay.h - util/HighsTimer.h - util/HighsUtils.h - util/HSet.h - util/HVector.h - util/HVectorBase.h - util/stringutil.h - Highs.h - interfaces/highs_c_api.h - ) - - set(headers_fast_build_ ${headers_fast_build_} ipm/IpxWrapper.h ${basiclu_headers} - ${ipx_headers}) - + # 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(highs PROPERTIES INSTALL_RPATH - # "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - # install the header files of highs - foreach(file ${headers_fast_build_}) + foreach(file ${headers}) get_filename_component(dir ${file} DIRECTORY) if(NOT dir STREQUAL "") @@ -741,9 +262,8 @@ else() # target_compile_options(highs PRIVATE "-Wall") # target_compile_options(highs PRIVATE "-Wunused") - target_sources(highs PRIVATE ${basiclu_sources} ${ipx_sources} ipm/IpxWrapper.cpp) - - if(UNIX) + + if (UNIX) target_compile_options(highs PRIVATE "-Wno-unused-variable") target_compile_options(highs PRIVATE "-Wno-unused-const-variable") endif() @@ -772,6 +292,47 @@ else() DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/highs) install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + + + if (BUILD_DOTNET) + + # see: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)") + set(DOTNET_PLATFORM arm64) + else() + set(DOTNET_PLATFORM x64) + endif() + + if(APPLE) + set(DOTNET_RID osx-${DOTNET_PLATFORM}) + elseif(UNIX) + set(DOTNET_RID linux-${DOTNET_PLATFORM}) + elseif(WIN32) + set(DOTNET_RID win-${DOTNET_PLATFORM}) + else() + message(FATAL_ERROR "Unsupported system !") + endif() + message(STATUS ".Net RID: ${DOTNET_RID}") + + set(DOTNET_PROJECT Highs.Native) + set(DOTNET_PROJECT_DIR ${PROJECT_BINARY_DIR}/dotnet/${DOTNET_PROJECT}) + file(MAKE_DIRECTORY ${DOTNET_PROJECT_DIR}/runtimes/${DOTNET_RID}/native) + + if (APPLE) + set(TARGET_FILE_NAME "highs.dylib") + elseif(UNIX) + set(TARGET_FILE_NAME "highs.so") + elseif(WIN32) + set(TARGET_FILE_NAME "highs.dll") + endif() + + add_custom_command(TARGET highs POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + ${DOTNET_PROJECT_DIR}/runtimes/${DOTNET_RID}/native/${TARGET_FILE_NAME} + COMMENT "Copying to output directory") + endif() + endif() if(FORTRAN_FOUND) @@ -785,22 +346,28 @@ if(FORTRAN_FOUND) target_link_libraries(FortranHighs PUBLIC highs) endif() - install(TARGETS FortranHighs + install(TARGETS FortranHighs LIBRARY ARCHIVE RUNTIME INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs MODULES DESTINATION modules) - install(FILES ${HIGHS_BINARY_DIR}/modules/highs_fortran_api.mod DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs/fortran) - set_target_properties(FortranHighs PROPERTIES INSTALL_RPATH - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + if(NOT MSVC) + install(FILES ${HIGHS_BINARY_DIR}/modules/highs_fortran_api.mod DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs/fortran) + else() + install(FILES ${HIGHS_BINARY_DIR}/modules/${CMAKE_BUILD_TYPE}/highs_fortran_api.mod DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs/fortran) + endif() + # use link rpath + # set_target_properties(FortranHighs PROPERTIES INSTALL_RPATH + # "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") endif(FORTRAN_FOUND) if(CSHARP_FOUND) message(STATUS "CSharp supported") set(csharpsources interfaces/highs_csharp_api.cs) - add_library(HighsCsharp interfaces/highs_csharp_api.cs) + add_library(HighsCsharp SHARED interfaces/highs_csharp_api.cs) + add_library(${PROJECT_NAMESPACE}::HighsCsharp ALIAS HighsCsharp) target_compile_options(HighsCsharp PUBLIC "/unsafe") add_executable(csharpexample ../examples/call_highs_from_csharp.cs) target_compile_options(csharpexample PUBLIC "/unsafe") @@ -809,7 +376,17 @@ else() message(STATUS "No CSharp support") endif() -find_package(Threads REQUIRED) +find_package(Threads) +if(Threads_FOUND) + include(CheckAtomic) + if(HAVE_CXX_ATOMICS64_WITH_LIB) + if(FAST_BUILD) + target_link_libraries(highs atomic) + else() + target_link_libraries(libhighs atomic) + endif() + endif() +endif() if(FAST_BUILD) target_link_libraries(highs Threads::Threads) diff --git a/HConfig.h.bazel b/src/HConfig.h.bazel.in similarity index 71% rename from HConfig.h.bazel rename to src/HConfig.h.bazel.in index cf32fd5584..d6cd6003fa 100644 --- a/HConfig.h.bazel +++ b/src/HConfig.h.bazel.in @@ -3,6 +3,7 @@ #define FAST_BUILD /* #undef ZLIB_FOUND */ +#define CUPDLP_CPU #define CMAKE_BUILD_TYPE "RELEASE" #define HiGHSRELEASE /* #undef HIGHSINT64 */ @@ -10,11 +11,10 @@ #define HIGHS_HAVE_BUILTIN_CLZ /* #undef HIGHS_HAVE_BITSCAN_REVERSE */ -#define HIGHS_GITHASH "b66d596c6" -#define HIGHS_COMPILATION_DATE "2022-10-10" +#define HIGHS_GITHASH "50670fd4c" #define HIGHS_VERSION_MAJOR 1 -#define HIGHS_VERSION_MINOR 2 -#define HIGHS_VERSION_PATCH 2 +#define HIGHS_VERSION_MINOR 7 +#define HIGHS_VERSION_PATCH 0 /* #undef HIGHS_DIR */ #endif /* HCONFIG_H_ */ diff --git a/src/HConfig.h.in b/src/HConfig.h.in index 652f6651ff..198e07b0d3 100644 --- a/src/HConfig.h.in +++ b/src/HConfig.h.in @@ -3,15 +3,16 @@ #cmakedefine FAST_BUILD #cmakedefine ZLIB_FOUND +#cmakedefine CUPDLP_CPU #cmakedefine CMAKE_BUILD_TYPE "@CMAKE_BUILD_TYPE@" #cmakedefine CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" #cmakedefine HIGHSINT64 +#cmakedefine HIGHS_NO_DEFAULT_THREADS #cmakedefine HIGHS_HAVE_MM_PAUSE #cmakedefine HIGHS_HAVE_BUILTIN_CLZ #cmakedefine HIGHS_HAVE_BITSCAN_REVERSE #define HIGHS_GITHASH "@GITHASH@" -#define HIGHS_COMPILATION_DATE "@TODAY@" #define HIGHS_VERSION_MAJOR @HIGHS_VERSION_MAJOR@ #define HIGHS_VERSION_MINOR @HIGHS_VERSION_MINOR@ #define HIGHS_VERSION_PATCH @HIGHS_VERSION_PATCH@ diff --git a/src/HConfig.h.meson.in b/src/HConfig.h.meson.in index c3912df6fc..eff4bc810c 100644 --- a/src/HConfig.h.meson.in +++ b/src/HConfig.h.meson.in @@ -3,13 +3,14 @@ #mesondefine FAST_BUILD #mesondefine ZLIB_FOUND +#mesondefine CUPDLP_CPU #mesondefine HIGHSINT64 #mesondefine HIGHS_HAVE_MM_PAUSE #mesondefine HIGHS_HAVE_BUILTIN_CLZ #mesondefine HIGHS_HAVE_BITSCAN_REVERSE +#mesondefine HIGHS_NO_DEFAULT_THREADS -#define HIGHS_GITHASH @HIGHS_GITHASH@ -#define HIGHS_COMPILATION_DATE @HIGHS_COMPILATION_DATE@ +#define HIGHS_GITHASH "_HIGHS_GITHASH_" #define HIGHS_VERSION_MAJOR @HIGHS_VERSION_MAJOR@ #define HIGHS_VERSION_MINOR @HIGHS_VERSION_MINOR@ #define HIGHS_VERSION_PATCH @HIGHS_VERSION_PATCH@ diff --git a/src/Highs.h b/src/Highs.h index 76b39da24f..f104d72f1d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -37,7 +37,6 @@ HighsInt highsVersionMajor(); HighsInt highsVersionMinor(); HighsInt highsVersionPatch(); const char* highsGithash(); -const char* highsCompilationDate(); /** * @brief Class to set parameters and run HiGHS @@ -78,11 +77,6 @@ class Highs { */ std::string githash() const { return highsGithash(); } - /** - * @brief Return compilation date - */ - std::string compilationDate() const { return highsCompilationDate(); } - /** * @brief Reset the options and then call clearModel() */ @@ -384,6 +378,13 @@ class Highs { */ double getInfinity() { return kHighsInf; } + /** + * @brief Get the size of HighsInt + */ + HighsInt getSizeofHighsInt() { + return sizeof(options_.num_user_settable_options_); + } + /** * @brief Get the run time of HiGHS */ @@ -409,6 +410,22 @@ class Highs { */ const HighsPresolveLog& getPresolveLog() const { return presolve_log_; } + /** + * @brief Return a const pointer to the original column indices for + * the presolved model + */ + const HighsInt* getPresolveOrigColsIndex() const { + return presolve_.data_.postSolveStack.getOrigColsIndex(); + } + + /** + * @brief Return a const pointer to the original row indices for the + * presolved model + */ + const HighsInt* getPresolveOrigRowsIndex() const { + return presolve_.data_.postSolveStack.getOrigRowsIndex(); + } + /** * @brief Return a const reference to the incumbent LP */ @@ -482,6 +499,14 @@ class Highs { */ HighsStatus getRanging(HighsRanging& ranging); + /** + * @brief Get the ill-conditioning information for the current basis + */ + HighsStatus getIllConditioning(HighsIllConditioning& ill_conditioning, + const bool constraint, + const HighsInt method = 0, + const double ill_conditioning_bound = 1e-4); + /** * @brief Get the current model objective value */ @@ -617,7 +642,7 @@ class Highs { * @brief Get multiple columns from the model given by a set */ HighsStatus getCols( - const HighsInt num_set_entries, //!< The number of indides in the set + const HighsInt num_set_entries, //!< The number of indices in the set const HighsInt* set, //!< Array of size num_set_entries with indices of //!< columns to get HighsInt& num_col, //!< Number of columns got from the model @@ -689,7 +714,7 @@ class Highs { * @brief Get multiple rows from the model given by a set */ HighsStatus getRows( - const HighsInt num_set_entries, //!< The number of indides in the set + const HighsInt num_set_entries, //!< The number of indices in the set const HighsInt* set, //!< Array of size num_set_entries with indices of rows to get HighsInt& num_row, //!< Number of rows got from the model @@ -739,6 +764,17 @@ class Highs { */ HighsStatus writeModel(const std::string& filename = ""); + /** + * @brief Write out the incumbent presolved model to a file + */ + HighsStatus writePresolvedModel(const std::string& filename = ""); + + /** + * @brief Write out the given model to a file + */ + HighsStatus writeLocalModel(HighsModel& model, + const std::string& filename = ""); + /** * @brief Write out the internal HighsBasis instance to a file */ @@ -786,6 +822,14 @@ class Highs { HighsStatus changeColsIntegrality(const HighsInt* mask, const HighsVarType* integrality); + /** + * @brief Clear the integrality of all columns + */ + HighsStatus clearIntegrality() { + this->model_.lp_.integrality_.clear(); + return HighsStatus::kOk; + } + /** * @brief Change the cost of a column */ @@ -900,7 +944,7 @@ class Highs { * @brief Adds a variable to the incumbent model, without the cost or matrix * coefficients */ - HighsStatus addVar(const double lower, const double upper) { + HighsStatus addVar(const double lower = 0, const double upper = kHighsInf) { return this->addVars(1, &lower, &upper); } @@ -943,8 +987,9 @@ class Highs { /** * @brief Delete multiple columns from the incumbent model given by - * a mask (full length array with 1 => change; 0 => not). New index - * of any column not deleted is returned in place of the value 0. + * a mask (full length array with 1 => delete; 0 => keep). New index + * of any column kept is returned in place of the value 0. For + * deleted columns, a value of -1 is returned. */ HighsStatus deleteCols(HighsInt* mask); @@ -964,9 +1009,10 @@ class Highs { } /** - * @brief Delete multiple variables from the incumbent model given by - * a mask (full length array with 1 => change; 0 => not). New index - * of any variable not deleted is returned in place of the value 0. + * @brief Delete multiple variables from the incumbent model given + * by a mask (full length array with 1 => delete; 0 => keep). New + * index of any variable not deleted is returned in place of the + * value 0. For deleted variables, a value of -1 is returned. */ HighsStatus deleteVars(HighsInt* mask) { return deleteCols(mask); } @@ -983,8 +1029,9 @@ class Highs { /** * @brief Delete multiple rows from the incumbent model given by a - * mask (full length array with 1 => change; 0 => not). New index of - * any row not deleted is returned in place of the value 0. + * mask (full length array with 1 => delete; 0 => keep). New index + * of any row not deleted is returned in place of the value 0. For + * deleted rows, a value of -1 is returned. */ HighsStatus deleteRows(HighsInt* mask); @@ -1016,23 +1063,31 @@ class Highs { */ HighsStatus setSolution(const HighsSolution& solution); + /** + * @brief Pass a sparse primal solution + */ + HighsStatus setSolution(const HighsInt num_entries, const HighsInt* index, + const double* value); + /** * @brief Set the callback method to use for HiGHS */ - HighsStatus setCallback(void (*user_callback)(const int, const char*, - const HighsCallbackDataOut*, - HighsCallbackDataIn*, void*), + HighsStatus setCallback(HighsCallbackFunctionType user_callback, + void* user_callback_data = nullptr); + HighsStatus setCallback(HighsCCallbackType c_callback, void* user_callback_data = nullptr); /** * @brief Start callback of given type */ HighsStatus startCallback(const int callback_type); + HighsStatus startCallback(const HighsCallbackType callback_type); /** * @brief Stop callback of given type */ HighsStatus stopCallback(const int callback_type); + HighsStatus stopCallback(const HighsCallbackType callback_type); /** * @brief Use the HighsBasis passed to set the internal HighsBasis @@ -1163,6 +1218,8 @@ class Highs { // Start of deprecated methods + std::string compilationDate() const { return "deprecated"; } + HighsStatus setLogCallback(void (*user_log_callback)(HighsLogType, const char*, void*), void* user_log_callback_data = nullptr); @@ -1336,7 +1393,7 @@ class Highs { // // Methods to clear solver data for users in Highs class members // before (possibly) updating them with data from trying to solve - // the inumcumbent model. + // the incumbent model. // // Invalidates all solver data in Highs class members by calling // invalidateModelStatus(), invalidateSolution(), invalidateBasis(), @@ -1445,6 +1502,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(); @@ -1455,6 +1513,23 @@ class Highs { HighsStatus handleInfCost(); void restoreInfCost(HighsStatus& return_status); + HighsStatus optionChangeAction(); + HighsStatus computeIllConditioning(HighsIllConditioning& ill_conditioning, + const bool constraint, + const HighsInt method, + const double ill_conditioning_bound); + void formIllConditioningLp0(HighsLp& ill_conditioning_lp, + std::vector& basic_var, + const bool constraint); + void formIllConditioningLp1(HighsLp& ill_conditioning_lp, + std::vector& basic_var, + const bool constraint, + const double ill_conditioning_bound); + bool infeasibleBoundsOk(); }; +// Start of deprecated methods not in the Highs class + +const char* highsCompilationDate(); + #endif diff --git a/highspy/highs_bindings.cpp b/src/highs_bindings.cpp similarity index 86% rename from highspy/highs_bindings.cpp rename to src/highs_bindings.cpp index 1a674c3da2..99563a4486 100644 --- a/highspy/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -1,3 +1,5 @@ +#define PYBIND11_DETAILED_ERROR_MESSAGES 1 +#include #include #include #include @@ -5,6 +7,7 @@ #include #include "Highs.h" +#include "lp_data/HighsCallback.h" namespace py = pybind11; using namespace pybind11::literals; @@ -274,12 +277,39 @@ HighsStatus highs_changeColsIntegrality(Highs* h, HighsInt num_set_entries, // Same as deleteVars HighsStatus highs_deleteCols(Highs* h, HighsInt num_set_entries, - std::vector& indices) { + std::vector& indices) { return h->deleteCols(num_set_entries, indices.data()); } -HighsStatus highs_deleteRows(Highs* h, HighsInt num_set_entries, std::vector& indices) { - return h->deleteRows(num_set_entries, indices.data()); +HighsStatus highs_deleteRows(Highs* h, HighsInt num_set_entries, + std::vector& indices) { + return h->deleteRows(num_set_entries, indices.data()); +} + + +HighsStatus highs_setSolution(Highs* h, HighsSolution& solution) { + return h->setSolution(solution); +} + +HighsStatus highs_setSparseSolution(Highs* h, HighsInt num_entries, + py::array_t index, + py::array_t value) { + py::buffer_info index_info = index.request(); + py::buffer_info value_info = value.request(); + + HighsInt* index_ptr = reinterpret_cast(index_info.ptr); + double* value_ptr = static_cast(value_info.ptr); + + return h->setSolution(num_entries, index_ptr, value_ptr); +} + + +HighsStatus highs_setBasis(Highs* h, HighsBasis& basis) { + return h->setBasis(basis); +} + +HighsStatus highs_setLogicalBasis(Highs* h) { + return h->setBasis(); } std::tuple highs_getOptionValue( @@ -547,32 +577,31 @@ std::tuple highs_getRowByName(Highs* h, return std::make_tuple(status, row); } -PYBIND11_MODULE(_highs, m) { - // enum classes - py::enum_(m, "ObjSense") - .value("kMinimize", ObjSense::kMinimize) - .value("kMaximize", ObjSense::kMaximize) - .export_values(); - py::enum_(m, "MatrixFormat") - .value("kColwise", MatrixFormat::kColwise) - .value("kRowwise", MatrixFormat::kRowwise) - .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned) - .export_values(); - py::enum_(m, "HessianFormat") - .value("kTriangular", HessianFormat::kTriangular) - .value("kSquare", HessianFormat::kSquare) - .export_values(); - py::enum_(m, "SolutionStatus") +PYBIND11_MODULE(_core, m) { + // enumerations + // Older enums, need to have values exported + py::enum_(m, "SolutionStatus", py::module_local()) .value("kSolutionStatusNone", SolutionStatus::kSolutionStatusNone) .value("kSolutionStatusInfeasible", SolutionStatus::kSolutionStatusInfeasible) .value("kSolutionStatusFeasible", SolutionStatus::kSolutionStatusFeasible) .export_values(); - py::enum_(m, "BasisValidity") + py::enum_(m, "BasisValidity", py::module_local()) .value("kBasisValidityInvalid", BasisValidity::kBasisValidityInvalid) .value("kBasisValidityValid", BasisValidity::kBasisValidityValid) .export_values(); - py::enum_(m, "HighsModelStatus") + // C++ enum classes do not need to have values exported + py::enum_(m, "ObjSense", py::module_local()) + .value("kMinimize", ObjSense::kMinimize) + .value("kMaximize", ObjSense::kMaximize); + py::enum_(m, "MatrixFormat", py::module_local()) + .value("kColwise", MatrixFormat::kColwise) + .value("kRowwise", MatrixFormat::kRowwise) + .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned); + py::enum_(m, "HessianFormat", py::module_local()) + .value("kTriangular", HessianFormat::kTriangular) + .value("kSquare", HessianFormat::kSquare); + py::enum_(m, "HighsModelStatus", py::module_local()) .value("kNotset", HighsModelStatus::kNotset) .value("kLoadError", HighsModelStatus::kLoadError) .value("kModelError", HighsModelStatus::kModelError) @@ -591,8 +620,8 @@ PYBIND11_MODULE(_highs, m) { .value("kUnknown", HighsModelStatus::kUnknown) .value("kSolutionLimit", HighsModelStatus::kSolutionLimit) .value("kInterrupt", HighsModelStatus::kInterrupt) - .export_values(); - py::enum_(m, "HighsPresolveStatus") + .value("kMemoryLimit", HighsModelStatus::kMemoryLimit); + py::enum_(m, "HighsPresolveStatus", py::module_local()) .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) .value("kNotReduced", HighsPresolveStatus::kNotReduced) .value("kInfeasible", HighsPresolveStatus::kInfeasible) @@ -602,46 +631,39 @@ PYBIND11_MODULE(_highs, m) { .value("kReducedToEmpty", HighsPresolveStatus::kReducedToEmpty) .value("kTimeout", HighsPresolveStatus::kTimeout) .value("kNullError", HighsPresolveStatus::kNullError) - .value("kOptionsError", HighsPresolveStatus::kOptionsError) - .export_values(); - py::enum_(m, "HighsBasisStatus") + .value("kOptionsError", HighsPresolveStatus::kOptionsError); + py::enum_(m, "HighsBasisStatus", py::module_local()) .value("kLower", HighsBasisStatus::kLower) .value("kBasic", HighsBasisStatus::kBasic) .value("kUpper", HighsBasisStatus::kUpper) .value("kZero", HighsBasisStatus::kZero) - .value("kNonbasic", HighsBasisStatus::kNonbasic) - .export_values(); - py::enum_(m, "HighsVarType") + .value("kNonbasic", HighsBasisStatus::kNonbasic); + py::enum_(m, "HighsVarType", py::module_local()) .value("kContinuous", HighsVarType::kContinuous) .value("kInteger", HighsVarType::kInteger) .value("kSemiContinuous", HighsVarType::kSemiContinuous) - .value("kSemiInteger", HighsVarType::kSemiInteger) - .export_values(); - py::enum_(m, "HighsOptionType") + .value("kSemiInteger", HighsVarType::kSemiInteger); + py::enum_(m, "HighsOptionType", py::module_local()) .value("kBool", HighsOptionType::kBool) .value("kInt", HighsOptionType::kInt) .value("kDouble", HighsOptionType::kDouble) - .value("kString", HighsOptionType::kString) - .export_values(); - py::enum_(m, "HighsInfoType") + .value("kString", HighsOptionType::kString); + py::enum_(m, "HighsInfoType", py::module_local()) .value("kInt64", HighsInfoType::kInt64) .value("kInt", HighsInfoType::kInt) - .value("kDouble", HighsInfoType::kDouble) - .export_values(); - py::enum_(m, "HighsStatus") + .value("kDouble", HighsInfoType::kDouble); + py::enum_(m, "HighsStatus", py::module_local()) .value("kError", HighsStatus::kError) .value("kOk", HighsStatus::kOk) - .value("kWarning", HighsStatus::kWarning) - .export_values(); - py::enum_(m, "HighsLogType") + .value("kWarning", HighsStatus::kWarning); + py::enum_(m, "HighsLogType", py::module_local()) .value("kInfo", HighsLogType::kInfo) .value("kDetailed", HighsLogType::kDetailed) .value("kVerbose", HighsLogType::kVerbose) .value("kWarning", HighsLogType::kWarning) - .value("kError", HighsLogType::kError) - .export_values(); + .value("kError", HighsLogType::kError); // Classes - py::class_(m, "HighsSparseMatrix") + py::class_(m, "HighsSparseMatrix", py::module_local()) .def(py::init<>()) .def_readwrite("format_", &HighsSparseMatrix::format_) .def_readwrite("num_col_", &HighsSparseMatrix::num_col_) @@ -650,7 +672,7 @@ PYBIND11_MODULE(_highs, m) { .def_readwrite("p_end_", &HighsSparseMatrix::p_end_) .def_readwrite("index_", &HighsSparseMatrix::index_) .def_readwrite("value_", &HighsSparseMatrix::value_); - py::class_(m, "HighsLp") + py::class_(m, "HighsLp", py::module_local()) .def(py::init<>()) .def_readwrite("num_col_", &HighsLp::num_col_) .def_readwrite("num_row_", &HighsLp::num_row_) @@ -670,18 +692,18 @@ PYBIND11_MODULE(_highs, m) { .def_readwrite("is_scaled_", &HighsLp::is_scaled_) .def_readwrite("is_moved_", &HighsLp::is_moved_) .def_readwrite("mods_", &HighsLp::mods_); - py::class_(m, "HighsHessian") + py::class_(m, "HighsHessian", py::module_local()) .def(py::init<>()) .def_readwrite("dim_", &HighsHessian::dim_) .def_readwrite("format_", &HighsHessian::format_) .def_readwrite("start_", &HighsHessian::start_) .def_readwrite("index_", &HighsHessian::index_) .def_readwrite("value_", &HighsHessian::value_); - py::class_(m, "HighsModel") + py::class_(m, "HighsModel", py::module_local()) .def(py::init<>()) .def_readwrite("lp_", &HighsModel::lp_) .def_readwrite("hessian_", &HighsModel::hessian_); - py::class_(m, "HighsInfo") + py::class_(m, "HighsInfo", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsInfo::valid) .def_readwrite("mip_node_count", &HighsInfo::mip_node_count) @@ -691,6 +713,7 @@ PYBIND11_MODULE(_highs, m) { .def_readwrite("qp_iteration_count", &HighsInfo::qp_iteration_count) .def_readwrite("crossover_iteration_count", &HighsInfo::crossover_iteration_count) + .def_readwrite("pdlp_iteration_count", &HighsInfo::pdlp_iteration_count) .def_readwrite("primal_solution_status", &HighsInfo::primal_solution_status) .def_readwrite("dual_solution_status", &HighsInfo::dual_solution_status) @@ -712,8 +735,12 @@ PYBIND11_MODULE(_highs, m) { .def_readwrite("max_dual_infeasibility", &HighsInfo::max_dual_infeasibility) .def_readwrite("sum_dual_infeasibilities", - &HighsInfo::sum_dual_infeasibilities); - py::class_(m, "HighsOptions") + &HighsInfo::sum_dual_infeasibilities) + .def_readwrite("max_complementarity_violation", + &HighsInfo::max_complementarity_violation) + .def_readwrite("sum_complementarity_violations", + &HighsInfo::sum_complementarity_violations); + py::class_(m, "HighsOptions", py::module_local()) .def(py::init<>()) .def_readwrite("presolve", &HighsOptions::presolve) .def_readwrite("solver", &HighsOptions::solver) @@ -801,14 +828,13 @@ PYBIND11_MODULE(_highs, m) { &HighsOptions::mip_heuristic_effort) .def_readwrite("mip_min_logging_interval", &HighsOptions::mip_min_logging_interval); - py::class_(m, "Highs") + py::class_(m, "_Highs", py::module_local()) .def(py::init<>()) .def("version", &Highs::version) .def("versionMajor", &Highs::versionMajor) .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) @@ -897,6 +923,7 @@ PYBIND11_MODULE(_highs, m) { .def("getRowByName", &highs_getRowByName) .def("writeModel", &Highs::writeModel) + .def("writePresolvedModel", &Highs::writePresolvedModel) .def("crossover", &Highs::crossover) .def("changeObjectiveSense", &Highs::changeObjectiveSense) .def("changeObjectiveOffset", &Highs::changeObjectiveOffset) @@ -915,15 +942,32 @@ PYBIND11_MODULE(_highs, m) { .def("changeColsBounds", &highs_changeColsBounds) .def("changeColsIntegrality", &highs_changeColsIntegrality) .def("deleteCols", &highs_deleteCols) - .def("deleteVars", &highs_deleteCols) // alias + .def("deleteVars", &highs_deleteCols) // alias .def("deleteRows", &highs_deleteRows) - .def("setSolution", &Highs::setSolution) + .def("setSolution", &highs_setSolution) + .def("setSolution", &highs_setSparseSolution) + .def("setBasis", &highs_setBasis) + .def("setBasis", &highs_setLogicalBasis) .def("modelStatusToString", &Highs::modelStatusToString) .def("solutionStatusToString", &Highs::solutionStatusToString) .def("basisStatusToString", &Highs::basisStatusToString) - .def("basisValidityToString", &Highs::basisValidityToString); + .def("basisValidityToString", &Highs::basisValidityToString) + .def( + "setCallback", + static_cast( + &Highs::setCallback)) + .def("startCallback", + static_cast( + &Highs::startCallback)) + .def("stopCallback", + static_cast( + &Highs::stopCallback)) + .def("startCallbackInt", static_cast( + &Highs::startCallback)) + .def("stopCallbackInt", static_cast( + &Highs::stopCallback)); // structs - py::class_(m, "HighsSolution") + py::class_(m, "HighsSolution", py::module_local()) .def(py::init<>()) .def_readwrite("value_valid", &HighsSolution::value_valid) .def_readwrite("dual_valid", &HighsSolution::dual_valid) @@ -931,11 +975,11 @@ PYBIND11_MODULE(_highs, m) { .def_readwrite("col_dual", &HighsSolution::col_dual) .def_readwrite("row_value", &HighsSolution::row_value) .def_readwrite("row_dual", &HighsSolution::row_dual); - py::class_(m, "HighsObjectiveSolution") + py::class_(m, "HighsObjectiveSolution", py::module_local()) .def(py::init<>()) .def_readwrite("objective", &HighsObjectiveSolution::objective) .def_readwrite("col_value", &HighsObjectiveSolution::col_value); - py::class_(m, "HighsBasis") + py::class_(m, "HighsBasis", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsBasis::valid) .def_readwrite("alien", &HighsBasis::alien) @@ -945,13 +989,13 @@ PYBIND11_MODULE(_highs, m) { .def_readwrite("debug_origin_name", &HighsBasis::debug_origin_name) .def_readwrite("col_status", &HighsBasis::col_status) .def_readwrite("row_status", &HighsBasis::row_status); - py::class_(m, "HighsRangingRecord") + py::class_(m, "HighsRangingRecord", py::module_local()) .def(py::init<>()) .def_readwrite("value_", &HighsRangingRecord::value_) .def_readwrite("objective_", &HighsRangingRecord::objective_) .def_readwrite("in_var_", &HighsRangingRecord::in_var_) .def_readwrite("ou_var_", &HighsRangingRecord::ou_var_); - py::class_(m, "HighsRanging") + py::class_(m, "HighsRanging", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsRanging::valid) .def_readwrite("col_cost_up", &HighsRanging::col_cost_up) @@ -963,11 +1007,16 @@ PYBIND11_MODULE(_highs, m) { // constants m.attr("kHighsInf") = kHighsInf; m.attr("kHighsIInf") = kHighsIInf; + + m.attr("HIGHS_VERSION_MAJOR") = HIGHS_VERSION_MAJOR; + m.attr("HIGHS_VERSION_MINOR") = HIGHS_VERSION_MINOR; + m.attr("HIGHS_VERSION_PATCH") = HIGHS_VERSION_PATCH; + // Submodules py::module_ simplex_constants = m.def_submodule("simplex_constants", "Submodule for simplex constants"); - py::enum_(simplex_constants, "SimplexStrategy") + py::enum_(simplex_constants, "SimplexStrategy", py::module_local()) .value("kSimplexStrategyMin", SimplexStrategy::kSimplexStrategyMin) .value("kSimplexStrategyChoose", SimplexStrategy::kSimplexStrategyChoose) .value("kSimplexStrategyDual", SimplexStrategy::kSimplexStrategyDual) @@ -982,7 +1031,7 @@ PYBIND11_MODULE(_highs, m) { .value("kSimplexStrategyNum", SimplexStrategy::kSimplexStrategyNum) .export_values(); // needed since it isn't an enum class py::enum_(simplex_constants, - "SimplexUnscaledSolutionStrategy") + "SimplexUnscaledSolutionStrategy", py::module_local()) .value( "kSimplexUnscaledSolutionStrategyMin", SimplexUnscaledSolutionStrategy::kSimplexUnscaledSolutionStrategyMin) @@ -1002,7 +1051,7 @@ PYBIND11_MODULE(_highs, m) { "kSimplexUnscaledSolutionStrategyNum", SimplexUnscaledSolutionStrategy::kSimplexUnscaledSolutionStrategyNum) .export_values(); - py::enum_(simplex_constants, "SimplexSolvePhase") + py::enum_(simplex_constants, "SimplexSolvePhase", py::module_local()) .value("kSolvePhaseMin", SimplexSolvePhase::kSolvePhaseMin) .value("kSolvePhaseError", SimplexSolvePhase::kSolvePhaseError) .value("kSolvePhaseExit", SimplexSolvePhase::kSolvePhaseExit) @@ -1018,7 +1067,7 @@ PYBIND11_MODULE(_highs, m) { .value("kSolvePhaseMax", SimplexSolvePhase::kSolvePhaseMax) .export_values(); py::enum_(simplex_constants, - "SimplexEdgeWeightStrategy") + "SimplexEdgeWeightStrategy", py::module_local()) .value("kSimplexEdgeWeightStrategyMin", SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMin) .value("kSimplexEdgeWeightStrategyChoose", @@ -1032,7 +1081,7 @@ PYBIND11_MODULE(_highs, m) { .value("kSimplexEdgeWeightStrategyMax", SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMax) .export_values(); - py::enum_(simplex_constants, "SimplexPriceStrategy") + py::enum_(simplex_constants, "SimplexPriceStrategy", py::module_local()) .value("kSimplexPriceStrategyMin", SimplexPriceStrategy::kSimplexPriceStrategyMin) .value("kSimplexPriceStrategyCol", @@ -1047,7 +1096,7 @@ PYBIND11_MODULE(_highs, m) { SimplexPriceStrategy::kSimplexPriceStrategyMax) .export_values(); py::enum_( - simplex_constants, "SimplexPivotalRowRefinementStrategy") + simplex_constants, "SimplexPivotalRowRefinementStrategy", py::module_local()) .value("kSimplexInfeasibilityProofRefinementMin", SimplexPivotalRowRefinementStrategy:: kSimplexInfeasibilityProofRefinementMin) @@ -1065,7 +1114,7 @@ PYBIND11_MODULE(_highs, m) { kSimplexInfeasibilityProofRefinementMax) .export_values(); py::enum_(simplex_constants, - "SimplexPrimalCorrectionStrategy") + "SimplexPrimalCorrectionStrategy", py::module_local()) .value( "kSimplexPrimalCorrectionStrategyNone", SimplexPrimalCorrectionStrategy::kSimplexPrimalCorrectionStrategyNone) @@ -1076,14 +1125,14 @@ PYBIND11_MODULE(_highs, m) { SimplexPrimalCorrectionStrategy:: kSimplexPrimalCorrectionStrategyAlways) .export_values(); - py::enum_(simplex_constants, "SimplexNlaOperation") + py::enum_(simplex_constants, "SimplexNlaOperation", py::module_local()) .value("kSimplexNlaNull", SimplexNlaOperation::kSimplexNlaNull) .value("kSimplexNlaBtranFull", SimplexNlaOperation::kSimplexNlaBtranFull) .value("kSimplexNlaPriceFull", SimplexNlaOperation::kSimplexNlaPriceFull) .value("kSimplexNlaBtranBasicFeasibilityChange", SimplexNlaOperation::kSimplexNlaBtranBasicFeasibilityChange) - .value("kSimplexNlaPriceBasicFeasibilityChange", - SimplexNlaOperation::kSimplexNlaPriceBasicFeasibilityChange) + // .value("kSimplexNlaPriceBasicFeasibilityChange", + // /khighsSimplexNlaOperation::kSimplexNlaPriceBasicFeasibilityChange) .value("kSimplexNlaBtranEp", SimplexNlaOperation::kSimplexNlaBtranEp) .value("kSimplexNlaPriceAp", SimplexNlaOperation::kSimplexNlaPriceAp) .value("kSimplexNlaFtran", SimplexNlaOperation::kSimplexNlaFtran) @@ -1093,9 +1142,60 @@ PYBIND11_MODULE(_highs, m) { .value("kNumSimplexNlaOperation", SimplexNlaOperation::kNumSimplexNlaOperation) .export_values(); - py::enum_(simplex_constants, "EdgeWeightMode") + py::enum_(simplex_constants, "EdgeWeightMode", py::module_local()) .value("kDantzig", EdgeWeightMode::kDantzig) .value("kDevex", EdgeWeightMode::kDevex) .value("kSteepestEdge", EdgeWeightMode::kSteepestEdge) .value("kCount", EdgeWeightMode::kCount); + + py::module_ callbacks = m.def_submodule("cb", "Callback interface submodule"); + // Types for interface + py::enum_(callbacks, "HighsCallbackType", py::module_local()) + .value("kCallbackMin", HighsCallbackType::kCallbackMin) + .value("kCallbackLogging", HighsCallbackType::kCallbackLogging) + .value("kCallbackSimplexInterrupt", + HighsCallbackType::kCallbackSimplexInterrupt) + .value("kCallbackIpmInterrupt", HighsCallbackType::kCallbackIpmInterrupt) + .value("kCallbackMipImprovingSolution", + HighsCallbackType::kCallbackMipImprovingSolution) + .value("kCallbackMipLogging", HighsCallbackType::kCallbackMipLogging) + .value("kCallbackMipInterrupt", HighsCallbackType::kCallbackMipInterrupt) + .value("kCallbackMipGetCutPool", + HighsCallbackType::kCallbackMipGetCutPool) + .value("kCallbackMipDefineLazyConstraints", + HighsCallbackType::kCallbackMipDefineLazyConstraints) + .value("kCallbackMax", HighsCallbackType::kCallbackMax) + .value("kNumCallbackType", HighsCallbackType::kNumCallbackType) + .export_values(); + // Classes + py::class_(callbacks, "HighsCallbackDataOut", py::module_local()) + .def(py::init<>()) + .def_readwrite("log_type", &HighsCallbackDataOut::log_type) + .def_readwrite("running_time", &HighsCallbackDataOut::running_time) + .def_readwrite("simplex_iteration_count", + &HighsCallbackDataOut::simplex_iteration_count) + .def_readwrite("ipm_iteration_count", + &HighsCallbackDataOut::ipm_iteration_count) + .def_readwrite("pdlp_iteration_count", + &HighsCallbackDataOut::pdlp_iteration_count) + .def_readwrite("objective_function_value", + &HighsCallbackDataOut::objective_function_value) + .def_readwrite("mip_node_count", &HighsCallbackDataOut::mip_node_count) + .def_readwrite("mip_primal_bound", + &HighsCallbackDataOut::mip_primal_bound) + .def_readwrite("mip_dual_bound", &HighsCallbackDataOut::mip_dual_bound) + .def_readwrite("mip_gap", &HighsCallbackDataOut::mip_gap) + .def_property( + "mip_solution", + [](const HighsCallbackDataOut& self) -> py::array { + // XXX: This is clearly wrong, most likely we need to have the + // length as an input data parameter + return py::array(3, self.mip_solution); + }, + [](HighsCallbackDataOut& self, py::array_t new_mip_solution) { + self.mip_solution = new_mip_solution.mutable_data(); + }); + py::class_(callbacks, "HighsCallbackDataIn", py::module_local()) + .def(py::init<>()) + .def_readwrite("user_interrupt", &HighsCallbackDataIn::user_interrupt); } diff --git a/highspy/highs_options.cpp b/src/highs_options.cpp similarity index 60% rename from highspy/highs_options.cpp rename to src/highs_options.cpp index 05eb0f3d11..a7f6e140d7 100644 --- a/highspy/highs_options.cpp +++ b/src/highs_options.cpp @@ -8,14 +8,10 @@ namespace py = pybind11; -bool log_to_console = false; -bool output_flag = true; -HighsLogOptions highs_log_options = {nullptr, &output_flag, &log_to_console, - nullptr}; - class HighsOptionsManager { public: HighsOptionsManager() { + initialize_log_options(); for (const auto& record : highs_options_.records) { record_type_lookup_.emplace(record->name, record->type); } @@ -53,6 +49,21 @@ class HighsOptionsManager { HighsOptions highs_options_; std::mutex highs_options_mutex; std::map record_type_lookup_; + HighsLogOptions highs_log_options; + + static constexpr bool log_to_console = false; + static constexpr bool output_flag = true; + + void initialize_log_options() { + highs_log_options.log_stream = nullptr; + highs_log_options.output_flag = const_cast(&output_flag); + highs_log_options.log_to_console = const_cast(&log_to_console); + highs_log_options.log_dev_level = nullptr; + highs_log_options.user_log_callback = nullptr; + highs_log_options.user_log_callback_data = nullptr; + highs_log_options.user_callback_data = nullptr; + highs_log_options.user_callback_active = false; + } }; PYBIND11_MODULE(_highs_options, m) { @@ -77,16 +88,32 @@ PYBIND11_MODULE(_highs_options, m) { }) .def("check_int_option", [](HighsOptionsManager& self, const std::string& name, int value) { - return self.check_option(name, value); + try { + return self.check_option(name, value); + } catch (const std::exception& e) { + py::print("Exception caught in check_int_option:", e.what()); + return false; + } }) .def( "check_double_option", [](HighsOptionsManager& self, const std::string& name, double value) { - return self.check_option(name, value); + try { + return self.check_option(name, value); + } catch (const std::exception& e) { + py::print("Exception caught in check_double_option:", e.what()); + return false; + } }) - .def("check_string_option", [](HighsOptionsManager& self, - const std::string& name, - const std::string& value) { - return self.check_option(name, value); - }); + .def("check_string_option", + [](HighsOptionsManager& self, const std::string& name, + const std::string& value) { + try { + return self.check_option(name, + value); + } catch (const std::exception& e) { + py::print("Exception caught in check_string_option:", e.what()); + return false; + } + }); } diff --git a/src/highspy/__init__.py b/src/highspy/__init__.py new file mode 100644 index 0000000000..78005667b7 --- /dev/null +++ b/src/highspy/__init__.py @@ -0,0 +1,193 @@ +# from __future__ import annotations + +from highspy._core import \ + ObjSense, \ + MatrixFormat, \ + HessianFormat, \ + SolutionStatus, \ + BasisValidity, \ + HighsModelStatus, \ + HighsPresolveStatus, \ + HighsBasisStatus, \ + HighsVarType, \ + HighsOptionType, \ + HighsInfoType, \ + HighsStatus, \ + HighsLogType, \ + HighsSparseMatrix, \ + HighsLp, \ + HighsHessian, \ + HighsModel, \ + HighsInfo, \ + HighsOptions, \ + _Highs, \ + HighsSolution, \ + HighsObjectiveSolution, \ + HighsBasis, \ + HighsRangingRecord, \ + HighsRanging, \ + kHighsInf, \ + kHighsIInf, \ + HIGHS_VERSION_MAJOR, \ + HIGHS_VERSION_MINOR, \ + HIGHS_VERSION_PATCH, \ + simplex_constants, \ + cb, \ + kSolutionStatusNone, \ + kSolutionStatusInfeasible, \ + kSolutionStatusFeasible, \ + kBasisValidityInvalid, \ + kBasisValidityValid + +# kMaximize, \ +# kColwise, \ +# kRowwise, \ +# kRowwisePartitioned, \ +# kTriangular, \ +# kSquare, \ + +# kNotset, \ +# kLoadError, \ +# kModelError, \ +# kPresolveError, \ +# kSolveError, \ +# kPostsolveError, \ +# kModelEmpty, \ +# kOptimal, \ +# kInfeasible, \ +# kUnboundedOrInfeasible, \ +# kUnbounded, \ +# kObjectiveBound, \ +# kObjectiveTarget, \ +# kTimeLimit, \ +# kUnknown, \ +# kSolutionLimit, \ +# kInterrupt, \ +# kMemoryLimit, \ +# kNotPresolved, \ +# kNotReduced, \ +# kInfeasible, \ +# kUnboundedOrInfeasible, \ +# kReduced, \ +# kReducedToEmpty, \ +# kTimeout, \ +# kNullError, \ +# kOptionsError, \ +# kOutOfMemory, \ +# kLower, \ +# kBasic, \ +# kUpper, \ +# kZero, \ +# kNonbasic, \ +# kContinuous, \ +# kInteger, \ +# kSemiContinuous, \ +# kSemiInteger, \ +# kBool, \ +# kInt, \ +# kDouble, \ +# , \ +# , \ +# , \ +# , \ +# , \ +# , \ +# , \ + +from .highs import Highs + +__all__ = ["__doc__", + "__version__", + "ObjSense", + "MatrixFormat", + "HessianFormat", + "SolutionStatus", + "BasisValidity", + "HighsModelStatus", + "HighsPresolveStatus", + "HighsBasisStatus", + "HighsVarType", + "HighsOptionType", + "HighsInfoType", + "HighsStatus", + "HighsLogType", + "HighsSparseMatrix", + "HighsLp", + "HighsHessian", + "HighsModel", + "HighsInfo", + "HighsOptions", + "_Highs", + "Highs", + "HighsSolution", + "HighsObjectiveSolution", + "HighsBasis", + "HighsRangingRecord", + "HighsRanging", + "kHighsInf", + "kHighsIInf", + "HIGHS_VERSION_MAJOR", + "HIGHS_VERSION_MINOR", + "HIGHS_VERSION_PATCH", + "simplex_constants", + "cb", + # "kMinimize", + # "kMaximize", + # "kColwise", + # "kRowwise", + # "kRowwisePartitioned", + # "kTriangular", + # "kSquare", + "kSolutionStatusNone", + "kSolutionStatusInfeasible", + "kSolutionStatusFeasible", + "kBasisValidityInvalid", + "kBasisValidityValid", + # "kNotset", + # "kLoadError", + # "kModelError", + # "kPresolveError", + # "kSolveError", + # "kPostsolveError", + # "kModelEmpty", + # "kOptimal", + # "kInfeasible", + # "kUnboundedOrInfeasible", + # "kUnbounded", + # "kObjectiveBound", + # "kObjectiveTarget", + # "kTimeLimit", + # "kUnknown", + # "kSolutionLimit", + # "kInterrupt", + # "kMemoryLimit", + # "kNotPresolved", + # "kNotReduced", + # "kInfeasible", + # "kUnboundedOrInfeasible", + # "kReduced", + # "kReducedToEmpty", + # "kTimeout", + # "kNullError", + # "kOptionsError", + # "kOutOfMemory", + # "kLower", + # "kBasic", + # "kUpper", + # "kZero", + # "kNonbasic", + # "kContinuous", + # "kInteger", + # "kSemiContinuous", + # "kSemiInteger", + # "kBool", + # "kInt", + # "kDouble", + # "", + # "", + # "", + # "", + # "", + # "", + # "", + ] diff --git a/src/highspy/highs.py b/src/highspy/highs.py new file mode 100644 index 0000000000..5576abfc91 --- /dev/null +++ b/src/highspy/highs.py @@ -0,0 +1,561 @@ +from highspy._core import ( + # enum classes + ObjSense, + MatrixFormat, + HessianFormat, + SolutionStatus, + BasisValidity, + HighsModelStatus, + HighsPresolveStatus, + HighsBasisStatus, + HighsVarType, + HighsOptionType, + HighsInfoType, + HighsStatus, + HighsLogType, + # classes + HighsSparseMatrix, + HighsLp, + HighsHessian, + HighsModel, + HighsInfo, + HighsOptions, + _Highs, + # structs + HighsSolution, + HighsObjectiveSolution, + HighsBasis, + HighsRangingRecord, + HighsRanging, + # constants + kHighsInf, + kHighsIInf, +) + + +from itertools import groupby +from operator import itemgetter +from decimal import Decimal + +class Highs(_Highs): + """HiGHS solver interface""" + __slots__ = ['_batch', '_vars', '_cons'] + + def __init__(self): + super().__init__() + + self._batch = highs_batch(self) + self._vars = [] + self._cons = [] + + # Silence logging + def silent(self): + super().setOptionValue("output_flag", False) + + # solve + def solve(self): + return super().run() + + # reset the objective and sense, then solve + def minimize(self, obj=None): + if obj != None: + # if we have a single variable, wrap it in a linear expression + if isinstance(obj, highs_var) == True: + obj = highs_linear_expression(obj) + + if isinstance(obj, highs_linear_expression) == False or obj.LHS != -self.inf or obj.RHS != self.inf: + raise Exception('Objective cannot be an inequality') + + # reset objective + self.update() + super().changeColsCost(self.numVariables, range(self.numVariables), [0]*self.numVariables) + + # if we have duplicate variables, add the vals + vars,vals = zip(*[(var, sum(v[1] for v in Vals)) for var, Vals in groupby(sorted(zip(obj.vars, obj.vals)), key=itemgetter(0))]) + super().changeColsCost(len(vars), vars, vals) + super().changeObjectiveOffset(obj.constant) + + super().changeObjectiveSense(ObjSense.kMinimize) + return super().run() + + # reset the objective and sense, then solve + def maximize(self, obj=None): + if obj != None: + # if we have a single variable, wrap it in a linear expression + if isinstance(obj, highs_var) == True: + obj = highs_linear_expression(obj) + + if isinstance(obj, highs_linear_expression) == False or obj.LHS != -self.inf or obj.RHS != self.inf: + raise Exception('Objective cannot be an inequality') + + # reset objective + self.update() + super().changeColsCost(self.numVariables, range(self.numVariables), [0]*self.numVariables) + + # if we have duplicate variables, add the vals + vars,vals = zip(*[(var, sum(v[1] for v in Vals)) for var, Vals in groupby(sorted(zip(obj.vars, obj.vals)), key=itemgetter(0))]) + super().changeColsCost(len(vars), vars, vals) + super().changeObjectiveOffset(obj.constant) + + super().changeObjectiveSense(ObjSense.kMaximize) + return super().run() + + + # update variables + def update(self): + current_batch_size = len(self._batch.obj) + if current_batch_size > 0: + names = [self._batch.name[i] for i in range(current_batch_size)] + super().addVars(int(current_batch_size), self._batch.lb, self._batch.ub) + super().changeColsCost(current_batch_size, self._batch.idx, self._batch.obj) + + # only set integrality if we have non-continuous variables + if any([t != HighsVarType.kContinuous for t in self._batch.type]): + super().changeColsIntegrality(current_batch_size, self._batch.idx, self._batch.type) + + for i in range(current_batch_size): + super().passColName(int(self._batch.idx[i]), str(names[i])) + self._batch = highs_batch(self) + + def val(self, var): + return super().getSolution().col_value[var.index] + + def vals(self, vars): + sol = super().getSolution() + return [sol.col_value[v.index] for v in vars] + + def variableName(self, var): + [status, name] = super().getColName(var.index) + failed = status != HighsStatus.kOk + if failed: + raise Exception('Variable name not found') + return name + + def variableNames(self, vars): + names = list() + for v in vars: + [status, name] = super().getColName(v.index) + failed = status != HighsStatus.kOk + if failed: + raise Exception('Variable name not found') + names.append(name) + return names + + def allVariableNames(self): + return super().getLp().col_names_ + + def variableValue(self, var): + return super().getSolution().col_value[var.index] + + def variableValues(self, vars): + col_value = super().getSolution().col_value + return [col_value[v.index] for v in vars] + + def allVariableValues(self): + return super().getSolution().col_value + + def variableDual(self, var): + return super().getSolution().col_dual[var.index] + + def variableDuals(self, vars): + col_dual = super().getSolution() + return [col_dual[v.index] for v in vars] + + def allVariableDuals(self): + return super().getSolution().col_dual + + def constrValue(self, constr_name): + status_index = super().getRowByName(constr_name) + failed = status_index[0] != HighsStatus.kOk + if failed: + raise Exception('Constraint name not found') + return super().getSolution().row_value[status_index[1]] + + def constrValues(self, constr_names): + row_value = super().getSolution().row_value + index = list() + for name in constr_names: + status_index = super().getRowByName(name) + failed = status_index[0] != HighsStatus.kOk + if failed: + raise Exception('Constraint name not found') + index.append(status_index[1]) + return [row_value[index[v]] for v in range(len(index))] + + def allConstrValues(self): + return super().getSolution().row_value + + def constrDual(self, constr_name): + status_index = super().getRowByName(constr_name) + failed = status_index[0] != HighsStatus.kOk + if failed: + raise Exception('Constraint name not found') + return super().getSolution().row_dual[status_index[1]] + + def constrDuals(self, constr_names): + row_dual = super().getSolution().row_dual + index = list() + for name in constr_names: + status_index = super().getRowByName(name) + failed = status_index[0] != HighsStatus.kOk + if failed: + raise Exception('Constraint name not found') + index.append(status_index[1]) + return [row_dual[index[v]] for v in range(len(index))] + + def allConstrDuals(self): + return super().getSolution().row_dual + + # + # add variable & useful constants + # + # Change the name of addVar to addVariable to prevent shadowing of + # highspy binding to Highs::addVar + def addVariable(self, lb = 0, ub = kHighsInf, obj = 0, type=HighsVarType.kContinuous, name = None): + var = self._batch.add(obj, lb, ub, type, name, self) + self._vars.append(var) + # No longer acumulate a batch of variables so that addVariable + # behaves like Highs::addVar and highspy bindings modifying + # column data and adding rows can be used + self.update() + return var + + def addIntegral(self, lb = 0, ub = kHighsInf, obj = 0, name = None): + return self.addVariable(lb, ub, obj, HighsVarType.kInteger, name) + + def addBinary(self, obj = 0, name = None): + return self.addVariable(0, 1, obj, HighsVarType.kInteger, name) + + # Change the name of removeVar to deleteVariable + def deleteVariable(self, var): + for i in self._vars[var.index+1:]: + i.index -= 1 + + del self._vars[var.index] + + # only delete from model if it exists + if var.index < self.numVariables: + super().deleteVars(1, [var.index]) + + # Change the name of getVars to getVariables + def getVariables(self): + return self._vars + + @property + def inf(self): + return kHighsInf + + @property + def numVariables(self): + return super().getNumCol() + + @property + def numConstrs(self): + return super().getNumRow() + + # + # add constraints + # + def addConstr(self, cons, name=None): + self.update() + + # if we have duplicate variables, add the vals + vars,vals = zip(*[(var, sum(v[1] for v in Vals)) for var, Vals in groupby(sorted(zip(cons.vars, cons.vals)), key=itemgetter(0))]) + super().addRow(cons.LHS - cons.constant, cons.RHS - cons.constant, len(vars), vars, vals) + + cons = highs_cons(self.numConstrs - 1, self, name) + self._cons.append(cons) + return cons + + def chgCoeff(self, cons, var, val): + super().changeCoeff(cons.index, var.index, val) + + def getConstrs(self): + return self._cons + + def removeConstr(self, cons): + for i in self._cons[cons.index+1:]: + i.index -= 1 + + del self._cons[cons.index] + super().deleteRows(1, [cons.index]) + + # set to minimization + def setMinimize(self): + super().changeObjectiveSense(ObjSense.kMinimize) + + # set to maximization + def setMaximize(self): + super().changeObjectiveSense(ObjSense.kMaximize) + + # Set to integer + def setInteger(self, var): + super().changeColIntegrality(var.index, HighsVarType.kInteger) + + # Set to continuous + def setContinuous(self, var): + super().changeColIntegrality(var.index, HighsVarType.kContinuous) + +## The following classes keep track of variables +## It is currently quite basic and may fail in complex scenarios + +# highs variable +class highs_var(object): + """Basic constraint builder for HiGHS""" + __slots__ = ['index', '_variableName', 'highs'] + + def __init__(self, i, highs, name=None): + self.index = i + self.highs = highs + self.name = f"__v{i}" if name == None else name + + def __repr__(self): + return f"{self.name}" + + @property + def name(self): + if self.index < self.highs.numVariables and self.highs.numVariables > 0: + return self.highs.getLp().col_names_[self.index] + else: + return self._variableName + + @name.setter + def name(self, value): + if value == None or len(value) == 0: + raise Exception('Name cannot be empty') + + self._variableName = value + if self.index < self.highs.numVariables and self.highs.numVariables > 0: + self.highs.passColName(self.index, self._variableName) + + def __hash__(self): + return self.index + + def __neg__(self): + return -1.0 * highs_linear_expression(self) + + def __le__(self, other): + return highs_linear_expression(self) <= other + + def __eq__(self, other): + return highs_linear_expression(self) == other + + def __ge__(self, other): + return highs_linear_expression(self) >= other + + def __add__(self, other): + return highs_linear_expression(self) + other + + def __radd__(self, other): + return highs_linear_expression(self) + other + + def __mul__(self, other): + return highs_linear_expression(self) * other + + def __rmul__(self, other): + return highs_linear_expression(self) * other + + def __rsub__(self, other): + return -1.0 * highs_linear_expression(self) + other + + def __sub__(self, other): + return highs_linear_expression(self) - other + +# highs constraint +class highs_cons(object): + """Basic constraint for HiGHS""" + __slots__ = ['index', '_constrName', 'highs'] + + def __init__(self, i, highs, name): + self.index = i + self.highs = highs + self.name = f"__c{i}" if name == None else name + + def __repr__(self): + return f"{self.name}" + + @property + def name(self): + return self._constrName + + @name.setter + def name(self, value): + if value == None or len(value) == 0: + raise Exception('Name cannot be empty') + + self._constrName = value + self.highs.passRowName(self.index, self._constrName) + + +# highs constraint builder +class highs_linear_expression(object): + """Basic constraint builder for HiGHS""" + __slots__ = ['vars', 'vals', 'LHS', 'RHS', 'constant'] + + def __init__(self, other=None): + self.constant = 0 + self.LHS = -kHighsInf + self.RHS = kHighsInf + + if isinstance(other, highs_linear_expression): + self.vars = list(other.vars) + self.vals = list(other.vals) + self.constant = other.constant + self.LHS = other.LHS + self.RHS = other.RHS + + elif isinstance(other, highs_var): + self.vars = [other.index] + self.vals = [1.0] + else: + self.vars = [] + self.vals = [] + + def __neg__(self): + return -1.0 * self + + # (LHS <= self <= RHS) <= (other.LHS <= other <= other.RHS) + def __le__(self, other): + if isinstance(other, highs_linear_expression): + if self.LHS != -kHighsInf and self.RHS != kHighsInf and len(other.vars) > 0 or other.LHS != -kHighsInf: + raise Exception('Cannot construct constraint with variables as bounds.') + + # move variables from other to self + self.vars.extend(other.vars) + self.vals.extend([-1.0 * v for v in other.vals]) + self.constant -= other.constant + self.RHS = 0 + return self + + elif isinstance(other, highs_var): + return NotImplemented + + elif isinstance(other, (int, float, Decimal)): + self.RHS = min(self.RHS, other) + return self + + else: + return NotImplemented + + # (LHS <= self <= RHS) == (other.LHS <= other <= other.RHS) + def __eq__(self, other): + if isinstance(other, highs_linear_expression): + if self.LHS != -kHighsInf and len(other.vars) > 0 or other.LHS != -kHighsInf: + raise Exception('Cannot construct constraint with variables as bounds.') + + # move variables from other to self + self.vars.extend(other.vars) + self.vals.extend([-1.0 * v for v in other.vals]) + self.constant -= other.constant + self.LHS = 0 + self.RHS = 0 + return self + + elif isinstance(other, highs_var): + return NotImplemented + + elif isinstance(other, (int, float, Decimal)): + if self.LHS != -kHighsInf or self.RHS != kHighsInf: + raise Exception('Logic error in constraint equality.') + + self.LHS = other + self.RHS = other + return self + + else: + return NotImplemented + + # (other.LHS <= other <= other.RHS) <= (LHS <= self <= RHS) + def __ge__(self, other): + + if isinstance(other, highs_linear_expression): + return other <= self + + elif isinstance(other, highs_var): + return NotImplemented + + elif isinstance(other, (int, float, Decimal)): + self.LHS = max(self.LHS, other) + return self + + else: + return NotImplemented + + def __radd__(self, other): + return self + other + + # (LHS <= self <= RHS) + (LHS <= other <= RHS) + def __add__(self, other): + if isinstance(other, highs_linear_expression): + self.vars.extend(other.vars) + self.vals.extend(other.vals) + self.constant += other.constant + self.LHS = max(self.LHS, other.LHS) + self.RHS = min(self.RHS, other.RHS) + return self + + elif isinstance(other, highs_var): + self.vars.append(other.index) + self.vals.append(1.0) + return self + + elif isinstance(other, (int, float, Decimal)): + self.constant += other + return self + + else: + return NotImplemented + + def __rmul__(self, other): + return self * other + + def __mul__(self, other): + result = highs_linear_expression(self) + + if isinstance(other, (int, float, Decimal)): + result.vals = [float(other) * v for v in self.vals] + result.constant *= float(other) + return result + elif isinstance(other, highs_var): + raise Exception('Only linear expressions are allowed.') + else: + return NotImplemented + + def __rsub__(self, other): + return other + -1.0 * self + + def __sub__(self, other): + if isinstance(other, highs_linear_expression): + return self + (-1.0 * other) + elif isinstance(other, highs_var): + return self + (-1.0 * highs_linear_expression(other)) + elif isinstance(other, (int, float, Decimal)): + return self + (-1.0 * other) + else: + return NotImplemented + +# used to batch add new variables +class highs_batch(object): + """Batch constraint builder for HiGHS""" + __slots__ = ['obj', 'lb', 'ub', 'type', 'name', 'highs', 'idx'] + + def __init__(self, highs): + self.highs = highs + + self.obj = [] + self.lb = [] + self.ub = [] + self.type = [] + self.idx = [] + self.name = [] + + def add(self, obj, lb, ub, type, name, solver): + self.obj.append(obj) + self.lb.append(lb) + self.ub.append(ub) + self.type.append(type) + self.name.append(name) + + newIndex = self.highs.numVariables + len(self.obj)-1 + self.idx.append(newIndex) + return highs_var(newIndex, solver, name) diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 0298b0727c..59b73668b6 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -24,10 +24,11 @@ HighsInt Highs_lpCall(const HighsInt num_col, const HighsInt num_row, HighsInt* row_basis_status, HighsInt* model_status) { Highs highs; highs.setOptionValue("output_flag", false); + *model_status = kHighsModelStatusNotset; HighsStatus status = highs.passModel( num_col, num_row, num_nz, a_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value); - if (status != HighsStatus::kOk) return (HighsInt)status; + if (status == HighsStatus::kError) return (HighsInt)status; status = highs.run(); @@ -77,10 +78,11 @@ HighsInt Highs_mipCall(const HighsInt num_col, const HighsInt num_row, double* row_value, HighsInt* model_status) { Highs highs; highs.setOptionValue("output_flag", false); + *model_status = kHighsModelStatusNotset; HighsStatus status = highs.passModel( num_col, num_row, num_nz, a_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value, integrality); - if (status != HighsStatus::kOk) return (HighsInt)status; + if (status == HighsStatus::kError) return (HighsInt)status; status = highs.run(); @@ -120,11 +122,12 @@ HighsInt Highs_qpCall( HighsInt* row_basis_status, HighsInt* model_status) { Highs highs; highs.setOptionValue("output_flag", false); + *model_status = kHighsModelStatusNotset; HighsStatus status = highs.passModel( num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value, q_start, q_index, q_value); - if (status != HighsStatus::kOk) return (HighsInt)status; + if (status == HighsStatus::kError) return (HighsInt)status; status = highs.run(); @@ -172,10 +175,46 @@ 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_presolve(void* highs) { + return (HighsInt)((Highs*)highs)->presolve(); +} HighsInt Highs_run(void* highs) { return (HighsInt)((Highs*)highs)->run(); } +HighsInt Highs_postsolve(void* highs, const double* col_value, + const double* col_dual, const double* row_dual) { + const HighsLp& presolved_lp = ((Highs*)highs)->getPresolvedLp(); + HighsInt num_col = presolved_lp.num_col_; + HighsInt num_row = presolved_lp.num_row_; + // Create a HighsSolution from what's been passed + HighsSolution solution; + if (col_value) { + solution.value_valid = true; + solution.col_value.resize(num_col); + // No need for primal row values, but resize the vector for later + // use + solution.row_value.resize(num_row); + } + if (col_dual || row_dual) { + // If column or row duals are passed, assume that they are + // valid. If either is a null pointer, then the corresponding + // vector will have no data, and the size check will fail + solution.dual_valid = true; + if (col_dual) solution.col_dual.resize(num_col); + if (row_dual) solution.row_dual.resize(num_row); + } + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + if (col_value) solution.col_value[iCol] = col_value[iCol]; + if (col_dual) solution.col_dual[iCol] = col_dual[iCol]; + } + if (row_dual) { + for (HighsInt iRow = 0; iRow < num_row; iRow++) + solution.row_dual[iRow] = row_dual[iRow]; + } + return (HighsInt)((Highs*)highs)->postsolve(solution); +} + HighsInt Highs_readModel(void* highs, const char* filename) { return (HighsInt)((Highs*)highs)->readModel(std::string(filename)); } @@ -184,6 +223,10 @@ HighsInt Highs_writeModel(void* highs, const char* filename) { return (HighsInt)((Highs*)highs)->writeModel(std::string(filename)); } +HighsInt Highs_writePresolvedModel(void* highs, const char* filename) { + return (HighsInt)((Highs*)highs)->writePresolvedModel(std::string(filename)); +} + HighsInt Highs_writeSolution(const void* highs, const char* filename) { return (HighsInt)((Highs*)highs) ->writeSolution(std::string(filename), kSolutionStyleRaw); @@ -432,25 +475,25 @@ HighsInt Highs_getSolution(const void* highs, double* col_value, const HighsSolution& solution = ((Highs*)highs)->getSolution(); if (col_value != nullptr) { - for (HighsInt i = 0; i < (HighsInt)solution.col_value.size(); i++) { + for (size_t i = 0; i < solution.col_value.size(); i++) { col_value[i] = solution.col_value[i]; } } if (col_dual != nullptr) { - for (HighsInt i = 0; i < (HighsInt)solution.col_dual.size(); i++) { + for (size_t i = 0; i < solution.col_dual.size(); i++) { col_dual[i] = solution.col_dual[i]; } } if (row_value != nullptr) { - for (HighsInt i = 0; i < (HighsInt)solution.row_value.size(); i++) { + for (size_t i = 0; i < solution.row_value.size(); i++) { row_value[i] = solution.row_value[i]; } } if (row_dual != nullptr) { - for (HighsInt i = 0; i < (HighsInt)solution.row_dual.size(); i++) { + for (size_t i = 0; i < solution.row_dual.size(); i++) { row_dual[i] = solution.row_dual[i]; } } @@ -460,12 +503,12 @@ HighsInt Highs_getSolution(const void* highs, double* col_value, HighsInt Highs_getBasis(const void* highs, HighsInt* col_status, HighsInt* row_status) { 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]; + for (size_t i = 0; i < basis.col_status.size(); i++) { + col_status[i] = static_cast(basis.col_status[i]); } - for (HighsInt i = 0; i < (HighsInt)basis.row_status.size(); i++) { - row_status[i] = (HighsInt)basis.row_status[i]; + for (size_t i = 0; i < basis.row_status.size(); i++) { + row_status[i] = static_cast(basis.row_status[i]); } return kHighsStatusOk; } @@ -625,14 +668,16 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, return (HighsInt)((Highs*)highs)->setSolution(solution); } -HighsInt Highs_setCallback( - void* highs, - void (*user_callback)(const int, const char*, - const struct HighsCallbackDataOut*, - struct HighsCallbackDataIn*, void*), - void* user_callback_data) { - return (HighsInt)((Highs*)highs) - ->setCallback(user_callback, user_callback_data); +HighsInt Highs_setSparseSolution(void* highs, const HighsInt num_entries, + const HighsInt* index, const double* value) { + return (HighsInt)((Highs*)highs)->setSolution(num_entries, index, value); +} + +HighsInt Highs_setCallback(void* highs, HighsCCallbackType user_callback, + void* user_callback_data) { + auto status = static_cast(highs)->setCallback(user_callback, + user_callback_data); + return static_cast(status); } HighsInt Highs_startCallback(void* highs, const int callback_type) { @@ -754,6 +799,10 @@ HighsInt Highs_changeColsIntegralityByMask(void* highs, const HighsInt* mask, ->changeColsIntegrality(mask, pass_integrality.data()); } +HighsInt Highs_clearIntegrality(void* highs) { + return (HighsInt)((Highs*)highs)->clearIntegrality(); +} + HighsInt Highs_changeColCost(void* highs, const HighsInt col, const double cost) { return (HighsInt)((Highs*)highs)->changeColCost(col, cost); @@ -1007,6 +1056,10 @@ double Highs_getInfinity(const void* highs) { return ((Highs*)highs)->getInfinity(); } +HighsInt Highs_getSizeofHighsInt(const void* highs) { + return ((Highs*)highs)->getSizeofHighsInt(); +} + HighsInt Highs_getNumCol(const void* highs) { return ((Highs*)highs)->getNumCol(); } @@ -1023,70 +1076,149 @@ HighsInt Highs_getHessianNumNz(const void* highs) { return ((Highs*)highs)->getHessianNumNz(); } -HighsInt Highs_getModel(const void* highs, const HighsInt a_format, - const HighsInt q_format, HighsInt* num_col, - HighsInt* num_row, HighsInt* num_nz, HighsInt* q_num_nz, - HighsInt* sense, double* offset, double* col_cost, - double* col_lower, double* col_upper, double* row_lower, - double* row_upper, HighsInt* a_start, HighsInt* a_index, - double* a_value, HighsInt* q_start, HighsInt* q_index, - double* q_value, HighsInt* integrality) { - const HighsModel& model = ((Highs*)highs)->getModel(); - const HighsLp& lp = model.lp_; - const HighsHessian& hessian = model.hessian_; - ObjSense obj_sense = ObjSense::kMinimize; - *sense = (HighsInt)obj_sense; +HighsInt Highs_getPresolvedNumCol(const void* highs) { + return ((Highs*)highs)->getPresolvedLp().num_col_; +} + +HighsInt Highs_getPresolvedNumRow(const void* highs) { + return ((Highs*)highs)->getPresolvedLp().num_row_; +} + +HighsInt Highs_getPresolvedNumNz(const void* highs) { + return ((Highs*)highs)->getPresolvedLp().a_matrix_.numNz(); +} + +// Gets pointers to all the public data members of HighsLp: avoids +// duplicate code in Highs_getModel, Highs_getPresolvedLp, +HighsInt Highs_getHighsLpData(const HighsLp& lp, const HighsInt a_format, + HighsInt* num_col, HighsInt* num_row, + HighsInt* num_nz, HighsInt* sense, double* offset, + double* col_cost, double* col_lower, + double* col_upper, double* row_lower, + double* row_upper, HighsInt* a_start, + HighsInt* a_index, double* a_value, + HighsInt* integrality) { + const MatrixFormat desired_a_format = + a_format == HighsInt(MatrixFormat::kColwise) ? MatrixFormat::kColwise + : MatrixFormat::kRowwise; + *sense = (HighsInt)lp.sense_; *offset = lp.offset_; *num_col = lp.num_col_; *num_row = lp.num_row_; + *num_nz = 0; // In case one of the matrix dimensions is zero if (*num_col > 0) { - 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 (col_cost) + memcpy(col_cost, lp.col_cost_.data(), *num_col * sizeof(double)); + if (col_lower) + memcpy(col_lower, lp.col_lower_.data(), *num_col * sizeof(double)); + if (col_upper) + memcpy(col_upper, lp.col_upper_.data(), *num_col * sizeof(double)); } if (*num_row > 0) { - memcpy(row_lower, lp.row_lower_.data(), *num_row * sizeof(double)); - memcpy(row_upper, lp.row_upper_.data(), *num_row * sizeof(double)); + if (row_lower) + memcpy(row_lower, lp.row_lower_.data(), *num_row * sizeof(double)); + if (row_upper) + memcpy(row_upper, lp.row_upper_.data(), *num_row * sizeof(double)); } - // Save the original orientation so that it is recovered - MatrixFormat original_a_format = lp.a_matrix_.format_; - // Determine the desired orientation and number of start entries to - // be copied - MatrixFormat desired_a_format = MatrixFormat::kColwise; - HighsInt num_start_entries = *num_col; - if (a_format == (HighsInt)MatrixFormat::kRowwise) { - desired_a_format = MatrixFormat::kRowwise; - num_start_entries = *num_row; - } - // Ensure the desired orientation - HighsInt return_status; - return_status = (HighsInt)((Highs*)highs)->setMatrixFormat(desired_a_format); - if (return_status != kHighsStatusOk) return return_status; - + // Nothing to do if one of the matrix dimensions is zero if (*num_col > 0 && *num_row > 0) { - *num_nz = lp.a_matrix_.numNz(); - memcpy(a_start, lp.a_matrix_.start_.data(), - num_start_entries * sizeof(HighsInt)); - 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_.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)); + // Determine the desired orientation and number of start entries to + // be copied + const HighsInt num_start_entries = + desired_a_format == MatrixFormat::kColwise ? *num_col : *num_row; + if ((desired_a_format == MatrixFormat::kColwise && + lp.a_matrix_.isColwise()) || + (desired_a_format == MatrixFormat::kRowwise && + lp.a_matrix_.isRowwise())) { + // Incumbent format is OK + *num_nz = lp.a_matrix_.numNz(); + if (a_start) + memcpy(a_start, lp.a_matrix_.start_.data(), + num_start_entries * sizeof(HighsInt)); + if (a_index) + memcpy(a_index, lp.a_matrix_.index_.data(), *num_nz * sizeof(HighsInt)); + if (a_value) + memcpy(a_value, lp.a_matrix_.value_.data(), *num_nz * sizeof(double)); + } else { + // Take a copy and transpose it + HighsSparseMatrix local_matrix = lp.a_matrix_; + if (desired_a_format == MatrixFormat::kColwise) { + assert(local_matrix.isRowwise()); + local_matrix.ensureColwise(); + } else { + assert(local_matrix.isColwise()); + local_matrix.ensureRowwise(); + } + *num_nz = local_matrix.numNz(); + if (a_start) + memcpy(a_start, local_matrix.start_.data(), + num_start_entries * sizeof(HighsInt)); + if (a_index) + memcpy(a_index, local_matrix.index_.data(), *num_nz * sizeof(HighsInt)); + if (a_value) + memcpy(a_value, local_matrix.value_.data(), *num_nz * sizeof(double)); + } } - if ((HighsInt)lp.integrality_.size()) { + if (HighsInt(lp.integrality_.size()) && integrality) { for (int iCol = 0; iCol < *num_col; iCol++) - integrality[iCol] = (HighsInt)lp.integrality_[iCol]; + integrality[iCol] = HighsInt(lp.integrality_[iCol]); } - // Restore the original orientation - return_status = (HighsInt)((Highs*)highs)->setMatrixFormat(original_a_format); + return kHighsStatusOk; +} + +HighsInt Highs_getModel(const void* highs, const HighsInt a_format, + const HighsInt q_format, HighsInt* num_col, + HighsInt* num_row, HighsInt* num_nz, HighsInt* q_num_nz, + HighsInt* sense, double* offset, double* col_cost, + double* col_lower, double* col_upper, double* row_lower, + double* row_upper, HighsInt* a_start, HighsInt* a_index, + double* a_value, HighsInt* q_start, HighsInt* q_index, + double* q_value, HighsInt* integrality) { + HighsInt return_status = Highs_getHighsLpData( + ((Highs*)highs)->getLp(), a_format, num_col, num_row, num_nz, sense, + offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, + a_index, a_value, integrality); if (return_status != kHighsStatusOk) return return_status; + const HighsHessian& hessian = ((Highs*)highs)->getModel().hessian_; + if (hessian.dim_ > 0) { + *q_num_nz = hessian.start_[*num_col]; + if (q_start) + memcpy(q_start, hessian.start_.data(), *num_col * sizeof(HighsInt)); + if (q_index) + memcpy(q_index, hessian.index_.data(), *q_num_nz * sizeof(HighsInt)); + if (q_value) + memcpy(q_value, hessian.value_.data(), *q_num_nz * sizeof(double)); + } return kHighsStatusOk; } +HighsInt Highs_getLp(const void* highs, const HighsInt a_format, + HighsInt* num_col, HighsInt* num_row, HighsInt* num_nz, + HighsInt* sense, double* offset, double* col_cost, + double* col_lower, double* col_upper, double* row_lower, + double* row_upper, HighsInt* a_start, HighsInt* a_index, + double* a_value, HighsInt* integrality) { + return Highs_getHighsLpData(((Highs*)highs)->getLp(), a_format, num_col, + num_row, num_nz, sense, offset, col_cost, + col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, integrality); +} + +HighsInt Highs_getPresolvedLp(const void* highs, const HighsInt a_format, + HighsInt* num_col, HighsInt* num_row, + HighsInt* num_nz, HighsInt* sense, double* offset, + double* col_cost, double* col_lower, + double* col_upper, double* row_lower, + double* row_upper, HighsInt* a_start, + HighsInt* a_index, double* a_value, + HighsInt* integrality) { + return Highs_getHighsLpData(((Highs*)highs)->getPresolvedLp(), a_format, + num_col, num_row, num_nz, sense, offset, col_cost, + col_lower, col_upper, row_lower, row_upper, + a_start, a_index, a_value, integrality); +} + 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) { @@ -1213,13 +1345,67 @@ HighsInt Highs_getRanging( } void Highs_resetGlobalScheduler(HighsInt blocking) { - Highs::resetGlobalScheduler(blocking); + Highs::resetGlobalScheduler(blocking != 0); +} + +const void* Highs_getCallbackDataOutItem(const HighsCallbackDataOut* data_out, + const char* item_name) { + // Accessor function for HighsCallbackDataOut + // + // Remember that pointers in HighsCallbackDataOut don't need to be referenced! + if (!strcmp(item_name, kHighsCallbackDataOutLogTypeName)) { + return (void*)(&data_out->log_type); + } else if (!strcmp(item_name, kHighsCallbackDataOutRunningTimeName)) { + return (void*)(&data_out->running_time); + } else if (!strcmp(item_name, + kHighsCallbackDataOutSimplexIterationCountName)) { + return (void*)(&data_out->simplex_iteration_count); + } else if (!strcmp(item_name, kHighsCallbackDataOutIpmIterationCountName)) { + return (void*)(&data_out->ipm_iteration_count); + } else if (!strcmp(item_name, kHighsCallbackDataOutPdlpIterationCountName)) { + return (void*)(&data_out->pdlp_iteration_count); + } else if (!strcmp(item_name, + kHighsCallbackDataOutObjectiveFunctionValueName)) { + return (void*)(&data_out->objective_function_value); + } else if (!strcmp(item_name, kHighsCallbackDataOutMipNodeCountName)) { + return (void*)(&data_out->mip_node_count); + } else if (!strcmp(item_name, + kHighsCallbackDataOutMipTotalLpIterationsName)) { + return (void*)(&data_out->mip_total_lp_iterations); + } else if (!strcmp(item_name, kHighsCallbackDataOutMipPrimalBoundName)) { + return (void*)(&data_out->mip_primal_bound); + } else if (!strcmp(item_name, kHighsCallbackDataOutMipDualBoundName)) { + return (void*)(&data_out->mip_dual_bound); + } else if (!strcmp(item_name, kHighsCallbackDataOutMipGapName)) { + return (void*)(&data_out->mip_gap); + } else if (!strcmp(item_name, kHighsCallbackDataOutMipSolutionName)) { + return (void*)(data_out->mip_solution); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolNumColName)) { + return (void*)(&data_out->cutpool_num_col); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolNumCutName)) { + return (void*)(&data_out->cutpool_num_cut); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolNumNzName)) { + return (void*)(&data_out->cutpool_num_nz); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolStartName)) { + return (void*)(data_out->cutpool_start); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolIndexName)) { + return (void*)(data_out->cutpool_index); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolValueName)) { + return (void*)(data_out->cutpool_value); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolLowerName)) { + return (void*)(data_out->cutpool_lower); + } else if (!strcmp(item_name, kHighsCallbackDataOutCutpoolUpperName)) { + return (void*)(data_out->cutpool_upper); + } + return nullptr; } // ********************* // * Deprecated methods* // ********************* +const char* Highs_compilationDate(void) { return "Deprecated"; } + HighsInt Highs_call(const HighsInt num_col, const HighsInt num_row, const HighsInt num_nz, const double* col_cost, const double* col_lower, const double* col_upper, @@ -1394,5 +1580,5 @@ double Highs_getHighsInfinity(const void* highs) { } HighsInt Highs_getScaledModelStatus(const void* highs) { - return (HighsInt)((Highs*)highs)->getModelStatus(true); + return (HighsInt)((Highs*)highs)->getModelStatus(); } diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 333ef98c07..801ddf332e 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -10,8 +10,20 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HIGHS_C_API #define HIGHS_C_API - -//#include "util/HighsInt.h" +// +// Welcome to the HiGHS C API! +// +// The simplest way to use HiGHS to solve an LP, MIP or QP from C is +// to pass the problem data to the appropriate method Highs_lpCall, +// Highs_mipCall or Highs_qpCall, and these methods return the +// appropriate solution information +// +// For sophisticated applications, where esoteric solutiuon +// information is needed, or if a sequence of modified models need to +// be solved, use the Highs_create method to generate a pointer to an +// instance of the C++ Highs class, and then use any of a large number +// of models for which this pointer is the first parameter. +// #include "lp_data/HighsCallbackStruct.h" const HighsInt kHighsMaximumStringLength = 512; @@ -60,6 +72,7 @@ const HighsInt kHighsPresolveStatusReducedToEmpty = 4; const HighsInt kHighsPresolveStatusTimeout = 5; const HighsInt kHighsPresolveStatusNullError = 6; const HighsInt kHighsPresolveStatusOptionsError = 7; +const HighsInt kHighsPresolveStatusOutOfMemory = 8; const HighsInt kHighsModelStatusNotset = 0; const HighsInt kHighsModelStatusLoadError = 1; @@ -89,9 +102,38 @@ const HighsInt kHighsBasisStatusNonbasic = 4; const HighsInt kHighsCallbackLogging = 0; const HighsInt kHighsCallbackSimplexInterrupt = 1; const HighsInt kHighsCallbackIpmInterrupt = 2; -const HighsInt kHighsCallbackMipImprovingSolution = 3; -const HighsInt kHighsCallbackMipLogging = 4; -const HighsInt kHighsCallbackMipInterrupt = 5; +const HighsInt kHighsCallbackMipSolution = 3; +const HighsInt kHighsCallbackMipImprovingSolution = 4; +const HighsInt kHighsCallbackMipLogging = 5; +const HighsInt kHighsCallbackMipInterrupt = 6; +const HighsInt kHighsCallbackMipGetCutPool = 7; +const HighsInt kHighsCallbackMipDefineLazyConstraints = 8; + +const char* const kHighsCallbackDataOutLogTypeName = "log_type"; +const char* const kHighsCallbackDataOutRunningTimeName = "running_time"; +const char* const kHighsCallbackDataOutSimplexIterationCountName = + "simplex_iteration_count"; +const char* const kHighsCallbackDataOutIpmIterationCountName = + "ipm_iteration_count"; +const char* const kHighsCallbackDataOutPdlpIterationCountName = + "pdlp_iteration_count"; +const char* const kHighsCallbackDataOutObjectiveFunctionValueName = + "objective_function_value"; +const char* const kHighsCallbackDataOutMipNodeCountName = "mip_node_count"; +const char* const kHighsCallbackDataOutMipTotalLpIterationsName = + "mip_total_lp_iterations"; +const char* const kHighsCallbackDataOutMipPrimalBoundName = "mip_primal_bound"; +const char* const kHighsCallbackDataOutMipDualBoundName = "mip_dual_bound"; +const char* const kHighsCallbackDataOutMipGapName = "mip_gap"; +const char* const kHighsCallbackDataOutMipSolutionName = "mip_solution"; +const char* const kHighsCallbackDataOutCutpoolNumColName = "cutpool_num_col"; +const char* const kHighsCallbackDataOutCutpoolNumCutName = "cutpool_num_cut"; +const char* const kHighsCallbackDataOutCutpoolNumNzName = "cutpool_num_nz"; +const char* const kHighsCallbackDataOutCutpoolStartName = "cutpool_start"; +const char* const kHighsCallbackDataOutCutpoolIndexName = "cutpool_index"; +const char* const kHighsCallbackDataOutCutpoolValueName = "cutpool_value"; +const char* const kHighsCallbackDataOutCutpoolLowerName = "cutpool_lower"; +const char* const kHighsCallbackDataOutCutpoolUpperName = "cutpool_upper"; #ifdef __cplusplus extern "C" { @@ -261,13 +303,6 @@ HighsInt Highs_versionPatch(void); */ const char* Highs_githash(void); -/** - * Return the HiGHS compilation date. - * - * @returns Thse HiGHS compilation date. - */ -const char* Highs_compilationDate(void); - /** * Read a model from `filename` into `highs`. * @@ -288,6 +323,16 @@ HighsInt Highs_readModel(void* highs, const char* filename); */ HighsInt Highs_writeModel(void* highs, const char* filename); +/** + * Write the presolved model in `highs` to `filename`. + * + * @param highs A pointer to the Highs instance. + * @param filename The filename to write. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_writePresolvedModel(void* highs, const char* filename); + /** * Reset the options and then call `clearModel`. * @@ -321,6 +366,15 @@ HighsInt Highs_clearModel(void* highs); */ HighsInt Highs_clearSolver(void* highs); +/** + * Presolve a model. + * + * @param highs A pointer to the Highs instance. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_presolve(void* highs); + /** * Optimize a model. The algorithm used by HiGHS depends on the options that * have been set. @@ -331,6 +385,22 @@ HighsInt Highs_clearSolver(void* highs); */ HighsInt Highs_run(void* highs); +/** + * Postsolve a model using a primal (and possibly dual) solution. + * + * @param highs A pointer to the Highs instance. + * @param col_value An array of length [num_col] with the column solution + * values. + * @param col_dual An array of length [num_col] with the column dual + * values, or a null pointer if not known. + * @param row_dual An array of length [num_row] with the row dual values, + * or a null pointer if not known. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_postsolve(void* highs, const double* col_value, + const double* col_dual, const double* row_dual); + /** * Write the solution information (including dual and basis status, if * available) to a file. @@ -349,7 +419,7 @@ HighsInt Highs_writeSolution(const void* highs, const char* filename); * available) to a file in a human-readable format. * * The method identical to `Highs_writeSolution`, except that the - * printout is in a human-readiable format. + * printout is in a human-readable format. * * @param highs A pointer to the Highs instance. * @param filename The name of the file to write the results to. @@ -437,7 +507,7 @@ HighsInt Highs_passMip(void* highs, const HighsInt num_col, * @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. + * constant for each column. * * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ @@ -967,10 +1037,10 @@ HighsInt Highs_getBasisSolve(const void* highs, const double* rhs, * * @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 + * @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 whcih to store the + * @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. @@ -1069,6 +1139,19 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, const double* row_value, const double* col_dual, const double* row_dual); +/** + * Set a partial primal solution by passing values for a set of variables + * + * @param highs A pointer to the Highs instance. + * @param num_entries Number of variables in the set + * @param index Indices of variables in the set + * @param value Values of variables in the set + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_setSparseSolution(void* highs, const HighsInt num_entries, + const HighsInt* index, const double* value); + /** * Set the callback method to use for HiGHS * @@ -1078,12 +1161,8 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, * * @returns A `kHighsStatus` constant indicating whether the call succeeded. */ -HighsInt Highs_setCallback( - void* highs, - void (*user_callback)(const int, const char*, - const struct HighsCallbackDataOut*, - struct HighsCallbackDataIn*, void*), - void* user_callback_data); +HighsInt Highs_setCallback(void* highs, HighsCCallbackType user_callback, + void* user_callback_data); /** * Start callback of given type @@ -1325,6 +1404,15 @@ HighsInt Highs_changeColsIntegralityBySet(void* highs, HighsInt Highs_changeColsIntegralityByMask(void* highs, const HighsInt* mask, const HighsInt* integrality); +/** + * Clear the integrality of all columns + * + * @param highs A pointer to the Highs instance. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_clearIntegrality(void* highs); + /** * Change the objective coefficient of a column. * @@ -1844,6 +1932,15 @@ HighsInt Highs_scaleRow(void* highs, const HighsInt row, const double scaleval); */ double Highs_getInfinity(const void* highs); +/** + * Return the size of integers used by HiGHS. + * + * @param highs A pointer to the Highs instance. + * + * @returns The size of integers used by HiGHS. + */ +HighsInt Highs_getSizeofHighsInt(const void* highs); + /** * Return the number of columns in the model. * @@ -1880,6 +1977,35 @@ HighsInt Highs_getNumNz(const void* highs); */ HighsInt Highs_getHessianNumNz(const void* highs); +/** + * Return the number of columns in the presolved model. + * + * @param highs A pointer to the Highs instance. + * + * @returns The number of columns in the presolved model. + */ +HighsInt Highs_getPresolvedNumCol(const void* highs); + +/** + * Return the number of rows in the presolved model. + * + * @param highs A pointer to the Highs instance. + * + * @returns The number of rows in the presolved model. + */ +HighsInt Highs_getPresolvedNumRow(const void* highs); + +/** + * Return the number of nonzeros in the constraint matrix of the presolved + * model. + * + * @param highs A pointer to the Highs instance. + * + * @returns The number of nonzeros in the constraint matrix of the presolved + * model. + */ +HighsInt Highs_getPresolvedNumNz(const void* highs); + /** * Get the data from a HiGHS model. * @@ -1906,6 +2032,52 @@ HighsInt Highs_getModel(const void* highs, const HighsInt a_format, HighsInt* q_start, HighsInt* q_index, double* q_value, HighsInt* integrality); +/** + * Get the data from a HiGHS LP. + * + * The input arguments have the same meaning (in a different order) to those + * used in `Highs_passModel`. + * + * Note that all arrays must be pre-allocated to the correct size before calling + * `Highs_getModel`. Use the following query methods to check the appropriate + * size: + * - `Highs_getNumCol` + * - `Highs_getNumRow` + * - `Highs_getNumNz` + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getLp(const void* highs, const HighsInt a_format, + HighsInt* num_col, HighsInt* num_row, HighsInt* num_nz, + HighsInt* sense, double* offset, double* col_cost, + double* col_lower, double* col_upper, double* row_lower, + double* row_upper, HighsInt* a_start, HighsInt* a_index, + double* a_value, HighsInt* integrality); + +/** + * Get the data from a HiGHS presolved LP. + * + * The input arguments have the same meaning (in a different order) to those + * used in `Highs_passModel`. + * + * Note that all arrays must be pre-allocated to the correct size before calling + * `Highs_getModel`. Use the following query methods to check the appropriate + * size: + * - `Highs_getPresolvedNumCol` + * - `Highs_getPresolvedNumRow` + * - `Highs_getPresolvedNumNz` + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_getPresolvedLp(const void* highs, const HighsInt a_format, + HighsInt* num_col, HighsInt* num_row, + HighsInt* num_nz, HighsInt* sense, double* offset, + double* col_cost, double* col_lower, + double* col_upper, double* row_lower, + double* row_upper, HighsInt* a_start, + HighsInt* a_index, double* a_value, + HighsInt* integrality); + /** * Set a primal (and possibly dual) solution as a starting point, then run * crossover to compute a basic feasible solution. @@ -1930,7 +2102,7 @@ HighsInt Highs_crossover(void* highs, const int num_col, const int num_row, /** * Compute the ranging information for all costs and bounds. For - * nonbasic variables the ranging informaiton is relative to the + * nonbasic variables the ranging information is relative to the * active bound. For basic variables the ranging information relates * to... * @@ -2019,10 +2191,29 @@ HighsInt Highs_getRanging( */ void Highs_resetGlobalScheduler(const HighsInt blocking); +/** + * Get a void* pointer to a callback data item + * + * @param data_out A pointer to the HighsCallbackDataOut instance. + * @param item_name The name of the item. + * + * @returns A void* pointer to the callback data item, or NULL if item_name not + * valid + */ +const void* Highs_getCallbackDataOutItem(const HighsCallbackDataOut* data_out, + const char* item_name); + // ********************* // * Deprecated methods* // ********************* +/** + * Return the HiGHS compilation date. + * + * @returns Thse HiGHS compilation date. + */ +const char* Highs_compilationDate(void); + // These are deprecated because they don't follow the style guide. Constants // must begin with `k`. const HighsInt HighsStatuskError = -1; diff --git a/src/interfaces/highs_csharp_api.cs b/src/interfaces/highs_csharp_api.cs index c566756971..dadb15caac 100644 --- a/src/interfaces/highs_csharp_api.cs +++ b/src/interfaces/highs_csharp_api.cs @@ -5,6 +5,7 @@ // mcs -out:highscslib.dll -t:library highs_csharp_api.cs -unsafe +namespace Highs { public enum HighsStatus { kError = -1, @@ -52,7 +53,8 @@ public enum HighsModelStatus kIterationLimit, kUnknown, kSolutionLimit, - kInterrupt + kInterrupt, + kMemoryLimit } public enum HighsIntegrality @@ -150,7 +152,7 @@ public class HighsLpSolver : IDisposable private bool _disposed; - private const string highslibname = "libhighs"; + private const string highslibname = "highs"; [DllImport(highslibname)] private static extern int Highs_call( @@ -188,6 +190,9 @@ private static extern int Highs_call( [DllImport(highslibname)] private static extern int Highs_writeModel(IntPtr highs, string filename); + [DllImport(highslibname)] + private static extern int Highs_writePresolvedModel(IntPtr highs, string filename); + [DllImport(highslibname)] private static extern int Highs_writeSolutionPretty(IntPtr highs, string filename); @@ -596,6 +601,11 @@ public HighsStatus writeModel(string filename) return (HighsStatus)HighsLpSolver.Highs_writeModel(this.highs, filename); } + public HighsStatus writePresolvedModel(string filename) + { + return (HighsStatus)HighsLpSolver.Highs_writePresolvedModel(this.highs, filename); + } + public HighsStatus writeSolutionPretty(string filename) { return (HighsStatus)HighsLpSolver.Highs_writeSolutionPretty(this.highs, filename); @@ -908,9 +918,10 @@ public SolutionInfo getInfo() 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), + NodeCount = this.GetValueOrFallback(HighsLpSolver.Highs_getInt64InfoValue, "mip_node_count", 0L), + IpmIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "ipm_iteration_count", 0), + SimplexIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "simplex_iteration_count", 0), + PdlpIterationCount = this.GetValueOrFallback(HighsLpSolver.Highs_getIntInfoValue, "pdlp_iteration_count", 0), }; return info; } @@ -1001,6 +1012,11 @@ public class SolutionInfo /// public int IpmIterationCount { get; set; } + /// + /// Gets or sets the PDLP iteration count. + /// + public int PdlpIterationCount { get; set; } + /// /// Gets or sets the MIP gap. /// @@ -1021,3 +1037,4 @@ public class SolutionInfo /// public double ObjectiveValue { get; set; } } +} diff --git a/src/interfaces/highs_fortran_api.f90 b/src/interfaces/highs_fortran_api.f90 index 05e2d17b1b..7014907f7a 100644 --- a/src/interfaces/highs_fortran_api.f90 +++ b/src/interfaces/highs_fortran_api.f90 @@ -125,6 +125,13 @@ function Highs_writeModel ( h, f ) result ( s ) bind ( c, name='Highs_writeModel integer ( c_int ) :: s end function Highs_writeModel + function Highs_writePresolvedModel ( h, f ) result ( s ) bind ( c, name='Highs_writePresolvedModel' ) + use iso_c_binding + type(c_ptr), VALUE :: h + character( c_char ) :: f(*) + integer ( c_int ) :: s + end function Highs_writePresolvedModel + function Highs_writeSolution ( h, f ) result ( s ) bind ( c, name='Highs_writeSolution' ) use iso_c_binding type(c_ptr), VALUE :: h diff --git a/src/interfaces/highs_python_api.py b/src/interfaces/highs_python_api.py deleted file mode 100644 index c174bdc794..0000000000 --- a/src/interfaces/highs_python_api.py +++ /dev/null @@ -1,197 +0,0 @@ -import ctypes -import os -from ctypes.util import find_library - -highslib = ctypes.cdll.LoadLibrary(ctypes.util.find_library("highs")) - -# highs lib folder must be in "LD_LIBRARY_PATH" environment variable -# ============ -# Highs_lpCall -highslib.Highs_lpCall.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_int, ctypes.c_double, -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) -highslib.Highs_lpCall.restype = ctypes.c_int - -def Highs_lpCall(col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value): - global highslib - n_col = len(col_cost) - n_row = len(row_lower) - n_nz = len(a_index) - a_format = 1 - sense = 1 - offset = 0 - - # In case a_start has the fictitious start of column n_col - a_start_length = len(a_start) - - dbl_array_type_col = ctypes.c_double * n_col - dbl_array_type_row = ctypes.c_double * n_row - int_array_type_a_start = ctypes.c_int * a_start_length - int_array_type_a_index = ctypes.c_int * n_nz - dbl_array_type_a_value = ctypes.c_double * n_nz - - int_array_type_col = ctypes.c_int * n_col - int_array_type_row = ctypes.c_int * n_row - - col_value = [0] * n_col - col_dual = [0] * n_col - - row_value = [0] * n_row - row_dual = [0] * n_row - - col_basis = [0] * n_col - row_basis = [0] * n_row - - model_status = ctypes.c_int(0) - - col_value = dbl_array_type_col(*col_value) - col_dual = dbl_array_type_col(*col_dual) - row_value = dbl_array_type_row(*row_value) - row_dual = dbl_array_type_row(*row_dual) - col_basis = int_array_type_col(*col_basis) - row_basis = int_array_type_row(*row_basis) - - return_status = highslib.Highs_lpCall( - ctypes.c_int(n_col), ctypes.c_int(n_row), ctypes.c_int(n_nz), ctypes.c_int(a_format), - ctypes.c_int(sense), ctypes.c_double(offset), - dbl_array_type_col(*col_cost), dbl_array_type_col(*col_lower), dbl_array_type_col(*col_upper), - dbl_array_type_row(*row_lower), dbl_array_type_row(*row_upper), - int_array_type_a_start(*a_start), int_array_type_a_index(*a_index), dbl_array_type_a_value(*a_value), - col_value, col_dual, - row_value, row_dual, - col_basis, row_basis, ctypes.byref(model_status)) - return return_status, model_status.value, list(col_value), list(col_dual), list(row_value), list(row_dual), list(col_basis), list(row_basis) - -# ============= -# Highs_mipCall -highslib.Highs_mipCall.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_int, ctypes.c_double, -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_int)) -highslib.Highs_mipCall.restype = ctypes.c_int - -def Highs_mipCall(col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value, integrality): - global highslib - n_col = len(col_cost) - n_row = len(row_lower) - n_nz = len(a_index) - a_format = 1 - sense = 1 - offset = 0 - - # In case a_start has the fictitious start of column n_col - a_start_length = len(a_start) - - dbl_array_type_col = ctypes.c_double * n_col - dbl_array_type_row = ctypes.c_double * n_row - int_array_type_a_start = ctypes.c_int * a_start_length - int_array_type_a_index = ctypes.c_int * n_nz - dbl_array_type_a_value = ctypes.c_double * n_nz - - int_array_type_col = ctypes.c_int * n_col - int_array_type_row = ctypes.c_int * n_row - - col_value = [0] * n_col - col_dual = [0] * n_col - - row_value = [0] * n_row - row_dual = [0] * n_row - - col_basis = [0] * n_col - row_basis = [0] * n_row - - model_status = ctypes.c_int(0) - - col_value = dbl_array_type_col(*col_value) - row_value = dbl_array_type_row(*row_value) - - return_status = highslib.Highs_mipCall( - ctypes.c_int(n_col), ctypes.c_int(n_row), ctypes.c_int(n_nz), ctypes.c_int(a_format), - ctypes.c_int(sense), ctypes.c_double(offset), - dbl_array_type_col(*col_cost), dbl_array_type_col(*col_lower), dbl_array_type_col(*col_upper), - dbl_array_type_row(*row_lower), dbl_array_type_row(*row_upper), - int_array_type_a_start(*a_start), int_array_type_a_index(*a_index), dbl_array_type_a_value(*a_value), - int_array_type_col(*integrality), - col_value, row_value, ctypes.byref(model_status)) - return return_status, model_status.value, list(col_value), list(row_value) - -# ============ -# Highs_qpCall -highslib.Highs_qpCall.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_double, -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double), -ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) -highslib.Highs_call.restype = ctypes.c_int - -def Highs_qpCall(col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value, q_start, q_index, q_value): - global highslib - n_col = len(col_cost) - n_row = len(row_lower) - n_nz = len(a_index) - q_n_nz = len(q_index) - a_format = 1 - q_format = 1 - sense = 1 - offset = 0 - - # In case a_start or q_start has the fictitious start of column n_col - a_start_length = len(a_start) - q_start_length = len(q_start) - - dbl_array_type_col = ctypes.c_double * n_col - dbl_array_type_row = ctypes.c_double * n_row - int_array_type_a_start = ctypes.c_int * a_start_length - int_array_type_a_index = ctypes.c_int * n_nz - dbl_array_type_a_value = ctypes.c_double * n_nz - - int_array_type_q_start = ctypes.c_int * q_start_length - int_array_type_q_index = ctypes.c_int * q_n_nz - dbl_array_type_q_value = ctypes.c_double * q_n_nz - - int_array_type_col = ctypes.c_int * n_col - int_array_type_row = ctypes.c_int * n_row - - col_value = [0] * n_col - col_dual = [0] * n_col - - row_value = [0] * n_row - row_dual = [0] * n_row - - col_basis = [0] * n_col - row_basis = [0] * n_row - - model_status = ctypes.c_int(0) - - col_value = dbl_array_type_col(*col_value) - col_dual = dbl_array_type_col(*col_dual) - row_value = dbl_array_type_row(*row_value) - row_dual = dbl_array_type_row(*row_dual) - col_basis = int_array_type_col(*col_basis) - row_basis = int_array_type_row(*row_basis) - - return_status = highslib.Highs_qpCall( - ctypes.c_int(n_col), ctypes.c_int(n_row), ctypes.c_int(n_nz), ctypes.c_int(q_n_nz), - ctypes.c_int(a_format), ctypes.c_int(q_format), - ctypes.c_int(sense), ctypes.c_double(offset), - dbl_array_type_col(*col_cost), dbl_array_type_col(*col_lower), dbl_array_type_col(*col_upper), - dbl_array_type_row(*row_lower), dbl_array_type_row(*row_upper), - int_array_type_a_start(*a_start), int_array_type_a_index(*a_index), dbl_array_type_a_value(*a_value), - int_array_type_q_start(*q_start), int_array_type_q_index(*q_index), dbl_array_type_q_value(*q_value), - col_value, col_dual, - row_value, row_dual, - col_basis, row_basis, ctypes.byref(model_status)) - return return_status, model_status.value, list(col_value), list(col_dual), list(row_value), list(row_dual), list(col_basis), list(row_basis) - diff --git a/src/io/Filereader.cpp b/src/io/Filereader.cpp index a749511308..2106fe54ce 100644 --- a/src/io/Filereader.cpp +++ b/src/io/Filereader.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/Filereader.h b/src/io/Filereader.h index 6abebcdee4..e71b8a3d08 100644 --- a/src/io/Filereader.h +++ b/src/io/Filereader.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/FilereaderEms.cpp b/src/io/FilereaderEms.cpp index d75dc86f93..3bfe669980 100644 --- a/src/io/FilereaderEms.cpp +++ b/src/io/FilereaderEms.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -272,11 +272,11 @@ HighsStatus FilereaderEms::writeModelToFile(const HighsOptions& options, f << "names" << std::endl; f << "columns" << std::endl; - for (HighsInt i = 0; i < (HighsInt)lp.col_names_.size(); i++) + for (size_t i = 0; i < lp.col_names_.size(); i++) f << lp.col_names_[i] << std::endl; f << "rows" << std::endl; - for (HighsInt i = 0; i < (HighsInt)lp.row_names_.size(); i++) + for (size_t i = 0; i < lp.row_names_.size(); i++) f << lp.row_names_[i] << std::endl; } diff --git a/src/io/FilereaderEms.h b/src/io/FilereaderEms.h index e2f4431beb..6d674f74fc 100644 --- a/src/io/FilereaderEms.h +++ b/src/io/FilereaderEms.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index f0ed2a289a..4c0382e31c 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -19,7 +19,7 @@ #include #include -#include "filereaderlp/reader.hpp" +#include "../extern/filereaderlp/reader.hpp" #include "lp_data/HighsLpUtils.h" const bool original_double_format = false; @@ -47,7 +47,7 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, lp.row_names_.resize(m.constraints.size()); lp.integrality_.assign(lp.num_col_, HighsVarType::kContinuous); HighsInt num_continuous = 0; - for (HighsUInt i = 0; i < m.variables.size(); i++) { + for (size_t i = 0; i < m.variables.size(); i++) { varindex[m.variables[i]->name] = i; lp.col_lower_.push_back(m.variables[i]->lowerbound); lp.col_upper_.push_back(m.variables[i]->upperbound); @@ -65,14 +65,15 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, } } // Clear lp.integrality_ if problem is pure LP - if ((size_t)num_continuous == m.variables.size()) lp.integrality_.clear(); + if (static_cast(num_continuous) == m.variables.size()) + lp.integrality_.clear(); // get objective lp.objective_name_ = m.objective->name; // ToDo: Fix m.objective->offset and then use it here // lp.offset_ = m.objective->offset; lp.col_cost_.resize(lp.num_col_, 0.0); - for (HighsUInt i = 0; i < m.objective->linterms.size(); i++) { + for (size_t i = 0; i < m.objective->linterms.size(); i++) { std::shared_ptr lt = m.objective->linterms[i]; lp.col_cost_[varindex[lt->var->name]] = lt->coef; } @@ -96,7 +97,7 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, // nonzero entries unsigned int qnnz = 0; for (std::shared_ptr var : m.variables) - for (unsigned int i = 0; i < mat[var].size(); i++) + for (size_t i = 0; i < mat[var].size(); i++) if (mat2[var][i]) qnnz++; if (qnnz) { hessian.dim_ = m.variables.size(); @@ -107,7 +108,7 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, assert((int)hessian.start_.size() == 0); for (std::shared_ptr var : m.variables) { hessian.start_.push_back(qnnz); - for (unsigned int i = 0; i < mat[var].size(); i++) { + for (size_t i = 0; i < mat[var].size(); i++) { double value = mat2[var][i]; if (value) { hessian.index_.push_back(varindex[mat[var][i]->name]); @@ -126,10 +127,10 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, std::map, std::vector> consofvarmap_index; std::map, std::vector> consofvarmap_value; - for (HighsUInt i = 0; i < m.constraints.size(); i++) { + for (size_t i = 0; i < m.constraints.size(); i++) { std::shared_ptr con = m.constraints[i]; lp.row_names_[i] = con->expr->name; - for (HighsUInt j = 0; j < con->expr->linterms.size(); j++) { + for (size_t j = 0; j < con->expr->linterms.size(); j++) { std::shared_ptr lt = con->expr->linterms[j]; if (consofvarmap_index.count(lt->var) == 0) { consofvarmap_index[lt->var] = std::vector(); @@ -182,7 +183,7 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, for (HighsInt i = 0; i < lp.num_col_; i++) { std::shared_ptr var = m.variables[i]; lp.a_matrix_.start_.push_back(nz); - for (HighsUInt j = 0; j < consofvarmap_index[var].size(); j++) { + for (size_t j = 0; j < consofvarmap_index[var].size(); j++) { double value = consofvarmap_value[var][j]; if (value) { lp.a_matrix_.index_.push_back(consofvarmap_index[var][j]); @@ -218,6 +219,7 @@ void FilereaderLp::writeToFile(FILE* file, const char* format, ...) { char stringbuffer[LP_MAX_LINE_LENGTH + 1]; HighsInt tokenlength = vsnprintf(stringbuffer, sizeof stringbuffer, format, argptr); + va_end(argptr); if (this->linelength + tokenlength >= LP_MAX_LINE_LENGTH) { fprintf(file, "\n"); fprintf(file, "%s", stringbuffer); @@ -287,9 +289,11 @@ HighsStatus FilereaderLp::writeModelToFile(const HighsOptions& options, ar_matrix.ensureRowwise(); const bool has_col_names = - allow_model_names && HighsInt(lp.col_names_.size()) == lp.num_col_; + allow_model_names && + lp.col_names_.size() == static_cast(lp.num_col_); const bool has_row_names = - allow_model_names && HighsInt(lp.row_names_.size()) == lp.num_row_; + allow_model_names && + lp.row_names_.size() == static_cast(lp.num_row_); FILE* file = fopen(filename.c_str(), "w"); // write comment at the start of the file diff --git a/src/io/FilereaderLp.h b/src/io/FilereaderLp.h index 4140bde1a6..6155d68ae4 100644 --- a/src/io/FilereaderLp.h +++ b/src/io/FilereaderLp.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/FilereaderMps.cpp b/src/io/FilereaderMps.cpp index 5a928209c2..b1da7d627e 100644 --- a/src/io/FilereaderMps.cpp +++ b/src/io/FilereaderMps.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/FilereaderMps.h b/src/io/FilereaderMps.h index 476ea6876e..8ae7f20162 100644 --- a/src/io/FilereaderMps.h +++ b/src/io/FilereaderMps.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/HMPSIO.cpp b/src/io/HMPSIO.cpp index 698c893024..b6037d3624 100644 --- a/src/io/HMPSIO.cpp +++ b/src/io/HMPSIO.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -24,7 +24,7 @@ #include "util/stringutil.h" #ifdef ZLIB_FOUND -#include "zstr/zstr.hpp" +#include "../extern/zstr/zstr.hpp" #endif using std::map; @@ -339,7 +339,7 @@ FilereaderRetcode readMps( colUpper.assign(numCol, kHighsInf); if (flag[0] == 'B') { while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { - // Find the column index associated woith the name "data[2]". If + // Find the column index associated with the name "data[2]". If // the name is in colIndex then the value stored is the true // column index plus one. Otherwise 0 will be returned. HighsInt iCol = colIndex[data[2]] - 1; @@ -539,8 +539,8 @@ HighsStatus writeModelAsMps(const HighsOptions& options, bool warning_found = false; const HighsLp& lp = model.lp_; const HighsHessian& hessian = model.hessian_; - bool have_col_names = lp.col_names_.size(); - bool have_row_names = lp.row_names_.size(); + bool have_col_names = (lp.col_names_.size() != 0); + bool have_row_names = (lp.row_names_.size() != 0); std::vector local_col_names; std::vector local_row_names; local_col_names.resize(lp.num_col_); @@ -564,7 +564,7 @@ HighsStatus writeModelAsMps(const HighsOptions& options, HighsStatus row_name_status = normaliseNames(options.log_options, "row", lp.num_row_, local_row_names, max_row_name_length); - if (row_name_status == HighsStatus::kError) return col_name_status; + if (row_name_status == HighsStatus::kError) return row_name_status; warning_found = row_name_status == HighsStatus::kWarning || warning_found; HighsInt max_name_length = std::max(max_col_name_length, max_row_name_length); @@ -633,6 +633,7 @@ HighsStatus writeMps( "Cannot write fixed MPS with names of length (up to) %" HIGHSINT_FORMAT "\n", max_name_length); + fclose(file); return HighsStatus::kError; } assert(objective_name != ""); @@ -905,7 +906,7 @@ HighsStatus writeMps( } else { if (!highs_isInfinity(-lb)) { // Finite lower bound. No need to state this if LB is - // zero unless UB is infinte + // zero unless UB is infinite if (lb || highs_isInfinity(ub)) fprintf(file, " LI BOUND %-8s %.10g\n", col_names[c_n].c_str(), lb); diff --git a/src/io/HMPSIO.h b/src/io/HMPSIO.h index 899167f9f2..8bd8d3c42f 100644 --- a/src/io/HMPSIO.h +++ b/src/io/HMPSIO.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 6b73579c94..14f0bf5dc0 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -14,7 +14,7 @@ #include "lp_data/HighsModelUtils.h" #ifdef ZLIB_FOUND -#include "zstr/zstr.hpp" +#include "../extern/zstr/zstr.hpp" #endif namespace free_format_parser { @@ -91,7 +91,7 @@ FreeFormatParserReturnCode HMpsFF::loadProblem( lp.a_matrix_.value_ = std::move(a_value); // a must have at least start_[0]=0 for the fictitious column // 0 - if ((int)lp.a_matrix_.start_.size() == 0) lp.a_matrix_.clear(); + if (lp.a_matrix_.start_.size() == 0) lp.a_matrix_.clear(); lp.col_cost_ = std::move(col_cost); lp.col_lower_ = std::move(col_lower); lp.col_upper_ = std::move(col_upper); @@ -104,7 +104,7 @@ FreeFormatParserReturnCode HMpsFF::loadProblem( // Only set up lp.integrality_ if non-continuous bool is_mip = false; - for (HighsInt iCol = 0; iCol < (int)col_integrality.size(); iCol++) { + for (size_t iCol = 0; iCol < col_integrality.size(); iCol++) { if (col_integrality[iCol] != HighsVarType::kContinuous) { is_mip = true; break; @@ -129,8 +129,8 @@ FreeFormatParserReturnCode HMpsFF::loadProblem( } HighsInt HMpsFF::fillMatrix(const HighsLogOptions& log_options) { - HighsInt num_entries = entries.size(); - if (num_entries != num_nz) return 1; + size_t num_entries = entries.size(); + if (num_entries != static_cast(num_nz)) return 1; a_value.resize(num_nz); a_index.resize(num_nz); @@ -171,7 +171,7 @@ HighsInt HMpsFF::fillMatrix(const HighsLogOptions& log_options) { } HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) { - HighsInt num_entries = q_entries.size(); + size_t num_entries = q_entries.size(); if (!num_entries) { q_dim = 0; return 0; @@ -189,7 +189,7 @@ HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) { std::vector q_length; q_length.assign(q_dim, 0); - for (HighsInt iEl = 0; iEl < num_entries; iEl++) { + for (size_t iEl = 0; iEl < num_entries; iEl++) { HighsInt iCol = std::get<1>(q_entries[iEl]); q_length[iCol]++; } @@ -199,7 +199,7 @@ HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) { q_length[iCol] = q_start[iCol]; } - for (HighsInt iEl = 0; iEl < num_entries; iEl++) { + for (size_t iEl = 0; iEl < num_entries; iEl++) { HighsInt iRow = std::get<0>(q_entries[iEl]); HighsInt iCol = std::get<1>(q_entries[iEl]); double value = std::get<2>(q_entries[iEl]); @@ -315,8 +315,8 @@ FreeFormatParserReturnCode HMpsFF::parse(const HighsLogOptions& log_options, if (keyword == HMpsFF::Parsekey::kTimeout) return FreeFormatParserReturnCode::kTimeout; - assert(col_lower.size() == unsigned(num_col)); - assert(row_lower.size() == unsigned(num_row)); + assert(col_lower.size() == static_cast(num_col)); + assert(row_lower.size() == static_cast(num_row)); return FreeFormatParserReturnCode::kSuccess; } @@ -359,11 +359,10 @@ bool HMpsFF::cannotParseSection(const HighsLogOptions& log_options, } // Assuming string is not empty. -HMpsFF::Parsekey HMpsFF::checkFirstWord(std::string& strline, HighsInt& start, - HighsInt& end, - std::string& word) const { +HMpsFF::Parsekey HMpsFF::checkFirstWord(std::string& strline, size_t& start, + size_t& end, std::string& word) const { start = strline.find_first_not_of(" "); - if ((start == (HighsInt)strline.size() - 1) || is_empty(strline[start + 1])) { + if ((start + 1 == strline.size()) || is_empty(strline[start + 1])) { end = start + 1; word = strline[start]; return HMpsFF::Parsekey::kNone; @@ -452,11 +451,11 @@ HMpsFF::Parsekey HMpsFF::parseDefault(const HighsLogOptions& log_options, if (getline(file, strline)) { strline = trim(strline); if (strline.empty()) return HMpsFF::Parsekey::kComment; - HighsInt s, e; + size_t s, e; HMpsFF::Parsekey key = checkFirstWord(strline, s, e, word); if (key == HMpsFF::Parsekey::kName) { // Save name of the MPS file - if (e < (HighsInt)strline.length()) { + if (e < strline.length()) { mps_name = first_word(strline, e); } highsLogDev(log_options, HighsLogType::kInfo, @@ -466,7 +465,7 @@ HMpsFF::Parsekey HMpsFF::parseDefault(const HighsLogOptions& log_options, if (key == HMpsFF::Parsekey::kObjsense) { // Look for Gurobi-style definition of MAX/MIN on OBJSENSE line - if (e < (HighsInt)strline.length()) { + if (e < strline.length()) { std::string sense = first_word(strline, e); if (sense.compare("MAX") == 0) { // Found MAX sense on OBJSENSE line @@ -499,8 +498,8 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, while (getline(file, strline)) { if (is_empty(strline) || strline[0] == '*') continue; - HighsInt start = 0; - HighsInt end = 0; + size_t start = 0; + size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word); @@ -542,8 +541,8 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, bool isobj = false; bool isFreeRow = false; - HighsInt start = 0; - HighsInt end = 0; + size_t start = 0; + size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word); @@ -583,13 +582,13 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, std::string unidentified = strline.substr(start); trim(unidentified); highsLogUser(log_options, HighsLogType::kError, - "Entry \"%s\" in ROWS section of MPS file is unidentifed\n", + "Entry \"%s\" in ROWS section of MPS file is unidentified\n", unidentified.c_str()); return HMpsFF::Parsekey::kFail; } std::string rowname = first_word(strline, start + 1); - HighsInt rowname_end = first_word_end(strline, start + 1); + size_t rowname_end = first_word_end(strline, start + 1); // Detect if file is in fixed format. if (!is_end(strline, rowname_end)) { @@ -647,7 +646,8 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, std::istream& file) { std::string colname = ""; std::string strline, word; - HighsInt rowidx, start, end; + HighsInt rowidx; + size_t start, end; bool integral_cols = false; assert(num_col == 0); // Define the scattered value vector, index vector and count @@ -713,7 +713,7 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, // check for integrality marker std::string marker = first_word(strline, end); - HighsInt end_marker = first_word_end(strline, end); + size_t end_marker = first_word_end(strline, end); if (marker == "'MARKER'") { marker = first_word(strline, end_marker); @@ -832,7 +832,13 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, "Row name \"%s\" in COLUMNS section is not defined: ignored\n", marker.c_str()); } else { - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "Coefficient for column \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } if (value) { parseName(marker); // rowidx set and num_nz incremented if (rowidx >= 0) { @@ -887,7 +893,13 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, marker.c_str()); continue; }; - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "Coefficient for column \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } if (value) { parseName(marker); // rowidx set and num_nz incremented if (rowidx >= 0) { @@ -982,8 +994,8 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, if (strline.size() == 0) continue; } - HighsInt begin = 0; - HighsInt end = 0; + size_t begin = 0; + size_t end = 0; std::string word; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, word); @@ -1004,7 +1016,7 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, HighsInt rowidx; std::string marker = first_word(strline, end); - HighsInt end_marker = first_word_end(strline, end); + size_t end_marker = first_word_end(strline, end); // here marker is the row name and end marks its end word = ""; @@ -1053,7 +1065,13 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, "ignored\n", marker.c_str()); } else { - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "RHS for row \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } addRhs(value, rowidx); } } @@ -1093,7 +1111,13 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, "ignored\n", marker.c_str()); } else { - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "RHS for row \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } addRhs(value, rowidx); } } @@ -1136,8 +1160,8 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, if (strline.size() == 0) continue; } - HighsInt begin = 0; - HighsInt end = 0; + size_t begin = 0; + size_t end = 0; std::string word; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, word); @@ -1248,10 +1272,10 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, } std::string bound_name = first_word(strline, end); - HighsInt end_bound_name = first_word_end(strline, end); + size_t end_bound_name = first_word_end(strline, end); std::string marker; - HighsInt end_marker; + size_t end_marker; if (colname2idx.find(bound_name) != colname2idx.end()) { // SIF format might not have the bound name, so skip // it here if we found the marker instead @@ -1327,7 +1351,13 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, marker.c_str()); return HMpsFF::Parsekey::kFail; } - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "Bound for column \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } if (is_integral) { assert(is_lb || is_ub || is_semi); // Must be LI, UI or SI, and value should be integer @@ -1409,7 +1439,7 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, if (strline.size() == 0) continue; } - HighsInt begin, end; + size_t begin, end; std::string word; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, word); @@ -1422,7 +1452,7 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, HighsInt rowidx; std::string marker = first_word(strline, end); - HighsInt end_marker = first_word_end(strline, end); + size_t end_marker = first_word_end(strline, end); // here marker is the row name and end marks its end word = ""; @@ -1455,14 +1485,20 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, "definition: ignored\n", marker.c_str()); } else { - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "Range for row \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } addRhs(value, rowidx); } } if (!is_end(strline, end)) { std::string marker = first_word(strline, end); - HighsInt end_marker = first_word_end(strline, end); + size_t end_marker = first_word_end(strline, end); // here marker is the row name and end marks its end word = ""; @@ -1495,7 +1531,13 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, "definition: ignored\n", marker.c_str()); } else { - double value = atof(word.c_str()); + bool is_nan = false; + double value = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "Range for row \"%s\" is NaN\n", marker.c_str()); + return HMpsFF::Parsekey::kFail; + } addRhs(value, rowidx); } } @@ -1529,8 +1571,8 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( std::string col_name; std::string row_name; std::string coeff_name; - HighsInt end_row_name; - HighsInt end_coeff_name; + size_t end_row_name; + size_t end_coeff_name; HighsInt colidx, rowidx; while (getline(file, strline)) { @@ -1549,8 +1591,8 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( if (strline.size() == 0) continue; } - HighsInt begin = 0; - HighsInt end = 0; + size_t begin = 0; + size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name); // start of new section? @@ -1590,7 +1632,15 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( rowidx = getColIdx(row_name); assert(rowidx >= 0 && rowidx < num_col); - double coeff = atof(coeff_name.c_str()); + bool is_nan = false; + double coeff = getValue(coeff_name, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser( + log_options, HighsLogType::kError, + "Hessian coefficient for entry \"%s\" in column \"%s\" is NaN\n", + row_name.c_str(), col_name.c_str()); + return HMpsFF::Parsekey::kFail; + } if (coeff) { if (qmatrix) { // QMATRIX has the whole Hessian, so store the entry if the @@ -1608,7 +1658,7 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( } end = end_coeff_name; // Don't read more if end of line reached - if (end == (HighsInt)strline.length()) break; + if (end == strline.length()) break; } } @@ -1631,8 +1681,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( std::string col_name; std::string row_name; std::string coeff_name; - HighsInt end_row_name; - HighsInt end_coeff_name; + size_t end_row_name; + size_t end_coeff_name; HighsInt rowidx; // index of quadratic row HighsInt qcolidx, qrowidx; // indices in quadratic coefs matrix @@ -1653,8 +1703,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( rowname.c_str(), section_name.c_str()); // read lines until start of new section while (getline(file, strline)) { - HighsInt begin = 0; - HighsInt end = 0; + size_t begin = 0; + size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name); // start of new section? @@ -1671,7 +1721,7 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( assert(rowidx < num_row); if (rowidx >= 0) qrows_entries.resize(num_row); - assert(rowidx == -1 || (HighsInt)qrows_entries.size() == num_row); + assert(rowidx == -1 || qrows_entries.size() == static_cast(num_row)); auto& qentries = (rowidx == -1 ? q_entries : qrows_entries[rowidx]); @@ -1691,8 +1741,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( if (strline.size() == 0) continue; } - HighsInt begin = 0; - HighsInt end = 0; + size_t begin = 0; + size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name); // start of new section? @@ -1732,7 +1782,15 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( qrowidx = getColIdx(row_name); assert(qrowidx >= 0 && qrowidx < num_col); - double coeff = atof(coeff_name.c_str()); + bool is_nan = false; + double coeff = getValue(coeff_name, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser( + log_options, HighsLogType::kError, + "Hessian coefficient for entry \"%s\" in column \"%s\" is NaN\n", + row_name.c_str(), col_name.c_str()); + return HMpsFF::Parsekey::kFail; + } if (coeff) { if (qcmatrix) { // QCMATRIX has the whole Hessian, so store the entry if the @@ -1746,7 +1804,7 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( } end = end_coeff_name; // Don't read more if end of line reached - if (end == (HighsInt)strline.length()) break; + if (end == strline.length()) break; } } @@ -1755,7 +1813,7 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, std::istream& file) { - HighsInt end = 0; + size_t end = 0; // first argument should be cone name std::string conename = first_word(section_args, end); @@ -1837,7 +1895,7 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, if (strline.size() == 0) continue; } - HighsInt begin; + size_t begin; std::string colname; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, colname); @@ -1880,7 +1938,7 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, if (strline.size() == 0) continue; } - HighsInt begin, end; + size_t begin, end; std::string word; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, word); @@ -1953,7 +2011,13 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, double weight = 0.0; if (!is_end(strline, end)) { word = first_word(strline, end); - weight = atof(word.c_str()); + bool is_nan = false; + weight = getValue(word, is_nan); // atof(word.c_str()); + if (is_nan) { + highsLogUser(log_options, HighsLogType::kError, + "Weight for column \"%s\" is NaN\n", colname.c_str()); + return HMpsFF::Parsekey::kFail; + } } sos_entries.back().push_back(std::make_pair(colidx, weight)); @@ -1968,4 +2032,29 @@ bool HMpsFF::allZeroed(const std::vector& value) { return true; } +double HMpsFF::getValue(const std::string& word, bool& is_nan, + const HighsInt id) const { + // Lambda to replace any d or D by E + auto dD2e = [&](std::string& word) { + size_t ix = word.find("D"); + if (ix != std::string::npos) { + word.replace(ix, 1, "E"); + } else { + ix = word.find("d"); + if (ix != std::string::npos) word.replace(ix, 1, "E"); + } + }; + + std::string local_word = word; + dD2e(local_word); + const double value = atof(local_word.c_str()); + is_nan = false; + // printf("value(%d) = %g\n", int(id), value); + // if (std::isnan(value)) return true; + // // atof('nan') yields 0 with some Windows compilers, so try a string + // // comparison + // std::string lower_word = word; + // if (str_tolower(lower_word) == "nan") return true; + return value; +} } // namespace free_format_parser diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index 6cff8f0595..6c17e0c7ef 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -192,8 +192,8 @@ class HMpsFF { FreeFormatParserReturnCode parse(const HighsLogOptions& log_options, const std::string& filename); // Checks first word of strline and wraps it by it_begin and it_end - HMpsFF::Parsekey checkFirstWord(std::string& strline, HighsInt& start, - HighsInt& end, std::string& word) const; + HMpsFF::Parsekey checkFirstWord(std::string& strline, size_t& start, + size_t& end, std::string& word) const; // Get index of column from column name, possibly adding new column // if no index is found @@ -227,6 +227,8 @@ class HMpsFF { bool cannotParseSection(const HighsLogOptions& log_options, const HMpsFF::Parsekey keyword); bool allZeroed(const std::vector& value); + double getValue(const std::string& word, bool& is_nan, + const HighsInt id = -1) const; }; } // namespace free_format_parser diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 78d7761a9a..8d7911e02f 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -20,10 +20,15 @@ #include "lp_data/HighsLp.h" #include "lp_data/HighsOptions.h" -void highsLogHeader(const HighsLogOptions& log_options) { - highsLogUser(log_options, HighsLogType::kInfo, "Running HiGHS %d.%d.%d: %s\n", - (int)HIGHS_VERSION_MAJOR, (int)HIGHS_VERSION_MINOR, - (int)HIGHS_VERSION_PATCH, kHighsCopyrightStatement.c_str()); +void highsLogHeader(const HighsLogOptions& log_options, + const bool log_githash) { + const std::string githash_string(HIGHS_GITHASH); + const std::string githash_text = + log_githash ? " (git hash: " + githash_string + ")" : ""; + highsLogUser(log_options, HighsLogType::kInfo, + "Running HiGHS %d.%d.%d%s: %s\n", (int)HIGHS_VERSION_MAJOR, + (int)HIGHS_VERSION_MINOR, (int)HIGHS_VERSION_PATCH, + githash_text.c_str(), kHighsCopyrightStatement.c_str()); } std::array highsDoubleToString(const double val, @@ -115,6 +120,7 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, 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_end(argptr); va_start(argptr, format); } // Write to stdout unless log file stream is stdout @@ -183,6 +189,7 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, // Write to log file stream vfprintf(log_options_.log_stream, format, argptr); if (flush_streams) fflush(log_options_.log_stream); + va_end(argptr); va_start(argptr, format); } // Write to stdout unless log file stream is stdout @@ -212,6 +219,16 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, va_end(argptr); } +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s) { + if (file == nullptr) return; + if (file == stdout) { + highsLogUser(log_options_, HighsLogType::kInfo, "%s", s.c_str()); + } else { + fprintf(file, "%s", s.c_str()); + } +} + void highsReportDevInfo(const HighsLogOptions* log_options, const std::string line) { if (log_options) { diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index e0b79e41ac..b63d47141f 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -45,19 +45,29 @@ struct HighsLogOptions { bool* output_flag; bool* log_to_console; HighsInt* log_dev_level; - void (*user_log_callback)(HighsLogType, const char*, void*) = nullptr; - void* user_log_callback_data = nullptr; - void (*user_callback)(const int, const char*, const HighsCallbackDataOut*, - HighsCallbackDataIn*, void*) = nullptr; - void* user_callback_data = nullptr; - bool user_callback_active = false; + void (*user_log_callback)(HighsLogType, const char*, void*); + void* user_log_callback_data; + std::function + user_callback; + void* user_callback_data; + bool user_callback_active; void clear(); + HighsLogOptions() + : log_stream(nullptr), + output_flag(nullptr), + log_to_console(nullptr), + log_dev_level(nullptr), + user_log_callback(nullptr), + user_log_callback_data(nullptr), + user_callback_data(nullptr), + user_callback_active(false){}; }; /** * @brief Write the HiGHS version and copyright statement */ -void highsLogHeader(const HighsLogOptions& log_options); +void highsLogHeader(const HighsLogOptions& log_options, const bool log_githash); /** * @brief Convert a double number to a string using given tolerance @@ -78,6 +88,14 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...); +/** + * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is + * used + */ +// Printing format: must contain exactly one "\n" at end of format +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s); + /** * @brief For development logging when true log_options may not be available - * indicated by null pointer diff --git a/src/io/LoadOptions.cpp b/src/io/LoadOptions.cpp index 521adda8a5..249a2a9909 100644 --- a/src/io/LoadOptions.cpp +++ b/src/io/LoadOptions.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -34,8 +34,8 @@ HighsLoadOptionsStatus loadOptionsFromFile( line_count++; if (line.size() == 0 || line[0] == '#') continue; - HighsInt equals = line.find_first_of("="); - if (equals < 0 || equals >= (HighsInt)line.size() - 1) { + size_t equals = line.find_first_of("="); + if (equals == std::string::npos || equals + 1 >= line.size()) { highsLogUser(report_log_options, HighsLogType::kError, "Error on line %" HIGHSINT_FORMAT " of options file.\n", line_count); @@ -46,12 +46,16 @@ HighsLoadOptionsStatus loadOptionsFromFile( trim(option, non_chars); trim(value, non_chars); if (setLocalOptionValue(report_log_options, option, options.log_options, - options.records, value) != OptionStatus::kOk) + options.records, value) != OptionStatus::kOk) { + highsLogUser(report_log_options, HighsLogType::kError, + "Cannot read value \"%s\" for option \"%s\"\n", + value.c_str(), option.c_str()); return HighsLoadOptionsStatus::kError; + } } } else { highsLogUser(report_log_options, HighsLogType::kError, - "Options file not found.\n"); + "Options file not found\n"); return HighsLoadOptionsStatus::kError; } diff --git a/src/io/LoadOptions.h b/src/io/LoadOptions.h index b6854c418a..fa46ee3157 100644 --- a/src/io/LoadOptions.h +++ b/src/io/LoadOptions.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/ipm/IpxSolution.h b/src/ipm/IpxSolution.h index d5579bf7f0..4b1c178670 100644 --- a/src/ipm/IpxSolution.h +++ b/src/ipm/IpxSolution.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/ipm/IpxWrapper.cpp b/src/ipm/IpxWrapper.cpp index a45eee48db..eae2aff189 100644 --- a/src/ipm/IpxWrapper.cpp +++ b/src/ipm/IpxWrapper.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -10,7 +10,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file ipm/IpxWrapper.cpp * @brief - * @author Julian Hall, Ivet Galabova, Qi Huangfu and Michael Feldmeier + * @author Julian Hall, Ivet Galabova and Michael Feldmeier */ #include "ipm/IpxWrapper.h" @@ -22,20 +22,17 @@ using std::min; HighsStatus solveLpIpx(HighsLpSolverObject& solver_object) { - return solveLpIpx(solver_object.options_, solver_object.timer_, solver_object.lp_, - solver_object.basis_, solver_object.solution_, - solver_object.model_status_, solver_object.highs_info_, - solver_object.callback_); + return solveLpIpx(solver_object.options_, solver_object.timer_, + solver_object.lp_, solver_object.basis_, + solver_object.solution_, solver_object.model_status_, + solver_object.highs_info_, solver_object.callback_); } -HighsStatus solveLpIpx(const HighsOptions& options, - HighsTimer& timer, - const HighsLp& lp, - HighsBasis& highs_basis, - HighsSolution& highs_solution, - HighsModelStatus& model_status, - HighsInfo& highs_info, - HighsCallback& callback) { +HighsStatus solveLpIpx(const HighsOptions& options, HighsTimer& timer, + const HighsLp& lp, HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsModelStatus& model_status, HighsInfo& highs_info, + HighsCallback& callback) { // Use IPX to try to solve the LP // // Can return HighsModelStatus (HighsStatus) values: @@ -90,6 +87,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, } else if (options.log_dev_level == kHighsLogDevLevelVerbose) { parameters.debug = 4; } + parameters.highs_logging = true; + parameters.log_options = &options.log_options; // Just test feasibility and optimality tolerances for now // ToDo Set more parameters // @@ -108,20 +107,27 @@ HighsStatus solveLpIpx(const HighsOptions& options, } else if (options.ipx_dualize_strategy == kIpxDualizeStrategyFilippo) { parameters.dualize = -2; } else { - assert(111==222); + assert(111 == 222); } - + parameters.ipm_feasibility_tol = min(options.primal_feasibility_tolerance, options.dual_feasibility_tolerance); parameters.ipm_optimality_tol = options.ipm_optimality_tolerance; parameters.start_crossover_tol = options.start_crossover_tolerance; - parameters.analyse_basis_data = kHighsAnalysisLevelNlaData & options.highs_analysis_level; + parameters.analyse_basis_data = + kHighsAnalysisLevelNlaData & options.highs_analysis_level; // Determine the run time allowed for IPX parameters.time_limit = options.time_limit - timer.readRunHighsClock(); - parameters.ipm_maxiter = options.ipm_iteration_limit - highs_info.ipm_iteration_count; + parameters.ipm_maxiter = + options.ipm_iteration_limit - highs_info.ipm_iteration_count; // Determine if crossover is to be run or not - if (options.run_crossover == kHighsOnString) { + // + // When doing analytic centring calculations, crossover must not be + // run + if (options.run_centring) { + parameters.run_crossover = 0; + } else if (options.run_crossover == kHighsOnString) { parameters.run_crossover = 1; } else if (options.run_crossover == kHighsOffString) { parameters.run_crossover = 0; @@ -136,6 +142,10 @@ HighsStatus solveLpIpx(const HighsOptions& options, parameters.start_crossover_tol = -1; } + parameters.run_centring = options.run_centring ? 1 : 0; + parameters.max_centring_steps = options.max_centring_steps; + parameters.centring_ratio_tolerance = options.centring_ratio_tolerance; + // Set the internal IPX parameters lps.SetParameters(parameters); @@ -153,9 +163,9 @@ HighsStatus solveLpIpx(const HighsOptions& options, " columns and %" HIGHSINT_FORMAT " nonzeros\n", num_row, num_col, Ap[num_col]); - 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()); + 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) { model_status = HighsModelStatus::kSolveError; @@ -165,7 +175,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, // Use IPX to solve the LP! ipx::Int solve_status = lps.Solve(); - const bool report_solve_data = kHighsAnalysisLevelSolverSummaryData & options.highs_analysis_level; + const bool report_solve_data = + kHighsAnalysisLevelSolverSummaryData & options.highs_analysis_level; // Get solver and solution information. // Struct ipx_info defined in ipx/ipx_info.h const ipx::Info ipx_info = lps.GetInfo(); @@ -197,10 +208,11 @@ HighsStatus solveLpIpx(const HighsOptions& options, } // Should only reach here if Solve() returned IPX_STATUS_solved or // IPX_STATUS_stopped - if (ipxStatusError(solve_status != IPX_STATUS_solved && - solve_status != IPX_STATUS_stopped, - options, "solve_status should be solved or stopped here but value is", - (int)solve_status)) + if (ipxStatusError( + solve_status != IPX_STATUS_solved && + solve_status != IPX_STATUS_stopped, + options, "solve_status should be solved or stopped here but value is", + (int)solve_status)) return HighsStatus::kError; // Only error returns so far @@ -212,8 +224,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, // know whether to worry about dual infeasibilities. const HighsModelStatus local_model_status = HighsModelStatus::kUnknown; getHighsNonVertexSolution(options, lp, num_col, num_row, rhs, - constraint_type, lps, - local_model_status, highs_solution); + constraint_type, lps, local_model_status, + highs_solution); // // Look at the reason why IPX stopped // @@ -290,8 +302,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, model_status = HighsModelStatus::kUnboundedOrInfeasible; } getHighsNonVertexSolution(options, lp, num_col, num_row, rhs, - constraint_type, lps, - model_status, highs_solution); + constraint_type, lps, model_status, + highs_solution); return HighsStatus::kOk; } @@ -306,8 +318,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, // Should only reach here if crossover is not run, optimal or imprecise if (ipxStatusError(ipx_info.status_crossover != IPX_STATUS_not_run && - ipx_info.status_crossover != IPX_STATUS_optimal && - ipx_info.status_crossover != IPX_STATUS_imprecise, + ipx_info.status_crossover != IPX_STATUS_optimal && + ipx_info.status_crossover != IPX_STATUS_imprecise, options, "crossover status should be not run, optimal or imprecise " "but value is", @@ -319,8 +331,8 @@ HighsStatus solveLpIpx(const HighsOptions& options, ipx_info.status_crossover != IPX_STATUS_not_run; // Both crossover and IPM can be imprecise const bool imprecise_solution = - ipx_info.status_crossover == IPX_STATUS_imprecise || - ipx_info.status_ipm == IPX_STATUS_imprecise; + ipx_info.status_crossover == IPX_STATUS_imprecise || + ipx_info.status_ipm == IPX_STATUS_imprecise; if (have_basic_solution) { IpxSolution ipx_solution; ipx_solution.num_col = num_col; @@ -331,22 +343,24 @@ 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.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()); + 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", - (int)errflag); + "IPX crossover getting basic solution: flag = %d\n", + (int)errflag); return HighsStatus::kError; } // Convert the IPX basic solution to a HiGHS basic solution - HighsStatus status = ipxBasicSolutionToHighsBasicSolution(options.log_options, lp, rhs, - constraint_type, ipx_solution, - highs_basis, highs_solution); + HighsStatus status = ipxBasicSolutionToHighsBasicSolution( + options.log_options, lp, rhs, constraint_type, ipx_solution, + highs_basis, highs_solution); if (status != HighsStatus::kOk) { - highsLogUser(options.log_options, HighsLogType::kError, - "Failed to convert IPX basic solution to Highs basic solution\n"); + highsLogUser( + options.log_options, HighsLogType::kError, + "Failed to convert IPX basic solution to Highs basic solution\n"); return HighsStatus::kError; } @@ -354,13 +368,16 @@ HighsStatus solveLpIpx(const HighsOptions& options, // No basic solution, so get a non-vertex HiGHS solution. This // needs the model status to know whether to worry about dual // infeasibilities. - const HighsModelStatus local_model_status = imprecise_solution ? HighsModelStatus::kUnknown : HighsModelStatus::kOptimal; + const HighsModelStatus local_model_status = + imprecise_solution ? HighsModelStatus::kUnknown + : HighsModelStatus::kOptimal; getHighsNonVertexSolution(options, lp, num_col, num_row, rhs, - constraint_type, lps, - local_model_status, highs_solution); + constraint_type, lps, local_model_status, + highs_solution); assert(!highs_basis.valid); } - highs_info.basis_validity = highs_basis.valid ? kBasisValidityValid : kBasisValidityInvalid; + highs_info.basis_validity = + highs_basis.valid ? kBasisValidityValid : kBasisValidityInvalid; HighsStatus return_status; if (imprecise_solution) { model_status = HighsModelStatus::kUnknown; @@ -398,7 +415,8 @@ void fillInIpxData(const HighsLp& lp, ipx::Int& num_col, ipx::Int& num_row, if (lp.row_lower_[row] < lp.row_upper_[row] && lp.row_lower_[row] > -kHighsInf && lp.row_upper_[row] < kHighsInf) general_bounded_rows.push_back(row); - else if (lp.row_lower_[row] <= -kHighsInf && lp.row_upper_[row] >= kHighsInf) + else if (lp.row_lower_[row] <= -kHighsInf && + lp.row_upper_[row] >= kHighsInf) free_rows.push_back(row); const HighsInt num_slack = general_bounded_rows.size(); @@ -449,7 +467,8 @@ void fillInIpxData(const HighsLp& lp, ipx::Int& num_col, ipx::Int& num_row, std::vector sizes(num_col, 0); for (HighsInt col = 0; col < lp.num_col_; col++) - for (HighsInt k = lp.a_matrix_.start_[col]; k < lp.a_matrix_.start_[col + 1]; k++) { + for (HighsInt k = lp.a_matrix_.start_[col]; + k < lp.a_matrix_.start_[col + 1]; k++) { HighsInt row = lp.a_matrix_.index_[k]; if (lp.row_lower_[row] > -kHighsInf || lp.row_upper_[row] < kHighsInf) sizes[col]++; @@ -565,15 +584,15 @@ HighsStatus reportIpxIpmCrossoverStatus(const HighsOptions& options, // Warn if method not run is IPM or method not run is crossover // and run_crossover option is "on" highsLogUser(options.log_options, HighsLogType::kWarning, - "Ipx: %s not run\n", method_name.c_str()); + "Ipx: %s not run\n", method_name.c_str()); return HighsStatus::kWarning; } // OK if method not run is crossover and run_crossover option is // not "on" return HighsStatus::kOk; } else if (status == IPX_STATUS_optimal) { - highsLogUser(options.log_options, HighsLogType::kInfo, - "Ipx: %s optimal\n", method_name.c_str()); + highsLogUser(options.log_options, HighsLogType::kInfo, "Ipx: %s optimal\n", + method_name.c_str()); return HighsStatus::kOk; } else if (status == IPX_STATUS_imprecise) { highsLogUser(options.log_options, HighsLogType::kWarning, @@ -588,7 +607,7 @@ HighsStatus reportIpxIpmCrossoverStatus(const HighsOptions& options, "Ipx: %s dual infeasible\n", method_name.c_str()); return HighsStatus::kWarning; } else if (status == IPX_STATUS_user_interrupt) { - highsLogUser(options.log_options, HighsLogType::kWarning, + highsLogUser(options.log_options, HighsLogType::kWarning, "Ipx: %s user interrupt\n", method_name.c_str()); return HighsStatus::kOk; } else if (status == IPX_STATUS_time_limit) { @@ -826,13 +845,12 @@ void reportIpmNoProgress(const HighsOptions& options, ipx_info.abs_dresidual); } -void getHighsNonVertexSolution(const HighsOptions& options, - const HighsLp& lp, const ipx::Int num_col, - const ipx::Int num_row, +void getHighsNonVertexSolution(const HighsOptions& options, const HighsLp& lp, + const ipx::Int num_col, const ipx::Int num_row, const std::vector& rhs, const std::vector& constraint_type, const ipx::LpSolver& lps, - const HighsModelStatus model_status, + const HighsModelStatus model_status, HighsSolution& highs_solution) { // Get the interior solution (available if IPM was started). // GetInteriorSolution() returns the final IPM iterate, regardless if the @@ -846,202 +864,213 @@ void getHighsNonVertexSolution(const HighsOptions& options, std::vector slack(num_row); std::vector y(num_row); - lps.GetInteriorSolution(x.data(), xl.data(), xu.data(), slack.data(), y.data(), zl.data(), - zu.data()); + 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, - model_status, highs_solution); + num_row, x, slack, y, zl, zu, model_status, + highs_solution); } -void reportSolveData(const HighsLogOptions& log_options, const ipx::Info& ipx_info) { +void reportSolveData(const HighsLogOptions& log_options, + const ipx::Info& ipx_info) { highsLogDev(log_options, HighsLogType::kInfo, "\nIPX Solve data\n"); - highsLogDev(log_options, HighsLogType::kInfo, - " IPX status = %4d\n", (int)ipx_info.status); - highsLogDev(log_options, HighsLogType::kInfo, - " IPM status = %4d\n", (int)ipx_info.status_ipm); - highsLogDev(log_options, HighsLogType::kInfo, - " Crossover status = %4d\n", (int)ipx_info.status_crossover); - highsLogDev(log_options, HighsLogType::kInfo, - " IPX errflag = %4d\n\n", (int)ipx_info.errflag); + highsLogDev(log_options, HighsLogType::kInfo, " IPX status = %4d\n", + (int)ipx_info.status); + highsLogDev(log_options, HighsLogType::kInfo, " IPM status = %4d\n", + (int)ipx_info.status_ipm); + highsLogDev(log_options, HighsLogType::kInfo, " Crossover status = %4d\n", + (int)ipx_info.status_crossover); + highsLogDev(log_options, HighsLogType::kInfo, + " IPX errflag = %4d\n\n", (int)ipx_info.errflag); - highsLogDev(log_options, HighsLogType::kInfo, - " LP variables = %8d\n", (int)ipx_info.num_var); - highsLogDev(log_options, HighsLogType::kInfo, - " LP constraints = %8d\n", (int)ipx_info.num_constr); - highsLogDev(log_options, HighsLogType::kInfo, - " LP entries = %8d\n\n", (int)ipx_info.num_entries); + highsLogDev(log_options, HighsLogType::kInfo, " LP variables = %8d\n", + (int)ipx_info.num_var); + highsLogDev(log_options, HighsLogType::kInfo, " LP constraints = %8d\n", + (int)ipx_info.num_constr); + highsLogDev(log_options, HighsLogType::kInfo, " LP entries = %8d\n\n", + (int)ipx_info.num_entries); - highsLogDev(log_options, HighsLogType::kInfo, - " Solver columns = %8d\n", (int)ipx_info.num_cols_solver); - highsLogDev(log_options, HighsLogType::kInfo, - " Solver rows = %8d\n", (int)ipx_info.num_rows_solver); - highsLogDev(log_options, HighsLogType::kInfo, - " Solver entries = %8d\n\n", (int)ipx_info.num_entries_solver); + highsLogDev(log_options, HighsLogType::kInfo, " Solver columns = %8d\n", + (int)ipx_info.num_cols_solver); + highsLogDev(log_options, HighsLogType::kInfo, " Solver rows = %8d\n", + (int)ipx_info.num_rows_solver); + highsLogDev(log_options, HighsLogType::kInfo, " Solver entries = %8d\n\n", + (int)ipx_info.num_entries_solver); + highsLogDev(log_options, HighsLogType::kInfo, " Dualized = %d\n", + (int)ipx_info.dualized); highsLogDev(log_options, HighsLogType::kInfo, - " Dualized = %d\n", (int)ipx_info.dualized); - highsLogDev(log_options, HighsLogType::kInfo, - " Number of dense columns detected = %d\n\n", (int)ipx_info.dense_cols); + " Number of dense columns detected = %d\n\n", + (int)ipx_info.dense_cols); - highsLogDev(log_options, HighsLogType::kInfo, - " Dependent rows = %d\n", (int)ipx_info.dependent_rows); - highsLogDev(log_options, HighsLogType::kInfo, - " Dependent cols = %d\n", (int)ipx_info.dependent_cols); - highsLogDev(log_options, HighsLogType::kInfo, - " Inconsistent rows = %d\n", (int)ipx_info.rows_inconsistent); - highsLogDev(log_options, HighsLogType::kInfo, - " Inconsistent cols = %d\n", (int)ipx_info.cols_inconsistent); - highsLogDev(log_options, HighsLogType::kInfo, - " Primal dropped = %d\n", (int)ipx_info.primal_dropped); - highsLogDev(log_options, HighsLogType::kInfo, - " Dual dropped = %d\n\n", (int)ipx_info.dual_dropped); + highsLogDev(log_options, HighsLogType::kInfo, " Dependent rows = %d\n", + (int)ipx_info.dependent_rows); + highsLogDev(log_options, HighsLogType::kInfo, " Dependent cols = %d\n", + (int)ipx_info.dependent_cols); + highsLogDev(log_options, HighsLogType::kInfo, " Inconsistent rows = %d\n", + (int)ipx_info.rows_inconsistent); + highsLogDev(log_options, HighsLogType::kInfo, " Inconsistent cols = %d\n", + (int)ipx_info.cols_inconsistent); + highsLogDev(log_options, HighsLogType::kInfo, " Primal dropped = %d\n", + (int)ipx_info.primal_dropped); + highsLogDev(log_options, HighsLogType::kInfo, + " Dual dropped = %d\n\n", (int)ipx_info.dual_dropped); highsLogDev(log_options, HighsLogType::kInfo, - " |Absolute primal residual| = %11.4g\n", ipx_info.abs_presidual); + " |Absolute primal residual| = %11.4g\n", + ipx_info.abs_presidual); highsLogDev(log_options, HighsLogType::kInfo, - " |Absolute dual residual| = %11.4g\n", ipx_info.abs_dresidual); + " |Absolute dual residual| = %11.4g\n", + ipx_info.abs_dresidual); highsLogDev(log_options, HighsLogType::kInfo, - " |Relative primal residual| = %11.4g\n", ipx_info.rel_presidual); + " |Relative primal residual| = %11.4g\n", + ipx_info.rel_presidual); highsLogDev(log_options, HighsLogType::kInfo, - " |Relative dual residual| = %11.4g\n\n", ipx_info.rel_dresidual); + " |Relative dual residual| = %11.4g\n\n", + ipx_info.rel_dresidual); highsLogDev(log_options, HighsLogType::kInfo, - " Primal objective value = %11.4g\n", ipx_info.pobjval); + " Primal objective value = %11.4g\n", ipx_info.pobjval); highsLogDev(log_options, HighsLogType::kInfo, - " Dual objective value = %11.4g\n", ipx_info.dobjval); + " Dual objective value = %11.4g\n", ipx_info.dobjval); highsLogDev(log_options, HighsLogType::kInfo, - " Relative objective gap = %11.4g\n", ipx_info.rel_objgap); + " Relative objective gap = %11.4g\n", ipx_info.rel_objgap); highsLogDev(log_options, HighsLogType::kInfo, - " Complementarity = %11.4g\n\n", ipx_info.complementarity); + " Complementarity = %11.4g\n\n", + ipx_info.complementarity); - highsLogDev(log_options, HighsLogType::kInfo, - " |x| = %11.4g\n", ipx_info.normx); - highsLogDev(log_options, HighsLogType::kInfo, - " |y| = %11.4g\n", ipx_info.normy); - highsLogDev(log_options, HighsLogType::kInfo, - " |z| = %11.4g\n\n", ipx_info.normz); + highsLogDev(log_options, HighsLogType::kInfo, " |x| = %11.4g\n", + ipx_info.normx); + highsLogDev(log_options, HighsLogType::kInfo, " |y| = %11.4g\n", + ipx_info.normy); + highsLogDev(log_options, HighsLogType::kInfo, " |z| = %11.4g\n\n", + ipx_info.normz); highsLogDev(log_options, HighsLogType::kInfo, - " Objective value = %11.4g\n", ipx_info.objval); - highsLogDev(log_options, HighsLogType::kInfo, - " Primal infeasiblility = %11.4g\n", ipx_info.primal_infeas); + " Objective value = %11.4g\n", ipx_info.objval); highsLogDev(log_options, HighsLogType::kInfo, - " Dual infeasiblility = %11.4g\n\n", ipx_info.dual_infeas); - + " Primal infeasibility = %11.4g\n", ipx_info.primal_infeas); highsLogDev(log_options, HighsLogType::kInfo, - " IPM iter = %d\n", (int)ipx_info.iter); - highsLogDev(log_options, HighsLogType::kInfo, - " KKT iter 1 = %d\n", (int)ipx_info.kktiter1); - highsLogDev(log_options, HighsLogType::kInfo, - " KKT iter 2 = %d\n", (int)ipx_info.kktiter2); - highsLogDev(log_options, HighsLogType::kInfo, - " Basis repairs = %d\n", (int)ipx_info.basis_repairs); - highsLogDev(log_options, HighsLogType::kInfo, - " Updates start = %d\n", (int)ipx_info.updates_start); - highsLogDev(log_options, HighsLogType::kInfo, - " Updates ipm = %d\n", (int)ipx_info.updates_ipm); - highsLogDev(log_options, HighsLogType::kInfo, - " Updates crossover = %d\n\n", (int)ipx_info.updates_crossover); + " Dual infeasibility = %11.4g\n\n", ipx_info.dual_infeas); + + highsLogDev(log_options, HighsLogType::kInfo, " IPM iter = %d\n", + (int)ipx_info.iter); + highsLogDev(log_options, HighsLogType::kInfo, " KKT iter 1 = %d\n", + (int)ipx_info.kktiter1); + highsLogDev(log_options, HighsLogType::kInfo, " KKT iter 2 = %d\n", + (int)ipx_info.kktiter2); + highsLogDev(log_options, HighsLogType::kInfo, " Basis repairs = %d\n", + (int)ipx_info.basis_repairs); + highsLogDev(log_options, HighsLogType::kInfo, " Updates start = %d\n", + (int)ipx_info.updates_start); + highsLogDev(log_options, HighsLogType::kInfo, " Updates ipm = %d\n", + (int)ipx_info.updates_ipm); + highsLogDev(log_options, HighsLogType::kInfo, + " Updates crossover = %d\n\n", + (int)ipx_info.updates_crossover); highsLogDev(log_options, HighsLogType::kInfo, - " Time total = %8.2f\n\n", ipx_info.time_total); + " Time total = %8.2f\n\n", ipx_info.time_total); double sum_time = 0; highsLogDev(log_options, HighsLogType::kInfo, - " Time IPM 1 = %8.2f\n", ipx_info.time_ipm1); + " Time IPM 1 = %8.2f\n", ipx_info.time_ipm1); sum_time += ipx_info.time_ipm1; highsLogDev(log_options, HighsLogType::kInfo, - " Time IPM 2 = %8.2f\n", ipx_info.time_ipm2); + " Time IPM 2 = %8.2f\n", ipx_info.time_ipm2); sum_time += ipx_info.time_ipm2; highsLogDev(log_options, HighsLogType::kInfo, - " Time starting basis = %8.2f\n", ipx_info.time_starting_basis); + " Time starting basis = %8.2f\n", + ipx_info.time_starting_basis); sum_time += ipx_info.time_starting_basis; highsLogDev(log_options, HighsLogType::kInfo, - " Time crossover = %8.2f\n", ipx_info.time_crossover); + " Time crossover = %8.2f\n", ipx_info.time_crossover); highsLogDev(log_options, HighsLogType::kInfo, - " Sum = %8.2f\n\n", sum_time); + " Sum = %8.2f\n\n", sum_time); sum_time = 0; highsLogDev(log_options, HighsLogType::kInfo, - " Time kkt_factorize = %8.2f\n", ipx_info.time_kkt_factorize); + " Time kkt_factorize = %8.2f\n", ipx_info.time_kkt_factorize); sum_time += ipx_info.time_kkt_factorize; highsLogDev(log_options, HighsLogType::kInfo, - " Time kkt_solve = %8.2f\n", ipx_info.time_kkt_solve); + " Time kkt_solve = %8.2f\n", ipx_info.time_kkt_solve); sum_time += ipx_info.time_kkt_solve; highsLogDev(log_options, HighsLogType::kInfo, - " Sum = %8.2f\n\n", sum_time); + " Sum = %8.2f\n\n", sum_time); sum_time = 0; highsLogDev(log_options, HighsLogType::kInfo, - " Time maxvol = %8.2f\n", ipx_info.time_maxvol); + " Time maxvol = %8.2f\n", ipx_info.time_maxvol); sum_time += ipx_info.time_maxvol; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr1 = %8.2f\n", ipx_info.time_cr1); + " Time cr1 = %8.2f\n", ipx_info.time_cr1); sum_time += ipx_info.time_cr1; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr2 = %8.2f\n", ipx_info.time_cr2); + " Time cr2 = %8.2f\n", ipx_info.time_cr2); sum_time += ipx_info.time_cr2; highsLogDev(log_options, HighsLogType::kInfo, - " Sum = %8.2f\n\n", sum_time); + " Sum = %8.2f\n\n", sum_time); sum_time = 0; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr1_AAt = %8.2f\n", ipx_info.time_cr1_AAt); + " Time cr1_AAt = %8.2f\n", ipx_info.time_cr1_AAt); sum_time += ipx_info.time_cr1_AAt; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr1_pre = %8.2f\n", ipx_info.time_cr1_pre); + " Time cr1_pre = %8.2f\n", ipx_info.time_cr1_pre); sum_time += ipx_info.time_cr1_pre; highsLogDev(log_options, HighsLogType::kInfo, - " Sum cr1 = %8.2f\n\n", sum_time); + " Sum cr1 = %8.2f\n\n", sum_time); sum_time = 0; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr2_NNt = %8.2f\n", ipx_info.time_cr2_NNt); + " Time cr2_NNt = %8.2f\n", ipx_info.time_cr2_NNt); sum_time += ipx_info.time_cr2_NNt; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr2_B = %8.2f\n", ipx_info.time_cr2_B); + " Time cr2_B = %8.2f\n", ipx_info.time_cr2_B); sum_time += ipx_info.time_cr2_B; highsLogDev(log_options, HighsLogType::kInfo, - " Time cr2_Bt = %8.2f\n", ipx_info.time_cr2_Bt); + " Time cr2_Bt = %8.2f\n", ipx_info.time_cr2_Bt); sum_time += ipx_info.time_cr2_Bt; highsLogDev(log_options, HighsLogType::kInfo, - " Sum cr2 = %8.2f\n\n", sum_time); + " Sum cr2 = %8.2f\n\n", sum_time); highsLogDev(log_options, HighsLogType::kInfo, - " Proportion of sparse FTRAN = %11.4g\n", ipx_info.ftran_sparse); + " Proportion of sparse FTRAN = %11.4g\n", + ipx_info.ftran_sparse); highsLogDev(log_options, HighsLogType::kInfo, - " Proportion of sparse BTRAN = %11.4g\n\n", ipx_info.btran_sparse); + " Proportion of sparse BTRAN = %11.4g\n\n", + ipx_info.btran_sparse); highsLogDev(log_options, HighsLogType::kInfo, - " Time FTRAN = %8.2f\n", ipx_info.time_ftran); + " Time FTRAN = %8.2f\n", ipx_info.time_ftran); highsLogDev(log_options, HighsLogType::kInfo, - " Time BTRAN = %8.2f\n", ipx_info.time_btran); + " Time BTRAN = %8.2f\n", ipx_info.time_btran); highsLogDev(log_options, HighsLogType::kInfo, - " Time LU INVERT = %8.2f\n", ipx_info.time_lu_invert); + " Time LU INVERT = %8.2f\n", ipx_info.time_lu_invert); highsLogDev(log_options, HighsLogType::kInfo, - " Time LU UPDATE = %8.2f\n", ipx_info.time_lu_update); + " Time LU UPDATE = %8.2f\n", ipx_info.time_lu_update); highsLogDev(log_options, HighsLogType::kInfo, - " Mean fill-in = %11.4g\n", ipx_info.mean_fill); + " Mean fill-in = %11.4g\n", ipx_info.mean_fill); highsLogDev(log_options, HighsLogType::kInfo, - " Max fill-in = %11.4g\n", ipx_info.max_fill); + " Max fill-in = %11.4g\n", ipx_info.max_fill); highsLogDev(log_options, HighsLogType::kInfo, - " Time symb INVERT = %11.4g\n\n", ipx_info.time_symb_invert); - + " Time symb INVERT = %11.4g\n\n", ipx_info.time_symb_invert); + highsLogDev(log_options, HighsLogType::kInfo, - " Maxvol updates = %d\n", (int)ipx_info.maxvol_updates); + " Maxvol updates = %d\n", (int)ipx_info.maxvol_updates); highsLogDev(log_options, HighsLogType::kInfo, - " Maxvol skipped = %d\n", (int)ipx_info.maxvol_skipped); + " Maxvol skipped = %d\n", (int)ipx_info.maxvol_skipped); highsLogDev(log_options, HighsLogType::kInfo, - " Maxvol passes = %d\n", (int)ipx_info.maxvol_passes); + " Maxvol passes = %d\n", (int)ipx_info.maxvol_passes); highsLogDev(log_options, HighsLogType::kInfo, - " Tableau num nonzeros = %d\n", (int)ipx_info.tbl_nnz); + " Tableau num nonzeros = %d\n", (int)ipx_info.tbl_nnz); highsLogDev(log_options, HighsLogType::kInfo, - " Tbl max? = %11.4g\n", ipx_info.tbl_max); + " Tbl max? = %11.4g\n", ipx_info.tbl_max); highsLogDev(log_options, HighsLogType::kInfo, - " Frobnorm squared = %11.4g\n", ipx_info.frobnorm_squared); + " Frobnorm squared = %11.4g\n", ipx_info.frobnorm_squared); highsLogDev(log_options, HighsLogType::kInfo, - " Lambda max = %11.4g\n", ipx_info.lambdamax); + " Lambda max = %11.4g\n", ipx_info.lambdamax); highsLogDev(log_options, HighsLogType::kInfo, - " Volume increase = %11.4g\n\n", ipx_info.volume_increase); - + " Volume increase = %11.4g\n\n", + ipx_info.volume_increase); } diff --git a/src/ipm/IpxWrapper.h b/src/ipm/IpxWrapper.h index 403aab2f99..660165d82d 100644 --- a/src/ipm/IpxWrapper.h +++ b/src/ipm/IpxWrapper.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/ipm/basiclu/basiclu_factorize.c b/src/ipm/basiclu/basiclu_factorize.c index 1a29a9348d..36c4df0f79 100644 --- a/src/ipm/basiclu/basiclu_factorize.c +++ b/src/ipm/basiclu/basiclu_factorize.c @@ -26,7 +26,7 @@ lu_int basiclu_factorize { struct lu this; lu_int status; - double tic[2], elapsed, factor_cost; + double factor_cost; status = lu_load(&this, istore, xstore, Li, Lx, Ui, Ux, Wi, Wx); if (status != BASICLU_OK) diff --git a/src/ipm/basiclu/lu_build_factors.c b/src/ipm/basiclu/lu_build_factors.c index 11e6cbea5a..77d00e0d4e 100644 --- a/src/ipm/basiclu/lu_build_factors.c +++ b/src/ipm/basiclu/lu_build_factors.c @@ -29,7 +29,7 @@ pivotcol[0..pivotlen-1] are vectors of length m <= pivotlen < 2*m which may contain duplicate - indices. For each index its last occurance is its position in the pivot + indices. For each index its last occurrence is its position in the pivot sequence, see lu_garbage_perm(). pmap[0..m-1], @@ -170,7 +170,7 @@ lu_int lu_build_factors(struct lu *this) * Calculate memory and reallocate. The rowwise and columnwise storage of * L both need space for Lnz nonzeros + m terminators. The same for the * columnwise storage of U except that Uindex[0] = -1 is reserved to - * accomodate pointers to empty columns. In the rowwise storage of U each + * accommodate pointers to empty columns. In the rowwise storage of U each * row with nz nonzeros is padded by stretch*nz + pad elements. */ need = 2*(Lnz+m); diff --git a/src/ipm/basiclu/lu_garbage_perm.c b/src/ipm/basiclu/lu_garbage_perm.c index 3512a7b309..f8cec80cdb 100644 --- a/src/ipm/basiclu/lu_garbage_perm.c +++ b/src/ipm/basiclu/lu_garbage_perm.c @@ -15,8 +15,8 @@ * pivotcol[0..pivotlen-1], pivotrow[0..pivotlen-1], * * where pivotlen >= m. When pivotlen > m, then the arrays contain duplicates. - * For each index its last occurence in the arrays is its position in the pivot - * sequence and occurences before mark unused slots. + * For each index its last occurrence in the arrays is its position in the pivot + * sequence and occurrences before mark unused slots. * * This routine removes duplicates and compresses the indices such that * pivotlen == m. diff --git a/src/ipm/basiclu/lu_markowitz.c b/src/ipm/basiclu/lu_markowitz.c index 73490296a1..fbc0e342f4 100644 --- a/src/ipm/basiclu/lu_markowitz.c +++ b/src/ipm/basiclu/lu_markowitz.c @@ -54,7 +54,7 @@ lu_int lu_markowitz(struct lu *this) lu_int i, j, pos, where, inext, nz, pivot_row, pivot_col; lu_int nsearch, cheap, found, min_colnz, min_rownz; - double cmx, x, tol, tic[2]; + double cmx, x, tol; /* integers for Markowitz cost must be 64 bit to prevent overflow */ const int_least64_t M = m; diff --git a/src/ipm/basiclu/lu_pivot.c b/src/ipm/basiclu/lu_pivot.c index d8edc2a5f4..e6b94fc411 100644 --- a/src/ipm/basiclu/lu_pivot.c +++ b/src/ipm/basiclu/lu_pivot.c @@ -79,7 +79,6 @@ lu_int lu_pivot(struct lu *this) lu_int room, need, pos, j; lu_int status = BASICLU_OK; - double tic[2]; assert(nz_row >= 1); assert(nz_col >= 1); @@ -410,8 +409,8 @@ static lu_int lu_pivot_any(struct lu *this) /* * Cleanup: - * store pivot elemnt; - * remove pivot colum from column file, pivot row from row file; + * store pivot element; + * remove pivot column from column file, pivot row from row file; * remove pivot column/row from column/row counts */ colmax[pivot_col] = pivot; @@ -726,8 +725,8 @@ static lu_int lu_pivot_small(struct lu *this) /* * Cleanup: - * store pivot elemnt; - * remove pivot colum from column file, pivot row from row file; + * store pivot element; + * remove pivot column from column file, pivot row from row file; * remove pivot column/row from column/row counts */ colmax[pivot_col] = pivot; @@ -822,8 +821,8 @@ static lu_int lu_pivot_singleton_row(struct lu *this) /* * Cleanup: - * store pivot elemnt; - * remove pivot colum from column file, pivot row from row file; + * store pivot element; + * remove pivot column from column file, pivot row from row file; * remove pivot column/row from column/row counts */ colmax[pivot_col] = pivot; @@ -922,8 +921,8 @@ static lu_int lu_pivot_singleton_col(struct lu *this) /* * Cleanup: - * store pivot elemnt; - * remove pivot colum from column file, pivot row from row file; + * store pivot element; + * remove pivot column from column file, pivot row from row file; * remove pivot column/row from column/row counts */ colmax[pivot_col] = pivot; @@ -1186,8 +1185,8 @@ static lu_int lu_pivot_doubleton_col(struct lu *this) /* * Cleanup: - * store pivot elemnt; - * remove pivot colum from column file, pivot row from row file; + * store pivot element; + * remove pivot column from column file, pivot row from row file; * remove pivot column/row from column/row counts */ colmax[pivot_col] = pivot; diff --git a/src/ipm/basiclu/lu_setup_bump.c b/src/ipm/basiclu/lu_setup_bump.c index 8695853073..c6f06b7a2c 100644 --- a/src/ipm/basiclu/lu_setup_bump.c +++ b/src/ipm/basiclu/lu_setup_bump.c @@ -20,7 +20,7 @@ * columnwise and additionally the nonzero pattern rowwise: * * Wbegin[j] points to the first element in column j. - * Wend[j] points to one past the last element in colum j. + * Wend[j] points to one past the last element in column j. * Wbegin[m+i] points to the first element in row i. * Wend[m+i] points to one past the last element in row i. * diff --git a/src/ipm/basiclu/lu_singletons.c b/src/ipm/basiclu/lu_singletons.c index bba0973abd..8f3b4a5720 100644 --- a/src/ipm/basiclu/lu_singletons.c +++ b/src/ipm/basiclu/lu_singletons.c @@ -164,7 +164,6 @@ lu_int lu_singletons( double *Btx = this->Wvalue; lu_int i, j, pos, put, rank, Bnz, ok; - double tic[2]; /* -------------------------------- */ /* Check matrix and build transpose */ @@ -412,7 +411,7 @@ static lu_int singleton_cols /* * singleton_rows() * - * Analogeous singleton_cols except that for each singleton row the + * Analogous singleton_cols except that for each singleton row the * associated column is stored in L and divided by the pivot element. The * pivot element is stored in col_pivot. */ diff --git a/src/ipm/basiclu/lu_solve_for_update.c b/src/ipm/basiclu/lu_solve_for_update.c index 745481515c..70cf921861 100644 --- a/src/ipm/basiclu/lu_solve_for_update.c +++ b/src/ipm/basiclu/lu_solve_for_update.c @@ -45,7 +45,6 @@ lu_int lu_solve_for_update( const lu_int want_solution = p_nlhs && ilhs && xlhs; lu_int Lflops = 0, Uflops = 0, Rflops = 0; - double tic[2], elapsed; if (trans == 't' || trans == 'T') { diff --git a/src/ipm/basiclu/lu_solve_sparse.c b/src/ipm/basiclu/lu_solve_sparse.c index b3c56a28bf..a8551f9b86 100644 --- a/src/ipm/basiclu/lu_solve_sparse.c +++ b/src/ipm/basiclu/lu_solve_sparse.c @@ -43,7 +43,6 @@ void lu_solve_sparse( double x; lu_int Lflops = 0, Uflops = 0, Rflops = 0; - double tic[2], elapsed; if (trans == 't' || trans == 'T') { diff --git a/src/ipm/basiclu/lu_update.c b/src/ipm/basiclu/lu_update.c index 9cf88f784c..2452dc5b5a 100644 --- a/src/ipm/basiclu/lu_update.c +++ b/src/ipm/basiclu/lu_update.c @@ -180,7 +180,7 @@ static lu_int compress_packed(const lu_int m, lu_int *begin, lu_int *index, * When row i was mapped to column jlist[nswap], then it will be mapped to * column jlist[0]. * - * This requires to update pmap, qmap and the rowwise and columwise storage + * This requires to update pmap, qmap and the rowwise and columnwise storage * of U. It also changes the pivot elements. * * Note: This is the most ugly part of the update code and looks horribly @@ -466,7 +466,6 @@ lu_int lu_update(struct lu *this, double xtbl) lu_int have_diag, intersect, istriangular, nz_roweta, nz_spike; lu_int nreach, *col_reach, *row_reach; double spike_diag, newpiv, piverr; - double tic[2], elapsed; assert(nforrest < m); @@ -519,7 +518,7 @@ lu_int lu_update(struct lu *this, double xtbl) * or * newpiv = xtbl * oldpiv, (2) * - * where spike_diag is the diaognal element in the spike column + * where spike_diag is the diagonal element in the spike column * before the Forrest-Tomlin update and oldpiv was the pivot element * in column jpivot before inserting the spike. This routine uses * newpiv from (1) and reports the difference to (2) to the user @@ -773,7 +772,7 @@ lu_int lu_update(struct lu *this, double xtbl) * If U is permuted triangular, then permute to zero-free diagonal. * Set up row_reach[0..nreach-1] and col_reach[0..nreach-1] for * updating the permutations below. The column reach is the combined - * reach of the path nodes. The row reach is is given through pmap. + * reach of the path nodes. The row reach is given through pmap. */ if (istriangular) { @@ -880,7 +879,7 @@ lu_int lu_update(struct lu *this, double xtbl) assert(nz == Unz); } - /* compress W if used memory is shrinked suficiently */ + /* compress W if used memory is shrinked sufficiently */ used = Wbegin[m]; need = Unz + stretch*Unz + m*pad; if ((used-need) > this->compress_thres * used) diff --git a/src/ipm/ipx/basiclu_kernel.cc b/src/ipm/ipx/basiclu_kernel.cc index 858b97944f..d39908217d 100644 --- a/src/ipm/ipx/basiclu_kernel.cc +++ b/src/ipm/ipx/basiclu_kernel.cc @@ -7,7 +7,7 @@ namespace ipx { -// Wraps the BASICLU object into a struct with contructor/destructor for RAII. +// Wraps the BASICLU object into a struct with constructor/destructor for RAII. struct BasicLuHelper { static_assert(sizeof(Int) == sizeof(lu_int), "IPX integer type does not match BASICLU integer type"); diff --git a/src/ipm/ipx/basiclu_wrapper.cc b/src/ipm/ipx/basiclu_wrapper.cc index c34036d67c..e38686f88c 100644 --- a/src/ipm/ipx/basiclu_wrapper.cc +++ b/src/ipm/ipx/basiclu_wrapper.cc @@ -28,6 +28,7 @@ BasicLu::BasicLu(const Control& control, Int dim) : control_(control) { xstore_[BASICLU_MEMORYL] = 1; xstore_[BASICLU_MEMORYU] = 1; xstore_[BASICLU_MEMORYW] = 1; + fill_factor_ = 0.0; } Int BasicLu::_Factorize(const Int* Bbegin, const Int* Bend, const Int* Bi, @@ -132,7 +133,7 @@ void BasicLu::_SolveDense(const Vector& rhs, Vector& lhs, char trans) { void BasicLu::_FtranForUpdate(Int nzrhs, const Int* bi, const double* bx) { Int status; - for (Int ncall = 0; ; ncall++) { + for (;;) { status = basiclu_solve_for_update(istore_.data(), xstore_.data(), Li_.data(), Lx_.data(), Ui_.data(), Ux_.data(), @@ -153,7 +154,7 @@ void BasicLu::_FtranForUpdate(Int nzrhs, const Int* bi, const double* bx, Int status; Int nzlhs = 0; lhs.set_to_zero(); - for (Int ncall = 0; ; ncall++) { + for (;;) { status = basiclu_solve_for_update(istore_.data(), xstore_.data(), Li_.data(), Lx_.data(), Ui_.data(), Ux_.data(), @@ -173,7 +174,7 @@ void BasicLu::_FtranForUpdate(Int nzrhs, const Int* bi, const double* bx, void BasicLu::_BtranForUpdate(Int j) { Int status; - for (Int ncall = 0; ; ncall++) { + for (;;) { status = basiclu_solve_for_update(istore_.data(), xstore_.data(), Li_.data(), Lx_.data(), Ui_.data(), Ux_.data(), @@ -193,7 +194,7 @@ void BasicLu::_BtranForUpdate(Int j, IndexedVector& lhs) { Int status; Int nzlhs = 0; lhs.set_to_zero(); - for (Int ncall = 0; ; ncall++) { + for (;;) { status = basiclu_solve_for_update(istore_.data(), xstore_.data(), Li_.data(), Lx_.data(), Ui_.data(), Ux_.data(), @@ -214,7 +215,7 @@ void BasicLu::_BtranForUpdate(Int j, IndexedVector& lhs) { Int BasicLu::_Update(double pivot) { double max_eta_old = xstore_[BASICLU_MAX_ETA]; Int status; - for (Int ncall = 0; ; ncall++) { + for (;;) { status = basiclu_update(istore_.data(), xstore_.data(), Li_.data(), Lx_.data(), Ui_.data(), Ux_.data(), @@ -265,12 +266,12 @@ void BasicLu::_pivottol(double new_pivottol) { } void BasicLu::Reallocate() { - assert(Li_.size() == xstore_[BASICLU_MEMORYL]); - assert(Lx_.size() == xstore_[BASICLU_MEMORYL]); - assert(Ui_.size() == xstore_[BASICLU_MEMORYU]); - assert(Ux_.size() == xstore_[BASICLU_MEMORYU]); - assert(Wi_.size() == xstore_[BASICLU_MEMORYW]); - assert(Wx_.size() == xstore_[BASICLU_MEMORYW]); + assert(Li_.size() == static_cast(xstore_[BASICLU_MEMORYL])); + assert(Lx_.size() == static_cast(xstore_[BASICLU_MEMORYL])); + assert(Ui_.size() == static_cast(xstore_[BASICLU_MEMORYU])); + assert(Ux_.size() == static_cast(xstore_[BASICLU_MEMORYU])); + assert(Wi_.size() == static_cast(xstore_[BASICLU_MEMORYW])); + assert(Wx_.size() == static_cast(xstore_[BASICLU_MEMORYW])); if (xstore_[BASICLU_ADD_MEMORYL] > 0) { Int new_size = xstore_[BASICLU_MEMORYL] + xstore_[BASICLU_ADD_MEMORYL]; diff --git a/src/ipm/ipx/basis.cc b/src/ipm/ipx/basis.cc index bccdfebcf8..607de407d1 100644 --- a/src/ipm/ipx/basis.cc +++ b/src/ipm/ipx/basis.cc @@ -353,8 +353,6 @@ void Basis::ComputeBasicSolution(Vector& x, Vector& y, Vector& z) const { } void Basis::ConstructBasisFromWeights(const double* colscale, Info* info) { - const Int m = model_.rows(); - const Int n = model_.cols(); assert(colscale); info->errflag = 0; info->dependent_rows = 0; @@ -368,7 +366,7 @@ void Basis::ConstructBasisFromWeights(const double* colscale, Info* info) { << '\n'; Repair(info); if (info->basis_repairs < 0) { - control_.Log() << " discarding crash basis\n"; + control_.hLog(" discarding crash basis\n"); SetToSlackBasis(); } else if (info->basis_repairs > 0) { @@ -501,8 +499,10 @@ bool Basis::TightenLuPivotTol() { lu_->pivottol(0.9); else return false; - control_.Log() - << " LU pivot tolerance tightened to " << lu_->pivottol() << '\n'; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << " LU pivot tolerance tightened to " << lu_->pivottol() << '\n'; + control_.hLog(h_logging_stream); return true; } @@ -776,8 +776,10 @@ void Basis::PivotFreeVariablesIntoBasis(const double* colweights, Info* info) { info->updates_start++; } } - control_.IntervalLog() - << " " << remaining.size() << " free variables remaining\n"; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << " " << remaining.size() << " free variables remaining\n"; + control_.hIntervalLog(h_logging_stream); } control_.Debug() << Textline("Number of free variables swapped for stability:") @@ -911,8 +913,11 @@ void Basis::PivotFixedVariablesOutOfBasis(const double* colweights, Info* info){ info->updates_start++; } } - control_.IntervalLog() - << " " << remaining.size() << " fixed variables remaining\n"; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << " " << remaining.size() << " fixed variables remaining\n"; + control_.hIntervalLog(h_logging_stream); + } control_.Debug() << Textline("Number of fixed variables swapped for stability:") diff --git a/src/ipm/ipx/conjugate_residuals.h b/src/ipm/ipx/conjugate_residuals.h index ba7f49be33..8b60aef914 100644 --- a/src/ipm/ipx/conjugate_residuals.h +++ b/src/ipm/ipx/conjugate_residuals.h @@ -35,7 +35,7 @@ class ConjugateResiduals { // some LP models with m << n, e.g. "rvb-sub" from MIPLIB2010, that the CR // method did not reach the termination criterion within m iterations, // causing the IPM to fail. Giving the CR method 100 extra iterations - // resolved the issue on all LP models from our test set where it occured.) + // resolved the issue on all LP models from our test set where it occurred.) // // If the @P argument is given, it is used as preconditioner (which // approximates inverse(C)) and must be symmetric positive definite. @@ -50,7 +50,7 @@ class ConjugateResiduals { // IPX_ERROR_cr_iter_limit if iteration limit was reached // IPX_ERROR_cr_matrix_not_posdef if v'*C*v <= 0 for some vector v // IPX_ERROR_cr_precond_not_posdef if v'*P*v <= 0 for some vector v - // IPX_ERROR_cr_inf_or_nan if overflow occured + // IPX_ERROR_cr_inf_or_nan if overflow occurred // IPX_ERROR_cr_no_progress if no progress due to round-off errors // IPX_ERROR_user_interrupt if interrupted by user in control // IPX_ERROR_time_interrupt if interrupted by time limit in control diff --git a/src/ipm/ipx/control.cc b/src/ipm/ipx/control.cc index 44d8ffc7dd..9a149fab15 100644 --- a/src/ipm/ipx/control.cc +++ b/src/ipm/ipx/control.cc @@ -19,7 +19,7 @@ Int Control::InterruptCheck(const Int ipm_iteration_count) const { // that it's not been set assert(callback_); if (callback_) { - if (callback_->user_callback && callback_->active[kCallbackIpmInterrupt] ) { + if (callback_->user_callback && callback_->active[kCallbackIpmInterrupt]) { callback_->clearHighsCallbackDataOut(); callback_->data_out.ipm_iteration_count = ipm_iteration_count; if (callback_->callbackAction(kCallbackIpmInterrupt, @@ -30,18 +30,41 @@ Int Control::InterruptCheck(const Int ipm_iteration_count) const { return 0; } -std::ostream& Control::Log() const { - return output_; +void Control::hLog(std::string str) const { + if (parameters_.highs_logging) { + assert(parameters_.log_options); + HighsLogOptions log_options_ = *(parameters_.log_options); + highsLogUser(log_options_, HighsLogType::kInfo, "%s", str.c_str()); + } else { + output_ << str; + } + } -std::ostream& Control::IntervalLog() const { - if (parameters_.print_interval >= 0.0 && - interval_.Elapsed() >= parameters_.print_interval) { - interval_.Reset(); - return output_; +void Control::hLog(std::stringstream& logging) const { + if (parameters_.highs_logging) { + assert(parameters_.log_options); + HighsLogOptions log_options_ = *(parameters_.log_options); + highsLogUser(log_options_, HighsLogType::kInfo, "%s", logging.str().c_str()); + } else { + output_ << logging.str(); + } + logging.str(std::string()); +} + +void Control::hIntervalLog(std::stringstream& logging) const { + if (parameters_.print_interval >= 0.0 && + interval_.Elapsed() >= parameters_.print_interval) { + interval_.Reset(); + if (parameters_.highs_logging) { + assert(parameters_.log_options); + HighsLogOptions log_options_ = *(parameters_.log_options); + highsLogUser(log_options_, HighsLogType::kInfo, "%s", logging.str().c_str()); } else { - return dummy_; + output_ << logging.str(); } + } + logging.str(std::string()); } std::ostream& Control::Debug(Int level) const { diff --git a/src/ipm/ipx/control.h b/src/ipm/ipx/control.h index 8ecf9f1fcd..d322b0d383 100644 --- a/src/ipm/ipx/control.h +++ b/src/ipm/ipx/control.h @@ -5,10 +5,10 @@ #include #include #include +#include "io/HighsIO.h" #include "ipm/ipx/ipx_internal.h" #include "ipm/ipx/multistream.h" #include "ipm/ipx/timer.h" -#include "lp_data/HighsCallback.h" namespace ipx { @@ -46,13 +46,16 @@ class Control { // control.Debug(3) << expensive_computation(...) << '\n'; // // If the debug level is < 3, expensive_computation() is not performed. - std::ostream& Log() const; + void hLog(std::stringstream& logging) const; + void hLog(std::string str) const; + void hDebug(std::stringstream& logging, Int level=1) const; std::ostream& Debug(Int level=1) const; - // Returns the log stream if >= parameters.print_interval seconds have been - // elapsed since the last call to IntervalLog() or to ResetPrintInterval(). - // Otherwise returns a stream that discards output. - std::ostream& IntervalLog() const; + // Sends logging to HiGHS logging or the log stream according to + // parameters.highs_logging, if >= parameters.print_interval + // seconds have been elapsed since the last call to IntervalLog() + // or to ResetPrintInterval(). + void hIntervalLog(std::stringstream& logging) const; void ResetPrintInterval() const; double Elapsed() const; // total runtime @@ -82,6 +85,12 @@ class Control { ipxint update_heuristic() const { return parameters_.update_heuristic; } ipxint maxpasses() const { return parameters_.maxpasses; } bool reportBasisData() const { return parameters_.analyse_basis_data; } + ipxint runCentring() const{return parameters_.run_centring; } + ipxint maxCentringSteps() const{return parameters_.max_centring_steps; } + double centringRatioTolerance() const{return parameters_.centring_ratio_tolerance; } + double centringRatioReduction() const {return parameters_.centring_ratio_reduction; } + double centringAlphaScaling() const{return parameters_.centring_alpha_scaling; } + ipxint badProductsTolerance() const{return parameters_.bad_products_tolerance; } const Parameters& parameters() const; void parameters(const Parameters& new_parameters); @@ -134,10 +143,10 @@ inline std::string fix8(double d) { return Fixed(d,0,8); } // Number of variables: 1464 // Number of constraints: 696 // -// consistently using +// consistently via control.hLog(h_logging_stream) using // -// control.Log() << Textline("Number of variables:") << 1464 << '\n' -// << Textline("Number of contraints:") << 696 << '\n'; +// h_logging_stream << Textline("Number of variables:") << 1464 << '\n' +// << Textline("Number of constraints:") << 696 << '\n'; // template std::string Textline(const T& text) { diff --git a/src/ipm/ipx/crossover.cc b/src/ipm/ipx/crossover.cc index 066eb66b00..19bfa9f06b 100644 --- a/src/ipm/ipx/crossover.cc +++ b/src/ipm/ipx/crossover.cc @@ -19,11 +19,14 @@ void Crossover::PushAll(Basis* basis, Vector& x, Vector& y, Vector& z, const Vector& ub = model.ub(); std::vector perm = Sortperm(n+m, weights, false); - control_.Log() - << Textline("Primal residual before push phase:") - << sci2(PrimalResidual(model, x)) << '\n' - << Textline("Dual residual before push phase:") - << sci2(DualResidual(model, y, z)) << '\n'; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << Textline("Primal residual before push phase:") + << sci2(PrimalResidual(model, x)) << '\n' + << Textline("Dual residual before push phase:") + << sci2(DualResidual(model, y, z)) << '\n'; + control_.hLog(h_logging_stream); // Run dual push phase. std::vector dual_superbasics; @@ -32,9 +35,10 @@ void Crossover::PushAll(Basis* basis, Vector& x, Vector& y, Vector& z, if (basis->IsBasic(j) && z[j] != 0.0) dual_superbasics.push_back(j); } - control_.Log() + h_logging_stream << Textline("Number of dual pushes required:") << dual_superbasics.size() << '\n'; + control_.hLog(h_logging_stream); PushDual(basis, y, z, dual_superbasics, x, info); assert(DualInfeasibility(model, x, z) == 0.0); if (info->status_crossover != IPX_STATUS_optimal) @@ -43,15 +47,16 @@ void Crossover::PushAll(Basis* basis, Vector& x, Vector& y, Vector& z, // Run primal push phase. Because z[j]==0 for all basic variables, none of // the primal variables is fixed at its bound. std::vector primal_superbasics; - for (Int p = perm.size()-1; p >= 0; p--) { - Int j = perm[p]; + for (size_t p = perm.size(); p > 0; p--) { + Int j = perm[p - 1]; if (basis->IsNonbasic(j) && x[j] != lb[j] && x[j] != ub[j] && !(std::isinf(lb[j]) && std::isinf(ub[j]) && x[j] == 0.0)) primal_superbasics.push_back(j); } - control_.Log() + h_logging_stream << Textline("Number of primal pushes required:") << primal_superbasics.size() << '\n'; + control_.hLog(h_logging_stream); PushPrimal(basis, x, primal_superbasics, nullptr, info); assert(PrimalInfeasibility(model, x) == 0.0); if (info->status_crossover != IPX_STATUS_optimal) @@ -110,8 +115,8 @@ void Crossover::PushPrimal(Basis* basis, Vector& x, } control_.ResetPrintInterval(); - Int next = 0; - while (next < (Int)variables.size()) { + size_t next = 0; + while (next < variables.size()) { if ((info->errflag = control_.InterruptCheck()) != 0) break; @@ -194,10 +199,13 @@ void Crossover::PushPrimal(Basis* basis, Vector& x, primal_pushes_++; next++; - control_.IntervalLog() - << " " << Format(static_cast(variables.size()-next), 8) - << " primal pushes remaining" - << " (" << Format(primal_pivots_, 7) << " pivots)\n"; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << " " << Format(static_cast(variables.size()-next), 8) + << " primal pushes remaining" + << " (" << Format(primal_pivots_, 7) << " pivots)\n"; + control_.hIntervalLog(h_logging_stream); } for (Int p = 0; p < m; p++) x[(*basis)[p]] = xbasic[p]; @@ -250,8 +258,8 @@ void Crossover::PushDual(Basis* basis, Vector& y, Vector& z, } control_.ResetPrintInterval(); - Int next = 0; - while (next < (Int)variables.size()) { + size_t next = 0; + while (next < variables.size()) { if ((info->errflag = control_.InterruptCheck()) != 0) break; @@ -319,10 +327,13 @@ void Crossover::PushDual(Basis* basis, Vector& y, Vector& z, dual_pushes_++; next++; - control_.IntervalLog() + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << " " << Format(static_cast(variables.size()-next), 8) << " dual pushes remaining" << " (" << Format(dual_pivots_, 7) << " pivots)\n"; + control_.hIntervalLog(h_logging_stream); } // Set status flag. diff --git a/src/ipm/ipx/crossover.h b/src/ipm/ipx/crossover.h index 3587014353..7e16b00eb1 100644 --- a/src/ipm/ipx/crossover.h +++ b/src/ipm/ipx/crossover.h @@ -44,7 +44,7 @@ class Crossover { // as long as the Crossover object is used. Crossover(const Control& control); - // First runs the dual push phase; if this was succesful, then runs the + // First runs the dual push phase; if this was successful, then runs the // primal push phase. // // weights: Must either be NULL or an array of size n+m. diff --git a/src/ipm/ipx/diagonal_precond.cc b/src/ipm/ipx/diagonal_precond.cc index 847d3e2422..7292a05c07 100644 --- a/src/ipm/ipx/diagonal_precond.cc +++ b/src/ipm/ipx/diagonal_precond.cc @@ -55,8 +55,8 @@ void DiagonalPrecond::_Apply(const Vector& rhs, Vector& lhs, Timer timer; assert(factorized_); - assert((Int)lhs.size() == m); - assert((Int)rhs.size() == m); + assert(lhs.size() == static_cast(m)); + assert(rhs.size() == static_cast(m)); for (Int i = 0; i < m; i++) { lhs[i] = rhs[i] / diagonal_[i]; diff --git a/src/ipm/ipx/forrest_tomlin.cc b/src/ipm/ipx/forrest_tomlin.cc index f7deb101a1..96004f8664 100644 --- a/src/ipm/ipx/forrest_tomlin.cc +++ b/src/ipm/ipx/forrest_tomlin.cc @@ -211,7 +211,7 @@ Int ForrestTomlin::_Update(double pivot) { bool ForrestTomlin::_NeedFreshFactorization() { Int num_updates = replaced_.size(); - Int Rnz = R_.entries(); // nnz in acculumated row etas + Int Rnz = R_.entries(); // nnz in accumulated row etas Int Lnz = L_.entries() + dim_; // nnz(L) incl. diagonal Int Unz = U_.entries(); // nnz(U) incl. zeroed out columns Int U0nz = U_.begin(dim_); // nnz(U) after factorization @@ -316,7 +316,6 @@ void ForrestTomlin::ComputeSpike(Int nb, const Int* bi, const double* bx) { // Store spike in U. Indices are sorted, which is required for the sparse // dot product in Update(). - Int nz = 0; U_.clear_queue(); for (Int p = 0; p < dim_+num_updates; p++) { if (work_[p] != 0.0) @@ -348,7 +347,6 @@ void ForrestTomlin::ComputeEta(Int j) { // Queue eta at end of R. Indices are sorted, which is required for the // sparse dot product in Update(). - Int nz = 0; R_.clear_queue(); double pivot = work_[pos]; for (Int i = pos+1; i < dim_+num_updates; i++) { diff --git a/src/ipm/ipx/forrest_tomlin.h b/src/ipm/ipx/forrest_tomlin.h index 32c373080b..dc60bfd6ab 100644 --- a/src/ipm/ipx/forrest_tomlin.h +++ b/src/ipm/ipx/forrest_tomlin.h @@ -11,7 +11,7 @@ namespace ipx { // Generic implementation of the Forrest-Tomlin update [1] that can be used with // any LU factorization. The implementation does not exploit hypersparsity, -// which exludes its use for such problems. For non-hypersparse problems the +// which excludes its use for such problems. For non-hypersparse problems the // implementation is better suited than BASICLU, however, because it stores L // and U in compressed form with permuted indices; hence solving triangular // systems with a dense rhs/lhs accesses memory contiguously. BASICLU could not diff --git a/src/ipm/ipx/indexed_vector.h b/src/ipm/ipx/indexed_vector.h index 6ad46af292..07a0864be1 100644 --- a/src/ipm/ipx/indexed_vector.h +++ b/src/ipm/ipx/indexed_vector.h @@ -24,7 +24,7 @@ namespace ipx { // otherwise. // // When modifying the vector changes its pattern (e.g. by writing to v[i] for an -// arbitray index i), you have to invalidate the pattern or provide the new one. +// arbitrary index i), you have to invalidate the pattern or provide the new one. class IndexedVector { public: diff --git a/src/ipm/ipx/ipm.cc b/src/ipm/ipx/ipm.cc index 90739b68a4..4a21364951 100644 --- a/src/ipm/ipx/ipm.cc +++ b/src/ipm/ipx/ipm.cc @@ -112,6 +112,82 @@ void IPM::Driver(KKTSolver* kkt, Iterate* iterate, Info* info) { info->status_ipm = IPX_STATUS_failed; } } + + if (control_.runCentring() && + info->status_ipm == IPX_STATUS_optimal && !info->centring_tried) { + // Centrality of a point is evaluated by the quantities + // min (xj * zj) / mu + // max (xj * zj) / mu + // Ideally, they are in the interval [0.1,10.0]. + // As soon as the ratio + // max (xj * zj) / min (xj * zj) + // is below centringRatioTolerance, the point is considered centred. + // If the new point after centring has a ratio that is lower than the + // previous ratio times centringRatioReduction, then the step is + // accepted. Otherwise, the step is rejected and no more centring steps are + // performed. + // + // If IPM is optimal and centring has not yet run, run centring + // (to avoid running it twice during initial IPM and main IPM). + control_.hLog("Performing centring steps...\n"); + + // freeze mu to its current value + const double mu_frozen = iterate_->mu(); + + // assess and print centrality of current point + AssessCentrality(iterate_->xl(), iterate_->xu(), iterate_->zl(), + iterate_->zu(), iterate_->mu()); + double prev_ratio = centring_ratio; + Int prev_bad_products = bad_products; + + info->centring_success = false; + // if ratio is below tolerance, point is centred + if (prev_ratio < control_.centringRatioTolerance()) { + control_.hLog("\tPoint is now centred\n"); + info->centring_success = true; + } else { + // perform centring steps + bool centring_complete = false; + for (int ii = 0; ii < control_.maxCentringSteps(); ++ii) { + // compute centring step + Centring(step, mu_frozen); + + // assess whether to take the step + bool accept = EvaluateCentringStep(step, prev_ratio, prev_bad_products); + if (!accept) { + control_.hLog("\tPoint cannot be centred further\n"); + centring_complete = true; + break; + } + + // take the step and print output + MakeStep(step, true); + info->iter++; + PrintOutput(); + AssessCentrality(iterate_->xl(), iterate_->xu(), iterate_->zl(), + iterate_->zu(), iterate_->mu()); + prev_ratio = centring_ratio; + prev_bad_products = bad_products; + + // if ratio is below tolerance, point is centred + if (prev_ratio < control_.centringRatioTolerance()) { + control_.hLog("\tPoint is now centred\n"); + info->centring_success = true; + centring_complete = true; + break; + } + } + if (!centring_complete) { + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << "\tPoint could not be centred within " + << control_.maxCentringSteps() << " iterations\n"; + control_.hLog(h_logging_stream); + } + } + info->centring_tried = true; + } // if (control_.runCentring() && info->status_ipm == + // IPX_STATUS_optimal && !info->centring_tried) } void IPM::ComputeStartingPoint() { @@ -184,9 +260,9 @@ void IPM::ComputeStartingPoint() { // When c lies in range(AI'), then the dual slack variables are (close // to) zero, and the initial point would be almost complementary but - // ususally not primal feasible. To prevent this from happening, add + // usually not primal feasible. To prevent this from happening, add // a fraction of the objective to zl and adjust y. In exact computation - // this does not affect dual feasiblity. + // this does not affect dual feasibility. const double znorm = Twonorm(zl); const double rho = 0.05; if (znorm < rho*cnorm) { @@ -370,7 +446,159 @@ void IPM::AddCorrector(Step& step) { step); } -void IPM::StepSizes(const Step& step) { +void IPM::Centring(Step& step, double mu) { + const Model& model = iterate_->model(); + const Int m = model.rows(); + const Int n = model.cols(); + const Vector& xl = iterate_->xl(); + const Vector& xu = iterate_->xu(); + const Vector& zl = iterate_->zl(); + const Vector& zu = iterate_->zu(); + + Vector sl(n + m); + Vector su(n + m); + + // Set sigma to 1 for pure centring + const double sigma = 1.0; + + // sl = -xl.*zl + sigma*mu + for (Int j = 0; j < n + m; j++) { + if (iterate_->has_barrier_lb(j)) { + sl[j] = -xl[j] * zl[j] + sigma * mu; + } else { + sl[j] = 0.0; + } + } + assert(AllFinite(sl)); + + // su = -xu.*zu + sigma*mu + for (Int j = 0; j < n + m; j++) { + if (iterate_->has_barrier_ub(j)) { + su[j] = -xu[j] * zu[j] + sigma * mu; + } else { + su[j] = 0.0; + } + } + assert(AllFinite(su)); + + SolveNewtonSystem(&iterate_->rb()[0], &iterate_->rc()[0], &iterate_->rl()[0], + &iterate_->ru()[0], &sl[0], &su[0], step); +} + +void IPM::AssessCentrality(const Vector& xl, const Vector& xu, + const Vector& zl, const Vector& zu, double mu, + bool print) { + // The function computes the ratio + // min(x_j * z_j) / max(x_j * z_j) + // and the number of products x_j * z_j that are not in the interval + // [0.1 * mu, 10 * mu] + // and prints information to screen if print is on. + + const Int m = iterate_->model().rows(); + const Int n = iterate_->model().cols(); + + double minxz = kHighsInf; + double maxxz = 0.0; + + const double gamma = 0.1; + bad_products = 0; + + for (Int j = 0; j < n + m; j++) { + if (iterate_->has_barrier_lb(j)) { + const double product = xl[j] * zl[j]; + if (product < gamma * mu || product > mu / gamma){ + ++bad_products; + } + minxz = std::min(minxz, product); + maxxz = std::max(maxxz, product); + } + } + + for (Int j = 0; j < n + m; j++) { + if (iterate_->has_barrier_ub(j)) { + const double product = xu[j] * zu[j]; + if (product < gamma * mu || product > mu / gamma){ + ++bad_products; + } + minxz = std::min(minxz, product); + maxxz = std::max(maxxz, product); + } + } + + maxxz = std::max(maxxz, mu); + minxz = std::min(minxz, mu); + + centring_ratio = maxxz / minxz; + + if (print) { + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << "\txj*zj in [ " + << Scientific(minxz / mu, 8, 2) << ", " + << Scientific(maxxz / mu, 8, 2) << "]; Ratio = " + << Scientific(centring_ratio, 8, 2) << "; (xj*zj / mu) not_in [0.1, 10]: " + << bad_products << "\n"; + control_.hLog(h_logging_stream); + } +} + +bool IPM::EvaluateCentringStep(const Step& step, double prev_ratio, Int prev_bad) { + // The function returns true is the step is to be accepted. + // The step is accepted if the ratio of the new point is not worse + // than the previous one times centringRatioReduction or if the + // number of outliers products is reduced. + + StepSizes(step, true); + + const Int n = iterate_->model().cols(); + const Int m = iterate_->model().rows(); + + Vector xl_temp = iterate_->xl(); + Vector xu_temp = iterate_->xu(); + Vector zl_temp = iterate_->zl(); + Vector zu_temp = iterate_->zu(); + + // perform temporary step + for (Int j = 0; j < n + m; j++) { + if (iterate_->has_barrier_lb(j)) { + xl_temp[j] += step_primal_ * step.xl[j]; + } + if (iterate_->has_barrier_ub(j)) { + xu_temp[j] += step_primal_ * step.xu[j]; + } + if (iterate_->has_barrier_lb(j)) { + zl_temp[j] += step_dual_ * step.zl[j]; + } + if (iterate_->has_barrier_ub(j)) { + zu_temp[j] += step_dual_ * step.zu[j]; + } + } + + // compute temporary mu + double mu_temp = 0.0; + Int num_finite = 0; + for (Int j = 0; j < n + m; j++) { + if (iterate_->has_barrier_lb(j)) { + mu_temp += xl_temp[j] * zl_temp[j]; + ++num_finite; + } + if (iterate_->has_barrier_ub(j)) { + mu_temp += xu_temp[j] * zu_temp[j]; + ++num_finite; + } + } + mu_temp /= num_finite; + + // assess quality of temporary point + AssessCentrality(xl_temp, xu_temp, zl_temp, zu_temp, mu_temp, false); + + // accept the step if the new ratio is not more than centringRatioReduction + // times the previous one, or if the new point has fewer outliers + return (centring_ratio < control_.centringRatioReduction() * prev_ratio || + bad_products < prev_bad); +} + +void IPM::StepSizes(const Step& step, bool isCentring) { const Model& model = iterate_->model(); const Int m = model.rows(); const Int n = model.cols(); @@ -382,7 +610,6 @@ void IPM::StepSizes(const Step& step) { const Vector& dxu = step.xu; const Vector& dzl = step.zl; const Vector& dzu = step.zu; - const double mu = iterate_->mu(); const double gammaf = 0.9; const double gammaa = 1.0 / (1.0-gammaf); @@ -451,18 +678,28 @@ void IPM::StepSizes(const Step& step) { } step_primal_ = std::min(alphap, 1.0-1e-6); step_dual_ = std::min(alphad, 1.0-1e-6); + + if (isCentring){ + // When computing stepsizes for a centring step, reduce them + // by centringAlphaScaling. This ensures that the point is + // well centred and does not get too close to the boundary. + step_primal_ = alphap * control_.centringAlphaScaling(); + step_dual_ = alphad * control_.centringAlphaScaling(); + } } -void IPM::MakeStep(const Step& step) { - StepSizes(step); +void IPM::MakeStep(const Step& step, bool isCentring) { + StepSizes(step, isCentring); iterate_->Update(step_primal_, &step.x[0], &step.xl[0], &step.xu[0], step_dual_, &step.y[0], &step.zl[0], &step.zu[0]); - if (std::min(step_primal_, step_dual_) < 0.05) - num_bad_iter_++; - else - num_bad_iter_ = 0; - best_complementarity_ = - std::min(best_complementarity_, iterate_->complementarity()); + if (!isCentring){ + if (std::min(step_primal_, step_dual_) < 0.05) + num_bad_iter_++; + else + num_bad_iter_ = 0; + best_complementarity_ = + std::min(best_complementarity_, iterate_->complementarity()); + } } void IPM::SolveNewtonSystem(const double* rb, const double* rc, @@ -581,40 +818,48 @@ void IPM::SolveNewtonSystem(const double* rb, const double* rc, } void IPM::PrintHeader() { - control_.Log() - << " " << Format("Iter", 4) - << " " << Format("P.res", 8) << " " << Format("D.res", 8) - << " " << Format("P.obj", 15) << " " << Format("D.obj", 15) - << " " << Format("mu", 8) - << " " << Format("Time", 7); - control_.Debug() - << " " << Format("stepsizes", 9) - << " " << Format("pivots", 7) << " " << Format("kktiter", 7) - << " " << Format("P.fixed", 7) << " " << Format("D.fixed", 7); - control_.Debug(4) << " " << Format("svdmin(B)", 9); - control_.Debug(4) << " " << Format("density", 8); - control_.Log() << '\n'; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << (kTerminationLogging ? "\n" : "") + << " " << Format("Iter", 4) + << " " << Format("P.res", 8) << " " << Format("D.res", 8) + << " " << Format("P.obj", 15) << " " << Format("D.obj", 15) + << " " << Format("mu", 8) + << " " << Format("Time", 7); + control_.hLog(h_logging_stream); + control_.Debug() + << " " << Format("stepsizes", 9) + << " " << Format("pivots", 7) << " " << Format("kktiter", 7) + << " " << Format("P.fixed", 7) << " " << Format("D.fixed", 7); + control_.Debug(4) << " " << Format("svdmin(B)", 9); + control_.Debug(4) << " " << Format("density", 8); + control_.hLog("\n"); } void IPM::PrintOutput() { const bool ipm_optimal = iterate_->feasible() && iterate_->optimal(); - control_.Log() - << " " << Format(info_->iter, 3) - << (ipm_optimal ? "*" : " ") - << " " << Scientific(iterate_->presidual(), 8, 2) - << " " << Scientific(iterate_->dresidual(), 8, 2) - << " " << Scientific(iterate_->pobjective_after_postproc(), 15, 8) - << " " << Scientific(iterate_->dobjective_after_postproc(), 15, 8) - << " " << Scientific(iterate_->mu(), 8, 2) - << " " << Fixed(control_.Elapsed(), 6, 0) << "s"; + if (kTerminationLogging) PrintHeader(); + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << " " << Format(info_->iter, 3) + << (ipm_optimal ? "*" : " ") + << " " << Scientific(iterate_->presidual(), 8, 2) + << " " << Scientific(iterate_->dresidual(), 8, 2) + << " " << Scientific(iterate_->pobjective_after_postproc(), 15, 8) + << " " << Scientific(iterate_->dobjective_after_postproc(), 15, 8) + << " " << Scientific(iterate_->mu(), 8, 2) + << " " << Fixed(control_.Elapsed(), 6, 0) << "s"; + control_.hLog(h_logging_stream); control_.Debug() - << " " << Fixed(step_primal_, 4, 2) << " " << Fixed(step_dual_, 4, 2) - << " " << Format(kkt_->basis_changes(), 7) - << " " << Format(kkt_->iter(), 7); + << " " << Fixed(step_primal_, 4, 2) << " " << Fixed(step_dual_, 4, 2) + << " " << Format(kkt_->basis_changes(), 7) + << " " << Format(kkt_->iter(), 7); control_.Debug() - << " " << Format(info_->dual_dropped, 7) - << " " << Format(info_->primal_dropped, 7); + << " " << Format(info_->dual_dropped, 7) + << " " << Format(info_->primal_dropped, 7); const Basis* basis = kkt_->basis(); if (basis) { @@ -630,7 +875,7 @@ void IPM::PrintOutput() { control_.Debug(4) << " " << Format("-", 9); control_.Debug(4) << " " << Format("-", 8); } - control_.Log() << '\n'; + control_.hLog("\n"); } } // namespace ipx diff --git a/src/ipm/ipx/ipm.h b/src/ipm/ipx/ipm.h index 6b0d67229b..7d5ae83e60 100644 --- a/src/ipm/ipx/ipm.h +++ b/src/ipm/ipx/ipm.h @@ -48,8 +48,12 @@ class IPM { void ComputeStartingPoint(); void Predictor(Step& step); void AddCorrector(Step& step); - void StepSizes(const Step& step); - void MakeStep(const Step& step); + void Centring(Step& step, double mu_to_use); + void AssessCentrality(const Vector& xl, const Vector& xu, const Vector& zl, + const Vector& zu,double mu, bool print = true); + bool EvaluateCentringStep(const Step& step, double prev_ratio, Int prev_bad); + void StepSizes(const Step& step, bool isCentring = false); + void MakeStep(const Step& step, bool isCentring = false); // Reduces the following linear system to KKT form: // [ AI ] [dx ] [rb] // [ I -I ] [dxl] = [rl] @@ -79,6 +83,10 @@ class IPM { double best_complementarity_{0.0}; Int maxiter_{-1}; + + // indicators of centrality for centring steps + double centring_ratio{0.0}; + Int bad_products{0}; }; } // namespace ipx diff --git a/src/ipm/ipx/ipx_c.cc b/src/ipm/ipx/ipx_c.cc index 91e885692e..e70f1dbfed 100644 --- a/src/ipm/ipx/ipx_c.cc +++ b/src/ipm/ipx/ipx_c.cc @@ -4,60 +4,6 @@ using namespace ipx; -struct ipx_parameters ipx_default_parameters() { - ipx_parameters p; - p.display = 1; - p.logfile = nullptr; - p.print_interval = 5.0; - p.time_limit = -1.0; - p.analyse_basis_data = false; - p.dualize = -1; - p.scale = 1; - p.ipm_maxiter = 300; - p.ipm_feasibility_tol = 1e-6; - p.ipm_optimality_tol = 1e-8; - p.ipm_drop_primal = 1e-9; - p.ipm_drop_dual = 1e-9; - p.kkt_tol = 0.3; - p.crash_basis = 1; - p.dependency_tol = 1e-6; - p.volume_tol = 2.0; - p.rows_per_slice = 10000; - p.maxskip_updates = 10; - p.lu_kernel = 0; - p.lu_pivottol = 0.0625; - p.run_crossover = 1; - p.start_crossover_tol = 1e-8; - p.pfeasibility_tol = 1e-7; - p.dfeasibility_tol = 1e-7; - p.debug = 0; - p.switchiter = -1; - p.stop_at_switch = 0; - p.update_heuristic = 1; - p.maxpasses = -1; - return p; -} - -void ipx_new(void** p_self) { - if (p_self) { - try { - LpSolver* solver = new LpSolver; - *p_self = static_cast(solver); - } - catch (...) { - *p_self = nullptr; - } - } -} - -void ipx_free(void** p_self) { - if (p_self && *p_self) { - LpSolver* solver = static_cast(*p_self); - delete solver; - *p_self = nullptr; - } -} - ipxint ipx_load_model(void* self, ipxint num_var, const double* obj, const double* lb, const double* ub, ipxint num_constr, const ipxint* Ap, const ipxint* Ai, const double* Ax, diff --git a/src/ipm/ipx/ipx_c.h b/src/ipm/ipx/ipx_c.h index 86169a00a3..b8af2ac0aa 100644 --- a/src/ipm/ipx/ipx_c.h +++ b/src/ipm/ipx/ipx_c.h @@ -9,22 +9,9 @@ #ifdef __cplusplus extern "C"{ #endif - /* Returns an ipx_parameters struct with default values. */ - struct ipx_parameters ipx_default_parameters(); - - /* Allocates a new LpSolver object. On success, *p_self holds a pointer to - the new object. If the memory allocation fails, *p_self becomes NULL - (the required memory is tiny). The function does nothing if @p_self is - NULL. */ - void ipx_new(void** p_self); - - /* Deallocates the LpSolver object pointed to by *p_self and sets *p_self to - NULL. If either p_self or *p_self is NULL, the function does nothing. */ - void ipx_free(void** p_self); - - /* The remaining functions call their equivalent method of LpSolver for the - object pointed to by @self. See src/lp_solver.h for documentation of the - methods. */ + /* These functions call their equivalent method of LpSolver for + the object pointed to by @self. See src/lp_solver.h for + documentation of the methods. */ ipxint ipx_load_model(void* self, ipxint num_var, const double* obj, const double* lb, const double* ub, ipxint num_constr, const ipxint* Ap, const ipxint* Ai, const double* Ax, diff --git a/src/ipm/ipx/ipx_info.h b/src/ipm/ipx/ipx_info.h index 6906e42f41..ed43d6718e 100644 --- a/src/ipm/ipx/ipx_info.h +++ b/src/ipm/ipx/ipx_info.h @@ -25,6 +25,9 @@ struct ipx_info { ipxint dualized; /* dualized model? */ ipxint dense_cols; /* # columns classified "dense" */ + ipxint centring_tried; /* centring steps tried? */ + ipxint centring_success; /* centring steps successful? */ + /* reductions in IPM */ ipxint dependent_rows; /* # dependent rows (to eq constr) removed */ ipxint dependent_cols; /* # dependent cols (to free vars) removed */ diff --git a/src/ipm/ipx/ipx_internal.h b/src/ipm/ipx/ipx_internal.h index 46bceb3e22..f51654b2ac 100644 --- a/src/ipm/ipx/ipx_internal.h +++ b/src/ipm/ipx/ipx_internal.h @@ -48,6 +48,14 @@ struct Parameters : public ipx_parameters { stop_at_switch = 0; update_heuristic = 1; maxpasses = -1; + run_centring = 0; + max_centring_steps = 5; + centring_ratio_tolerance = 100.0; + centring_ratio_reduction = 1.5; + centring_alpha_scaling = 0.5; + bad_products_tolerance = 3; + highs_logging = false; + log_options = nullptr; } Parameters(const ipx_parameters& p) : ipx_parameters(p) {} diff --git a/src/ipm/ipx/ipx_parameters.h b/src/ipm/ipx/ipx_parameters.h index fb626de7b8..2f9db89186 100644 --- a/src/ipm/ipx/ipx_parameters.h +++ b/src/ipm/ipx/ipx_parameters.h @@ -1,7 +1,9 @@ #ifndef IPX_PARAMETERS_H_ #define IPX_PARAMETERS_H_ +#include "io/HighsIO.h" #include "ipm/ipx/ipx_config.h" +#include #ifdef __cplusplus extern "C" { @@ -51,6 +53,19 @@ struct ipx_parameters { ipxint stop_at_switch; ipxint update_heuristic; ipxint maxpasses; + + /* Centring */ + ipxint run_centring; + ipxint max_centring_steps; + double centring_ratio_tolerance; + double centring_ratio_reduction; + double centring_alpha_scaling; + ipxint bad_products_tolerance; + + /* HiGHS logging parameters */ + bool highs_logging; + const HighsLogOptions* log_options; + }; #ifdef __cplusplus diff --git a/src/ipm/ipx/iterate.cc b/src/ipm/ipx/iterate.cc index 96c027f4a6..c407eb3de4 100644 --- a/src/ipm/ipx/iterate.cc +++ b/src/ipm/ipx/iterate.cc @@ -219,9 +219,22 @@ double Iterate::mu_max() const { Evaluate(); return mu_max_; } bool Iterate::feasible() const { Evaluate(); - return - presidual_ <= feasibility_tol_ * (1.0+model_.norm_bounds()) && - dresidual_ <= feasibility_tol_ * (1.0+model_.norm_c()); + const double bounds_measure = 1.0 + model_.norm_bounds(); + const double costs_measure = 1.0 + model_.norm_c(); + const double rel_presidual = presidual_ / bounds_measure; + const double rel_dresidual = dresidual_ / costs_measure; + const bool primal_feasible = presidual_ <= feasibility_tol_ * (bounds_measure); + const bool dual_feasible = dresidual_ <= feasibility_tol_ * (costs_measure); + const bool is_feasible = primal_feasible && dual_feasible; + if (kTerminationLogging) { + printf("\nIterate::feasible presidual_ = %11.4g; bounds_measure = %11.4g; " + "rel_presidual = %11.4g; feasibility_tol = %11.4g: primal_feasible = %d\n", + presidual_, bounds_measure, rel_presidual, feasibility_tol_, primal_feasible); + printf("Iterate::feasible dresidual_ = %11.4g; costs_measure = %11.4g; " + "rel_dresidual = %11.4g; feasibility_tol = %11.4g: dual_feasible = %d\n", + dresidual_, costs_measure, rel_dresidual, feasibility_tol_, dual_feasible); + } + return is_feasible; } bool Iterate::optimal() const { @@ -230,7 +243,17 @@ bool Iterate::optimal() const { double dobj = dobjective_after_postproc(); double obj = 0.5 * (pobj + dobj); double gap = pobj - dobj; - return std::abs(gap) <= optimality_tol_ * (1.0+std::abs(obj)); + const double abs_gap = std::abs(gap); + const double obj_measure = 1.0+std::abs(obj); + const bool is_optimal = abs_gap <= optimality_tol_ * obj_measure; + if (kTerminationLogging) { + const double rel_gap = abs_gap / obj_measure; + printf("Iterate::optimal abs_gap = %11.4g;" + " obj_measure = %11.4g; rel_gap = %11.4g;" + " optimality_tol = %11.4g: optimal = %d\n", + abs_gap, obj_measure, rel_gap, optimality_tol_, is_optimal); + } + return is_optimal; } bool Iterate::term_crit_reached() const { @@ -256,7 +279,7 @@ void Iterate::Postprocess() { // For fixed variables compute xl[j] and xu[j] from x[j]. If the lower and // upper bound are equal, set zl[j] or zu[j] such that the variable is dual - // feasibile. Otherwise leave them zero. + // feasible. Otherwise leave them zero. for (Int j = 0; j < n+m; j++) { if (StateOf(j) == State::fixed) { xl_[j] = x_[j] - lb[j]; @@ -539,7 +562,7 @@ void Iterate::ComputeResiduals() const { rb_ = model_.b(); MultiplyAdd(AI, x_, -1.0, rb_, 'N'); - // Dual residual: rc = c-AI'y-zl+zu. If the iteate has not been + // Dual residual: rc = c-AI'y-zl+zu. If the iterate has not been // postprocessed, then the dual residual for fixed variables is zero // because these variables are treated as non-existent by the IPM. rc_ = model_.c() - zl_ + zu_; diff --git a/src/ipm/ipx/iterate.h b/src/ipm/ipx/iterate.h index 53b05cce37..0f1ce529d4 100644 --- a/src/ipm/ipx/iterate.h +++ b/src/ipm/ipx/iterate.h @@ -77,7 +77,7 @@ class Iterate { double zl(Int j) const { return zl_[j]; } double zu(Int j) const { return zu_[j]; } - // Returns const rerefences to the residual vectors + // Returns const references to the residual vectors // rb = b-AI*x, // rl = lb-x+xl, // ru = ub-x-xu, @@ -155,7 +155,7 @@ class Iterate { double presidual() const; double dresidual() const; - // copmlementarity() returns the sum of the pairwise complementarity + // complementarity() returns the sum of the pairwise complementarity // products xl[j]*zl[j] and xu[j]*zu[j] from all barrier terms. mu() // returns the average, mu_min() the minimum and mu_max() the maximum. double complementarity() const; diff --git a/src/ipm/ipx/lp_solver.cc b/src/ipm/ipx/lp_solver.cc index 39e993ab4b..65a3805afc 100644 --- a/src/ipm/ipx/lp_solver.cc +++ b/src/ipm/ipx/lp_solver.cc @@ -53,7 +53,7 @@ Int LpSolver::Solve() { ClearSolution(); control_.ResetTimer(); control_.OpenLogfile(); - control_.Log() << "IPX version 1.0\n"; + control_.hLog("IPX version 1.0\n"); try { InteriorPointSolve(); const bool run_crossover_on = control_.run_crossover() == 1; @@ -66,10 +66,10 @@ Int LpSolver::Solve() { // info_.status_ipm == IPX_STATUS_imprecise) && run_crossover_on) { if (run_crossover) { if (run_crossover_on) { - control_.Log() << "Running crossover as requested\n"; + control_.hLog("Running crossover as requested\n"); } else if (run_crossover_choose) { assert(info_.status_ipm == IPX_STATUS_imprecise); - control_.Log() << "Running crossover since IPX is imprecise\n"; + control_.hLog("Running crossover since IPX is imprecise\n"); } else { assert(run_crossover_on || run_crossover_choose); } @@ -106,12 +106,15 @@ Int LpSolver::Solve() { PrintSummary(); } catch (const std::bad_alloc&) { - control_.Log() << " out of memory\n"; + control_.hLog(" out of memory\n"); info_.status = IPX_STATUS_out_of_memory; } catch (const std::exception& e) { - control_.Log() << " internal error: " << e.what() << '\n'; - info_.status = IPX_STATUS_internal_error; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << " internal error: " << e.what() << '\n'; + control_.hLog(h_logging_stream); + info_.status = IPX_STATUS_internal_error; } info_.time_total = control_.Elapsed(); control_.Debug(2) << info_; @@ -185,7 +188,7 @@ Int LpSolver::CrossoverFromStartingPoint(const double* x_start, const SparseMatrix& AI = model_.AI(); ClearSolution(); - control_.Log() << "Crossover from starting point\n"; + control_.hLog("Crossover from starting point\n"); x_crossover_.resize(n+m); y_crossover_.resize(m); @@ -351,7 +354,7 @@ void LpSolver::ClearSolution() { } void LpSolver::InteriorPointSolve() { - control_.Log() << "Interior Point Solve\n"; + control_.hLog("Interior Point Solve\n"); // Allocate new iterate and set tolerances for IPM termination test. iterate_.reset(new Iterate(model_)); @@ -373,14 +376,20 @@ void LpSolver::InteriorPointSolve() { info_.rel_dresidual > control_.ipm_feasibility_tol()) info_.status_ipm = IPX_STATUS_imprecise; } + if (info_.centring_tried) { + // Assess the success of analytic centre calculation + info_.status_ipm = info_.centring_success ? IPX_STATUS_optimal : IPX_STATUS_imprecise; + assert(info_.status_ipm == IPX_STATUS_optimal); + } } void LpSolver::RunIPM() { IPM ipm(control_); + info_.centring_tried = false; + info_.centring_success = false; if (x_start_.size() != 0) { - control_.Log() << " Using starting point provided by user." - " Skipping initial iterations.\n"; + control_.hLog(" Using starting point provided by user. Skipping initial iterations.\n"); iterate_->Initialize(x_start_, xl_start_, xu_start_, y_start_, zl_start_, zu_start_); } @@ -393,7 +402,8 @@ void LpSolver::RunIPM() { return; } BuildStartingBasis(); - if (info_.status_ipm != IPX_STATUS_not_run) + if (info_.status_ipm != IPX_STATUS_not_run || + info_.centring_tried) return; RunMainIPM(ipm); } @@ -508,7 +518,7 @@ void LpSolver::BuildStartingBasis() { return; } basis_.reset(new Basis(control_, model_)); - control_.Log() << " Constructing starting basis...\n"; + control_.hLog(" Constructing starting basis...\n"); StartingBasis(iterate_.get(), basis_.get(), &info_); if (info_.errflag == IPX_ERROR_user_interrupt) { info_.errflag = 0; @@ -598,7 +608,7 @@ void LpSolver::RunCrossover() { // Recompute vertex solution and set basic statuses. basis_->ComputeBasicSolution(x_crossover_, y_crossover_, z_crossover_); basic_statuses_.resize(n+m); - for (Int j = 0; j < (Int)basic_statuses_.size(); j++) { + for (size_t j = 0; j < basic_statuses_.size(); j++) { if (basis_->IsBasic(j)) { basic_statuses_[j] = IPX_basic; } else { @@ -632,34 +642,39 @@ void LpSolver::RunCrossover() { } void LpSolver::PrintSummary() { - control_.Log() << "Summary\n" + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream << "Summary\n" << Textline("Runtime:") << fix2(control_.Elapsed()) << "s\n" << Textline("Status interior point solve:") << StatusString(info_.status_ipm) << '\n' << Textline("Status crossover:") << StatusString(info_.status_crossover) << '\n'; - if (info_.status_ipm == IPX_STATUS_optimal || - info_.status_ipm == IPX_STATUS_imprecise) { - control_.Log() - << Textline("objective value:") << sci8(info_.pobjval) << '\n' - << Textline("interior solution primal residual (abs/rel):") - << sci2(info_.abs_presidual) << " / " << sci2(info_.rel_presidual) - << '\n' - << Textline("interior solution dual residual (abs/rel):") - << sci2(info_.abs_dresidual) << " / " << sci2(info_.rel_dresidual) - << '\n' - << Textline("interior solution objective gap (abs/rel):") - << sci2(info_.pobjval-info_.dobjval) << " / " - << sci2(info_.rel_objgap) << '\n'; - } - if (info_.status_crossover == IPX_STATUS_optimal || - info_.status_crossover == IPX_STATUS_imprecise) { - control_.Log() - << Textline("basic solution primal infeasibility:") - << sci2(info_.primal_infeas) << '\n' - << Textline("basic solution dual infeasibility:") - << sci2(info_.dual_infeas) << '\n'; - } + control_.hLog(h_logging_stream); + if (info_.status_ipm == IPX_STATUS_optimal || + info_.status_ipm == IPX_STATUS_imprecise) { + h_logging_stream + << Textline("objective value:") << sci8(info_.pobjval) << '\n' + << Textline("interior solution primal residual (abs/rel):") + << sci2(info_.abs_presidual) << " / " << sci2(info_.rel_presidual) + << '\n' + << Textline("interior solution dual residual (abs/rel):") + << sci2(info_.abs_dresidual) << " / " << sci2(info_.rel_dresidual) + << '\n' + << Textline("interior solution objective gap (abs/rel):") + << sci2(info_.pobjval-info_.dobjval) << " / " + << sci2(info_.rel_objgap) << '\n'; + control_.hLog(h_logging_stream); + } + if (info_.status_crossover == IPX_STATUS_optimal || + info_.status_crossover == IPX_STATUS_imprecise) { + h_logging_stream + << Textline("basic solution primal infeasibility:") + << sci2(info_.primal_infeas) << '\n' + << Textline("basic solution dual infeasibility:") + << sci2(info_.dual_infeas) << '\n'; + control_.hLog(h_logging_stream); + } } } // namespace ipx diff --git a/src/ipm/ipx/lu_factorization.h b/src/ipm/ipx/lu_factorization.h index 6effb8b094..5bc4340678 100644 --- a/src/ipm/ipx/lu_factorization.h +++ b/src/ipm/ipx/lu_factorization.h @@ -38,7 +38,7 @@ class LuFactorization { // kLuDependencyTol as absolute pivot tolerance and to // remove columns from the active submatrix // immediately when all entries became smaller than - // the abolute pivot tolerance. Need not be supported + // the absolute pivot tolerance. Need not be supported // by the implementation. // @L, @U: return the matrix factors with sorted indices. The objects are // resized as necessary. diff --git a/src/ipm/ipx/lu_update.h b/src/ipm/ipx/lu_update.h index 2ef13e0228..299ec4d8c0 100644 --- a/src/ipm/ipx/lu_update.h +++ b/src/ipm/ipx/lu_update.h @@ -25,7 +25,7 @@ class LuUpdate { // kLuDependencyTol as absolute pivot tolerance and to // remove columns from the active submatrix // immediately when all entries became smaller than - // the abolute pivot tolerance. Need not be supported + // the absolute pivot tolerance. Need not be supported // by the implementation. // // Factorize() cannot fail other than for out of memory, in which case diff --git a/src/ipm/ipx/model.cc b/src/ipm/ipx/model.cc index 8cfa3eec6c..94d251c2e4 100644 --- a/src/ipm/ipx/model.cc +++ b/src/ipm/ipx/model.cc @@ -3,7 +3,7 @@ #include #include #include "ipm/ipx/utils.h" -#include "pdqsort/pdqsort.h" +#include "../extern/pdqsort/pdqsort.h" namespace ipx { @@ -16,13 +16,16 @@ Int Model::Load(const Control& control, Int num_constr, Int num_var, obj, lbuser, ubuser); if (errflag) return errflag; - control.Log() - << "Input\n" - << Textline("Number of variables:") << num_var_ << '\n' - << Textline("Number of free variables:") << num_free_var_ << '\n' - << Textline("Number of constraints:") << num_constr_ << '\n' - << Textline("Number of equality constraints:") << num_eqconstr_ << '\n' - << Textline("Number of matrix entries:") << num_entries_ << '\n'; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << "Input\n" + << Textline("Number of variables:") << num_var_ << '\n' + << Textline("Number of free variables:") << num_free_var_ << '\n' + << Textline("Number of constraints:") << num_constr_ << '\n' + << Textline("Number of equality constraints:") << num_eqconstr_ << '\n' + << Textline("Number of matrix entries:") << num_entries_ << '\n'; + control.hLog(h_logging_stream); PrintCoefficientRange(control); ScaleModel(control); @@ -862,10 +865,13 @@ void Model::PrintCoefficientRange(const Control& control) const { } if (amin == INFINITY) // no nonzero entries in A_ amin = 0.0; - control.Log() - << Textline("Matrix range:") - << "[" << Scientific(amin, 5, 0) << ", " - << Scientific(amax, 5, 0) << "]\n"; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << Textline("Matrix range:") + << "[" << Scientific(amin, 5, 0) << ", " + << Scientific(amax, 5, 0) << "]\n"; + control.hLog(h_logging_stream); double rhsmin = INFINITY; double rhsmax = 0.0; @@ -877,10 +883,11 @@ void Model::PrintCoefficientRange(const Control& control) const { } if (rhsmin == INFINITY) // no nonzero entries in rhs rhsmin = 0.0; - control.Log() - << Textline("RHS range:") - << "[" << Scientific(rhsmin, 5, 0) << ", " - << Scientific(rhsmax, 5, 0) << "]\n"; + h_logging_stream + << Textline("RHS range:") + << "[" << Scientific(rhsmin, 5, 0) << ", " + << Scientific(rhsmax, 5, 0) << "]\n"; + control.hLog(h_logging_stream); double objmin = INFINITY; double objmax = 0.0; @@ -892,10 +899,11 @@ void Model::PrintCoefficientRange(const Control& control) const { } if (objmin == INFINITY) // no nonzero entries in obj objmin = 0.0; - control.Log() - << Textline("Objective range:") - << "[" << Scientific(objmin, 5, 0) << ", " - << Scientific(objmax, 5, 0) << "]\n"; + h_logging_stream + << Textline("Objective range:") + << "[" << Scientific(objmin, 5, 0) << ", " + << Scientific(objmax, 5, 0) << "]\n"; + control.hLog(h_logging_stream); double boundmin = INFINITY; double boundmax = 0.0; @@ -913,10 +921,11 @@ void Model::PrintCoefficientRange(const Control& control) const { } if (boundmin == INFINITY) // no finite nonzeros entries in bounds boundmin = 0.0; - control.Log() - << Textline("Bounds range:") - << "[" << Scientific(boundmin, 5, 0) << ", " - << Scientific(boundmax, 5, 0) << "]\n"; + h_logging_stream + << Textline("Bounds range:") + << "[" << Scientific(boundmin, 5, 0) << ", " + << Scientific(boundmax, 5, 0) << "]\n"; + control.hLog(h_logging_stream); } void Model::PrintPreprocessingLog(const Control& control) const { @@ -940,15 +949,19 @@ void Model::PrintPreprocessingLog(const Control& control) const { if (maxscale == 0.0) maxscale = 1.0; - control.Log() - << "Preprocessing\n" - << Textline("Dualized model:") << (dualized() ? "yes" : "no") << '\n' - << Textline("Number of dense columns:") << num_dense_cols() << '\n'; + std::stringstream h_logging_stream; + h_logging_stream.str(std::string()); + h_logging_stream + << "Preprocessing\n" + << Textline("Dualized model:") << (dualized() ? "yes" : "no") << '\n' + << Textline("Number of dense columns:") << num_dense_cols() << '\n'; + control.hLog(h_logging_stream); if (control.scale() > 0) { - control.Log() - << Textline("Range of scaling factors:") << "[" - << Scientific(minscale, 8, 2) << ", " - << Scientific(maxscale, 8, 2) << "]\n"; + h_logging_stream + << Textline("Range of scaling factors:") << "[" + << Scientific(minscale, 8, 2) << ", " + << Scientific(maxscale, 8, 2) << "]\n"; + control.hLog(h_logging_stream); } } @@ -1069,13 +1082,13 @@ void Model::DualizeBasicSolution(const Vector& x_user, if (dualized_) { assert(num_var_ == m); - assert(num_constr_ + (Int)boxed_vars_.size() == n); + assert(num_constr_ + boxed_vars_.size() == static_cast(n)); // Build dual solver variables from primal user variables. y_solver = -x_user; for (Int i = 0; i < num_constr_; i++) z_solver[i] = -slack_user[i]; - for (Int k = 0; k < (Int)boxed_vars_.size(); k++) { + for (size_t k = 0; k < boxed_vars_.size(); k++) { Int j = boxed_vars_[k]; z_solver[num_constr_+k] = c(num_constr_+k) + y_solver[j]; } @@ -1085,7 +1098,7 @@ void Model::DualizeBasicSolution(const Vector& x_user, // Build primal solver variables from dual user variables. std::copy_n(std::begin(y_user), num_constr_, std::begin(x_solver)); std::copy_n(std::begin(z_user), num_var_, std::begin(x_solver) + n); - for (Int k = 0; k < (Int)boxed_vars_.size(); k++) { + for (size_t k = 0; k < boxed_vars_.size(); k++) { Int j = boxed_vars_[k]; if (x_solver[n+j] < 0.0) { // j is a boxed variable and z_user[j] < 0 diff --git a/src/ipm/ipx/model.h b/src/ipm/ipx/model.h index dcc10baa2a..e57fe4bd0e 100644 --- a/src/ipm/ipx/model.h +++ b/src/ipm/ipx/model.h @@ -263,11 +263,12 @@ class Model { // more than 1000 dense columns, then no columns are classified as dense. void FindDenseColumns(); - // Prints the coefficient ranges of input data to control.Log(). Must be - // called after CopyInput() and before ScaleModel(). + // Prints the coefficient ranges of input data via + // control.hLog(). Must be called after CopyInput() and before + // ScaleModel(). void PrintCoefficientRange(const Control& control) const; - // Prints preprocessing operations to control.Log(). + // Prints preprocessing operations via control.hLog(). void PrintPreprocessingLog(const Control& control) const; // Applies the operations from ScaleModel() to a primal-dual point. diff --git a/src/ipm/ipx/multistream.h b/src/ipm/ipx/multistream.h index b4c3ddd39e..8cdff8d138 100644 --- a/src/ipm/ipx/multistream.h +++ b/src/ipm/ipx/multistream.h @@ -37,7 +37,7 @@ class Multistream : public std::ostream { } int overflow(int c) override { for (std::streambuf* b : buffers) - b->sputc(c); + b->sputc(static_cast(c)); return c; } private: diff --git a/src/ipm/ipx/sparse_matrix.cc b/src/ipm/ipx/sparse_matrix.cc index fc82e3fc6a..727af8a4d6 100644 --- a/src/ipm/ipx/sparse_matrix.cc +++ b/src/ipm/ipx/sparse_matrix.cc @@ -4,7 +4,7 @@ #include #include #include "ipm/ipx/utils.h" -#include "pdqsort/pdqsort.h" +#include "../extern/pdqsort/pdqsort.h" namespace ipx { diff --git a/src/ipm/ipx/sparse_utils.h b/src/ipm/ipx/sparse_utils.h index 54bdbc782c..db45600ef7 100644 --- a/src/ipm/ipx/sparse_utils.h +++ b/src/ipm/ipx/sparse_utils.h @@ -16,7 +16,7 @@ namespace ipx { // have previously been unmarked; they are marked now and newtop // is returned. // @marked, @marker Node i is "marked" iff @marked[i] == @marker. -// @work worksapce of size # rows of A. +// @work workspace of size # rows of A. // // The code has been copied and adapted from cs_dfs.c, included in the CSPARSE // package [1]. diff --git a/src/ipm/ipx/splitted_normal_matrix.cc b/src/ipm/ipx/splitted_normal_matrix.cc index a51b91385a..e09a295a52 100644 --- a/src/ipm/ipx/splitted_normal_matrix.cc +++ b/src/ipm/ipx/splitted_normal_matrix.cc @@ -45,11 +45,11 @@ void SplittedNormalMatrix::Prepare(const Basis& basis, const double* colscale) { PermuteRows(N_, rowperm_inv_); // Scale columns of N. - for (Int k = 0; k < (Int)nonbasic_vars.size(); k++) { + for (size_t k = 0; k < nonbasic_vars.size(); k++) { Int j = nonbasic_vars[k]; double d = colscale[j]; assert(std::isfinite(d)); - ScaleColumn(N_, k, d); + ScaleColumn(N_, (Int)k, d); } // Build list of free variables. diff --git a/src/ipm/ipx/utils.cc b/src/ipm/ipx/utils.cc index 5d4d009827..4cd1e3073c 100644 --- a/src/ipm/ipx/utils.cc +++ b/src/ipm/ipx/utils.cc @@ -3,7 +3,7 @@ #include #include #include -#include "pdqsort/pdqsort.h" +#include "../extern/pdqsort/pdqsort.h" namespace ipx { @@ -45,36 +45,33 @@ double Dot(const Vector& x, const Vector& y) { Int FindMaxAbs(const Vector& x) { double xmax = 0.0; - Int imax = 0; - for (Int i = 0; i < (Int)x.size(); i++) { + size_t imax = 0; + for (size_t i = 0; i < x.size(); i++) { if (std::abs(x[i]) > xmax) { xmax = std::abs(x[i]); imax = i; } } - return imax; + return static_cast(imax); } void Permute(const std::vector& permuted_index, const Vector& rhs, Vector& lhs) { - Int m = permuted_index.size(); - for (Int i = 0; i < m; i++) + for (size_t i = 0; i < permuted_index.size(); i++) lhs[permuted_index[i]] = rhs[i]; } void PermuteBack(const std::vector& permuted_index, const Vector& rhs, Vector& lhs) { - Int m = permuted_index.size(); - for (Int i = 0; i < m; i++) + for (size_t i = 0; i < permuted_index.size(); i++) lhs[i] = rhs[permuted_index[i]]; } std::vector InversePerm(const std::vector& perm) { - Int m = perm.size(); - std::vector invperm(m); + std::vector invperm(perm.size()); // at() throws an exception if the index is out of range - for (Int i = 0; i < m; i++) - invperm.at(perm[i]) = i; + for (size_t i = 0; i < perm.size(); i++) + invperm.at(perm[i]) = static_cast(i); return invperm; } diff --git a/src/ipm/ipx/utils.h b/src/ipm/ipx/utils.h index b262f125d9..cdf86b47b0 100644 --- a/src/ipm/ipx/utils.h +++ b/src/ipm/ipx/utils.h @@ -4,6 +4,8 @@ #include #include "ipm/ipx/ipx_internal.h" +const bool kTerminationLogging = false; + namespace ipx { bool AllFinite(const Vector& x); diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index 85d93cc4b2..d7980f4cc8 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -21,11 +21,13 @@ #include "util/HighsInt.h" const std::string kHighsCopyrightStatement = - "Copyright (c) 2023 HiGHS under MIT licence terms"; + "Copyright (c) 2024 HiGHS under MIT licence terms"; const size_t kHighsSize_tInf = std::numeric_limits::max(); const HighsInt kHighsIInf = std::numeric_limits::max(); +const HighsInt kHighsIInf32 = std::numeric_limits::max(); const double kHighsInf = std::numeric_limits::infinity(); +const double kHighsUndefined = kHighsInf; const double kHighsTiny = 1e-14; const double kHighsMacheps = std::ldexp(1, -52); const double kHighsZero = 1e-50; @@ -35,6 +37,10 @@ const std::string kHighsOnString = "on"; const HighsInt kHighsMaxStringLength = 512; const HighsInt kSimplexConcurrencyLimit = 8; const double kRunningAverageMultiplier = 0.05; +const double kExcessivelyLargeBoundValue = 1e10; +const double kExcessivelyLargeCostValue = 1e10; +const double kExcessivelySmallBoundValue = 1e-4; +const double kExcessivelySmallCostValue = 1e-4; const bool kAllowDeveloperAssert = false; const bool kExtendInvertWhenAddingRows = false; @@ -168,6 +174,7 @@ enum class HighsPresolveStatus { kNullError, // V2.0: Delete since it's not used! kOptionsError, // V2.0: Delete since it's not used! kNotSet, + kOutOfMemory, // V2.0: Move above kNotSet }; enum class HighsPostsolveStatus { // V2.0: Delete if not used! @@ -202,19 +209,23 @@ enum class HighsModelStatus { kUnknown, kSolutionLimit, kInterrupt, + kMemoryLimit, kMin = kNotset, - kMax = kInterrupt + kMax = kMemoryLimit }; enum HighsCallbackType : int { kCallbackMin = 0, - kCallbackLogging = kCallbackMin, - kCallbackSimplexInterrupt, - kCallbackIpmInterrupt, - kCallbackMipImprovingSolution, - kCallbackMipLogging, - kCallbackMipInterrupt, - kCallbackMax = kCallbackMipInterrupt, + kCallbackLogging = kCallbackMin, // 0 + kCallbackSimplexInterrupt, // 1 + kCallbackIpmInterrupt, // 2 + kCallbackMipSolution, // 3 + kCallbackMipImprovingSolution, // 4 + kCallbackMipLogging, // 5 + kCallbackMipInterrupt, // 6 + kCallbackMipGetCutPool, // 7 + kCallbackMipDefineLazyConstraints, // 8 + kCallbackMax = kCallbackMipDefineLazyConstraints, kNumCallbackType }; diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 5cb697a15d..e54657d406 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -33,6 +33,7 @@ struct HighsSolution { std::vector col_dual; std::vector row_value; std::vector row_dual; + bool hasUndefined(); void invalidate(); void clear(); }; @@ -117,6 +118,8 @@ struct HighsNameHash { std::unordered_map name2index; void form(const std::vector& name); bool hasDuplicate(const std::vector& name); + void update(int index, const std::string& old_name, + const std::string& new_name); void clear(); }; @@ -131,4 +134,14 @@ struct HighsPresolveLog { void clear(); }; +struct HighsIllConditioningRecord { + HighsInt index; + double multiplier; +}; + +struct HighsIllConditioning { + std::vector record; + void clear(); +}; + #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index c33e6a24fa..ac8196715a 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -16,12 +16,14 @@ #include #include #include +#include #include #include #include #include "io/Filereader.h" #include "io/LoadOptions.h" +#include "lp_data/HighsCallbackStruct.h" #include "lp_data/HighsInfoDebug.h" #include "lp_data/HighsLpSolverObject.h" #include "lp_data/HighsSolve.h" @@ -46,14 +48,9 @@ HighsInt highsVersionMajor() { return HIGHS_VERSION_MAJOR; } HighsInt highsVersionMinor() { return HIGHS_VERSION_MINOR; } HighsInt highsVersionPatch() { return HIGHS_VERSION_PATCH; } const char* highsGithash() { return HIGHS_GITHASH; } -const char* highsCompilationDate() { return HIGHS_COMPILATION_DATE; } +const char* highsCompilationDate() { return "deprecated"; } -void highsSignalHandler(int signum) { - // std::cout << "Interrupt signal (" << signum << ") received.\n"; - exit(signum); -} - -Highs::Highs() { signal(SIGINT, highsSignalHandler); } +Highs::Highs() {} HighsStatus Highs::clear() { resetOptions(); @@ -75,7 +72,7 @@ HighsStatus Highs::clearSolver() { HighsStatus Highs::setOptionValue(const std::string& option, const bool value) { if (setLocalOptionValue(options_.log_options, option, options_.records, value) == OptionStatus::kOk) - return HighsStatus::kOk; + return optionChangeAction(); return HighsStatus::kError; } @@ -83,7 +80,7 @@ HighsStatus Highs::setOptionValue(const std::string& option, const HighsInt value) { if (setLocalOptionValue(options_.log_options, option, options_.records, value) == OptionStatus::kOk) - return HighsStatus::kOk; + return optionChangeAction(); return HighsStatus::kError; } @@ -91,7 +88,7 @@ HighsStatus Highs::setOptionValue(const std::string& option, const double value) { if (setLocalOptionValue(options_.log_options, option, options_.records, value) == OptionStatus::kOk) - return HighsStatus::kOk; + return optionChangeAction(); return HighsStatus::kError; } @@ -100,7 +97,7 @@ HighsStatus Highs::setOptionValue(const std::string& option, HighsLogOptions report_log_options = options_.log_options; if (setLocalOptionValue(report_log_options, option, options_.log_options, options_.records, value) == OptionStatus::kOk) - return HighsStatus::kOk; + return optionChangeAction(); return HighsStatus::kError; } @@ -109,7 +106,7 @@ HighsStatus Highs::setOptionValue(const std::string& option, HighsLogOptions report_log_options = options_.log_options; if (setLocalOptionValue(report_log_options, option, options_.log_options, options_.records, value) == OptionStatus::kOk) - return HighsStatus::kOk; + return optionChangeAction(); return HighsStatus::kError; } @@ -127,19 +124,19 @@ HighsStatus Highs::readOptions(const std::string& filename) { default: break; } - return HighsStatus::kOk; + return optionChangeAction(); } HighsStatus Highs::passOptions(const HighsOptions& options) { if (passLocalOptions(options_.log_options, options, options_) == OptionStatus::kOk) - return HighsStatus::kOk; + return optionChangeAction(); return HighsStatus::kError; } HighsStatus Highs::resetOptions() { resetLocalOptions(options_.records); - return HighsStatus::kOk; + return optionChangeAction(); } HighsStatus Highs::writeOptions(const std::string& filename, @@ -292,12 +289,17 @@ HighsStatus Highs::writeInfo(const std::string& filename) const { return return_status; } -// Methods below change the incumbent model or solver infomation +/** + * @brief Get the size of HighsInt + */ +// HighsInt getSizeofHighsInt() { + +// Methods below change the incumbent model or solver information // associated with it. Hence returnFromHighs is called at the end of // each HighsStatus Highs::passModel(HighsModel model) { // This is the "master" Highs::passModel, in that all the others - // eventually call it + // (and readModel) eventually call it this->logHeader(); // Possibly analyse the LP data if (kHighsAnalysisLevelModelData & options_.highs_analysis_level) @@ -316,6 +318,9 @@ HighsStatus Highs::passModel(HighsModel model) { // rows. Clearly the matrix is empty, so may have no orientation // or starts assigned. HiGHS assumes that such a model will have // null starts, so make it column-wise + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Model has either no columns or no rows, so ignoring user " + "constraint matrix data and initialising empty matrix\n"); lp.a_matrix_.format_ = MatrixFormat::kColwise; lp.a_matrix_.start_.assign(lp.num_col_ + 1, 0); lp.a_matrix_.index_.clear(); @@ -336,12 +341,13 @@ HighsStatus Highs::passModel(HighsModel model) { return HighsStatus::kError; // Check that the Hessian format is valid if (!hessian.formatOk()) return HighsStatus::kError; - // Ensure that the LP is column-wise - lp.ensureColwise(); // Check validity of the LP, normalising its values return_status = interpretCallStatus( options_.log_options, assessLp(lp, options_), return_status, "assessLp"); if (return_status == HighsStatus::kError) return return_status; + // Now legality of matrix is established, ensure that it is + // column-wise + lp.ensureColwise(); // Check validity of any Hessian, normalising its entries return_status = interpretCallStatus(options_.log_options, assessHessian(hessian, options_), @@ -357,11 +363,18 @@ HighsStatus Highs::passModel(HighsModel model) { hessian.clear(); } } + // Ensure that any non-zero Hessian of dimension less than the + // number of columns in the model is completed + if (hessian.dim_) completeHessian(this->model_.lp_.num_col_, hessian); // Clear solver status, solution, basis and info associated with any // previous model; clear any HiGHS model object; create a HiGHS // model object for this LP return_status = interpretCallStatus(options_.log_options, clearSolver(), return_status, "clearSolver"); + // Apply any user scaling in call to optionChangeAction + return_status = + interpretCallStatus(options_.log_options, optionChangeAction(), + return_status, "optionChangeAction"); return returnFromHighs(return_status); } @@ -511,6 +524,24 @@ HighsStatus Highs::passHessian(HighsHessian hessian_) { hessian.clear(); } } + // Ensure that any non-zero Hessian of dimension less than the + // number of columns in the model is completed + if (hessian.dim_) completeHessian(this->model_.lp_.num_col_, hessian); + + if (this->model_.lp_.user_cost_scale_) { + // Assess and apply any user cost scaling + if (!hessian.scaleOk(this->model_.lp_.user_cost_scale_, + this->options_.small_matrix_value, + this->options_.large_matrix_value)) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "User cost scaling yields zeroed or excessive Hessian values\n"); + return HighsStatus::kError; + } + double cost_scale_value = std::pow(2, this->model_.lp_.user_cost_scale_); + for (HighsInt iEl = 0; iEl < hessian.numNz(); iEl++) + hessian.value_[iEl] *= cost_scale_value; + } return_status = interpretCallStatus(options_.log_options, clearSolver(), return_status, "clearSolver"); return returnFromHighs(return_status); @@ -560,8 +591,9 @@ HighsStatus Highs::passColName(const HighsInt col, const std::string& name) { return HighsStatus::kError; } this->model_.lp_.col_names_.resize(num_col); + this->model_.lp_.col_hash_.update(col, this->model_.lp_.col_names_[col], + name); this->model_.lp_.col_names_[col] = name; - this->model_.lp_.col_hash_.clear(); return HighsStatus::kOk; } @@ -580,8 +612,9 @@ HighsStatus Highs::passRowName(const HighsInt row, const std::string& name) { return HighsStatus::kError; } this->model_.lp_.row_names_.resize(num_row); + this->model_.lp_.row_hash_.update(row, this->model_.lp_.row_names_[row], + name); this->model_.lp_.row_names_[row] = name; - this->model_.lp_.row_hash_.clear(); return HighsStatus::kOk; } @@ -651,17 +684,26 @@ HighsStatus Highs::readBasis(const std::string& filename) { } HighsStatus Highs::writeModel(const std::string& filename) { + return writeLocalModel(model_, filename); +} + +HighsStatus Highs::writePresolvedModel(const std::string& filename) { + return writeLocalModel(presolved_model_, filename); +} + +HighsStatus Highs::writeLocalModel(HighsModel& model, + const std::string& filename) { HighsStatus return_status = HighsStatus::kOk; // Ensure that the LP is column-wise - model_.lp_.ensureColwise(); + 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_)) { + 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_)) { + 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); @@ -681,10 +723,10 @@ HighsStatus Highs::writeModel(const std::string& filename) { // Report to user that model is being written highsLogUser(options_.log_options, HighsLogType::kInfo, "Writing the model to %s\n", filename.c_str()); - return_status = interpretCallStatus( - options_.log_options, - writer->writeModelToFile(options_, filename, model_), return_status, - "writeModelToFile"); + return_status = + interpretCallStatus(options_.log_options, + writer->writeModelToFile(options_, filename, model), + return_status, "writeModelToFile"); delete writer; } return returnFromHighs(return_status); @@ -762,7 +804,9 @@ HighsStatus Highs::presolve() { // No reduction, so fill Highs presolved model with the // incumbent model presolved_model_ = model_; - } else if (model_presolve_status_ == HighsPresolveStatus::kReduced) { + } else if (model_presolve_status_ == HighsPresolveStatus::kReduced || + model_presolve_status_ == + HighsPresolveStatus::kReducedToEmpty) { // Nontrivial reduction, so fill Highs presolved model with the // presolved model using_reduced_lp = true; @@ -778,7 +822,10 @@ HighsStatus Highs::presolve() { break; } default: { - // case HighsPresolveStatus::kError + // case HighsPresolveStatus::kOutOfMemory + assert(model_presolve_status_ == HighsPresolveStatus::kOutOfMemory); + highsLogUser(options_.log_options, HighsLogType::kError, + "Presolve fails due to memory allocation error\n"); setHighsModelStatusAndClearSolutionAndBasis( HighsModelStatus::kPresolveError); return_status = HighsStatus::kError; @@ -869,7 +916,13 @@ HighsStatus Highs::run() { return HighsStatus::kError; } - // HiGHS solvers require models with no inifinite costs, and no semi-variables + // Check whether model is consistent with any user bound/cost scaling + assert(this->model_.lp_.user_bound_scale_ == this->options_.user_bound_scale); + assert(this->model_.lp_.user_cost_scale_ == this->options_.user_cost_scale); + // Assess whether to warn the user about excessive bounds and costs + assessExcessiveBoundCost(options_.log_options, this->model_); + + // HiGHS solvers require models with no infinite costs, and no semi-variables // // Since completeSolutionFromDiscreteAssignment() may require a call // to run() - with initial check that called_return_from_run is true @@ -933,8 +986,9 @@ HighsStatus Highs::run() { setHighsModelStatusAndClearSolutionAndBasis(HighsModelStatus::kModelEmpty); return returnFromRun(HighsStatus::kOk, undo_mods); } - // Return immediately if the model is infeasible due to inconsistent bounds - if (isBoundInfeasible(options_.log_options, model_.lp_)) { + // Return immediately if the model is infeasible due to inconsistent + // bounds, modifying any bounds with tiny infeasibilities + if (!infeasibleBoundsOk()) { setHighsModelStatusAndClearSolutionAndBasis(HighsModelStatus::kInfeasible); return returnFromRun(return_status, undo_mods); } @@ -986,7 +1040,8 @@ HighsStatus Highs::run() { return returnFromRun(HighsStatus::kError, undo_mods); } } - const bool use_simplex_or_ipm = options_.solver.compare(kHighsChooseString); + const bool use_simplex_or_ipm = + (options_.solver.compare(kHighsChooseString) != 0); if (!use_simplex_or_ipm) { // Leaving HiGHS to choose method according to model class if (model_.isQp()) { @@ -1117,32 +1172,44 @@ HighsStatus Highs::run() { assert(basis_.valid); } - if (basis_.valid || options_.presolve == kHighsOffString) { - // There is a valid basis for the problem or presolve is off - ekk_instance_.lp_name_ = "LP without presolve or with basis"; - // If there is a valid HiGHS basis, refine any status values that - // are simply HighsBasisStatus::kNonbasic - if (basis_.valid) refineBasis(incumbent_lp, solution_, basis_); - this_solve_original_lp_time = -timer_.read(timer_.solve_clock); + // lambda for Lp solving + auto solveLp = [&](HighsLp& lp, const std::string& lpSolveDescription, + double& time) { + time = -timer_.read(timer_.solve_clock); if (possibly_use_log_dev_level_2) { options_.log_dev_level = use_log_dev_level; options_.output_flag = use_output_flag; } timer_.start(timer_.solve_clock); - call_status = - callSolveLp(incumbent_lp, "Solving LP without presolve or with basis"); + call_status = callSolveLp(lp, lpSolveDescription); timer_.stop(timer_.solve_clock); if (possibly_use_log_dev_level_2) { options_.log_dev_level = log_dev_level; options_.output_flag = output_flag; } - this_solve_original_lp_time += timer_.read(timer_.solve_clock); + time += timer_.read(timer_.solve_clock); + }; + + const bool unconstrained_lp = incumbent_lp.a_matrix_.numNz() == 0; + assert(incumbent_lp.num_row_ || unconstrained_lp); + if (basis_.valid || options_.presolve == kHighsOffString || + unconstrained_lp) { + // There is a valid basis for the problem, presolve is off, or LP + // has no constraint matrix + ekk_instance_.lp_name_ = + "LP without presolve, or with basis, or unconstrained"; + // If there is a valid HiGHS basis, refine any status values that + // are simply HighsBasisStatus::kNonbasic + if (basis_.valid) refineBasis(incumbent_lp, solution_, basis_); + solveLp(incumbent_lp, + "Solving LP without presolve, or with basis, or unconstrained", + this_solve_original_lp_time); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "callSolveLp"); if (return_status == HighsStatus::kError) return returnFromRun(return_status, undo_mods); } else { - // No HiGHS basis so consider presolve + // Otherwise, consider presolve // // If using IPX to solve the reduced LP, but not crossover, set // lp_presolve_requires_basis_postsolve so that presolve can use @@ -1177,20 +1244,8 @@ HighsStatus Highs::run() { switch (model_presolve_status_) { case HighsPresolveStatus::kNotPresolved: { ekk_instance_.lp_name_ = "Original LP"; - this_solve_original_lp_time = -timer_.read(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = use_log_dev_level; - options_.output_flag = use_output_flag; - } - timer_.start(timer_.solve_clock); - call_status = - callSolveLp(incumbent_lp, "Not presolved: solving the LP"); - timer_.stop(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = log_dev_level; - options_.output_flag = output_flag; - } - this_solve_original_lp_time += timer_.read(timer_.solve_clock); + solveLp(incumbent_lp, "Not presolved: solving the LP", + this_solve_original_lp_time); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "callSolveLp"); if (return_status == HighsStatus::kError) @@ -1201,20 +1256,8 @@ HighsStatus Highs::run() { ekk_instance_.lp_name_ = "Unreduced LP"; // Log the presolve reductions reportPresolveReductions(log_options, incumbent_lp, false); - this_solve_original_lp_time = -timer_.read(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = use_log_dev_level; - options_.output_flag = use_output_flag; - } - timer_.start(timer_.solve_clock); - call_status = callSolveLp( - incumbent_lp, "Problem not reduced by presolve: solving the LP"); - timer_.stop(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = log_dev_level; - options_.output_flag = output_flag; - } - this_solve_original_lp_time += timer_.read(timer_.solve_clock); + solveLp(incumbent_lp, "Problem not reduced by presolve: solving the LP", + this_solve_original_lp_time); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "callSolveLp"); if (return_status == HighsStatus::kError) @@ -1227,7 +1270,7 @@ HighsStatus Highs::run() { if (kAllowDeveloperAssert) { // Validate the reduced LP // - // Although preseolve can yield small values in the matrix, + // Although presolve 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 @@ -1261,19 +1304,8 @@ HighsStatus Highs::run() { // objective values aren't correct const double save_objective_bound = options_.objective_bound; options_.objective_bound = kHighsInf; - this_solve_presolved_lp_time = -timer_.read(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = use_log_dev_level; - options_.output_flag = use_output_flag; - } - timer_.start(timer_.solve_clock); - call_status = callSolveLp(reduced_lp, "Solving the presolved LP"); - timer_.stop(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = log_dev_level; - options_.output_flag = output_flag; - } - this_solve_presolved_lp_time += timer_.read(timer_.solve_clock); + solveLp(reduced_lp, "Solving the presolved LP", + this_solve_presolved_lp_time); if (ekk_instance_.status_.initialised_for_solve) { // Record the pivot threshold resulting from solving the presolved LP // with simplex @@ -1327,26 +1359,15 @@ HighsStatus Highs::run() { return returnFromRun(return_status, undo_mods); } // Presolve has returned kUnboundedOrInfeasible, but HiGHS - // can't reurn this. Use primal simplex solver on the original + // can't return this. Use primal simplex solver on the original // LP HighsOptions save_options = options_; options_.solver = "simplex"; options_.simplex_strategy = kSimplexStrategyPrimal; - this_solve_original_lp_time = -timer_.read(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = use_log_dev_level; - options_.output_flag = use_output_flag; - } - timer_.start(timer_.solve_clock); - call_status = callSolveLp(incumbent_lp, - "Solving the original LP with primal simplex " - "to determine infeasible or unbounded"); - timer_.stop(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = log_dev_level; - options_.output_flag = output_flag; - } - this_solve_original_lp_time += timer_.read(timer_.solve_clock); + solveLp(incumbent_lp, + "Solving the original LP with primal simplex " + "to determine infeasible or unbounded", + this_solve_original_lp_time); // Recover the options options_ = save_options; if (return_status == HighsStatus::kError) @@ -1362,15 +1383,15 @@ HighsStatus Highs::run() { case HighsPresolveStatus::kTimeout: { setHighsModelStatusAndClearSolutionAndBasis( HighsModelStatus::kTimeLimit); - highsLogDev(log_options, HighsLogType::kError, + highsLogDev(log_options, HighsLogType::kWarning, "Presolve reached timeout\n"); return returnFromRun(HighsStatus::kWarning, undo_mods); } - case HighsPresolveStatus::kOptionsError: { + case HighsPresolveStatus::kOutOfMemory: { setHighsModelStatusAndClearSolutionAndBasis( - HighsModelStatus::kPresolveError); - highsLogDev(log_options, HighsLogType::kError, - "Presolve options error\n"); + HighsModelStatus::kMemoryLimit); + highsLogUser(options_.log_options, HighsLogType::kError, + "Presolve fails due to memory allocation error\n"); return returnFromRun(HighsStatus::kError, undo_mods); } default: { @@ -1384,6 +1405,9 @@ HighsStatus Highs::run() { } } // End of presolve + // + // Cases of infeasibility/unboundedness timeout and memory errors + // all handled, so just the successes remain assert(model_presolve_status_ == HighsPresolveStatus::kNotPresolved || model_presolve_status_ == HighsPresolveStatus::kNotReduced || model_presolve_status_ == HighsPresolveStatus::kReduced || @@ -1478,23 +1502,11 @@ HighsStatus Highs::run() { // adding the corresponding values after callSolveLp gives // difference postsolve_iteration_count = -info_.simplex_iteration_count; - this_solve_original_lp_time = -timer_.read(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = use_log_dev_level; - options_.output_flag = use_output_flag; - } - timer_.start(timer_.solve_clock); - call_status = callSolveLp( - incumbent_lp, - "Solving the original LP from the solution after postsolve"); - timer_.stop(timer_.solve_clock); - if (possibly_use_log_dev_level_2) { - options_.log_dev_level = log_dev_level; - options_.output_flag = output_flag; - } - // Determine the iteration count and timing records + solveLp(incumbent_lp, + "Solving the original LP from the solution after postsolve", + this_solve_original_lp_time); + // Determine the iteration count postsolve_iteration_count += info_.simplex_iteration_count; - this_solve_original_lp_time += timer_.read(timer_.solve_clock); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "callSolveLp"); @@ -1522,7 +1534,7 @@ HighsStatus Highs::run() { if (no_incumbent_lp_solution_or_basis) { // In solving the (strictly reduced) presolved LP, it is found to // be infeasible or unbounded, the time/iteration limit has been - // reached, a user interrupt has ocurred, or the status is unknown + // reached, a user interrupt has occurred, or the status is unknown // (cycling) // // Hence there's no incumbent lp solution or basis to drive dual @@ -1606,7 +1618,8 @@ HighsStatus Highs::run() { // something worse has happened earlier call_status = highsStatusFromHighsModelStatus(model_status_); return_status = - interpretCallStatus(options_.log_options, call_status, return_status); + interpretCallStatus(options_.log_options, call_status, return_status, + "highsStatusFromHighsModelStatus"); return returnFromRun(return_status, undo_mods); } @@ -1653,6 +1666,19 @@ HighsStatus Highs::getRanging(HighsRanging& ranging) { return return_status; } +HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, + const bool constraint, + const HighsInt method, + const double ill_conditioning_bound) { + if (!basis_.valid) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Cannot get ill-conditioning without a valid basis\n"); + return HighsStatus::kError; + } + return computeIllConditioning(ill_conditioning, constraint, method, + ill_conditioning_bound); +} + bool Highs::hasInvert() const { return ekk_instance_.status_.has_invert; } const HighsInt* Highs::getBasicVariablesArray() const { @@ -1797,7 +1823,6 @@ HighsStatus Highs::getBasisTransposeSolve(const double* Xrhs, HighsStatus Highs::getReducedRow(const HighsInt row, double* row_vector, HighsInt* row_num_nz, HighsInt* row_indices, const double* pass_basis_inverse_row_vector) { - HighsStatus return_status = HighsStatus::kOk; HighsLp& lp = model_.lp_; // Ensure that the LP is column-wise lp.ensureColwise(); @@ -1852,7 +1877,6 @@ HighsStatus Highs::getReducedRow(const HighsInt row, double* row_vector, HighsStatus Highs::getReducedColumn(const HighsInt col, double* col_vector, HighsInt* col_num_nz, HighsInt* col_indices) { - HighsStatus return_status = HighsStatus::kOk; HighsLp& lp = model_.lp_; // Ensure that the LP is column-wise lp.ensureColwise(); @@ -1889,13 +1913,26 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { // the old solution and any basis are cleared const bool new_primal_solution = model_.lp_.num_col_ > 0 && - (HighsInt)solution.col_value.size() >= model_.lp_.num_col_; + solution.col_value.size() >= static_cast(model_.lp_.num_col_); const bool new_dual_solution = model_.lp_.num_row_ > 0 && - (HighsInt)solution.row_dual.size() >= model_.lp_.num_row_; + solution.row_dual.size() >= static_cast(model_.lp_.num_row_); const bool new_solution = new_primal_solution || new_dual_solution; - if (new_solution) invalidateUserSolverData(); + if (new_solution) { + invalidateUserSolverData(); + } else { + // Solution is rejected, so give a logging message and error + // return + highsLogUser( + options_.log_options, HighsLogType::kError, + "setSolution: User solution is rejected due to mismatch between " + "size of col_value and row_dual vectors (%d, %d) and number " + "of columns and rows in the model (%d, %d)\n", + int(solution.col_value.size()), int(solution.row_dual.size()), + int(model_.lp_.num_col_), int(model_.lp_.num_row_)); + return_status = HighsStatus::kError; + } if (new_primal_solution) { solution_.col_value = solution.col_value; @@ -1905,8 +1942,8 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { // Matrix must be column-wise model_.lp_.a_matrix_.ensureColwise(); return_status = interpretCallStatus( - options_.log_options, calculateRowValues(model_.lp_, solution_), - return_status, "calculateRowValues"); + options_.log_options, calculateRowValuesQuad(model_.lp_, solution_), + return_status, "calculateRowValuesQuad"); if (return_status == HighsStatus::kError) return return_status; } solution_.value_valid = true; @@ -1919,7 +1956,7 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { // Matrix must be column-wise model_.lp_.a_matrix_.ensureColwise(); return_status = interpretCallStatus( - options_.log_options, calculateColDuals(model_.lp_, solution_), + options_.log_options, calculateColDualsQuad(model_.lp_, solution_), return_status, "calculateColDuals"); if (return_status == HighsStatus::kError) return return_status; } @@ -1928,10 +1965,58 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { return returnFromHighs(return_status); } -HighsStatus Highs::setCallback( - void (*user_callback)(const int, const char*, const HighsCallbackDataOut*, - HighsCallbackDataIn*, void*), - void* user_callback_data) { +HighsStatus Highs::setSolution(const HighsInt num_entries, + const HighsInt* index, const double* value) { + HighsStatus return_status = HighsStatus::kOk; + // Warn about duplicates in index + HighsInt num_duplicates = 0; + std::vector is_set; + is_set.assign(model_.lp_.num_col_, false); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + if (iCol < 0 || iCol > model_.lp_.num_col_) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution index %d has value %d out of " + "range [0, %d)", + int(iX), int(iCol), int(model_.lp_.num_col_)); + return HighsStatus::kError; + } else if (value[iX] < model_.lp_.col_lower_[iCol] - + options_.primal_feasibility_tolerance || + model_.lp_.col_upper_[iCol] + + options_.primal_feasibility_tolerance < + value[iX]) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution value %d of %g is infeasible " + "for bounds [%g, %g]", + int(iX), value[iX], model_.lp_.col_lower_[iCol], + model_.lp_.col_upper_[iCol]); + return HighsStatus::kError; + } + if (is_set[iCol]) num_duplicates++; + is_set[iCol] = true; + } + if (num_duplicates > 0) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "setSolution: User set of indices has %d duplicate%s: last " + "value used\n", + int(num_duplicates), num_duplicates > 1 ? "s" : ""); + return_status = HighsStatus::kWarning; + } + + // Clear the solution, indicate the values not determined by the + // user, and insert the values determined by the user + HighsSolution new_solution; + new_solution.col_value.assign(model_.lp_.num_col_, kHighsUndefined); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + new_solution.col_value[iCol] = value[iX]; + } + return interpretCallStatus(options_.log_options, setSolution(new_solution), + return_status, "setSolution"); +} + +HighsStatus Highs::setCallback(HighsCallbackFunctionType user_callback, + void* user_callback_data) { this->callback_.clear(); this->callback_.user_callback = user_callback; this->callback_.user_callback_data = user_callback_data; @@ -1942,6 +2027,21 @@ HighsStatus Highs::setCallback( return HighsStatus::kOk; } +HighsStatus Highs::setCallback(HighsCCallbackType c_callback, + void* user_callback_data) { + this->callback_.clear(); + this->callback_.user_callback = + [c_callback](int a, const std::string& b, const HighsCallbackDataOut* c, + HighsCallbackDataIn* d, + void* e) { c_callback(a, b.c_str(), c, d, e); }; + this->callback_.user_callback_data = user_callback_data; + + options_.log_options.user_callback = this->callback_.user_callback; + options_.log_options.user_callback_data = this->callback_.user_callback_data; + options_.log_options.user_callback_active = false; + return HighsStatus::kOk; +} + HighsStatus Highs::startCallback(const int callback_type) { const bool callback_type_ok = callback_type >= kCallbackMin && callback_type <= kCallbackMax; @@ -1960,6 +2060,24 @@ HighsStatus Highs::startCallback(const int callback_type) { return HighsStatus::kOk; } +HighsStatus Highs::startCallback(const HighsCallbackType callback_type) { + const bool callback_type_ok = + callback_type >= kCallbackMin && callback_type <= kCallbackMax; + assert(callback_type_ok); + if (!callback_type_ok) return HighsStatus::kError; + if (!this->callback_.user_callback) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Cannot start callback when user_callback not defined\n"); + return HighsStatus::kError; + } + assert(int(this->callback_.active.size()) == kNumCallbackType); + this->callback_.active[callback_type] = true; + // Possibly modify the logging callback activity + if (callback_type == kCallbackLogging) + options_.log_options.user_callback_active = true; + return HighsStatus::kOk; +} + HighsStatus Highs::stopCallback(const int callback_type) { const bool callback_type_ok = callback_type >= kCallbackMin && callback_type <= kCallbackMax; @@ -1970,7 +2088,24 @@ HighsStatus Highs::stopCallback(const int callback_type) { "Cannot stop callback when user_callback not defined\n"); return HighsStatus::kWarning; } - std::vector& active = this->callback_.active; + assert(int(this->callback_.active.size()) == kNumCallbackType); + this->callback_.active[callback_type] = false; + // Possibly modify the logging callback activity + if (callback_type == kCallbackLogging) + options_.log_options.user_callback_active = false; + return HighsStatus::kOk; +} + +HighsStatus Highs::stopCallback(const HighsCallbackType callback_type) { + const bool callback_type_ok = + callback_type >= kCallbackMin && callback_type <= kCallbackMax; + assert(callback_type_ok); + if (!callback_type_ok) return HighsStatus::kError; + if (!this->callback_.user_callback) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "Cannot stop callback when user_callback not defined\n"); + return HighsStatus::kWarning; + } assert(int(this->callback_.active.size()) == kNumCallbackType); this->callback_.active[callback_type] = false; // Possibly modify the logging callback activity @@ -1994,6 +2129,17 @@ HighsStatus Highs::setBasis(const HighsBasis& basis, : basis.col_status[iCol]; basis_.alien = false; } else { + // Check whether a new basis can be defined + if (!isBasisRightSize(model_.lp_, basis)) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "setBasis: User basis is rejected due to mismatch between " + "size of column and row status vectors (%d, %d) and number " + "of columns and rows in the model (%d, %d)\n", + int(basis_.col_status.size()), int(basis_.row_status.size()), + int(model_.lp_.num_col_), int(model_.lp_.num_row_)); + return HighsStatus::kError; + } HighsBasis modifiable_basis = basis; modifiable_basis.was_alien = true; HighsLpSolverObject solver_object(model_.lp_, modifiable_basis, solution_, @@ -2206,10 +2352,13 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt from_col, const HighsVarType* integrality) { clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, from_col, to_col, model_.lp_.num_col_)) { - highsLogUser( - options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::changeColsIntegrality is out of range\n"); + const HighsInt create_error = + create(index_collection, from_col, to_col, model_.lp_.num_col_); + if (create_error) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Interval [%d, %d) supplied to Highs::changeColsIntegrality " + "is out of range [0, %d)\n", + int(from_col), int(to_col), int(model_.lp_.num_col_)); return HighsStatus::kError; } HighsStatus call_status = @@ -2221,10 +2370,44 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt from_col, return returnFromHighs(return_status); } +static HighsStatus analyseSetCreateError(HighsLogOptions log_options, + const std::string method, + const HighsInt create_error, + const bool ordered, + const HighsInt num_set_entries, + const HighsInt dimension) { + if (create_error == kIndexCollectionCreateIllegalSetSize) { + highsLogUser(log_options, HighsLogType::kError, + "Set supplied to Highs::%s has illegal size of %d\n", + method.c_str(), int(num_set_entries)); + } else if (create_error == kIndexCollectionCreateIllegalSetOrder) { + if (ordered) { + // Creating an index_collection data structure for the set + // includes a test that the indices increase strictly. If this + // is not the case then, since an increasing set was created + // locally, it must contain duplicate entries. return with an + // error + highsLogUser(log_options, HighsLogType::kError, + "Set supplied to Highs::%s contains duplicate entries\n", + method.c_str()); + } else { + highsLogUser(log_options, HighsLogType::kError, + "Set supplied to Highs::%s not ordered\n", method.c_str()); + } + } else if (create_error < 0) { + highsLogUser( + log_options, HighsLogType::kError, + "Set supplied to Highs::%s has entry %d out of range [0, %d)\n", + method.c_str(), int(-1 - create_error), int(dimension)); + } + assert(create_error != kIndexCollectionCreateIllegalSetDimension); + return HighsStatus::kError; +} + HighsStatus Highs::changeColsIntegrality(const HighsInt num_set_entries, const HighsInt* set, const HighsVarType* integrality) { - if (num_set_entries <= 0) return HighsStatus::kOk; + if (num_set_entries == 0) return HighsStatus::kOk; clearPresolve(); // Ensure that the set and data are in ascending order std::vector local_integrality{integrality, @@ -2233,9 +2416,12 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt num_set_entries, 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.data(), model_.lp_.num_col_); - assert(create_ok); + const HighsInt create_error = create(index_collection, num_set_entries, + local_set.data(), model_.lp_.num_col_); + if (create_error) + return analyseSetCreateError(options_.log_options, "changeColsIntegrality", + create_error, true, num_set_entries, + model_.lp_.num_col_); HighsStatus call_status = changeIntegralityInterface(index_collection, local_integrality.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2249,7 +2435,9 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt* mask, const HighsVarType* integrality) { clearPresolve(); HighsIndexCollection index_collection; - create(index_collection, mask, model_.lp_.num_col_); + const bool create_error = create(index_collection, mask, model_.lp_.num_col_); + assert(!create_error); + (void)create_error; HighsStatus call_status = changeIntegralityInterface(index_collection, integrality); HighsStatus return_status = HighsStatus::kOk; @@ -2267,10 +2455,13 @@ HighsStatus Highs::changeColsCost(const HighsInt from_col, const HighsInt to_col, const double* cost) { clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, from_col, to_col, model_.lp_.num_col_)) { - highsLogUser( - options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::changeColsCost is out of range\n"); + const HighsInt create_error = + create(index_collection, from_col, to_col, model_.lp_.num_col_); + if (create_error) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Interval [%d, %d) supplied to Highs::changeColsCost is out " + "of range [0, %d)\n", + int(from_col), int(to_col), int(model_.lp_.num_col_)); return HighsStatus::kError; } HighsStatus call_status = changeCostsInterface(index_collection, cost); @@ -2283,7 +2474,7 @@ HighsStatus Highs::changeColsCost(const HighsInt from_col, HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, const HighsInt* set, const double* cost) { - if (num_set_entries <= 0) return HighsStatus::kOk; + if (num_set_entries == 0) return HighsStatus::kOk; // Check for NULL data in "set" version of changeColsCost since // values are sorted with set if (doubleUserDataNotNull(options_.log_options, cost, "column costs")) @@ -2295,9 +2486,12 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, 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.data(), model_.lp_.num_col_); - assert(create_ok); + const HighsInt create_error = create(index_collection, num_set_entries, + local_set.data(), model_.lp_.num_col_); + if (create_error) + return analyseSetCreateError(options_.log_options, "changeColsCost", + create_error, true, num_set_entries, + model_.lp_.num_col_); HighsStatus call_status = changeCostsInterface(index_collection, local_cost.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2310,7 +2504,9 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, HighsStatus Highs::changeColsCost(const HighsInt* mask, const double* cost) { clearPresolve(); HighsIndexCollection index_collection; - create(index_collection, mask, model_.lp_.num_col_); + const bool create_error = create(index_collection, mask, model_.lp_.num_col_); + assert(!create_error); + (void)create_error; HighsStatus call_status = changeCostsInterface(index_collection, cost); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, @@ -2329,10 +2525,13 @@ HighsStatus Highs::changeColsBounds(const HighsInt from_col, const double* upper) { clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, from_col, to_col, model_.lp_.num_col_)) { - highsLogUser( - options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::changeColsBounds is out of range\n"); + const HighsInt create_error = + create(index_collection, from_col, to_col, model_.lp_.num_col_); + if (create_error) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Interval [%d, %d) supplied to Highs::changeColsBounds is out " + "of range [0, %d)\n", + int(from_col), int(to_col), int(model_.lp_.num_col_)); return HighsStatus::kError; } HighsStatus call_status = @@ -2347,7 +2546,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt from_col, HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, const HighsInt* set, const double* lower, const double* upper) { - if (num_set_entries <= 0) return HighsStatus::kOk; + if (num_set_entries == 0) return HighsStatus::kOk; // Check for NULL data in "set" version of changeColsBounds since // values are sorted with set bool null_data = false; @@ -2366,9 +2565,12 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, 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.data(), model_.lp_.num_col_); - assert(create_ok); + const HighsInt create_error = create(index_collection, num_set_entries, + local_set.data(), model_.lp_.num_col_); + if (create_error) + return analyseSetCreateError(options_.log_options, "changeColsBounds", + create_error, true, num_set_entries, + model_.lp_.num_col_); HighsStatus call_status = changeColBoundsInterface( index_collection, local_lower.data(), local_upper.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2382,7 +2584,9 @@ HighsStatus Highs::changeColsBounds(const HighsInt* mask, const double* lower, const double* upper) { clearPresolve(); HighsIndexCollection index_collection; - create(index_collection, mask, model_.lp_.num_col_); + const bool create_error = create(index_collection, mask, model_.lp_.num_col_); + assert(!create_error); + (void)create_error; HighsStatus call_status = changeColBoundsInterface(index_collection, lower, upper); HighsStatus return_status = HighsStatus::kOk; @@ -2402,10 +2606,13 @@ HighsStatus Highs::changeRowsBounds(const HighsInt from_row, const double* upper) { clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, from_row, to_row, model_.lp_.num_row_)) { - highsLogUser( - options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::changeRowsBounds is out of range\n"); + const HighsInt create_error = + create(index_collection, from_row, to_row, model_.lp_.num_row_); + if (create_error) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Interval [%d, %d) supplied to Highs::changeRowsBounds is out " + "of range [0, %d)\n", + int(from_row), int(to_row), int(model_.lp_.num_row_)); return HighsStatus::kError; } HighsStatus call_status = @@ -2420,7 +2627,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt from_row, HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, const HighsInt* set, const double* lower, const double* upper) { - if (num_set_entries <= 0) return HighsStatus::kOk; + if (num_set_entries == 0) return HighsStatus::kOk; // Check for NULL data in "set" version of changeRowsBounds since // values are sorted with set bool null_data = false; @@ -2439,9 +2646,12 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, 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.data(), model_.lp_.num_row_); - assert(create_ok); + const HighsInt create_error = create(index_collection, num_set_entries, + local_set.data(), model_.lp_.num_row_); + if (create_error) + return analyseSetCreateError(options_.log_options, "changeRowsBounds", + create_error, true, num_set_entries, + model_.lp_.num_row_); HighsStatus call_status = changeRowBoundsInterface( index_collection, local_lower.data(), local_upper.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2455,7 +2665,9 @@ HighsStatus Highs::changeRowsBounds(const HighsInt* mask, const double* lower, const double* upper) { clearPresolve(); HighsIndexCollection index_collection; - create(index_collection, mask, model_.lp_.num_row_); + const bool create_error = create(index_collection, mask, model_.lp_.num_row_); + assert(!create_error); + (void)create_error; HighsStatus call_status = changeRowBoundsInterface(index_collection, lower, upper); HighsStatus return_status = HighsStatus::kOk; @@ -2508,10 +2720,20 @@ HighsStatus Highs::getCols(const HighsInt from_col, const HighsInt to_col, HighsInt& num_col, double* costs, double* lower, double* upper, HighsInt& num_nz, HighsInt* start, HighsInt* index, double* value) { + if (from_col > to_col) { + // Empty interval + num_col = 0; + num_nz = 0; + return HighsStatus::kOk; + } HighsIndexCollection index_collection; - if (!create(index_collection, from_col, to_col, model_.lp_.num_col_)) { + const HighsInt create_error = + create(index_collection, from_col, to_col, model_.lp_.num_col_); + if (create_error) { highsLogUser(options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::getCols is out of range\n"); + "Interval [%d, %d) supplied to Highs::getCols is out of range " + "[0, %d)\n", + int(from_col), int(to_col), int(model_.lp_.num_col_)); return HighsStatus::kError; } getColsInterface(index_collection, num_col, costs, lower, upper, num_nz, @@ -2523,13 +2745,18 @@ HighsStatus Highs::getCols(const HighsInt num_set_entries, const HighsInt* set, HighsInt& num_col, double* costs, double* lower, double* upper, HighsInt& num_nz, HighsInt* start, HighsInt* index, double* value) { - if (num_set_entries <= 0) return HighsStatus::kOk; - HighsIndexCollection index_collection; - if (!create(index_collection, num_set_entries, set, model_.lp_.num_col_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Set supplied to Highs::getCols not ordered\n"); - return HighsStatus::kError; + if (num_set_entries == 0) { + // Empty interval + num_col = 0; + num_nz = 0; + return HighsStatus::kOk; } + HighsIndexCollection index_collection; + const HighsInt create_error = + create(index_collection, num_set_entries, set, model_.lp_.num_col_); + if (create_error) + return analyseSetCreateError(options_.log_options, "getCols", create_error, + false, num_set_entries, model_.lp_.num_col_); getColsInterface(index_collection, num_col, costs, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2540,7 +2767,9 @@ HighsStatus Highs::getCols(const HighsInt* mask, HighsInt& num_col, HighsInt& num_nz, HighsInt* start, HighsInt* index, double* value) { HighsIndexCollection index_collection; - create(index_collection, mask, model_.lp_.num_col_); + const bool create_error = create(index_collection, mask, model_.lp_.num_col_); + assert(!create_error); + (void)create_error; getColsInterface(index_collection, num_col, costs, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2597,7 +2826,7 @@ HighsStatus Highs::getColIntegrality(const HighsInt col, int(col), int(num_col)); return HighsStatus::kError; } - if (col < int(this->model_.lp_.integrality_.size())) { + if (static_cast(col) < this->model_.lp_.integrality_.size()) { integrality = this->model_.lp_.integrality_[col]; return HighsStatus::kOk; } else { @@ -2611,10 +2840,20 @@ 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, double* value) { + if (from_row > to_row) { + // Empty interval + num_row = 0; + num_nz = 0; + return HighsStatus::kOk; + } HighsIndexCollection index_collection; - if (!create(index_collection, from_row, to_row, model_.lp_.num_row_)) { + const HighsInt create_error = + create(index_collection, from_row, to_row, model_.lp_.num_row_); + if (create_error) { highsLogUser(options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::getRows is out of range\n"); + "Interval [%d, %d) supplied to Highs::getRows is out of range " + "[0, %d)\n", + int(from_row), int(to_row), int(model_.lp_.num_row_)); return HighsStatus::kError; } getRowsInterface(index_collection, num_row, lower, upper, num_nz, start, @@ -2626,13 +2865,17 @@ HighsStatus Highs::getRows(const HighsInt num_set_entries, const HighsInt* set, HighsInt& num_row, double* lower, double* upper, HighsInt& num_nz, HighsInt* start, HighsInt* index, double* value) { - if (num_set_entries <= 0) return HighsStatus::kOk; - HighsIndexCollection index_collection; - if (!create(index_collection, num_set_entries, set, model_.lp_.num_row_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Set supplied to Highs::getRows is not ordered\n"); - return HighsStatus::kError; + if (num_set_entries == 0) { + num_row = 0; + num_nz = 0; + return HighsStatus::kOk; } + HighsIndexCollection index_collection; + const HighsInt create_error = + create(index_collection, num_set_entries, set, model_.lp_.num_row_); + if (create_error) + return analyseSetCreateError(options_.log_options, "getRows", create_error, + false, num_set_entries, model_.lp_.num_row_); getRowsInterface(index_collection, num_row, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2642,7 +2885,9 @@ HighsStatus Highs::getRows(const HighsInt* mask, HighsInt& num_row, double* lower, double* upper, HighsInt& num_nz, HighsInt* start, HighsInt* index, double* value) { HighsIndexCollection index_collection; - create(index_collection, mask, model_.lp_.num_row_); + const bool create_error = create(index_collection, mask, model_.lp_.num_row_); + assert(!create_error); + (void)create_error; getRowsInterface(index_collection, num_row, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2716,9 +2961,13 @@ HighsStatus Highs::getCoeff(const HighsInt row, const HighsInt col, HighsStatus Highs::deleteCols(const HighsInt from_col, const HighsInt to_col) { clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, from_col, to_col, model_.lp_.num_col_)) { + const HighsInt create_error = + create(index_collection, from_col, to_col, model_.lp_.num_col_); + if (create_error) { highsLogUser(options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::deleteCols is out of range\n"); + "Interval [%d, %d) supplied to Highs::deleteCols is out of " + "range [0, %d)\n", + int(from_col), int(to_col), int(model_.lp_.num_col_)); return HighsStatus::kError; } deleteColsInterface(index_collection); @@ -2727,14 +2976,15 @@ HighsStatus Highs::deleteCols(const HighsInt from_col, const HighsInt to_col) { HighsStatus Highs::deleteCols(const HighsInt num_set_entries, const HighsInt* set) { - if (num_set_entries <= 0) return HighsStatus::kOk; + if (num_set_entries == 0) return HighsStatus::kOk; clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, num_set_entries, set, model_.lp_.num_col_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Set supplied to Highs::deleteCols is not ordered\n"); - return HighsStatus::kError; - } + const HighsInt create_error = + create(index_collection, num_set_entries, set, model_.lp_.num_col_); + if (create_error) + return analyseSetCreateError(options_.log_options, "deleteCols", + create_error, false, num_set_entries, + model_.lp_.num_col_); deleteColsInterface(index_collection); return returnFromHighs(HighsStatus::kOk); } @@ -2743,7 +2993,9 @@ HighsStatus Highs::deleteCols(HighsInt* mask) { clearPresolve(); const HighsInt original_num_col = model_.lp_.num_col_; HighsIndexCollection index_collection; - create(index_collection, mask, original_num_col); + const bool create_error = create(index_collection, mask, original_num_col); + assert(!create_error); + (void)create_error; deleteColsInterface(index_collection); for (HighsInt iCol = 0; iCol < original_num_col; iCol++) mask[iCol] = index_collection.mask_[iCol]; @@ -2753,9 +3005,13 @@ HighsStatus Highs::deleteCols(HighsInt* mask) { HighsStatus Highs::deleteRows(const HighsInt from_row, const HighsInt to_row) { clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, from_row, to_row, model_.lp_.num_row_)) { + const HighsInt create_error = + create(index_collection, from_row, to_row, model_.lp_.num_row_); + if (create_error) { highsLogUser(options_.log_options, HighsLogType::kError, - "Interval supplied to Highs::deleteRows is out of range\n"); + "Interval [%d, %d) supplied to Highs::deleteRows is out of " + "range [0, %d)\n", + int(from_row), int(to_row), int(model_.lp_.num_row_)); return HighsStatus::kError; } deleteRowsInterface(index_collection); @@ -2764,14 +3020,15 @@ HighsStatus Highs::deleteRows(const HighsInt from_row, const HighsInt to_row) { HighsStatus Highs::deleteRows(const HighsInt num_set_entries, const HighsInt* set) { - if (num_set_entries <= 0) return HighsStatus::kOk; + if (num_set_entries == 0) return HighsStatus::kOk; clearPresolve(); HighsIndexCollection index_collection; - if (!create(index_collection, num_set_entries, set, model_.lp_.num_row_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Set supplied to Highs::deleteRows is not ordered\n"); - return HighsStatus::kError; - } + const HighsInt create_error = + create(index_collection, num_set_entries, set, model_.lp_.num_row_); + if (create_error) + return analyseSetCreateError(options_.log_options, "deleteRows", + create_error, false, num_set_entries, + model_.lp_.num_row_); deleteRowsInterface(index_collection); return returnFromHighs(HighsStatus::kOk); } @@ -2780,7 +3037,9 @@ HighsStatus Highs::deleteRows(HighsInt* mask) { clearPresolve(); const HighsInt original_num_row = model_.lp_.num_row_; HighsIndexCollection index_collection; - create(index_collection, mask, original_num_row); + const bool create_error = create(index_collection, mask, original_num_row); + assert(!create_error); + (void)create_error; deleteRowsInterface(index_collection); for (HighsInt iRow = 0; iRow < original_num_row; iRow++) mask[iRow] = index_collection.mask_[iRow]; @@ -2816,9 +3075,11 @@ HighsStatus Highs::postsolve(const HighsSolution& solution, const HighsBasis& basis) { const bool can_run_postsolve = model_presolve_status_ == HighsPresolveStatus::kNotPresolved || + model_presolve_status_ == HighsPresolveStatus::kNotReduced || model_presolve_status_ == HighsPresolveStatus::kReduced || model_presolve_status_ == HighsPresolveStatus::kReducedToEmpty || - model_presolve_status_ == HighsPresolveStatus::kTimeout; + model_presolve_status_ == HighsPresolveStatus::kTimeout || + model_presolve_status_ == HighsPresolveStatus::kOutOfMemory; if (!can_run_postsolve) { highsLogUser(options_.log_options, HighsLogType::kWarning, "Cannot run postsolve with presolve status: %s\n", @@ -2854,7 +3115,7 @@ HighsStatus Highs::writeSolution(const std::string& filename, if (options_.ranging == kHighsOnString) { if (model_.isMip() || model_.isQp()) { highsLogUser(options_.log_options, HighsLogType::kError, - "Cannot determing ranging information for MIP or QP\n"); + "Cannot determine ranging information for MIP or QP\n"); return_status = HighsStatus::kError; return returnFromWriteSolution(file, return_status); } @@ -2899,10 +3160,8 @@ std::string Highs::presolveStatusToString( return "Reduced to empty"; case HighsPresolveStatus::kTimeout: return "Timeout"; - case HighsPresolveStatus::kNullError: - return "Null error"; - case HighsPresolveStatus::kOptionsError: - return "Options error"; + case HighsPresolveStatus::kOutOfMemory: + return "Memory allocation error"; default: assert(1 == 0); return "Unrecognised presolve status"; @@ -2968,7 +3227,8 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, if (original_lp.num_col_ == 0 && original_lp.num_row_ == 0) return HighsPresolveStatus::kNullError; - // Clear info from previous runs if original_lp has been modified. + // Ensure that the RunHighsClock is running + if (!timer_.runningRunHighsClock()) timer_.startRunHighsClock(); double start_presolve = timer_.readRunHighsClock(); // Set time limit. @@ -2995,7 +3255,7 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, // Presolved model is extracted now since it's part of solver, // which is lost on return HighsMipSolver solver(callback_, options_, original_lp, solution_); - solver.runPresolve(); + solver.runPresolve(options_.presolve_reduction_limit); presolve_return_status = solver.getPresolveStatus(); // Assign values to data members of presolve_ presolve_.data_.reduced_lp_ = solver.getPresolvedModel(); @@ -3055,6 +3315,9 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, default: break; } + // Presolve creates integrality vector for an LP, so clear it + if (!model_.isMip()) presolve_.data_.reduced_lp_.integrality_.clear(); + return presolve_return_status; } @@ -3135,62 +3398,120 @@ void Highs::invalidateEkk() { ekk_instance_.invalidate(); } HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // Determine whether the current solution of a MIP is feasible and, - // if not, try to assign values to continous variables to achieve a - // feasible solution. Valuable in the case where users make a - // heuristic assignment of discrete variables + // if not, try to assign values to continuous variables and discrete + // variables not at integer values to achieve a feasible + // solution. Valuable in the case where users make a heuristic + // (partial) assignment of discrete variables assert(model_.isMip() && solution_.value_valid); HighsLp& lp = model_.lp_; - bool valid, integral, feasible; - // Determine whether this solution is feasible, or just integer feasible - HighsStatus return_status = assessLpPrimalSolution(options_, lp, solution_, - valid, integral, feasible); - assert(return_status != HighsStatus::kError); - assert(valid); - // If the current solution is feasible, then solution can be used by - // MIP solver to get a primal bound - if (feasible) return HighsStatus::kOk; + // Determine whether the solution contains undefined values, in + // order to decide whether to check its feasibility + const bool contains_undefined_values = solution_.hasUndefined(); + if (!contains_undefined_values) { + bool valid, integral, feasible; + // Determine whether this solution is integer feasible + HighsStatus return_status = assessLpPrimalSolution( + options_, lp, solution_, valid, integral, feasible); + assert(return_status != HighsStatus::kError); + assert(valid); + // If the current solution is integer feasible, then it can be + // used by MIP solver to get a primal bound + if (feasible) return HighsStatus::kOk; + } // Save the column bounds and integrality in preparation for fixing - // the non-continuous variables when user-supplied values are + // the discrete variables when user-supplied values are // integer std::vector save_col_lower = lp.col_lower_; std::vector save_col_upper = lp.col_upper_; std::vector save_integrality = lp.integrality_; - const bool have_integrality = lp.integrality_.size(); - bool is_integer = true; + const bool have_integrality = (lp.integrality_.size() != 0); + assert(have_integrality); + // Count the number of fixed and unfixed discrete variables + HighsInt num_fixed_discrete_variable = 0; + HighsInt num_unfixed_discrete_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; - // Fix non-continuous variable if it has integer value const double primal = solution_.col_value[iCol]; - const double lower = lp.col_lower_[iCol]; - const double upper = lp.col_upper_[iCol]; - const HighsVarType type = - have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; - double col_infeasibility = 0; - double integer_infeasibility = 0; - assessColPrimalSolution(options_, primal, lower, upper, type, - col_infeasibility, integer_infeasibility); - if (integer_infeasibility > options_.mip_feasibility_tolerance) { - // Variable is not integer feasible, so record that a MIP will - // have to be solved - is_integer = false; + // Default value is lower bound, unless primal is integer for a + // discrete variable + solution_.col_value[iCol] = lp.col_lower_[iCol]; + if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; + // Fix discrete variable if its value is defined and integer + if (primal == kHighsUndefined) { + num_unfixed_discrete_variable++; } else { - // Variable is integer feasible, so fix it at this value and - // remove its integrality - lp.col_lower_[iCol] = solution_.col_value[iCol]; - lp.col_upper_[iCol] = solution_.col_value[iCol]; - lp.integrality_[iCol] = HighsVarType::kContinuous; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + const HighsVarType type = + have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; + double col_infeasibility = 0; + double integer_infeasibility = 0; + assessColPrimalSolution(options_, primal, lower, upper, type, + col_infeasibility, integer_infeasibility); + if (integer_infeasibility > options_.mip_feasibility_tolerance) { + num_unfixed_discrete_variable++; + } else { + // Variable is integer feasible, so fix it at this value and + // remove its integrality + num_fixed_discrete_variable++; + lp.col_lower_[iCol] = primal; + lp.col_upper_[iCol] = primal; + lp.integrality_[iCol] = HighsVarType::kContinuous; + } } } - // If the solution is integer valued, only an LP needs to be solved, - // so clear all integrality - if (is_integer) lp.integrality_.clear(); + assert(!solution_.hasUndefined()); + const HighsInt num_discrete_variable = + num_unfixed_discrete_variable + num_fixed_discrete_variable; + const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; + assert(num_continuous_variable >= 0); + bool call_run = true; + if (num_unfixed_discrete_variable == 0) { + // Solution is integer valued + if (num_continuous_variable == 0) { + // There are no continuous variables, so no feasible solution can be + // deduced + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values of discrete variables cannot yield " + "feasible solution\n"); + call_run = false; + } else { + // Solve an LP, so clear all integrality + lp.integrality_.clear(); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving LP for user-supplied values of discrete variables\n"); + } + } else { + // There are unfixed discrete variables + if (10 * num_fixed_discrete_variable < num_discrete_variable) { + // Too few discrete variables are fixed + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values fix only %d / %d discrete variables, " + "so not attempting to complete a feasible solution\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + call_run = false; + } else { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving MIP for user-supplied values of %d / %d " + "discrete variables\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + } + } + HighsStatus return_status = HighsStatus::kOk; + // Clear the current solution since either the user solution has + // been used to fix (a subset of) discrete variables - so a valid + // solution will be obtained from run() if the local model is + // feasible - or it's not worth using the user solution solution_.clear(); - basis_.clear(); - // Solve the model - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Attempting to find feasible solution " - "for (partial) user-supplied values of discrete variables\n"); - return_status = this->run(); + if (call_run) { + // Solve the model + basis_.clear(); + return_status = this->run(); + } // Recover the column bounds and integrality lp.col_lower_ = save_col_lower; lp.col_upper_ = save_col_upper; @@ -3207,7 +3528,6 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // The method below runs calls solveLp for the given LP HighsStatus Highs::callSolveLp(HighsLp& lp, const string message) { HighsStatus return_status = HighsStatus::kOk; - HighsStatus call_status; HighsLpSolverObject solver_object(lp, basis_, solution_, info_, ekk_instance_, callback_, options_, timer_); @@ -3229,11 +3549,11 @@ HighsStatus Highs::callSolveQp() { HighsLp& lp = model_.lp_; HighsHessian& hessian = model_.hessian_; assert(model_.lp_.a_matrix_.isColwise()); - if (hessian.dim_ != lp.num_col_) { - highsLogDev(options_.log_options, HighsLogType::kError, - "Hessian dimension = %" HIGHSINT_FORMAT - " incompatible with matrix dimension = %" HIGHSINT_FORMAT "\n", - hessian.dim_, lp.num_col_); + if (hessian.dim_ > lp.num_col_) { + highsLogDev( + options_.log_options, HighsLogType::kError, + "Hessian dimension = %d is incompatible with matrix dimension = %d\n", + int(hessian.dim_), int(lp.num_col_)); model_status_ = HighsModelStatus::kModelError; solution_.value_valid = false; solution_.dual_valid = false; @@ -3243,6 +3563,7 @@ HighsStatus Highs::callSolveQp() { // Run the QP solver Instance instance(lp.num_col_, lp.num_row_); + instance.sense = HighsInt(lp.sense_); instance.num_con = lp.num_row_; instance.num_var = lp.num_col_; @@ -3281,98 +3602,78 @@ HighsStatus Highs::callSolveQp() { Settings settings; Statistics stats; - settings.reportingfequency = 1000; + settings.reportingfequency = 100; - settings.endofiterationevent.subscribe([this](Statistics& stats) { + // Setting qp_update_limit = 10 leads to error with lpHighs3 + const HighsInt qp_update_limit = 1000; // 1000; // default + if (qp_update_limit != settings.reinvertfrequency) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Changing QP reinversion frequency from %d to %d\n", + int(settings.reinvertfrequency), int(qp_update_limit)); + settings.reinvertfrequency = qp_update_limit; + } + + settings.iteration_limit = options_.qp_iteration_limit; + settings.nullspace_limit = options_.qp_nullspace_limit; + + // Define the QP model status logging function + settings.qp_model_status_log.subscribe( + [this](QpModelStatus& qp_model_status) { + if (qp_model_status == QpModelStatus::kUndetermined || + qp_model_status == QpModelStatus::kLargeNullspace || + qp_model_status == QpModelStatus::kError || + qp_model_status == QpModelStatus::kNotset) + highsLogUser(options_.log_options, HighsLogType::kInfo, + "QP solver model status: %s\n", + qpModelStatusToString(qp_model_status).c_str()); + }); + + // Define the QP solver iteration logging function + settings.iteration_log.subscribe([this](Statistics& stats) { int rep = stats.iteration.size() - 1; - highsLogUser(options_.log_options, HighsLogType::kInfo, - "%" HIGHSINT_FORMAT ", %lf, %lf, %" HIGHSINT_FORMAT "\n", - stats.iteration[rep], stats.time[rep], stats.objval[rep], - stats.nullspacedimension[rep]); + "%11d %15.8g %6d %9.2fs\n", + int(stats.iteration[rep]), stats.objval[rep], + int(stats.nullspacedimension[rep]), stats.time[rep]); }); - settings.timelimit = options_.time_limit; - settings.iterationlimit = options_.simplex_iteration_limit; + // Define the QP nullspace limit logging function + settings.nullspace_limit_log.subscribe([this](HighsInt& nullspace_limit) { + highsLogUser(options_.log_options, HighsLogType::kError, + "QP solver has exceeded nullspace limit of %d\n", + int(nullspace_limit)); + }); + + settings.time_limit = options_.time_limit; settings.lambda_zero_threshold = options_.dual_feasibility_tolerance; + switch (options_.simplex_primal_edge_weight_strategy) { + case 0: + settings.pricing = PricingStrategy::DantzigWolfe; + break; + case 1: + settings.pricing = PricingStrategy::Devex; + break; + case 2: + settings.pricing = PricingStrategy::SteepestEdge; + break; + default: + settings.pricing = PricingStrategy::Devex; + } + // print header for QP solver output highsLogUser(options_.log_options, HighsLogType::kInfo, - "Iteration, Runtime, ObjVal, NullspaceDim\n"); - - QpModelStatus qp_model_status = QpModelStatus::INDETERMINED; + " Iteration Objective NullspaceDim\n"); - QpSolution qp_solution(instance); + QpAsmStatus status = solveqp(instance, settings, stats, model_status_, basis_, + solution_, timer_); + // QP solver can fail, so should return something other than QpAsmStatus::kOk + if (status == QpAsmStatus::kError) return HighsStatus::kError; - QpAsmStatus qpstatus = - solveqp(instance, settings, stats, qp_model_status, qp_solution, timer_); - - HighsStatus call_status = HighsStatus::kOk; - HighsStatus return_status = HighsStatus::kOk; - return_status = interpretCallStatus(options_.log_options, call_status, - return_status, "QpSolver"); - if (return_status == HighsStatus::kError) return return_status; - model_status_ = qp_model_status == QpModelStatus::OPTIMAL - ? HighsModelStatus::kOptimal - : qp_model_status == QpModelStatus::UNBOUNDED - ? HighsModelStatus::kUnbounded - : qp_model_status == QpModelStatus::INFEASIBLE - ? HighsModelStatus::kInfeasible - : qp_model_status == QpModelStatus::ITERATIONLIMIT - ? HighsModelStatus::kIterationLimit - : qp_model_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; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - solution_.col_value[iCol] = qp_solution.primal.value[iCol]; - solution_.col_dual[iCol] = - objective_multiplier * qp_solution.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 - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - solution_.row_value[iRow] = qp_solution.rowactivity.value[iRow]; - solution_.row_dual[iRow] = - objective_multiplier * qp_solution.dualcon.value[iRow]; - } - 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 (qp_solution.status_var[i] == BasisStatus::ActiveAtLower) { - basis_.col_status[i] = HighsBasisStatus::kLower; - } else if (qp_solution.status_var[i] == BasisStatus::ActiveAtUpper) { - basis_.col_status[i] = HighsBasisStatus::kUpper; - } else if (qp_solution.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 (qp_solution.status_con[i] == BasisStatus::ActiveAtLower) { - basis_.row_status[i] = HighsBasisStatus::kLower; - } else if (qp_solution.status_con[i] == BasisStatus::ActiveAtUpper) { - basis_.row_status[i] = HighsBasisStatus::kUpper; - } else if (qp_solution.status_con[i] == BasisStatus::InactiveInBasis) { - basis_.row_status[i] = HighsBasisStatus::kNonbasic; - } else { - basis_.row_status[i] = HighsBasisStatus::kBasic; - } - } - basis_.valid = true; - basis_.alien = false; + assert(status == QpAsmStatus::kOk || status == QpAsmStatus::kWarning); + HighsStatus return_status = status == QpAsmStatus::kWarning + ? HighsStatus::kWarning + : HighsStatus::kOk; // Get the objective and any KKT failures info_.objective_function_value = model_.objectiveValue(solution_.col_value); @@ -3482,8 +3783,6 @@ HighsStatus Highs::callSolveMip() { if (solver.solution_objective_ != kHighsInf) { const double mip_max_bound_violation = std::max(solver.row_violation_, solver.bound_violation_); - const double mip_max_infeasibility = - std::max(mip_max_bound_violation, solver.integrality_violation_); const double delta_max_bound_violation = std::abs(mip_max_bound_violation - info_.max_primal_infeasibility); // Possibly report a mis-match between the max bound violation @@ -3515,19 +3814,40 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, HighsStatus call_status; const HighsLp& presolved_lp = presolve_.getReducedProblem(); + // Must at least have a primal column solution of the right size + if (HighsInt(solution.col_value.size()) != presolved_lp.num_col_) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Primal solution provided to postsolve is incorrect size\n"); + return HighsStatus::kError; + } + // Check any basis that is supplied + const bool basis_supplied = + basis.col_status.size() > 0 || basis.row_status.size() > 0 || basis.valid; + if (basis_supplied) { + if (!isBasisConsistent(presolved_lp, basis)) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Basis provided to postsolve is incorrect size or inconsistent\n"); + return HighsStatus::kError; + } + } + // Copy in the solution provided + presolve_.data_.recovered_solution_ = solution; + // Ignore any row values + presolve_.data_.recovered_solution_.row_value.assign(presolved_lp.num_row_, + 0); + presolve_.data_.recovered_solution_.value_valid = true; + if (this->model_.isMip() && !basis.valid) { // Postsolving a MIP without a valid basis - which, if valid, // would imply that the relaxation had been solved, a case handled // below - presolve_.data_.recovered_solution_ = solution; - if (HighsInt(presolve_.data_.recovered_solution_.col_value.size()) < - presolved_lp.num_col_) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Solution provided to postsolve is incorrect size\n"); - return HighsStatus::kError; - } - presolve_.data_.recovered_solution_.row_value.assign(presolved_lp.num_row_, - 0); + // + // Ignore any dual values + presolve_.data_.recovered_solution_.dual_valid = false; + presolve_.data_.recovered_solution_.col_dual.clear(); + presolve_.data_.recovered_solution_.row_dual.clear(); + // Ignore any basis presolve_.data_.recovered_basis_.valid = false; HighsPostsolveStatus postsolve_status = runPostsolve(); @@ -3563,72 +3883,107 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, } else { // Postsolving an LP, or a MIP after solving the relaxation // (identified by passing a valid basis). - const bool solution_ok = isSolutionRightSize(presolved_lp, solution); - if (!solution_ok) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Solution provided to postsolve is incorrect size\n"); - return HighsStatus::kError; - } - if (basis.valid) { - // Check a valid basis - const bool basis_ok = isBasisConsistent(presolved_lp, basis); - if (!basis_ok) { + // + // If there are dual values, make sure that both vectors are the + // right size + const bool dual_supplied = + presolve_.data_.recovered_solution_.col_dual.size() > 0 || + presolve_.data_.recovered_solution_.row_dual.size() > 0 || + presolve_.data_.recovered_solution_.dual_valid; + if (dual_supplied) { + if (!isDualSolutionRightSize(presolved_lp, + presolve_.data_.recovered_solution_)) { highsLogUser(options_.log_options, HighsLogType::kError, - "Basis provided to postsolve is incorrect size\n"); + "Dual solution provided to postsolve is incorrect size\n"); return HighsStatus::kError; } + presolve_.data_.recovered_solution_.dual_valid = true; + } else { + presolve_.data_.recovered_solution_.dual_valid = false; } - presolve_.data_.recovered_solution_ = solution; + // Copy in the basis provided. It's already been checked for + // consistency, so the basis is valid iff it was supplied presolve_.data_.recovered_basis_ = basis; + presolve_.data_.recovered_basis_.valid = basis_supplied; HighsPostsolveStatus postsolve_status = runPostsolve(); + if (postsolve_status == HighsPostsolveStatus::kSolutionRecovered) { highsLogDev(options_.log_options, HighsLogType::kVerbose, "Postsolve finished\n"); // Set solution and its status solution_.clear(); solution_ = presolve_.data_.recovered_solution_; - solution_.value_valid = true; - solution_.dual_valid = true; + assert(solution_.value_valid); + if (!solution_.dual_valid) { + solution_.col_dual.assign(model_.lp_.num_col_, 0); + solution_.row_dual.assign(model_.lp_.num_row_, 0); + } + basis_ = presolve_.data_.recovered_basis_; + // Validity of the solution and basis should be inherited + // + // solution_.value_valid = true; + // solution_.dual_valid = true; + // // Set basis and its status - basis_.valid = true; - basis_.col_status = presolve_.data_.recovered_basis_.col_status; - basis_.row_status = presolve_.data_.recovered_basis_.row_status; + // + // basis_.valid = true; + // basis_.col_status = presolve_.data_.recovered_basis_.col_status; + // basis_.row_status = presolve_.data_.recovered_basis_.row_status; basis_.debug_origin_name += ": after postsolve"; - // Save the options to allow the best simplex strategy to - // be used - HighsOptions save_options = options_; - options_.simplex_strategy = kSimplexStrategyChoose; - // Ensure that the parallel solver isn't used - options_.simplex_min_concurrency = 1; - options_.simplex_max_concurrency = 1; - // Use any pivot threshold resulting from solving the presolved LP - // if (factor_pivot_threshold > 0) - // options_.factor_pivot_threshold = factor_pivot_threshold; - // The basis returned from postsolve is just basic/nonbasic - // and EKK expects a refined basis, so set it up now - HighsLp& incumbent_lp = model_.lp_; - refineBasis(incumbent_lp, solution_, basis_); - // Scrap the EKK data from solving the presolved LP - ekk_instance_.invalidate(); - ekk_instance_.lp_name_ = "Postsolve LP"; - // Set up the timing record so that adding the corresponding - // values after callSolveLp gives difference - timer_.start(timer_.solve_clock); - call_status = callSolveLp( - incumbent_lp, - "Solving the original LP from the solution after postsolve"); - // Determine the timing record - timer_.stop(timer_.solve_clock); - return_status = interpretCallStatus(options_.log_options, call_status, - return_status, "callSolveLp"); - // Recover the options - options_ = save_options; - if (return_status == HighsStatus::kError) { - // Set undo_mods = false, since passing models requiring - // modification to Highs::presolve is illegal - const bool undo_mods = false; - return returnFromRun(return_status, undo_mods); + if (basis_.valid) { + // Save the options to allow the best simplex strategy to be + // used + HighsOptions save_options = options_; + options_.simplex_strategy = kSimplexStrategyChoose; + // Ensure that the parallel solver isn't used + options_.simplex_min_concurrency = 1; + options_.simplex_max_concurrency = 1; + // Use any pivot threshold resulting from solving the presolved LP + // if (factor_pivot_threshold > 0) + // options_.factor_pivot_threshold = factor_pivot_threshold; + // The basis returned from postsolve is just basic/nonbasic + // and EKK expects a refined basis, so set it up now + HighsLp& incumbent_lp = model_.lp_; + refineBasis(incumbent_lp, solution_, basis_); + // Scrap the EKK data from solving the presolved LP + ekk_instance_.invalidate(); + ekk_instance_.lp_name_ = "Postsolve LP"; + // Set up the timing record so that adding the corresponding + // values after callSolveLp gives difference + timer_.start(timer_.solve_clock); + call_status = callSolveLp( + incumbent_lp, + "Solving the original LP from the solution after postsolve"); + // Determine the timing record + timer_.stop(timer_.solve_clock); + return_status = interpretCallStatus(options_.log_options, call_status, + return_status, "callSolveLp"); + // Recover the options + options_ = save_options; + if (return_status == HighsStatus::kError) { + // Set undo_mods = false, since passing models requiring + // modification to Highs::presolve is illegal + const bool undo_mods = false; + return returnFromRun(return_status, undo_mods); + } + } else { + basis_.clear(); + info_.objective_function_value = + model_.lp_.objectiveValue(solution_.col_value); + getLpKktFailures(options_, model_.lp_, solution_, basis_, info_); + if (info_.num_primal_infeasibilities == 0 && + info_.num_dual_infeasibilities == 0) { + model_status_ = HighsModelStatus::kOptimal; + } else { + model_status_ = HighsModelStatus::kUnknown; + } + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Pure postsolve yields primal %ssolution, but no basis: model " + "status is %s\n", + solution_.dual_valid ? "and dual " : "", + modelStatusToString(model_status_).c_str()); } } else { highsLogUser(options_.log_options, HighsLogType::kError, @@ -3651,7 +4006,7 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, // End of public methods void Highs::logHeader() { if (written_log_header) return; - highsLogHeader(options_.log_options); + highsLogHeader(options_.log_options, options_.log_githash); written_log_header = true; return; } @@ -3683,11 +4038,11 @@ void Highs::forceHighsSolutionBasisSize() { solution_.row_dual.resize(model_.lp_.num_row_); // Ensure that the HiGHS basis vectors are the right size, // invalidating the basis if they aren't - if ((HighsInt)basis_.col_status.size() != model_.lp_.num_col_) { + if (basis_.col_status.size() != static_cast(model_.lp_.num_col_)) { basis_.col_status.resize(model_.lp_.num_col_); basis_.valid = false; } - if ((HighsInt)basis_.row_status.size() != model_.lp_.num_row_) { + if (basis_.row_status.size() != static_cast(model_.lp_.num_row_)) { basis_.row_status.resize(model_.lp_.num_row_); basis_.valid = false; } @@ -3723,8 +4078,8 @@ HighsStatus Highs::openWriteFile(const string filename, file = fopen(filename.c_str(), "w"); if (file == 0) { highsLogUser(options_.log_options, HighsLogType::kError, - "Cannot open writeable file \"%s\" in %s\n", - filename.c_str(), method_name.c_str()); + "Cannot open writable file \"%s\" in %s\n", filename.c_str(), + method_name.c_str()); return HighsStatus::kError; } const char* dot = strrchr(filename.c_str(), '.'); @@ -3755,6 +4110,13 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, const bool undo_mods) { assert(!called_return_from_run); HighsStatus return_status = highsStatusFromHighsModelStatus(model_status_); + if (return_status != run_return_status) { + printf( + "Highs::returnFromRun: return_status = %d != %d = run_return_status " + "For model_status_ = %s\n", + int(return_status), int(run_return_status), + modelStatusToString(model_status_).c_str()); + } assert(return_status == run_return_status); // return_status = run_return_status; switch (model_status_) { @@ -3765,6 +4127,7 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, case HighsModelStatus::kPresolveError: case HighsModelStatus::kSolveError: case HighsModelStatus::kPostsolveError: + case HighsModelStatus::kMemoryLimit: // Don't clear the model status! // invalidateUserSolverData(); invalidateInfo(); @@ -3805,11 +4168,11 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, if (options_.allow_unbounded_or_infeasible || (options_.solver == kIpmString && options_.run_crossover == kHighsOnString) || - model_.isMip()) { + (options_.solver == kPdlpString) || model_.isMip()) { assert(return_status == HighsStatus::kOk); } else { // This model status is not permitted unless IPM is run without - // crossover + // crossover, or if PDLP is used highsLogUser( options_.log_options, HighsLogType::kError, "returnFromHighs: HighsModelStatus::kUnboundedOrInfeasible is not " @@ -3848,6 +4211,7 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, case HighsModelStatus::kSolveError: case HighsModelStatus::kPostsolveError: case HighsModelStatus::kModelEmpty: + case HighsModelStatus::kMemoryLimit: // No info, primal solution or basis assert(have_info == false); assert(have_primal_solution == false); @@ -3987,6 +4351,10 @@ void Highs::reportSolvedLpQpStats() { highsLogUser(log_options, HighsLogType::kInfo, "Crossover iterations: %" HIGHSINT_FORMAT "\n", info_.crossover_iteration_count); + if (info_.pdlp_iteration_count) + highsLogUser(log_options, HighsLogType::kInfo, + "PDLP iterations: %" HIGHSINT_FORMAT "\n", + info_.pdlp_iteration_count); if (info_.qp_iteration_count) highsLogUser(log_options, HighsLogType::kInfo, "QP ASM iterations: %" HIGHSINT_FORMAT "\n", diff --git a/src/lp_data/HighsAnalysis.h b/src/lp_data/HighsAnalysis.h index d2e3b916d9..30930411ba 100644 --- a/src/lp_data/HighsAnalysis.h +++ b/src/lp_data/HighsAnalysis.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsCallback.cpp b/src/lp_data/HighsCallback.cpp index 1d49260de5..962c4f355a 100644 --- a/src/lp_data/HighsCallback.cpp +++ b/src/lp_data/HighsCallback.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -20,6 +20,7 @@ void HighsCallback::clearHighsCallbackDataOut() { this->data_out.running_time = -1; this->data_out.simplex_iteration_count = -1; this->data_out.ipm_iteration_count = -1; + this->data_out.pdlp_iteration_count = -1; this->data_out.objective_function_value = -kHighsInf; this->data_out.mip_node_count = -1; this->data_out.mip_primal_bound = kHighsInf; @@ -64,7 +65,10 @@ bool HighsCallback::callbackAction(const int callback_type, // Check for no action if case not handled internally if (callback_type == kCallbackMipImprovingSolution || - callback_type == kCallbackMipLogging) + callback_type == kCallbackMipSolution || + callback_type == kCallbackMipLogging || + callback_type == kCallbackMipGetCutPool || + callback_type == kCallbackMipDefineLazyConstraints) assert(!action); return action; } diff --git a/src/lp_data/HighsCallback.h b/src/lp_data/HighsCallback.h index dda5dedb6f..cc98c5a0c0 100644 --- a/src/lp_data/HighsCallback.h +++ b/src/lp_data/HighsCallback.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -14,12 +14,19 @@ #ifndef LP_DATA_HIGHSCALLBACK_H_ #define LP_DATA_HIGHSCALLBACK_H_ +#include + #include "lp_data/HStruct.h" #include "lp_data/HighsCallbackStruct.h" +using HighsCallbackFunctionType = + std::function; + struct HighsCallback { - void (*user_callback)(const int, const char*, const HighsCallbackDataOut*, - HighsCallbackDataIn*, void*) = nullptr; + // Function pointers cannot be used for Pybind11, so use std::function + HighsCallbackFunctionType user_callback = nullptr; + HighsCCallbackType c_callback = nullptr; void* user_callback_data = nullptr; std::vector active; HighsCallbackDataOut data_out; diff --git a/src/lp_data/HighsCallbackStruct.h b/src/lp_data/HighsCallbackStruct.h index b030912726..a5f7140834 100644 --- a/src/lp_data/HighsCallbackStruct.h +++ b/src/lp_data/HighsCallbackStruct.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -16,21 +16,48 @@ #include "util/HighsInt.h" -struct HighsCallbackDataOut { +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Struct to handle callback output data + * + */ +typedef struct { int log_type; // cast of HighsLogType double running_time; HighsInt simplex_iteration_count; HighsInt ipm_iteration_count; + HighsInt pdlp_iteration_count; double objective_function_value; int64_t mip_node_count; + int64_t mip_total_lp_iterations; double mip_primal_bound; double mip_dual_bound; double mip_gap; double* mip_solution; -}; + HighsInt cutpool_num_col; + HighsInt cutpool_num_cut; + HighsInt cutpool_num_nz; + HighsInt* cutpool_start; + HighsInt* cutpool_index; + double* cutpool_value; + double* cutpool_lower; + double* cutpool_upper; +} HighsCallbackDataOut; -struct HighsCallbackDataIn { +typedef struct { int user_interrupt; -}; +} HighsCallbackDataIn; + +// Additional callback handling +typedef void (*HighsCCallbackType)(int, const char*, + const HighsCallbackDataOut*, + HighsCallbackDataIn*, void*); + +#ifdef __cplusplus +} +#endif #endif /* LP_DATA_HIGHSCALLBACKSTRUCT_H_ */ diff --git a/src/lp_data/HighsDebug.cpp b/src/lp_data/HighsDebug.cpp index 58fc9f1b7e..b27fe045bd 100644 --- a/src/lp_data/HighsDebug.cpp +++ b/src/lp_data/HighsDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsDebug.h b/src/lp_data/HighsDebug.h index ad00cac7df..435db59dd2 100644 --- a/src/lp_data/HighsDebug.h +++ b/src/lp_data/HighsDebug.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsDeprecated.cpp b/src/lp_data/HighsDeprecated.cpp index 57944a7fcf..be7fa1e913 100644 --- a/src/lp_data/HighsDeprecated.cpp +++ b/src/lp_data/HighsDeprecated.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsInfo.cpp b/src/lp_data/HighsInfo.cpp index 9fd56976db..a7f36a4e81 100644 --- a/src/lp_data/HighsInfo.cpp +++ b/src/lp_data/HighsInfo.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -23,6 +23,7 @@ void HighsInfo::invalidate() { simplex_iteration_count = -1; ipm_iteration_count = -1; crossover_iteration_count = -1; + pdlp_iteration_count = -1; qp_iteration_count = -1; primal_solution_status = kSolutionStatusNone; dual_solution_status = kSolutionStatusNone; @@ -281,7 +282,6 @@ HighsStatus writeInfoToFile(FILE* file, const bool valid, void reportInfo(FILE* file, const std::vector& info_records, 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; diff --git a/src/lp_data/HighsInfo.h b/src/lp_data/HighsInfo.h index abd99f408c..216e78d601 100644 --- a/src/lp_data/HighsInfo.h +++ b/src/lp_data/HighsInfo.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -138,6 +138,7 @@ struct HighsInfoStruct { HighsInt simplex_iteration_count; HighsInt ipm_iteration_count; HighsInt crossover_iteration_count; + HighsInt pdlp_iteration_count; HighsInt qp_iteration_count; HighsInt primal_solution_status; HighsInt dual_solution_status; @@ -152,6 +153,8 @@ struct HighsInfoStruct { HighsInt num_dual_infeasibilities; double max_dual_infeasibility; double sum_dual_infeasibilities; + double max_complementarity_violation; + double sum_complementarity_violations; }; class HighsInfo : public HighsInfoStruct { @@ -192,7 +195,7 @@ class HighsInfo : public HighsInfoStruct { private: void deleteRecords() { - for (HighsUInt i = 0; i < records.size(); i++) delete records[i]; + for (size_t i = 0; i < records.size(); i++) delete records[i]; } void initRecords() { @@ -216,6 +219,11 @@ class HighsInfo : public HighsInfoStruct { &crossover_iteration_count, 0); records.push_back(record_int); + record_int = new InfoRecordInt("pdlp_iteration_count", + "Iteration count for PDLP solver", advanced, + &pdlp_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); @@ -295,6 +303,16 @@ class HighsInfo : public HighsInfoStruct { "sum_dual_infeasibilities", "Sum of dual infeasibilities", advanced, &sum_dual_infeasibilities, 0); records.push_back(record_double); + + record_double = new InfoRecordDouble( + "max_complementarity_violation", "Max complementarity violation", + advanced, &max_complementarity_violation, 0); + records.push_back(record_double); + + record_double = new InfoRecordDouble( + "sum_complementarity_violations", "Sum of complementarity violations", + advanced, &sum_complementarity_violations, 0); + records.push_back(record_double); } public: diff --git a/src/lp_data/HighsInfoDebug.cpp b/src/lp_data/HighsInfoDebug.cpp index 3ca68d83f7..be90937ef2 100644 --- a/src/lp_data/HighsInfoDebug.cpp +++ b/src/lp_data/HighsInfoDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -34,6 +34,7 @@ HighsDebugStatus debugInfo(const HighsOptions& options, const HighsLp& lp, case HighsModelStatus::kSolveError: case HighsModelStatus::kPostsolveError: case HighsModelStatus::kModelEmpty: + case HighsModelStatus::kMemoryLimit: // Should have no info, so check this and return assert(!have_primal_solution); assert(!have_dual_solution); diff --git a/src/lp_data/HighsInfoDebug.h b/src/lp_data/HighsInfoDebug.h index 6f3730d784..b1aba26d46 100644 --- a/src/lp_data/HighsInfoDebug.h +++ b/src/lp_data/HighsInfoDebug.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 42ebc6368d..d5ffd342a0 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -11,9 +11,12 @@ /**@file lp_data/HighsInterface.cpp * @brief */ +#include + #include "Highs.h" #include "lp_data/HighsLpUtils.h" #include "lp_data/HighsModelUtils.h" +#include "model/HighsHessianUtils.h" #include "simplex/HSimplex.h" #include "util/HighsMatrixUtils.h" #include "util/HighsSort.h" @@ -121,11 +124,37 @@ HighsStatus Highs::addColsInterface( local_colLower, local_colUpper, options.infinite_bound), return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; + if (lp.user_bound_scale_) { + // Assess and apply any user bound scaling + if (!boundScaleOk(local_colLower, local_colUpper, lp.user_bound_scale_, + options.infinite_bound)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "User bound scaling yields infinite bound\n"); + return HighsStatus::kError; + } + double bound_scale_value = std::pow(2, lp.user_bound_scale_); + for (HighsInt iCol = 0; iCol < ext_num_new_col; iCol++) { + local_colLower[iCol] *= bound_scale_value; + local_colUpper[iCol] *= bound_scale_value; + } + } + if (lp.user_cost_scale_) { + // Assess and apply any user cost scaling + if (!costScaleOk(local_colCost, lp.user_cost_scale_, + options.infinite_cost)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "User cost scaling yields infinite cost\n"); + return HighsStatus::kError; + } + double cost_scale_value = std::pow(2, lp.user_cost_scale_); + for (HighsInt iCol = 0; iCol < ext_num_new_col; iCol++) + local_colCost[iCol] *= cost_scale_value; + } // Append the columns to the LP vectors and matrix appendColsToLpVectors(lp, ext_num_new_col, local_colCost, local_colLower, local_colUpper); // Form a column-wise HighsSparseMatrix of the new matrix columns so - // that is is easy to handle and, if there are nonzeros, it can be + // that is easy to handle and, if there are nonzeros, it can be // normalised HighsSparseMatrix local_a_matrix; local_a_matrix.num_col_ = ext_num_new_col; @@ -166,7 +195,7 @@ HighsStatus Highs::addColsInterface( local_a_matrix.considerColScaling(options.allowed_matrix_scale_factor, &scale.col[lp.num_col_]); } - // Update the basis correponding to new nonbasic columns + // Update the basis corresponding to new nonbasic columns if (valid_basis) appendNonbasicColsToBasisInterface(ext_num_new_col); // Possibly add column names @@ -185,6 +214,10 @@ HighsStatus Highs::addColsInterface( // Determine any implications for simplex data ekk_instance_.addCols(lp, local_a_matrix); + + // Extend any Hessian with zeros on the diagonal + if (this->model_.hessian_.dim_) + completeHessian(lp.num_col_, this->model_.hessian_); return return_status; } @@ -246,12 +279,26 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, local_rowLower, local_rowUpper, options.infinite_bound), return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; + if (lp.user_bound_scale_) { + // Assess and apply any user bound scaling + if (!boundScaleOk(local_rowLower, local_rowUpper, lp.user_bound_scale_, + options_.infinite_bound)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "User bound scaling yields infinite bound\n"); + return HighsStatus::kError; + } + double bound_scale_value = std::pow(2, lp.user_bound_scale_); + for (HighsInt iRow = 0; iRow < ext_num_new_row; iRow++) { + local_rowLower[iRow] *= bound_scale_value; + local_rowUpper[iRow] *= bound_scale_value; + } + } // Append the rows to the LP vectors appendRowsToLpVectors(lp, ext_num_new_row, local_rowLower, local_rowUpper); // Form a row-wise HighsSparseMatrix of the new matrix rows so that - // is is easy to handle and, if there are nonzeros, it can be + // is easy to handle and, if there are nonzeros, it can be // normalised HighsSparseMatrix local_ar_matrix; local_ar_matrix.num_col_ = lp.num_col_; @@ -292,7 +339,7 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, local_ar_matrix.considerRowScaling(options.allowed_matrix_scale_factor, &scale.row[lp.num_row_]); } - // Update the basis correponding to new basic rows + // Update the basis corresponding to new basic rows if (valid_basis) appendBasicRowsToBasisInterface(ext_num_new_row); // Possibly add row names @@ -319,7 +366,8 @@ void Highs::deleteColsInterface(HighsIndexCollection& index_collection) { // any columns have been removed, and if there is mask to be updated HighsInt original_num_col = lp.num_col_; - deleteLpCols(lp, index_collection); + lp.deleteCols(index_collection); + model_.hessian_.deleteCols(index_collection); assert(lp.num_col_ <= original_num_col); if (lp.num_col_ < original_num_col) { // Nontrivial deletion so reset the model_status and invalidate @@ -364,7 +412,7 @@ void Highs::deleteRowsInterface(HighsIndexCollection& index_collection) { // any rows have been removed, and if there is mask to be updated HighsInt original_num_row = lp.num_row_; - deleteLpRows(lp, index_collection); + lp.deleteRows(index_collection); assert(lp.num_row_ <= original_num_row); if (lp.num_row_ < original_num_row) { // Nontrivial deletion so reset the model_status and invalidate @@ -464,8 +512,8 @@ void Highs::getRowsInterface(const HighsIndexCollection& index_collection, // Surely this is checked elsewhere assert(0 <= from_k && to_k < lp.num_row_); assert(from_k <= to_k); - // "Out" means not in the set to be extrated - // "In" means in the set to be extrated + // "Out" means not in the set to be extracted + // "In" means in the set to be extracted HighsInt out_from_row; HighsInt out_to_row; HighsInt in_from_row; @@ -611,8 +659,6 @@ HighsStatus Highs::changeIntegralityInterface( if (index_collection.is_set_) assert(increasingSetOk(index_collection.set_, 0, index_collection.dimension_, true)); - HighsStatus return_status = HighsStatus::kOk; - HighsStatus call_status; changeLpIntegrality(model_.lp_, index_collection, local_integrality); // Deduce the consequences of new integrality invalidateModelStatus(); @@ -638,6 +684,18 @@ HighsStatus Highs::changeCostsInterface(HighsIndexCollection& index_collection, return_status, "assessCosts"); if (return_status == HighsStatus::kError) return return_status; HighsLp& lp = model_.lp_; + if (lp.user_cost_scale_) { + // Assess and apply any user cost scaling + if (!costScaleOk(local_colCost, lp.user_cost_scale_, + options_.infinite_cost)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "User cost scaling yields infinite cost\n"); + return HighsStatus::kError; + } + double cost_scale_value = std::pow(2, lp.user_cost_scale_); + for (HighsInt iCol = 0; iCol < num_cost; iCol++) + local_colCost[iCol] *= cost_scale_value; + } changeLpCosts(lp, index_collection, local_colCost, options_.infinite_cost); // Interpret possible introduction of infinite costs @@ -682,10 +740,23 @@ HighsStatus Highs::changeColBoundsInterface( local_colUpper, options_.infinite_bound), return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; + HighsLp& lp = model_.lp_; + if (lp.user_bound_scale_) { + // Assess and apply any user bound scaling + if (!boundScaleOk(local_colLower, local_colUpper, lp.user_bound_scale_, + options_.infinite_bound)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "User bound scaling yields infinite bound\n"); + return HighsStatus::kError; + } + double bound_scale_value = std::pow(2, lp.user_bound_scale_); + for (HighsInt iCol = 0; iCol < num_col_bounds; iCol++) { + local_colLower[iCol] *= bound_scale_value; + local_colUpper[iCol] *= bound_scale_value; + } + } - HighsStatus call_status; - changeLpColBounds(model_.lp_, index_collection, local_colLower, - local_colUpper); + changeLpColBounds(lp, index_collection, local_colLower, local_colUpper); // Update HiGHS basis status and (any) simplex move status of // nonbasic variables whose bounds have changed setNonbasicStatusInterface(index_collection, true); @@ -727,10 +798,23 @@ HighsStatus Highs::changeRowBoundsInterface( local_rowUpper, options_.infinite_bound), return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; + HighsLp& lp = model_.lp_; + if (lp.user_bound_scale_) { + // Assess and apply any user bound scaling + if (!boundScaleOk(local_rowLower, local_rowUpper, lp.user_bound_scale_, + options_.infinite_bound)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "User bound scaling yields infinite bound\n"); + return HighsStatus::kError; + } + double bound_scale_value = std::pow(2, lp.user_bound_scale_); + for (HighsInt iRow = 0; iRow < num_row_bounds; iRow++) { + local_rowLower[iRow] *= bound_scale_value; + local_rowUpper[iRow] *= bound_scale_value; + } + } - HighsStatus call_status; - changeLpRowBounds(model_.lp_, index_collection, local_rowLower, - local_rowUpper); + changeLpRowBounds(lp, index_collection, local_rowLower, local_rowUpper); // Update HiGHS basis status and (any) simplex move status of // nonbasic variables whose bounds have changed setNonbasicStatusInterface(index_collection, false); @@ -1115,7 +1199,6 @@ void Highs::appendBasicRowsToBasisInterface(const HighsInt ext_num_new_row) { HighsStatus Highs::getBasicVariablesInterface(HighsInt* basic_variables) { HighsStatus return_status = HighsStatus::kOk; HighsLp& lp = model_.lp_; - HighsLp& ekk_lp = ekk_instance_.lp_; HighsInt num_row = lp.num_row_; HighsInt num_col = lp.num_col_; HighsSimplexStatus& ekk_status = ekk_instance_.status_; @@ -1162,7 +1245,6 @@ HighsStatus Highs::basisSolveInterface(const vector& rhs, HighsStatus return_status = HighsStatus::kOk; HighsLp& lp = model_.lp_; HighsInt num_row = lp.num_row_; - HighsInt num_col = lp.num_col_; // For an LP with no rows the solution is vacuous if (num_row == 0) return return_status; // EKK must have an INVERT, but simplex NLA may need the pointer to @@ -1175,7 +1257,6 @@ HighsStatus Highs::basisSolveInterface(const vector& rhs, HVector solve_vector; solve_vector.setup(num_row); solve_vector.clear(); - HighsScale& scale = lp.scale_; HighsInt rhs_num_nz = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) { if (rhs[iRow]) { @@ -1393,6 +1474,7 @@ void Highs::zeroIterationCounts() { info_.simplex_iteration_count = 0; info_.ipm_iteration_count = 0; info_.crossover_iteration_count = 0; + info_.pdlp_iteration_count = 0; info_.qp_iteration_count = 0; } @@ -1530,14 +1612,14 @@ HighsStatus Highs::checkOptimality(const std::string& solver_type, std::stringstream ss; ss.str(std::string()); ss << highsFormatToString( - "%s solver claims optimality, but with num/sum/max " - "primal(%" HIGHSINT_FORMAT "/%g/%g)", - solver_type.c_str(), info_.num_primal_infeasibilities, - info_.sum_primal_infeasibilities, info_.max_primal_infeasibility); + "%s solver claims optimality, but with num/max/sum " + "primal(%d/%g/%g)", + solver_type.c_str(), int(info_.num_primal_infeasibilities), + info_.max_primal_infeasibility, info_.sum_primal_infeasibilities); if (info_.num_dual_infeasibilities > 0) ss << highsFormatToString( - "and dual(%" HIGHSINT_FORMAT "/%g/%g)", info_.num_dual_infeasibilities, - info_.sum_dual_infeasibilities, info_.max_dual_infeasibility); + "and dual(%d/%g/%g)", int(info_.num_dual_infeasibilities), + info_.max_dual_infeasibility, info_.sum_dual_infeasibilities); ss << " infeasibilities\n"; const std::string report_string = ss.str(); highsLogUser(options_.log_options, log_type, "%s", report_string.c_str()); @@ -1658,8 +1740,6 @@ void Highs::restoreInfCost(HighsStatus& return_status) { double cost = mods.save_inf_cost_variable_cost[ix]; double lower = mods.save_inf_cost_variable_lower[ix]; double upper = mods.save_inf_cost_variable_upper[ix]; - HighsBasisStatus status = - basis.valid ? basis.col_status[iCol] : HighsBasisStatus::kBasic; double value = solution_.value_valid ? solution_.col_value[iCol] : 0; if (basis.valid) { assert(basis.col_status[iCol] != HighsBasisStatus::kBasic); @@ -1687,3 +1767,763 @@ void Highs::restoreInfCost(HighsStatus& return_status) { return_status = highsStatusFromHighsModelStatus(model_status_); } } + +// Modify status and info if user bound or cost scaling, or +// primal/dual feasibility tolerances have changed +HighsStatus Highs::optionChangeAction() { + HighsModel& model = this->model_; + HighsLp& lp = model.lp_; + HighsInfo& info = this->info_; + HighsOptions& options = this->options_; + const bool is_mip = lp.isMip(); + HighsInt dl_user_bound_scale = 0; + double dl_user_bound_scale_value = 1; + // Ensure that user bound scaling does not yield infinite bounds + const bool changed_user_bound_scale = + options.user_bound_scale != lp.user_bound_scale_; + bool user_bound_scale_ok = + !changed_user_bound_scale || + lp.userBoundScaleOk(options.user_bound_scale, options.infinite_bound); + if (!user_bound_scale_ok) { + options.user_bound_scale = lp.user_bound_scale_; + highsLogUser(options_.log_options, HighsLogType::kError, + "New user bound scaling yields infinite bound: reverting user " + "bound scaling to %d\n", + int(options.user_bound_scale)); + } else if (changed_user_bound_scale) { + dl_user_bound_scale = options.user_bound_scale - lp.user_bound_scale_; + dl_user_bound_scale_value = std::pow(2, dl_user_bound_scale); + } + // Now consider impact on primal feasibility of user bound scaling + // and/or primal_feasibility_tolerance change + double new_max_primal_infeasibility = + info.max_primal_infeasibility * dl_user_bound_scale_value; + if (new_max_primal_infeasibility > options.primal_feasibility_tolerance) { + // Not primal feasible + this->model_status_ = HighsModelStatus::kNotset; + if (info.primal_solution_status == kSolutionStatusFeasible) + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to loss of primal feasibility\n"); + info.primal_solution_status = kSolutionStatusInfeasible; + info.num_primal_infeasibilities = kHighsIllegalInfeasibilityCount; + } else if (!is_mip && + info.primal_solution_status == kSolutionStatusInfeasible) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to gain of primal feasibility\n"); + info.primal_solution_status = kSolutionStatusFeasible; + info.num_primal_infeasibilities = 0; + } + if (is_mip && dl_user_bound_scale) { + // MIP with non-trivial bound scaling loses optimality + this->model_status_ = HighsModelStatus::kNotset; + if (dl_user_bound_scale < 0) { + // MIP with negative bound scaling exponent loses feasibility + if (info.primal_solution_status == kSolutionStatusFeasible) { + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Option change leads to loss of primal feasibility for MIP\n"); + } + info.primal_solution_status = kSolutionStatusInfeasible; + } + } + if (dl_user_bound_scale) { + // Update info and solution with respect to non-trivial user bound scaling + info.objective_function_value *= dl_user_bound_scale_value; + info.max_primal_infeasibility *= dl_user_bound_scale_value; + info.sum_primal_infeasibilities *= dl_user_bound_scale_value; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + this->solution_.col_value[iCol] *= dl_user_bound_scale_value; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + this->solution_.row_value[iRow] *= dl_user_bound_scale_value; + // Update LP with respect to non-trivial user bound scaling + lp.userBoundScale(options_.user_bound_scale); + } + // Now consider whether options.user_cost_scale has changed + HighsInt dl_user_cost_scale = 0; + double dl_user_cost_scale_value = 1; + const bool changed_user_cost_scale = + options.user_cost_scale != lp.user_cost_scale_; + bool user_cost_scale_ok = + !changed_user_cost_scale || + model.userCostScaleOk(options.user_cost_scale, options.small_matrix_value, + options.large_matrix_value, options.infinite_cost); + if (!user_cost_scale_ok) { + options.user_cost_scale = lp.user_cost_scale_; + highsLogUser(options_.log_options, HighsLogType::kError, + "New user cost scaling yields excessive cost coefficient: " + "reverting user cost scaling to %d\n", + int(options.user_cost_scale)); + } else if (changed_user_cost_scale) { + dl_user_cost_scale = options.user_cost_scale - lp.user_cost_scale_; + dl_user_cost_scale_value = std::pow(2, dl_user_cost_scale); + } + if (!is_mip) { + // Now consider impact on dual feasibility of user cost scaling + // and/or dual_feasibility_tolerance change + double new_max_dual_infeasibility = + info.max_dual_infeasibility * dl_user_cost_scale_value; + if (new_max_dual_infeasibility > options.dual_feasibility_tolerance) { + // Not dual feasible + this->model_status_ = HighsModelStatus::kNotset; + if (info.dual_solution_status == kSolutionStatusFeasible) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to loss of dual feasibility\n"); + info.dual_solution_status = kSolutionStatusInfeasible; + } + info.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; + } else if (info.dual_solution_status == kSolutionStatusInfeasible) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to gain of dual feasibility\n"); + info.dual_solution_status = kSolutionStatusFeasible; + info.num_dual_infeasibilities = 0; + } + } + if (dl_user_cost_scale) { + if (is_mip) { + // MIP with non-trivial cost scaling loses optimality + this->model_status_ = HighsModelStatus::kNotset; + } + // Now update data and solution with respect to non-trivial user + // cost scaling + info.objective_function_value *= dl_user_cost_scale_value; + info.max_dual_infeasibility *= dl_user_cost_scale_value; + info.sum_dual_infeasibilities *= dl_user_cost_scale_value; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + this->solution_.col_dual[iCol] *= dl_user_cost_scale_value; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + this->solution_.row_dual[iRow] *= dl_user_cost_scale_value; + model.userCostScale(options.user_cost_scale); + } + if (this->model_status_ != HighsModelStatus::kOptimal) { + if (info.primal_solution_status == kSolutionStatusFeasible && + info.dual_solution_status == kSolutionStatusFeasible) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to gain of optimality\n"); + this->model_status_ = HighsModelStatus::kOptimal; + } + } + if (!user_bound_scale_ok || !user_cost_scale_ok) return HighsStatus::kError; + return HighsStatus::kOk; +} + +void HighsIllConditioning::clear() { this->record.clear(); } + +HighsStatus Highs::computeIllConditioning( + HighsIllConditioning& ill_conditioning, const bool constraint, + const HighsInt method, const double ill_conditioning_bound) { + const double kZeroMultiplier = 1e-6; + ill_conditioning.clear(); + HighsLp& incumbent_lp = this->model_.lp_; + Highs conditioning; + const bool dev_conditioning = false; + conditioning.setOptionValue("output_flag", false); // dev_conditioning); + std::vector basic_var; + HighsLp& ill_conditioning_lp = conditioning.model_.lp_; + // Form the ill-conditioning LP according to method + if (method == 0) { + formIllConditioningLp0(ill_conditioning_lp, basic_var, constraint); + } else { + formIllConditioningLp1(ill_conditioning_lp, basic_var, constraint, + ill_conditioning_bound); + // conditioning.writeModel(""); + } + + // if (dev_conditioning) conditioning.writeModel(""); + + assert(assessLp(ill_conditioning_lp, this->options_) == HighsStatus::kOk); + // Solve the ill-conditioning analysis LP + HighsStatus return_status = conditioning.run(); + HighsModelStatus model_status = conditioning.getModelStatus(); + const std::string type = constraint ? "Constraint" : "Column"; + const bool failed = + return_status != HighsStatus::kOk || + (method == 0 && model_status != HighsModelStatus::kOptimal) || + (method == 1 && (model_status != HighsModelStatus::kOptimal && + model_status != HighsModelStatus::kInfeasible)); + if (failed) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "\n%s view ill-conditioning analysis has failed\n", + type.c_str()); + return HighsStatus::kError; + } + if (method == 1 && model_status == HighsModelStatus::kInfeasible) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "\n%s view ill-conditioning bound of %g is insufficient for " + "analysis: try %g\n", + type.c_str(), ill_conditioning_bound, + 1e1 * ill_conditioning_bound); + return HighsStatus::kOk; + } + if (dev_conditioning) conditioning.writeSolution("", 1); + // Extract and normalise the multipliers + HighsSolution& solution = conditioning.solution_; + double multiplier_norm = 0; + for (HighsInt iRow = 0; iRow < incumbent_lp.num_row_; iRow++) + multiplier_norm += std::fabs(solution.col_value[iRow]); + assert(multiplier_norm > 0); + const double ill_conditioning_measure = + (method == 0 ? conditioning.getInfo().objective_function_value + : solution.row_value[conditioning.getNumRow() - 1]) / + multiplier_norm; + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "\n%s view ill-conditioning analysis: 1-norm distance of basis matrix " + "from singularity is estimated to be %g\n", + type.c_str(), ill_conditioning_measure); + std::vector> abs_list; + for (HighsInt iRow = 0; iRow < incumbent_lp.num_row_; iRow++) { + double abs_multiplier = + std::fabs(solution.col_value[iRow]) / multiplier_norm; + if (abs_multiplier <= kZeroMultiplier) continue; + abs_list.push_back(std::make_pair(abs_multiplier, iRow)); + } + std::sort(abs_list.begin(), abs_list.end()); + // Report on ill-conditioning multipliers + std::stringstream ss; + const bool has_row_names = + HighsInt(incumbent_lp.row_names_.size()) == incumbent_lp.num_row_; + const bool has_col_names = + HighsInt(incumbent_lp.col_names_.size()) == incumbent_lp.num_col_; + const double coefficient_zero_tolerance = 1e-8; + auto printCoefficient = [&](const double multiplier, const bool first) { + if (std::fabs(multiplier) < coefficient_zero_tolerance) { + ss << "+ 0"; + } else if (std::fabs(multiplier - 1) < coefficient_zero_tolerance) { + std::string str = first ? "" : "+ "; + ss << str; + } else if (std::fabs(multiplier + 1) < coefficient_zero_tolerance) { + std::string str = first ? "-" : "- "; + ss << str; + } else if (multiplier < 0) { + std::string str = first ? "-" : "- "; + ss << str << -multiplier << " "; + } else { + std::string str = first ? "" : "+ "; + ss << str << multiplier << " "; + } + }; + + for (HighsInt iX = int(abs_list.size()) - 1; iX >= 0; iX--) { + HighsInt iRow = abs_list[iX].second; + HighsIllConditioningRecord record; + record.index = iRow; + record.multiplier = solution.col_value[iRow] / multiplier_norm; + ill_conditioning.record.push_back(record); + } + HighsSparseMatrix& incumbent_matrix = incumbent_lp.a_matrix_; + if (constraint) { + HighsInt num_nz; + std::vector index(incumbent_lp.num_col_); + std::vector value(incumbent_lp.num_col_); + HighsInt* p_index = index.data(); + double* p_value = value.data(); + for (HighsInt iX = 0; iX < HighsInt(ill_conditioning.record.size()); iX++) { + ss.str(std::string()); + bool newline = false; + HighsInt iRow = ill_conditioning.record[iX].index; + double multiplier = ill_conditioning.record[iX].multiplier; + // Extract the row corresponding to this constraint + num_nz = 0; + incumbent_matrix.getRow(iRow, num_nz, p_index, p_value); + std::string row_name = has_row_names ? incumbent_lp.row_names_[iRow] + : "R" + std::to_string(iRow); + ss << "(Mu=" << multiplier << ")" << row_name << ": "; + const double lower = incumbent_lp.row_lower_[iRow]; + const double upper = incumbent_lp.row_upper_[iRow]; + if (lower > -kHighsInf && lower != upper) + ss << incumbent_lp.row_lower_[iRow] << " <= "; + for (HighsInt iEl = 0; iEl < num_nz; iEl++) { + if (newline) { + ss << " "; + newline = false; + } + HighsInt iCol = index[iEl]; + printCoefficient(value[iEl], iEl == 0); + std::string col_name = has_col_names ? incumbent_lp.col_names_[iCol] + : "C" + std::to_string(iCol); + ss << col_name << " "; + HighsInt length_ss = ss.str().length(); + if (length_ss > 72 && iEl < num_nz - 1) { + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + ss.str(std::string()); + newline = true; + } + } + if (upper < kHighsInf) { + if (lower == upper) { + ss << "= " << upper; + } else { + ss << "<= " << upper; + } + } + if (ss.str().length()) + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + } + } else { + for (HighsInt iX = 0; iX < HighsInt(ill_conditioning.record.size()); iX++) { + ss.str(std::string()); + bool newline = false; + double multiplier = ill_conditioning.record[iX].multiplier; + HighsInt iCol = basic_var[ill_conditioning.record[iX].index]; + if (iCol < incumbent_lp.num_col_) { + std::string col_name = has_col_names ? incumbent_lp.col_names_[iCol] + : "C" + std::to_string(iCol); + ss << "(Mu=" << multiplier << ")" << col_name << ": "; + for (HighsInt iEl = incumbent_matrix.start_[iCol]; + iEl < incumbent_matrix.start_[iCol + 1]; iEl++) { + if (newline) { + ss << " "; + newline = false; + } else { + if (iEl > incumbent_matrix.start_[iCol]) ss << " | "; + } + HighsInt iRow = incumbent_matrix.index_[iEl]; + printCoefficient(incumbent_matrix.value_[iEl], true); + std::string row_name = has_row_names ? incumbent_lp.row_names_[iRow] + : "R" + std::to_string(iRow); + ss << row_name; + HighsInt length_ss = ss.str().length(); + if (length_ss > 72 && iEl < incumbent_matrix.start_[iCol + 1] - 1) { + ss << " | "; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + ss.str(std::string()); + newline = true; + } + } + } else { + HighsInt iRow = iCol - incumbent_lp.num_col_; + std::string col_name = has_row_names + ? "Slack_" + incumbent_lp.row_names_[iRow] + : "Slack_R" + std::to_string(iRow); + ss << "(Mu=" << multiplier << ")" << col_name << ": "; + } + if (ss.str().length()) + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + } + } + return HighsStatus::kOk; +} + +void Highs::formIllConditioningLp0(HighsLp& ill_conditioning_lp, + std::vector& basic_var, + const bool constraint) { + HighsLp& incumbent_lp = this->model_.lp_; + // Conditioning LP minimizes the infeasibilities of + // + // [B^T]y = [0]; y free - for constraint view + // [e^T] [1] + // + // [ B ]y = [0]; y free - for column view + // [e^T] [1] + // + ill_conditioning_lp.num_row_ = incumbent_lp.num_row_ + 1; + for (HighsInt iRow = 0; iRow < incumbent_lp.num_row_; iRow++) { + ill_conditioning_lp.row_lower_.push_back(0); + ill_conditioning_lp.row_upper_.push_back(0); + } + ill_conditioning_lp.row_lower_.push_back(1); + ill_conditioning_lp.row_upper_.push_back(1); + HighsSparseMatrix& incumbent_matrix = incumbent_lp.a_matrix_; + incumbent_matrix.ensureColwise(); + HighsSparseMatrix& ill_conditioning_matrix = ill_conditioning_lp.a_matrix_; + ill_conditioning_matrix.num_row_ = ill_conditioning_lp.num_row_; + // Form the basis matrix and + // + // * For constraint view, add the column e, and transpose the + // * resulting matrix + // + // * For column view, add a unit entry to each column + // + const HighsInt ill_conditioning_lp_e_row = ill_conditioning_lp.num_row_ - 1; + for (HighsInt iCol = 0; iCol < incumbent_lp.num_col_; iCol++) { + if (this->basis_.col_status[iCol] != HighsBasisStatus::kBasic) continue; + // Basic column goes into conditioning LP, possibly with unit + // coefficient for constraint e^Ty=1 + basic_var.push_back(iCol); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(-kHighsInf); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + for (HighsInt iEl = incumbent_matrix.start_[iCol]; + iEl < incumbent_matrix.start_[iCol + 1]; iEl++) { + ill_conditioning_matrix.index_.push_back(incumbent_matrix.index_[iEl]); + ill_conditioning_matrix.value_.push_back(incumbent_matrix.value_[iEl]); + } + if (!constraint) { + ill_conditioning_matrix.index_.push_back(ill_conditioning_lp_e_row); + ill_conditioning_matrix.value_.push_back(1.0); + } + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + } + for (HighsInt iRow = 0; iRow < incumbent_lp.num_row_; iRow++) { + if (this->basis_.row_status[iRow] != HighsBasisStatus::kBasic) continue; + // Basic slack goes into conditioning LP + basic_var.push_back(incumbent_lp.num_col_ + iRow); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(-kHighsInf); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(-1.0); + if (!constraint) { + ill_conditioning_matrix.index_.push_back(ill_conditioning_lp_e_row); + ill_conditioning_matrix.value_.push_back(1.0); + } + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + } + if (constraint) { + // Add the column e, and transpose the resulting matrix + for (HighsInt iRow = 0; iRow < incumbent_lp.num_row_; iRow++) { + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(1.0); + } + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_matrix.num_row_ = incumbent_lp.num_row_; + ill_conditioning_matrix.num_col_ = incumbent_lp.num_row_ + 1; + ill_conditioning_matrix.ensureRowwise(); + ill_conditioning_matrix.format_ = MatrixFormat::kColwise; + } + // Now add the variables to measure the infeasibilities + for (HighsInt iRow = 0; iRow < incumbent_lp.num_row_; iRow++) { + // Adding x_+ with cost 1 + ill_conditioning_lp.col_cost_.push_back(1); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + // Subtracting x_- with cost 1 + ill_conditioning_lp.col_cost_.push_back(1); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(-1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + } + ill_conditioning_lp.num_col_ = 3 * incumbent_lp.num_row_; + ill_conditioning_matrix.num_col_ = ill_conditioning_lp.num_col_; + ill_conditioning_matrix.num_row_ = ill_conditioning_lp.num_row_; +} + +void Highs::formIllConditioningLp1(HighsLp& ill_conditioning_lp, + std::vector& basic_var, + const bool constraint, + const double ill_conditioning_bound) { + HighsLp& incumbent_lp = this->model_.lp_; + const HighsInt incumbent_num_row = incumbent_lp.num_row_; + // + // Using notation from Klotz14 + // + // For constraint view, conditioning LP minimizes the + // infeasibilities of c7 + // + // c4: B^Ty - s + t = 0 + // c1: y - u + w = 0 + // c7: u + w = 0 + // c6: e^Ty = 1 + // c5: e^Ts + e^Tt <= eps + // y free; u, w, s, t >= 0 + // + // Column view uses B rather than B^T + // + // Set up offsets + // + const HighsInt c4_offset = 0; + const HighsInt c1_offset = incumbent_num_row; + const HighsInt c7_offset = 2 * incumbent_num_row; + const HighsInt c6_offset = 3 * incumbent_num_row; + const HighsInt c5_offset = 3 * incumbent_num_row + 1; + for (HighsInt iRow = 0; iRow < c6_offset; iRow++) { + ill_conditioning_lp.row_lower_.push_back(0); + ill_conditioning_lp.row_upper_.push_back(0); + } + HighsSparseMatrix& incumbent_matrix = incumbent_lp.a_matrix_; + incumbent_matrix.ensureColwise(); + HighsSparseMatrix& ill_conditioning_matrix = ill_conditioning_lp.a_matrix_; + // Form the basis matrix and + // + // * For constraint view, add the identity matrix and vector of + // * ones, and transpose the resulting matrix + // + // * For column view, add an identity matrix column and unit entry + // * below each column + // + ill_conditioning_lp.num_col_ = 0; + for (HighsInt iCol = 0; iCol < incumbent_lp.num_col_; iCol++) { + if (this->basis_.col_status[iCol] != HighsBasisStatus::kBasic) continue; + // Basic column goes into ill-conditioning LP, possibly with + // identity matrix column for constraint y - u + w = 0 and unit + // entry for e^Ty = 1 + basic_var.push_back(iCol); + ill_conditioning_lp.col_names_.push_back( + "y_" + std::to_string(ill_conditioning_lp.num_col_)); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(-kHighsInf); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + for (HighsInt iEl = incumbent_matrix.start_[iCol]; + iEl < incumbent_matrix.start_[iCol + 1]; iEl++) { + ill_conditioning_matrix.index_.push_back(incumbent_matrix.index_[iEl]); + ill_conditioning_matrix.value_.push_back(incumbent_matrix.value_[iEl]); + } + if (!constraint) { + // Add identity matrix column for constraint y - u + w = 0 + ill_conditioning_matrix.index_.push_back(c1_offset + + ill_conditioning_lp.num_col_); + ill_conditioning_matrix.value_.push_back(1.0); + // Add unit entry for e^Ty = 1 + ill_conditioning_matrix.index_.push_back(c6_offset); + ill_conditioning_matrix.value_.push_back(1.0); + } + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + } + + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) { + if (this->basis_.row_status[iRow] != HighsBasisStatus::kBasic) continue; + // Basic slack goes into conditioning LP + basic_var.push_back(incumbent_lp.num_col_ + iRow); + ill_conditioning_lp.col_names_.push_back( + "y_" + std::to_string(ill_conditioning_lp.num_col_)); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(-kHighsInf); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(-1.0); + if (!constraint) { + // Add identity matrix column for constraint y - u + w = 0 + ill_conditioning_matrix.index_.push_back(c1_offset + + ill_conditioning_lp.num_col_); + ill_conditioning_matrix.value_.push_back(1.0); + // Add unit entry for e^Ty = 1 + ill_conditioning_matrix.index_.push_back(c6_offset); + ill_conditioning_matrix.value_.push_back(1.0); + } + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + } + assert(ill_conditioning_lp.num_col_ == incumbent_num_row); + if (constraint) { + // Add the identity matrix for constraint y - u + w = 0 + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) { + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + } + // Add the square zero matrix of c7 + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + // Add the vector of ones for e^Ty = 1 + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) { + ill_conditioning_matrix.index_.push_back(iRow); + ill_conditioning_matrix.value_.push_back(1.0); + } + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + + // Transpose the resulting matrix + ill_conditioning_matrix.num_col_ = c6_offset + 1; + ill_conditioning_matrix.num_row_ = incumbent_num_row; + ill_conditioning_matrix.ensureRowwise(); + ill_conditioning_matrix.format_ = MatrixFormat::kColwise; + ill_conditioning_matrix.num_col_ = incumbent_num_row; + ill_conditioning_matrix.num_row_ = c6_offset + 1; + } + + assert(ill_conditioning_lp.num_col_ == incumbent_num_row); + ill_conditioning_lp.num_row_ = 3 * incumbent_num_row + 2; + + // Now add the variables u and w + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) { + // Adding u with cost 0 + ill_conditioning_lp.col_names_.push_back("u_" + std::to_string(iRow)); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + // Contribution to c1: y - u + w = 0 + ill_conditioning_matrix.index_.push_back(c1_offset + iRow); + ill_conditioning_matrix.value_.push_back(-1.0); + // Contribution to c7: u + w = 0 + ill_conditioning_matrix.index_.push_back(c7_offset + iRow); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + // Adding w with cost 0 + ill_conditioning_lp.col_names_.push_back("w_" + std::to_string(iRow)); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + // Contribution to c1: y - u + w = 0 + ill_conditioning_matrix.index_.push_back(c1_offset + iRow); + ill_conditioning_matrix.value_.push_back(1.0); + // Contribution to c7: u + w = 0 + ill_conditioning_matrix.index_.push_back(c7_offset + iRow); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + } + // Now add the variables s and t + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) { + // Adding s with cost 0 + ill_conditioning_lp.col_names_.push_back("s_" + std::to_string(iRow)); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + // Contribution to c4: B^Ty - s + t = 0 + ill_conditioning_matrix.index_.push_back(c4_offset + iRow); + ill_conditioning_matrix.value_.push_back(-1.0); + // Contribution to c5: e^Ts + e^Tt <= eps + ill_conditioning_matrix.index_.push_back(c5_offset); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + // Adding t with cost 0 + ill_conditioning_lp.col_names_.push_back("t_" + std::to_string(iRow)); + ill_conditioning_lp.col_cost_.push_back(0); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + // Contribution to c4: B^Ty - s + t = 0 + ill_conditioning_matrix.index_.push_back(c4_offset + iRow); + ill_conditioning_matrix.value_.push_back(1.0); + // Contribution to c5: e^Ts + e^Tt <= eps + ill_conditioning_matrix.index_.push_back(c5_offset); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + } + // Add the bounds for c6: e^Ty = 1 + ill_conditioning_lp.row_lower_.push_back(1); + ill_conditioning_lp.row_upper_.push_back(1); + // Add the bounds for c5: e^Ts + e^Tt <= eps + assert(ill_conditioning_bound > 0); + ill_conditioning_lp.row_lower_.push_back(-kHighsInf); + ill_conditioning_lp.row_upper_.push_back(ill_conditioning_bound); + assert(HighsInt(ill_conditioning_lp.row_lower_.size()) == + ill_conditioning_lp.num_row_); + assert(HighsInt(ill_conditioning_lp.row_upper_.size()) == + ill_conditioning_lp.num_row_); + + // Now add the variables to measure the infeasibilities in + // + // c7: u + w = r^+ - r^- + for (HighsInt iRow = 0; iRow < incumbent_num_row; iRow++) { + // Adding r^+ with cost 1 + ill_conditioning_lp.col_names_.push_back("IfsPlus_" + std::to_string(iRow)); + ill_conditioning_lp.col_cost_.push_back(1); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + ill_conditioning_matrix.index_.push_back(c7_offset + iRow); + ill_conditioning_matrix.value_.push_back(-1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + // Adding r^- with cost 1 + ill_conditioning_lp.col_names_.push_back("IfsMinus_" + + std::to_string(iRow)); + ill_conditioning_lp.col_cost_.push_back(1); + ill_conditioning_lp.col_lower_.push_back(0); + ill_conditioning_lp.col_upper_.push_back(kHighsInf); + ill_conditioning_matrix.index_.push_back(c7_offset + iRow); + ill_conditioning_matrix.value_.push_back(1.0); + ill_conditioning_matrix.start_.push_back( + HighsInt(ill_conditioning_matrix.index_.size())); + ill_conditioning_lp.num_col_++; + } + assert(ill_conditioning_lp.num_col_ == 7 * incumbent_num_row); + assert(ill_conditioning_lp.num_row_ == 3 * incumbent_num_row + 2); + ill_conditioning_matrix.num_col_ = ill_conditioning_lp.num_col_; + ill_conditioning_matrix.num_row_ = ill_conditioning_lp.num_row_; +} + +bool Highs::infeasibleBoundsOk() { + const HighsLogOptions& log_options = this->options_.log_options; + HighsLp& lp = this->model_.lp_; + + HighsInt num_true_infeasible_bound = 0; + HighsInt num_ok_infeasible_bound = 0; + const bool has_integrality = lp.integrality_.size() > 0; + // Lambda for assessing infeasible bounds + auto assessInfeasibleBound = [&](const std::string type, const HighsInt iX, + double& lower, double& upper) { + double range = upper - lower; + if (range >= 0) return true; + if (range > -this->options_.primal_feasibility_tolerance) { + num_ok_infeasible_bound++; + bool report = num_ok_infeasible_bound <= 10; + bool integer_lower = lower == std::floor(lower + 0.5); + bool integer_upper = upper == std::floor(upper + 0.5); + assert(!integer_lower || !integer_upper); + if (integer_lower) { + if (report) + highsLogUser(log_options, HighsLogType::kInfo, + "%s %d bounds [%g, %g] have infeasibility = %g so set " + "upper bound to %g\n", + type.c_str(), int(iX), lower, upper, range, lower); + upper = lower; + } else if (integer_upper) { + if (report) + highsLogUser(log_options, HighsLogType::kInfo, + "%s %d bounds [%g, %g] have infeasibility = %g so set " + "lower bound to %g\n", + type.c_str(), int(iX), lower, upper, range, upper); + lower = upper; + } else { + double mid = 0.5 * (lower + upper); + if (report) + highsLogUser(log_options, HighsLogType::kInfo, + "%s %d bounds [%g, %g] have infeasibility = %g so set " + "both bounds to %g\n", + type.c_str(), int(iX), lower, upper, range, mid); + lower = mid; + upper = mid; + } + return true; + } + num_true_infeasible_bound++; + if (num_true_infeasible_bound <= 10) + highsLogUser(log_options, HighsLogType::kInfo, + "%s %d bounds [%g, %g] have excessive infeasibility = %g\n", + type.c_str(), int(iX), lower, upper, range); + return false; + }; + + 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_lower_[iCol] > lp.col_upper_[iCol]) + assessInfeasibleBound("Column", iCol, lp.col_lower_[iCol], + lp.col_upper_[iCol]); + } + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (lp.row_lower_[iRow] > lp.row_upper_[iRow]) + assessInfeasibleBound("Row", iRow, lp.row_lower_[iRow], + lp.row_upper_[iRow]); + } + if (num_ok_infeasible_bound > 0) + highsLogUser(log_options, HighsLogType::kInfo, + "Model has %d small inconsistent bound(s): rectified\n", + int(num_ok_infeasible_bound)); + if (num_true_infeasible_bound > 0) + highsLogUser(log_options, HighsLogType::kInfo, + "Model has %d significant inconsistent bound(s): infeasible\n", + int(num_true_infeasible_bound)); + return num_true_infeasible_bound == 0; +} diff --git a/src/lp_data/HighsLp.cpp b/src/lp_data/HighsLp.cpp index c15e762555..23546b5660 100644 --- a/src/lp_data/HighsLp.cpp +++ b/src/lp_data/HighsLp.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -15,6 +15,7 @@ #include +#include "lp_data/HighsLpUtils.h" #include "util/HighsMatrixUtils.h" bool HighsLp::isMip() const { @@ -175,6 +176,9 @@ void HighsLp::clear() { this->col_hash_.clear(); this->row_hash_.clear(); + this->user_cost_scale_ = 0; + this->user_bound_scale_ = 0; + this->clearScale(); this->is_scaled_ = false; this->is_moved_ = false; @@ -256,6 +260,51 @@ void HighsLp::moveBackLpAndUnapplyScaling(HighsLp& lp) { assert(this->is_moved_ == false); } +bool HighsLp::userBoundScaleOk(const HighsInt user_bound_scale, + const double infinite_bound) const { + const HighsInt dl_user_bound_scale = + user_bound_scale - this->user_bound_scale_; + if (!dl_user_bound_scale) return true; + if (!boundScaleOk(this->col_lower_, this->col_upper_, dl_user_bound_scale, + infinite_bound)) + return false; + return boundScaleOk(this->row_lower_, this->row_upper_, dl_user_bound_scale, + infinite_bound); +} + +void HighsLp::userBoundScale(const HighsInt user_bound_scale) { + const HighsInt dl_user_bound_scale = + user_bound_scale - this->user_bound_scale_; + if (!dl_user_bound_scale) return; + double dl_user_bound_scale_value = std::pow(2, dl_user_bound_scale); + for (HighsInt iCol = 0; iCol < this->num_col_; iCol++) { + this->col_lower_[iCol] *= dl_user_bound_scale_value; + this->col_upper_[iCol] *= dl_user_bound_scale_value; + } + for (HighsInt iRow = 0; iRow < this->num_row_; iRow++) { + this->row_lower_[iRow] *= dl_user_bound_scale_value; + this->row_upper_[iRow] *= dl_user_bound_scale_value; + } + // Record the current user bound scaling applied to the LP + this->user_bound_scale_ = user_bound_scale; +} + +bool HighsLp::userCostScaleOk(const HighsInt user_cost_scale, + const double infinite_cost) const { + const HighsInt dl_user_cost_scale = user_cost_scale - this->user_cost_scale_; + if (!dl_user_cost_scale) return true; + return costScaleOk(this->col_cost_, dl_user_cost_scale, infinite_cost); +} + +void HighsLp::userCostScale(const HighsInt user_cost_scale) { + const HighsInt dl_user_cost_scale = user_cost_scale - this->user_cost_scale_; + if (!dl_user_cost_scale) return; + double dl_user_cost_scale_value = std::pow(2, dl_user_cost_scale); + for (HighsInt iCol = 0; iCol < this->num_col_; iCol++) + this->col_cost_[iCol] *= dl_user_cost_scale_value; + this->user_cost_scale_ = user_cost_scale; +} + 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 @@ -344,6 +393,105 @@ void HighsLp::addRowNames(const std::string name, const HighsInt num_new_row) { } } +void HighsLp::deleteColsFromVectors( + HighsInt& new_num_col, const HighsIndexCollection& index_collection) { + assert(ok(index_collection)); + HighsInt from_k; + HighsInt to_k; + limits(index_collection, from_k, to_k); + // Initialise new_num_col in case none is removed due to from_k > to_k + new_num_col = this->num_col_; + if (from_k > to_k) return; + + HighsInt delete_from_col; + HighsInt delete_to_col; + HighsInt keep_from_col; + HighsInt keep_to_col = -1; + HighsInt current_set_entry = 0; + + HighsInt col_dim = this->num_col_; + new_num_col = 0; + bool have_names = (this->col_names_.size() != 0); + bool have_integrality = (this->integrality_.size() != 0); + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, delete_from_col, delete_to_col, + keep_from_col, keep_to_col, current_set_entry); + // Account for the initial columns being kept + if (k == from_k) new_num_col = delete_from_col; + if (delete_to_col >= col_dim - 1) break; + assert(delete_to_col < col_dim); + for (HighsInt col = keep_from_col; col <= keep_to_col; col++) { + this->col_cost_[new_num_col] = this->col_cost_[col]; + this->col_lower_[new_num_col] = this->col_lower_[col]; + this->col_upper_[new_num_col] = this->col_upper_[col]; + if (have_names) this->col_names_[new_num_col] = this->col_names_[col]; + if (have_integrality) + this->integrality_[new_num_col] = this->integrality_[col]; + new_num_col++; + } + if (keep_to_col >= col_dim - 1) break; + } + this->col_cost_.resize(new_num_col); + this->col_lower_.resize(new_num_col); + this->col_upper_.resize(new_num_col); + if (have_names) this->col_names_.resize(new_num_col); +} + +void HighsLp::deleteRowsFromVectors( + HighsInt& new_num_row, const HighsIndexCollection& index_collection) { + assert(ok(index_collection)); + HighsInt from_k; + HighsInt to_k; + limits(index_collection, from_k, to_k); + // Initialise new_num_row in case none is removed due to from_k > to_k + new_num_row = this->num_row_; + if (from_k > to_k) return; + + HighsInt delete_from_row; + HighsInt delete_to_row; + HighsInt keep_from_row; + HighsInt keep_to_row = -1; + HighsInt current_set_entry = 0; + + HighsInt row_dim = this->num_row_; + new_num_row = 0; + bool have_names = (HighsInt)this->row_names_.size() > 0; + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, delete_from_row, delete_to_row, + keep_from_row, keep_to_row, current_set_entry); + if (k == from_k) { + // Account for the initial rows being kept + new_num_row = delete_from_row; + } + if (delete_to_row >= row_dim - 1) break; + assert(delete_to_row < row_dim); + for (HighsInt row = keep_from_row; row <= keep_to_row; row++) { + this->row_lower_[new_num_row] = this->row_lower_[row]; + this->row_upper_[new_num_row] = this->row_upper_[row]; + if (have_names) this->row_names_[new_num_row] = this->row_names_[row]; + new_num_row++; + } + if (keep_to_row >= row_dim - 1) break; + } + this->row_lower_.resize(new_num_row); + this->row_upper_.resize(new_num_row); + if (have_names) this->row_names_.resize(new_num_row); +} + +void HighsLp::deleteCols(const HighsIndexCollection& index_collection) { + HighsInt new_num_col; + this->deleteColsFromVectors(new_num_col, index_collection); + this->a_matrix_.deleteCols(index_collection); + this->num_col_ = new_num_col; +} + +void HighsLp::deleteRows(const HighsIndexCollection& index_collection) { + HighsInt new_num_row; + this->deleteRowsFromVectors(new_num_row, index_collection); + this->a_matrix_.deleteRows(index_collection); + this->num_row_ = new_num_row; +} + void HighsLp::unapplyMods() { // Restore any non-semi types const HighsInt num_non_semi = this->mods_.save_non_semi_variable_index.size(); @@ -443,26 +591,25 @@ bool HighsLpMods::isClear() { } void HighsNameHash::form(const std::vector& name) { - HighsInt num_name = name.size(); + size_t num_name = name.size(); this->clear(); - for (HighsInt index = 0; index < num_name; index++) { - const bool duplicate = !this->name2index.emplace(name[index], index).second; + for (size_t index = 0; index < num_name; index++) { + auto emplace_result = this->name2index.emplace(name[index], index); + const bool duplicate = !emplace_result.second; if (duplicate) { // Find the original and mark it as duplicate - auto search = this->name2index.find(name[index]); - assert(search != this->name2index.end()); + auto& search = emplace_result.first; assert(int(search->second) < int(this->name2index.size())); - this->name2index.erase(search); - this->name2index.insert({name[index], kHashIsDuplicate}); + search->second = kHashIsDuplicate; } } } bool HighsNameHash::hasDuplicate(const std::vector& name) { - HighsInt num_name = name.size(); + size_t num_name = name.size(); this->clear(); bool has_duplicate = false; - for (HighsInt index = 0; index < num_name; index++) { + for (size_t index = 0; index < num_name; index++) { has_duplicate = !this->name2index.emplace(name[index], index).second; if (has_duplicate) break; } @@ -470,4 +617,17 @@ bool HighsNameHash::hasDuplicate(const std::vector& name) { return has_duplicate; } +void HighsNameHash::update(int index, const std::string& old_name, + const std::string& new_name) { + this->name2index.erase(old_name); + auto emplace_result = this->name2index.emplace(new_name, index); + const bool duplicate = !emplace_result.second; + if (duplicate) { + // Find the original and mark it as duplicate + auto& search = emplace_result.first; + assert(int(search->second) < int(this->name2index.size())); + search->second = kHashIsDuplicate; + } +} + void HighsNameHash::clear() { this->name2index.clear(); } diff --git a/src/lp_data/HighsLp.h b/src/lp_data/HighsLp.h index c5770c7ea7..1e8b766759 100644 --- a/src/lp_data/HighsLp.h +++ b/src/lp_data/HighsLp.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -50,6 +50,8 @@ class HighsLp { HighsNameHash col_hash_; HighsNameHash row_hash_; + HighsInt user_bound_scale_; + HighsInt user_cost_scale_; HighsScale scale_; bool is_scaled_; bool is_moved_; @@ -77,9 +79,21 @@ class HighsLp { void applyScale(); void unapplyScale(); void moveBackLpAndUnapplyScaling(HighsLp& lp); + bool userBoundScaleOk(const HighsInt user_bound_scale, + const double infinite_bound) const; + void userBoundScale(const HighsInt user_bound_scale); + bool userCostScaleOk(const HighsInt user_cost_scale, + const double infinite_cost) const; + void userCostScale(const HighsInt user_cost_scale); 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 deleteColsFromVectors(HighsInt& new_num_col, + const HighsIndexCollection& index_collection); + void deleteRowsFromVectors(HighsInt& new_num_row, + const HighsIndexCollection& index_collection); + void deleteCols(const HighsIndexCollection& index_collection); + void deleteRows(const HighsIndexCollection& index_collection); void unapplyMods(); void clear(); }; diff --git a/src/lp_data/HighsLpSolverObject.h b/src/lp_data/HighsLpSolverObject.h index 0a1f669558..895922487a 100644 --- a/src/lp_data/HighsLpSolverObject.h +++ b/src/lp_data/HighsLpSolverObject.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 77f941d9c2..248c6aa842 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -43,34 +43,29 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) { return_status, "assessLpDimensions"); if (return_status == HighsStatus::kError) return return_status; - // If the LP has no columns there is nothing left to test - if (lp.num_col_ == 0) return HighsStatus::kOk; - assert(lp.a_matrix_.isColwise()); - - // From here, any LP has lp.num_col_ > 0 and lp.a_matrix_.start_[lp.num_col_] - // exists (as the number of nonzeros) - assert(lp.num_col_ > 0); - - // Assess the LP column costs - HighsIndexCollection index_collection; - index_collection.dimension_ = lp.num_col_; - index_collection.is_interval_ = true; - index_collection.from_ = 0; - index_collection.to_ = lp.num_col_ - 1; - call_status = assessCosts(options, 0, index_collection, lp.col_cost_, - lp.has_infinite_cost_, options.infinite_cost); - return_status = interpretCallStatus(options.log_options, call_status, - return_status, "assessCosts"); - 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.integrality_.data()); - return_status = interpretCallStatus(options.log_options, call_status, - return_status, "assessBounds"); - if (return_status == HighsStatus::kError) return return_status; + if (lp.num_col_) { + // Assess the LP column costs + HighsIndexCollection index_collection; + index_collection.dimension_ = lp.num_col_; + index_collection.is_interval_ = true; + index_collection.from_ = 0; + index_collection.to_ = lp.num_col_ - 1; + call_status = assessCosts(options, 0, index_collection, lp.col_cost_, + lp.has_infinite_cost_, options.infinite_cost); + return_status = interpretCallStatus(options.log_options, call_status, + return_status, "assessCosts"); + 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.isMip() ? lp.integrality_.data() : nullptr); + return_status = interpretCallStatus(options.log_options, call_status, + return_status, "assessBounds"); + if (return_status == HighsStatus::kError) return return_status; + } if (lp.num_row_) { // Assess the LP row bounds + HighsIndexCollection index_collection; index_collection.dimension_ = lp.num_row_; index_collection.is_interval_ = true; index_collection.from_ = 0; @@ -82,6 +77,16 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) { return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; } + // If the LP has no columns the matrix must be empty and there is + // nothing left to test + if (lp.num_col_ == 0) { + assert(!lp.a_matrix_.numNz()); + return HighsStatus::kOk; + } + // From here, any LP has lp.num_col_ > 0 and lp.a_matrix_.start_[lp.num_col_] + // exists (as the number of nonzeros) + assert(lp.num_col_ > 0); + // Assess the LP matrix - even if there are no rows! call_status = lp.a_matrix_.assess(options.log_options, "LP", options.small_matrix_value, @@ -89,22 +94,13 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) { return_status = interpretCallStatus(options.log_options, call_status, return_status, "assessMatrix"); if (return_status == HighsStatus::kError) return return_status; - HighsInt lp_num_nz = lp.a_matrix_.start_[lp.num_col_]; // If entries have been removed from the matrix, resize the index // and value vectors to prevent bug in presolve + HighsInt lp_num_nz = lp.a_matrix_.numNz(); if ((HighsInt)lp.a_matrix_.index_.size() > lp_num_nz) lp.a_matrix_.index_.resize(lp_num_nz); if ((HighsInt)lp.a_matrix_.value_.size() > lp_num_nz) lp.a_matrix_.value_.resize(lp_num_nz); - if ((HighsInt)lp.a_matrix_.index_.size() > lp_num_nz) - lp.a_matrix_.index_.resize(lp_num_nz); - if ((HighsInt)lp.a_matrix_.value_.size() > lp_num_nz) - lp.a_matrix_.value_.resize(lp_num_nz); - - // if (return_status == HighsStatus::kError) - // return_status = HighsStatus::kError; - // else - // return_status = HighsStatus::kOk; if (return_status != HighsStatus::kOk) highsLogDev(options.log_options, HighsLogType::kInfo, "assessLp returns HighsStatus = %s\n", @@ -132,7 +128,6 @@ bool lpDimensionsOk(std::string message, const HighsLp& lp, HighsInt col_cost_size = lp.col_cost_.size(); HighsInt col_lower_size = lp.col_lower_.size(); HighsInt col_upper_size = lp.col_upper_.size(); - HighsInt matrix_start_size = lp.a_matrix_.start_.size(); bool legal_col_cost_size = col_cost_size >= num_col; bool legal_col_lower_size = col_lower_size >= num_col; bool legal_col_upper_size = col_lower_size >= num_col; @@ -510,7 +505,7 @@ HighsStatus assessSemiVariables(HighsLp& lp, const HighsOptions& options, if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || lp.integrality_[iCol] == HighsVarType::kSemiInteger) { if (lp.col_lower_[iCol] > lp.col_upper_[iCol]) { - // Semi-variables with inconsistent bounds become continous + // Semi-variables with inconsistent bounds become continuous // and fixed at zero num_inconsistent_semi++; inconsistent_semi_variable_index.push_back(iCol); @@ -665,7 +660,6 @@ void relaxSemiVariables(HighsLp& lp, bool& made_semi_variable_mods) { made_semi_variable_mods = false; if (!lp.integrality_.size()) return; assert((HighsInt)lp.integrality_.size() == lp.num_col_); - HighsInt num_modified_lower = 0; std::vector& relaxed_semi_variable_lower_index = lp.mods_.save_relaxed_semi_variable_lower_bound_index; std::vector& relaxed_semi_variable_lower_value = @@ -716,7 +710,7 @@ bool activeModifiedUpperBounds(const HighsOptions& options, const HighsLp& lp, " but there is no guarantee\n", min_semi_variable_margin); } - return num_active_modified_upper; + return (num_active_modified_upper != 0); } HighsStatus cleanBounds(const HighsOptions& options, HighsLp& lp) { @@ -767,6 +761,32 @@ HighsStatus cleanBounds(const HighsOptions& options, HighsLp& lp) { return HighsStatus::kOk; } +bool boundScaleOk(const vector& lower, const vector& upper, + const HighsInt bound_scale, const double infinite_bound) { + if (!bound_scale) return true; + double bound_scale_value = std::pow(2, bound_scale); + for (HighsInt iCol = 0; iCol < HighsInt(lower.size()); iCol++) { + if (lower[iCol] > -kHighsInf && + std::abs(lower[iCol] * bound_scale_value) > infinite_bound) + return false; + if (upper[iCol] < kHighsInf && + std::abs(upper[iCol] * bound_scale_value) > infinite_bound) + return false; + } + return true; +} + +bool costScaleOk(const vector& cost, const HighsInt cost_scale, + const double infinite_cost) { + if (!cost_scale) return true; + double cost_scale_value = std::pow(2, cost_scale); + for (HighsInt iCol = 0; iCol < HighsInt(cost.size()); iCol++) + if (std::abs(cost[iCol]) < kHighsInf && + std::abs(cost[iCol] * cost_scale_value) > infinite_cost) + return false; + return true; +} + bool considerScaling(const HighsOptions& options, HighsLp& lp) { // Indicate whether new scaling has been determined in the return value. bool new_scaling = false; @@ -830,7 +850,6 @@ void scaleLp(const HighsOptions& options, HighsLp& lp, // something more intelligent use_scale_strategy = kSimplexScaleStrategyForcedEquilibration; } - bool allow_cost_scaling = options.allowed_cost_scale_factor > 0; // Find out range of matrix values and skip matrix scaling if all // |values| are in [0.2, 5] const double no_scaling_original_matrix_min_value = 0.2; @@ -1088,7 +1107,7 @@ bool equilibrationScaleMatrix(const HighsOptions& options, HighsLp& lp, exp(sum_log_col_equilibration / numCol); const double geomean_row_equilibration = exp(sum_log_row_equilibration / numRow); - if (options.highs_analysis_level) { + if (options.log_dev_level) { highsLogDev( options.log_options, HighsLogType::kInfo, "Scaling: Original equilibration: min/mean/max %11.4g/%11.4g/%11.4g " @@ -1134,7 +1153,7 @@ bool equilibrationScaleMatrix(const HighsOptions& options, HighsLp& lp, original_matrix_max_value / original_matrix_min_value; const double matrix_value_ratio_improvement = original_matrix_value_ratio / matrix_value_ratio; - if (options.highs_analysis_level) { + if (options.log_dev_level) { highsLogDev( options.log_options, HighsLogType::kInfo, "Scaling: Extreme equilibration improvement = ( %11.4g + " @@ -1176,7 +1195,7 @@ bool equilibrationScaleMatrix(const HighsOptions& options, HighsLp& lp, const bool poor_improvement = improvement_factor < improvement_factor_required; - // Possibly abandon scaling if it's not improved equlibration significantly + // Possibly abandon scaling if it's not improved equilibration significantly if (possibly_abandon_scaling && poor_improvement) { // Unscale the matrix for (HighsInt iCol = 0; iCol < numCol; iCol++) { @@ -1185,14 +1204,18 @@ bool equilibrationScaleMatrix(const HighsOptions& options, HighsLp& lp, Avalue[k] /= (colScale[iCol] * rowScale[iRow]); } } - if (options.highs_analysis_level) + if (options.log_dev_level) highsLogDev(options.log_options, HighsLogType::kInfo, "Scaling: Improvement factor %0.4g < %0.4g required, so no " "scaling applied\n", improvement_factor, improvement_factor_required); return false; } else { - if (options.highs_analysis_level) { + if (options.log_dev_level) { + highsLogDev(options.log_options, HighsLogType::kInfo, + "Scaling: Factors are in [%0.4g, %0.4g] for columns and in " + "[%0.4g, %0.4g] for rows\n", + min_col_scale, max_col_scale, min_row_scale, max_row_scale); highsLogDev(options.log_options, HighsLogType::kInfo, "Scaling: Improvement factor is %0.4g >= %0.4g so scale LP\n", improvement_factor, improvement_factor_required); @@ -1236,8 +1259,6 @@ bool maxValueScaleMatrix(const HighsOptions& options, HighsLp& lp, vector& Aindex = lp.a_matrix_.index_; vector& Avalue = lp.a_matrix_.value_; - HighsInt simplex_scale_strategy = use_scale_strategy; - assert(options.simplex_scale_strategy == kSimplexScaleStrategyMaxValue015 || options.simplex_scale_strategy == kSimplexScaleStrategyMaxValue0157); @@ -1349,14 +1370,14 @@ bool maxValueScaleMatrix(const HighsOptions& options, HighsLp& lp, Avalue[k] /= (colScale[iCol] * rowScale[iRow]); } } - if (options.highs_analysis_level) + if (options.log_dev_level) highsLogDev(options.log_options, HighsLogType::kInfo, "Scaling: Improvement factor %0.4g < %0.4g required, so no " "scaling applied\n", improvement_factor, improvement_factor_required); return false; } else { - if (options.highs_analysis_level) { + if (options.log_dev_level) { highsLogDev(options.log_options, HighsLogType::kInfo, "Scaling: Factors are in [%0.4g, %0.4g] for columns and in " "[%0.4g, %0.4g] for rows\n", @@ -1410,6 +1431,22 @@ HighsStatus applyScalingToLpRow(HighsLp& lp, const HighsInt row, return HighsStatus::kOk; } +void unscaleSolution(HighsSolution& solution, const HighsScale& scale) { + assert(solution.col_value.size() == static_cast(scale.num_col)); + assert(solution.col_dual.size() == static_cast(scale.num_col)); + assert(solution.row_value.size() == static_cast(scale.num_row)); + assert(solution.row_dual.size() == static_cast(scale.num_row)); + + for (HighsInt iCol = 0; iCol < scale.num_col; iCol++) { + solution.col_value[iCol] *= scale.col[iCol]; + solution.col_dual[iCol] /= (scale.col[iCol] / scale.cost); + } + for (HighsInt iRow = 0; iRow < scale.num_row; iRow++) { + solution.row_value[iRow] /= scale.row[iRow]; + solution.row_dual[iRow] *= (scale.row[iRow] * scale.cost); + } +} + void appendColsToLpVectors(HighsLp& lp, const HighsInt num_new_col, const vector& colCost, const vector& colLower, @@ -1420,7 +1457,12 @@ void appendColsToLpVectors(HighsLp& lp, const HighsInt num_new_col, lp.col_cost_.resize(new_num_col); lp.col_lower_.resize(new_num_col); lp.col_upper_.resize(new_num_col); - bool have_names = lp.col_names_.size(); + const bool have_integrality = (lp.integrality_.size() != 0); + if (have_integrality) { + assert(HighsInt(lp.integrality_.size()) == lp.num_col_); + lp.integrality_.resize(new_num_col); + } + bool have_names = (lp.col_names_.size() != 0); if (have_names) lp.col_names_.resize(new_num_col); for (HighsInt new_col = 0; new_col < num_new_col; new_col++) { HighsInt iCol = lp.num_col_ + new_col; @@ -1429,6 +1471,7 @@ void appendColsToLpVectors(HighsLp& lp, const HighsInt num_new_col, lp.col_upper_[iCol] = colUpper[new_col]; // Cannot guarantee to create unique names, so name is blank if (have_names) lp.col_names_[iCol] = ""; + if (have_integrality) lp.integrality_[iCol] = HighsVarType::kContinuous; } } @@ -1440,7 +1483,7 @@ void appendRowsToLpVectors(HighsLp& lp, const HighsInt num_new_row, HighsInt new_num_row = lp.num_row_ + num_new_row; lp.row_lower_.resize(new_num_row); lp.row_upper_.resize(new_num_row); - bool have_names = lp.row_names_.size(); + bool have_names = (lp.row_names_.size() != 0); if (have_names) lp.row_names_.resize(new_num_row); for (HighsInt new_row = 0; new_row < num_new_row; new_row++) { @@ -1452,110 +1495,8 @@ void appendRowsToLpVectors(HighsLp& lp, const HighsInt num_new_row, } } -void deleteLpCols(HighsLp& lp, const HighsIndexCollection& index_collection) { - HighsInt new_num_col; - HighsStatus call_status; - deleteColsFromLpVectors(lp, new_num_col, index_collection); - lp.a_matrix_.deleteCols(index_collection); - lp.num_col_ = new_num_col; -} - -void deleteColsFromLpVectors(HighsLp& lp, HighsInt& new_num_col, - const HighsIndexCollection& index_collection) { - assert(ok(index_collection)); - HighsInt from_k; - HighsInt to_k; - limits(index_collection, from_k, to_k); - ; - // Initialise new_num_col in case none is removed due to from_k > to_k - new_num_col = lp.num_col_; - if (from_k > to_k) return; - - HighsInt delete_from_col; - HighsInt delete_to_col; - HighsInt keep_from_col; - HighsInt keep_to_col = -1; - HighsInt current_set_entry = 0; - - HighsInt col_dim = lp.num_col_; - new_num_col = 0; - bool have_names = lp.col_names_.size(); - bool have_integrality = lp.integrality_.size(); - for (HighsInt k = from_k; k <= to_k; k++) { - updateOutInIndex(index_collection, delete_from_col, delete_to_col, - keep_from_col, keep_to_col, current_set_entry); - // Account for the initial columns being kept - if (k == from_k) new_num_col = delete_from_col; - if (delete_to_col >= col_dim - 1) break; - assert(delete_to_col < col_dim); - for (HighsInt col = keep_from_col; col <= keep_to_col; col++) { - lp.col_cost_[new_num_col] = lp.col_cost_[col]; - lp.col_lower_[new_num_col] = lp.col_lower_[col]; - lp.col_upper_[new_num_col] = lp.col_upper_[col]; - if (have_names) lp.col_names_[new_num_col] = lp.col_names_[col]; - if (have_integrality) lp.integrality_[new_num_col] = lp.integrality_[col]; - new_num_col++; - } - if (keep_to_col >= col_dim - 1) break; - } - lp.col_cost_.resize(new_num_col); - lp.col_lower_.resize(new_num_col); - lp.col_upper_.resize(new_num_col); - if (have_names) lp.col_names_.resize(new_num_col); -} - -void deleteLpRows(HighsLp& lp, const HighsIndexCollection& index_collection) { - HighsStatus call_status; - HighsInt new_num_row; - deleteRowsFromLpVectors(lp, new_num_row, index_collection); - lp.a_matrix_.deleteRows(index_collection); - lp.num_row_ = new_num_row; -} - -void deleteRowsFromLpVectors(HighsLp& lp, HighsInt& new_num_row, - const HighsIndexCollection& index_collection) { - assert(ok(index_collection)); - HighsInt from_k; - HighsInt to_k; - limits(index_collection, from_k, to_k); - // Initialise new_num_row in case none is removed due to from_k > to_k - new_num_row = lp.num_row_; - if (from_k > to_k) return; - - HighsInt delete_from_row; - HighsInt delete_to_row; - HighsInt keep_from_row; - HighsInt keep_to_row = -1; - HighsInt current_set_entry = 0; - - HighsInt row_dim = lp.num_row_; - new_num_row = 0; - bool have_names = (HighsInt)lp.row_names_.size() > 0; - for (HighsInt k = from_k; k <= to_k; k++) { - updateOutInIndex(index_collection, delete_from_row, delete_to_row, - keep_from_row, keep_to_row, current_set_entry); - if (k == from_k) { - // Account for the initial rows being kept - new_num_row = delete_from_row; - } - if (delete_to_row >= row_dim - 1) break; - assert(delete_to_row < row_dim); - for (HighsInt row = keep_from_row; row <= keep_to_row; row++) { - lp.row_lower_[new_num_row] = lp.row_lower_[row]; - lp.row_upper_[new_num_row] = lp.row_upper_[row]; - if (have_names) lp.row_names_[new_num_row] = lp.row_names_[row]; - new_num_row++; - } - if (keep_to_row >= row_dim - 1) break; - } - lp.row_lower_.resize(new_num_row); - lp.row_upper_.resize(new_num_row); - if (have_names) lp.row_names_.resize(new_num_row); -} - void deleteScale(vector& scale, const HighsIndexCollection& index_collection) { - HighsStatus return_status = HighsStatus::kOk; assert(ok(index_collection)); HighsInt from_k; HighsInt to_k; @@ -1651,9 +1592,12 @@ void changeLpIntegrality(HighsLp& lp, // technique HighsInt lp_col; HighsInt usr_col = -1; - // May be adding integrality to a pure LP for which lp.integrality_ - // is of size 0. - lp.integrality_.resize(lp.num_col_); + // If changing integrality for a problem without an integrality + // vector (ie an LP), have to create it for the incumbent columns - + // which are naturally continuous + if (lp.integrality_.size() == 0) + lp.integrality_.assign(lp.num_col_, HighsVarType::kContinuous); + assert(HighsInt(lp.integrality_.size()) == lp.num_col_); for (HighsInt k = from_k; k < to_k + 1; k++) { if (interval || mask) { lp_col = k; @@ -1771,7 +1715,8 @@ HighsInt getNumInt(const HighsLp& lp) { void getLpCosts(const HighsLp& lp, const HighsInt from_col, const HighsInt to_col, double* XcolCost) { - assert(0 <= from_col && to_col < lp.num_col_); + assert(0 <= from_col && from_col < lp.num_col_); + assert(0 <= to_col && to_col < lp.num_col_); if (from_col > to_col) return; for (HighsInt col = from_col; col < to_col + 1; col++) XcolCost[col - from_col] = lp.col_cost_[col]; @@ -1780,22 +1725,24 @@ void getLpCosts(const HighsLp& lp, const HighsInt from_col, void getLpColBounds(const HighsLp& lp, const HighsInt from_col, const HighsInt to_col, double* XcolLower, double* XcolUpper) { - assert(0 <= from_col && to_col < lp.num_col_); + assert(0 <= from_col && from_col < lp.num_col_); + assert(0 <= to_col && to_col < lp.num_col_); if (from_col > to_col) return; for (HighsInt col = from_col; col < to_col + 1; col++) { - if (XcolLower != NULL) XcolLower[col - from_col] = lp.col_lower_[col]; - if (XcolUpper != NULL) XcolUpper[col - from_col] = lp.col_upper_[col]; + if (XcolLower != nullptr) XcolLower[col - from_col] = lp.col_lower_[col]; + if (XcolUpper != nullptr) XcolUpper[col - from_col] = lp.col_upper_[col]; } } void getLpRowBounds(const HighsLp& lp, const HighsInt from_row, const HighsInt to_row, double* XrowLower, double* XrowUpper) { - assert(0 <= to_row && from_row < lp.num_row_); + assert(0 <= from_row && from_row < lp.num_row_); + assert(0 <= to_row && to_row < lp.num_row_); if (from_row > to_row) return; for (HighsInt row = from_row; row < to_row + 1; row++) { - if (XrowLower != NULL) XrowLower[row - from_row] = lp.row_lower_[row]; - if (XrowUpper != NULL) XrowUpper[row - from_row] = lp.row_upper_[row]; + if (XrowLower != nullptr) XrowLower[row - from_row] = lp.row_lower_[row]; + if (XrowUpper != nullptr) XrowUpper[row - from_row] = lp.row_upper_[row]; } } @@ -1903,8 +1850,8 @@ void reportLpColVectors(const HighsLogOptions& log_options, const HighsLp& lp) { if (lp.num_col_ <= 0) return; std::string type; HighsInt count; - bool have_integer_columns = getNumInt(lp); - bool have_col_names = lp.col_names_.size(); + bool have_integer_columns = (getNumInt(lp) != 0); + bool have_col_names = (lp.col_names_.size() != 0); highsLogUser(log_options, HighsLogType::kInfo, " Column Lower Upper Cost " @@ -1946,7 +1893,7 @@ void reportLpRowVectors(const HighsLogOptions& log_options, const HighsLp& lp) { if (lp.num_row_ <= 0) return; std::string type; vector count; - bool have_row_names = lp.row_names_.size(); + bool have_row_names = (lp.row_names_.size() != 0); count.resize(lp.num_row_, 0); if (lp.num_col_ > 0) { @@ -1978,7 +1925,7 @@ void reportLpRowVectors(const HighsLogOptions& log_options, const HighsLp& lp) { void reportLpColMatrix(const HighsLogOptions& log_options, const HighsLp& lp) { if (lp.num_col_ <= 0) return; if (lp.num_row_) { - // With postitive number of rows, can assume that there are index and value + // With positive 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_.data(), @@ -2101,7 +2048,7 @@ HighsStatus readSolutionFile(const std::string filename, HighsInt num_row; const HighsInt lp_num_col = lp.num_col_; const HighsInt lp_num_row = lp.num_row_; - // Define idetifiers for reading in + // Define identifiers for reading in HighsSolution read_solution = solution; HighsBasis read_basis = basis; read_solution.clear(); @@ -2113,7 +2060,6 @@ HighsStatus readSolutionFile(const std::string filename, read_basis.col_status.resize(lp_num_col); read_basis.row_status.resize(lp_num_row); std::string section_name; - HighsInt status; if (!readSolutionFileIgnoreLineOk(in_file)) return readSolutionFileErrorReturn(in_file); // Model status if (!readSolutionFileIgnoreLineOk(in_file)) @@ -2140,7 +2086,7 @@ HighsStatus readSolutionFile(const std::string filename, assert(keyword == "Columns"); // The default style parameter is kSolutionStyleRaw, and this still // allows sparse files to be read. Recognise the latter from num_col - // <= 0. Doesn't matter if num_col = 0, sinc ethere's nothing to + // <= 0. Doesn't matter if num_col = 0, since there's nothing to // read either way const bool sparse = num_col <= 0; if (style == kSolutionStyleSparse) assert(sparse); @@ -2173,8 +2119,8 @@ HighsStatus readSolutionFile(const std::string filename, } read_solution.value_valid = true; if (sparse) { - if (calculateRowValues(lp, read_solution.col_value, - read_solution.row_value) != HighsStatus::kOk) + if (calculateRowValuesQuad(lp, read_solution.col_value, + read_solution.row_value) != HighsStatus::kOk) return readSolutionFileErrorReturn(in_file); return readSolutionFileReturn(HighsStatus::kOk, solution, basis, read_solution, read_basis, in_file); @@ -2183,8 +2129,8 @@ HighsStatus readSolutionFile(const std::string filename, // should be "Rows" and correct number if (!readSolutionFileHashKeywordIntLineOk(keyword, num_row, in_file)) { // Compute the row values since there are none to read - if (calculateRowValues(lp, read_solution.col_value, - read_solution.row_value) != HighsStatus::kOk) + if (calculateRowValuesQuad(lp, read_solution.col_value, + read_solution.row_value) != HighsStatus::kOk) return readSolutionFileErrorReturn(in_file); return readSolutionFileReturn(HighsStatus::kOk, solution, basis, read_solution, read_basis, in_file); @@ -2193,7 +2139,7 @@ HighsStatus readSolutionFile(const std::string filename, // 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 + // of variables, but increasing 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; @@ -2208,8 +2154,8 @@ HighsStatus readSolutionFile(const std::string filename, " 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) + if (calculateRowValuesQuad(lp, read_solution.col_value, + read_solution.row_value) != HighsStatus::kOk) return readSolutionFileErrorReturn(in_file); } // OK to have no EOL @@ -2401,13 +2347,13 @@ HighsStatus assessLpPrimalSolution(const HighsOptions& options, lp.isMip() ? options.mip_feasibility_tolerance : options.primal_feasibility_tolerance; highsLogUser(options.log_options, HighsLogType::kInfo, - "Assessing feasiblity of %s tolerance of %11.4g\n", + "Assessing feasibility of %s tolerance of %11.4g\n", lp.isMip() ? "MIP using primal feasibility and integrality" : "LP using primal feasibility", kPrimalFeasibilityTolerance); vector row_value; row_value.assign(lp.num_row_, 0); - const bool have_integrality = lp.integrality_.size(); + const bool have_integrality = (lp.integrality_.size() != 0); if (!solution.value_valid) return HighsStatus::kError; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { const double primal = solution.col_value[iCol]; @@ -2448,7 +2394,7 @@ HighsStatus assessLpPrimalSolution(const HighsOptions& options, } } HighsStatus return_status = - calculateRowValues(lp, solution.col_value, row_value); + calculateRowValuesQuad(lp, solution.col_value, row_value); if (return_status != HighsStatus::kOk) return return_status; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { const double primal = solution.row_value[iRow]; @@ -2603,14 +2549,15 @@ HighsStatus readBasisStream(const HighsLogOptions& log_options, return return_status; } -HighsStatus calculateColDuals(const HighsLp& lp, HighsSolution& solution) { +HighsStatus calculateColDualsQuad(const HighsLp& lp, HighsSolution& solution) { const bool correct_size = int(solution.row_dual.size()) == lp.num_row_; const bool is_colwise = lp.a_matrix_.isColwise(); 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); + std::vector col_dual_quad; + col_dual_quad.assign(lp.num_col_, HighsCDouble{0.0}); for (HighsInt col = 0; col < lp.num_col_; col++) { for (HighsInt i = lp.a_matrix_.start_[col]; @@ -2618,57 +2565,32 @@ HighsStatus calculateColDuals(const HighsLp& lp, HighsSolution& solution) { const HighsInt row = lp.a_matrix_.index_[i]; assert(row >= 0); assert(row < lp.num_row_); - // @FlipRowDual -= became += - solution.col_dual[col] += solution.row_dual[row] * lp.a_matrix_.value_[i]; + col_dual_quad[col] += solution.row_dual[row] * lp.a_matrix_.value_[i]; } - solution.col_dual[col] += lp.col_cost_[col]; + col_dual_quad[col] += lp.col_cost_[col]; } - return HighsStatus::kOk; -} - -HighsStatus calculateRowValues(const HighsLp& lp, - const std::vector& col_value, - std::vector& row_value) { - const bool correct_size = int(col_value.size()) == lp.num_col_; - const bool is_colwise = lp.a_matrix_.isColwise(); - 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); - - for (HighsInt col = 0; col < lp.num_col_; 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_); - - row_value[row] += col_value[col] * lp.a_matrix_.value_[i]; - } - } + // assign quad values to double vector + solution.col_dual.resize(lp.num_col_); + std::transform(col_dual_quad.begin(), col_dual_quad.end(), + solution.col_dual.begin(), + [](HighsCDouble x) { return double(x); }); return HighsStatus::kOk; } -HighsStatus calculateRowValues(const HighsLp& lp, HighsSolution& solution) { - return calculateRowValues(lp, solution.col_value, solution.row_value); -} - -HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution, +HighsStatus calculateRowValuesQuad(const HighsLp& lp, + const std::vector& col_value, + std::vector& row_value, const HighsInt report_row) { - const bool correct_size = int(solution.col_value.size()) == lp.num_col_; + const bool correct_size = int(col_value.size()) == lp.num_col_; const bool is_colwise = lp.a_matrix_.isColwise(); 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}); - - solution.row_value.assign(lp.num_row_, 0); + std::vector row_value_quad; + row_value_quad.assign(lp.num_row_, HighsCDouble{0.0}); for (HighsInt col = 0; col < lp.num_col_; col++) { for (HighsInt i = lp.a_matrix_.start_[col]; @@ -2676,45 +2598,28 @@ 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]; + row_value_quad[row] += 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]); + int(row), double(row_value_quad[row]), int(col), col_value[col]); } } } // assign quad values to double vector - solution.row_value.resize(lp.num_row_); - std::transform(row_value.begin(), row_value.end(), solution.row_value.begin(), - [](HighsCDouble x) { return double(x); }); + row_value.resize(lp.num_row_); + std::transform(row_value_quad.begin(), row_value_quad.end(), + row_value.begin(), [](HighsCDouble x) { return double(x); }); return HighsStatus::kOk; } -bool isBoundInfeasible(const HighsLogOptions& log_options, const HighsLp& lp) { - HighsInt num_bound_infeasible = 0; - 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) - highsLogUser(log_options, HighsLogType::kInfo, - "Model infeasible due to %" HIGHSINT_FORMAT - " inconsistent bound(s)\n", - num_bound_infeasible); - return num_bound_infeasible > 0; +HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution, + const HighsInt report_row) { + return calculateRowValuesQuad(lp, solution.col_value, solution.row_value, + report_row); } bool isColDataNull(const HighsLogOptions& log_options, @@ -2917,8 +2822,8 @@ HighsLp withoutSemiVariables(const HighsLp& lp_, HighsSolution& solution, HighsInt semi_row_num = 0; // Insert the new variables and their coefficients std::stringstream ss; - const bool has_col_names = lp.col_names_.size(); - const bool has_row_names = lp.row_names_.size(); + const bool has_col_names = (lp.col_names_.size() != 0); + const bool has_row_names = (lp.row_names_.size() != 0); const bool has_solution = solution.value_valid; if (has_solution) { // Create zeroed row values for the new rows diff --git a/src/lp_data/HighsLpUtils.h b/src/lp_data/HighsLpUtils.h index 53865cc062..692eb21950 100644 --- a/src/lp_data/HighsLpUtils.h +++ b/src/lp_data/HighsLpUtils.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -57,6 +57,13 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, HighsStatus cleanBounds(const HighsOptions& options, HighsLp& lp); +bool boundScaleOk(const std::vector& lower, + const std::vector& upper, const HighsInt bound_scale, + const double infinite_bound); + +bool costScaleOk(const std::vector& cost, const HighsInt cost_scale, + const double infinite_cost); + HighsStatus assessSemiVariables(HighsLp& lp, const HighsOptions& options, bool& made_semi_variable_mods); void relaxSemiVariables(HighsLp& lp, bool& made_semi_variable_mods); @@ -78,6 +85,8 @@ HighsStatus applyScalingToLpCol(HighsLp& lp, const HighsInt col, HighsStatus applyScalingToLpRow(HighsLp& lp, const HighsInt row, const double rowScale); +void unscaleSolution(HighsSolution& solution, const HighsScale& scale); + void appendColsToLpVectors(HighsLp& lp, const HighsInt num_new_col, const vector& colCost, const vector& colLower, @@ -87,16 +96,6 @@ void appendRowsToLpVectors(HighsLp& lp, const HighsInt num_new_row, const vector& rowLower, const vector& rowUpper); -void deleteLpCols(HighsLp& lp, const HighsIndexCollection& index_collection); - -void deleteColsFromLpVectors(HighsLp& lp, HighsInt& new_num_col, - const HighsIndexCollection& index_collection); - -void deleteLpRows(HighsLp& lp, const HighsIndexCollection& index_collection); - -void deleteRowsFromLpVectors(HighsLp& lp, HighsInt& new_num_row, - const HighsIndexCollection& index_collection); - void deleteScale(vector& scale, const HighsIndexCollection& index_collection); @@ -231,15 +230,14 @@ HighsStatus assessLpPrimalSolution(const HighsOptions& options, const HighsSolution& solution, bool& valid, bool& integral, bool& feasible); -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, + const std::vector& col_value, + std::vector& row_value, + const HighsInt report_row = -1); 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); +HighsStatus calculateColDualsQuad(const HighsLp& lp, HighsSolution& solution); bool isColDataNull(const HighsLogOptions& log_options, const double* usr_col_cost, const double* usr_col_lower, diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 89ec0c5853..0e4db30085 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -133,12 +133,12 @@ std::string typeToString(const HighsVarType type) { } void writeModelBoundSolution( - FILE* file, const bool columns, const HighsInt dim, - const std::vector& lower, const std::vector& upper, - const std::vector& names, const bool have_primal, - const std::vector& primal, const bool have_dual, - const std::vector& dual, const bool have_basis, - const std::vector& status, + FILE* file, const HighsLogOptions& log_options, const bool columns, + const HighsInt dim, const std::vector& lower, + const std::vector& upper, const std::vector& names, + const bool have_primal, const std::vector& primal, + const bool have_dual, const std::vector& dual, + const bool have_basis, const std::vector& status, const HighsVarType* integrality) { const bool have_names = names.size() > 0; if (have_names) assert((int)names.size() >= dim); @@ -146,73 +146,73 @@ void writeModelBoundSolution( if (have_dual) assert((int)dual.size() >= dim); if (have_basis) assert((int)status.size() >= dim); const bool have_integrality = integrality != NULL; - std::string var_status_string; - if (columns) { - fprintf(file, "Columns\n"); - } else { - fprintf(file, "Rows\n"); - } - fprintf( - file, - " Index Status Lower Upper Primal Dual"); - if (have_integrality) fprintf(file, " Type "); + std::stringstream ss; + std::string s = columns ? "Columns\n" : "Rows\n"; + highsFprintfString(file, log_options, s); + ss.str(std::string()); + ss << " Index Status Lower Upper Primal Dual"; + if (have_integrality) ss << " Type "; if (have_names) { - fprintf(file, " Name\n"); + ss << " Name\n"; } else { - fprintf(file, "\n"); + ss << "\n"; } + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < dim; ix++) { - if (have_basis) { - var_status_string = statusToString(status[ix], lower[ix], upper[ix]); - } else { - var_status_string = ""; - } - fprintf(file, "%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, - var_status_string.c_str(), lower[ix], upper[ix]); + ss.str(std::string()); + std::string var_status_string = + have_basis ? statusToString(status[ix], lower[ix], upper[ix]) : ""; + ss << highsFormatToString("%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, + var_status_string.c_str(), lower[ix], upper[ix]); if (have_primal) { - fprintf(file, " %12g", primal[ix]); + ss << highsFormatToString(" %12g", primal[ix]); } else { - fprintf(file, " "); + ss << " "; } if (have_dual) { - fprintf(file, " %12g", dual[ix]); + ss << highsFormatToString(" %12g", dual[ix]); } else { - fprintf(file, " "); + ss << " "; } if (have_integrality) - fprintf(file, " %s", typeToString(integrality[ix]).c_str()); + ss << highsFormatToString(" %s", typeToString(integrality[ix]).c_str()); if (have_names) { - fprintf(file, " %-s\n", names[ix].c_str()); + ss << highsFormatToString(" %-s\n", names[ix].c_str()); } else { - fprintf(file, "\n"); + ss << "\n"; } + highsFprintfString(file, log_options, ss.str()); } } -void writeModelObjective(FILE* file, const HighsModel& model, +void writeModelObjective(FILE* file, const HighsLogOptions& log_options, + 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); + writeObjectiveValue(file, log_options, (double)objective_value); } -void writeLpObjective(FILE* file, const HighsLp& lp, +void writeLpObjective(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution) { HighsCDouble objective_value = lp.objectiveCDoubleValue(primal_solution); - writeObjectiveValue(file, (double)objective_value); + writeObjectiveValue(file, log_options, (double)objective_value); } -void writeObjectiveValue(FILE* file, const double objective_value) { +void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, + const double objective_value) { std::array objStr = highsDoubleToString( objective_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "Objective %s\n", objStr.data()); + std::string s = highsFormatToString("Objective %s\n", objStr.data()); + highsFprintfString(file, log_options, s); } -void writePrimalSolution(FILE* file, const HighsLp& lp, +void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, + 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) { @@ -223,8 +223,12 @@ void writePrimalSolution(FILE* file, const HighsLp& lp, // 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_); + + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", + sparse ? -num_nonzero_primal_value : lp.num_col_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { if (sparse && !primal_solution[ix]) continue; std::array valStr = highsDoubleToString( @@ -233,20 +237,22 @@ void writePrimalSolution(FILE* file, const HighsLp& lp, 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"); + ss.str(std::string()); + ss << highsFormatToString("%-s %s", name.c_str(), valStr.data()); + if (sparse) ss << highsFormatToString(" %d", int(ix)); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); } } -void writeModelSolution(FILE* file, const HighsModel& model, - const HighsSolution& solution, const HighsInfo& info, - const bool sparse) { + +void writeModelSolution(FILE* file, const HighsLogOptions& log_options, + 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; const bool have_dual = solution.dual_valid; - std::stringstream ss; if (have_col_names) assert((int)lp.col_names_.size() >= lp.num_col_); if (have_row_names) assert((int)lp.row_names_.size() >= lp.num_row_); if (have_primal) { @@ -259,20 +265,24 @@ void writeModelSolution(FILE* file, const HighsModel& model, assert((int)solution.row_dual.size() >= lp.num_row_); assert(info.dual_solution_status != kSolutionStatusNone); } - fprintf(file, "\n# Primal solution values\n"); + std::stringstream ss; + highsFprintfString(file, log_options, "\n# Primal solution values\n"); if (!have_primal || info.primal_solution_status == kSolutionStatusNone) { - fprintf(file, "None\n"); + highsFprintfString(file, log_options, "None\n"); } else { if (info.primal_solution_status == kSolutionStatusFeasible) { - fprintf(file, "Feasible\n"); + highsFprintfString(file, log_options, "Feasible\n"); } else { assert(info.primal_solution_status == kSolutionStatusInfeasible); - fprintf(file, "Infeasible\n"); + highsFprintfString(file, log_options, "Infeasible\n"); } - writeModelObjective(file, model, solution.col_value); - writePrimalSolution(file, model.lp_, solution.col_value, sparse); + writeModelObjective(file, log_options, model, solution.col_value); + writePrimalSolution(file, log_options, model.lp_, solution.col_value, + sparse); if (sparse) return; - fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss.str(std::string()); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( solution.row_value[ix], kHighsSolutionValueToStringTolerance); @@ -280,36 +290,46 @@ void writeModelSolution(FILE* file, const HighsModel& model, ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } } - fprintf(file, "\n# Dual solution values\n"); + highsFprintfString(file, log_options, "\n# Dual solution values\n"); if (!have_dual || info.dual_solution_status == kSolutionStatusNone) { - fprintf(file, "None\n"); + highsFprintfString(file, log_options, "None\n"); } else { if (info.dual_solution_status == kSolutionStatusFeasible) { - fprintf(file, "Feasible\n"); + highsFprintfString(file, log_options, "Feasible\n"); } else { assert(info.dual_solution_status == kSolutionStatusInfeasible); - fprintf(file, "Infeasible\n"); + highsFprintfString(file, log_options, "Infeasible\n"); } - fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + ss.str(std::string()); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { std::array valStr = highsDoubleToString( solution.col_dual[ix], kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "C" << ix; const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } - fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss.str(std::string()); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( solution.row_dual[ix], kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } } } @@ -319,8 +339,8 @@ bool hasNamesWithSpaces(const HighsLogOptions& log_options, const std::vector& names) { HighsInt num_names_with_spaces = 0; for (HighsInt ix = 0; ix < num_name; ix++) { - HighsInt space_pos = names[ix].find(" "); - if (space_pos >= 0) { + size_t space_pos = names[ix].find(" "); + if (space_pos != std::string::npos) { if (num_names_with_spaces == 0) { highsLogDev( log_options, HighsLogType::kInfo, @@ -395,23 +415,33 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, const bool have_dual = solution.dual_valid; const bool have_basis = basis.valid; const HighsLp& lp = model.lp_; + const HighsLogOptions& log_options = options.log_options; if (style == kSolutionStyleOldRaw) { - writeOldRawSolution(file, lp, basis, solution); + writeOldRawSolution(file, log_options, lp, basis, solution); } else if (style == kSolutionStylePretty) { - 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, - have_basis, basis.row_status); - fprintf(file, "\nModel status: %s\n", - utilModelStatusToString(model_status).c_str()); + const HighsVarType* integrality = + lp.integrality_.size() > 0 ? lp.integrality_.data() : nullptr; + writeModelBoundSolution(file, log_options, 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); + writeModelBoundSolution(file, log_options, false, lp.num_row_, + lp.row_lower_, lp.row_upper_, lp.row_names_, + have_primal, solution.row_value, have_dual, + solution.row_dual, have_basis, basis.row_status); + highsFprintfString(file, log_options, "\n"); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("Model status: %s\n", + utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); std::array objStr = highsDoubleToString((double)info.objective_function_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "\nObjective value: %s\n", objStr.data()); + highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString("Objective value: %s\n", objStr.data()); + highsFprintfString(file, log_options, ss.str()); } else if (style == kSolutionStyleGlpsolRaw || style == kSolutionStyleGlpsolPretty) { const bool raw = style == kSolutionStyleGlpsolRaw; @@ -421,36 +451,45 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, // Standard raw solution file, possibly sparse => only nonzero primal values const bool sparse = style == kSolutionStyleSparse; assert(style == kSolutionStyleRaw || sparse); - fprintf(file, "Model status\n"); - fprintf(file, "%s\n", utilModelStatusToString(model_status).c_str()); - writeModelSolution(file, model, solution, info, sparse); + highsFprintfString(file, log_options, "Model status\n"); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("%s\n", + utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); + writeModelSolution(file, log_options, model, solution, info, sparse); } } -void writeGlpsolCostRow(FILE* file, const bool raw, const bool is_mip, +void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, + const bool raw, const bool is_mip, const HighsInt row_id, const std::string objective_name, const double objective_function_value) { + std::stringstream ss; + ss.str(std::string()); if (raw) { double double_value = objective_function_value; std::array double_string = highsDoubleToString( double_value, kGlpsolSolutionValueToStringTolerance); // Last term of 0 for dual should (also) be blank when not MIP - fprintf(file, "i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", - double_string.data(), is_mip ? "" : " 0"); + ss << highsFormatToString("i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", + double_string.data(), is_mip ? "" : " 0"); } else { - fprintf(file, "%6d ", (int)row_id); + ss << highsFormatToString("%6d ", (int)row_id); if (objective_name.length() <= 12) { - fprintf(file, "%-12s ", objective_name.c_str()); + ss << highsFormatToString("%-12s ", objective_name.c_str()); } else { - fprintf(file, "%s\n%20s", objective_name.c_str(), ""); + ss << highsFormatToString("%s\n%20s", objective_name.c_str(), ""); } if (is_mip) { - fprintf(file, " "); + ss << highsFormatToString(" "); } else { - fprintf(file, "B "); + ss << highsFormatToString("B "); } - fprintf(file, "%13.6g %13s %13s \n", objective_function_value, "", ""); + ss << highsFormatToString("%13.6g %13s %13s \n", objective_function_value, + "", ""); } + highsFprintfString(file, log_options, ss.str()); } void writeGlpsolSolution(FILE* file, const HighsOptions& options, @@ -466,8 +505,9 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const double kGlpsolLowQuality = 1e-3; const double kGlpsolPrintAsZero = 1e-9; const HighsLp& lp = model.lp_; - const bool have_col_names = lp.col_names_.size(); - const bool have_row_names = lp.row_names_.size(); + const HighsLogOptions& log_options = options.log_options; + const bool have_col_names = (lp.col_names_.size() != 0); + const bool have_row_names = (lp.row_names_.size() != 0); // Determine number of nonzeros including the objective function // and, hence, determine whether there is an objective function HighsInt num_nz = lp.a_matrix_.numNz(); @@ -484,12 +524,12 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // the (inconsistent) behaviour of Glpsol. // // If Glpsol is run from a .mod file then the cost row is reported - // unless there is no objecive [minimize/maximize "objname"] + // unless there is no objective [minimize/maximize "objname"] // statement in the .mod file. In this case, the N-row in the MPS // file is called "R0000000" and referred to below as being artificial. // // However, the position of a defined cost row depends on where the - // objecive appears in the .mod file. If Glpsol is run from a .mod + // objective appears in the .mod file. If Glpsol is run from a .mod // file, and reads a .sol file, it must be in the right format. // // HiGHS can't read ..mod files, so works from an MPS or LP file @@ -592,16 +632,24 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // prefix to raw lines std::string line_prefix = ""; if (raw) line_prefix = "c "; - fprintf(file, "%s%-12s%s\n", line_prefix.c_str(), - "Problem:", lp.model_name_.c_str()); - fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), - "Rows:", (int)glpsol_num_row); - fprintf(file, "%s%-12s%d", line_prefix.c_str(), "Columns:", (int)num_col); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%s\n", line_prefix.c_str(), + "Problem:", lp.model_name_.c_str())); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%d\n", line_prefix.c_str(), + "Rows:", (int)glpsol_num_row)); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("%s%-12s%d", line_prefix.c_str(), + "Columns:", (int)num_col); if (!raw && is_mip) - fprintf(file, " (%d integer, %d binary)", (int)num_integer, - (int)num_binary); - fprintf(file, "\n"); - fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), "Non-zeros:", (int)num_nz); + ss << highsFormatToString(" (%d integer, %d binary)", (int)num_integer, + (int)num_binary); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%d\n", line_prefix.c_str(), + "Non-zeros:", (int)num_nz)); // 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 @@ -648,8 +696,9 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } 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()); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%s\n", line_prefix.c_str(), + "Status:", model_status_text.c_str())); // If info is not valid, then cannot write more if (!info.valid) return; // Now write out the numerical information @@ -663,92 +712,106 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // non-trivial objective name if (have_row_names) assert(lp.objective_name_ != ""); const bool has_objective_name = lp.objective_name_ != ""; - fprintf(file, "%s%-12s%s%.10g (%s)\n", line_prefix.c_str(), "Objective:", + highsFprintfString( + file, log_options, + highsFormatToString( + "%s%-12s%s%.10g (%s)\n", line_prefix.c_str(), "Objective:", !(has_objective && has_objective_name) ? "" : (objective_name + " = ").c_str(), has_objective ? info.objective_function_value : 0, - lp.sense_ == ObjSense::kMinimize ? "MINimum" : "MAXimum"); + lp.sense_ == ObjSense::kMinimize ? "MINimum" : "MAXimum")); // No space after "c" on blank line! if (raw) line_prefix = "c"; - fprintf(file, "%s\n", line_prefix.c_str()); + highsFprintfString(file, log_options, + highsFormatToString("%s\n", line_prefix.c_str())); // Detailed lines are rather different if (raw) { - fprintf(file, "s %s %d %d ", is_mip ? "mip" : "bas", (int)glpsol_num_row, - (int)num_col); + ss.str(std::string()); + ss << highsFormatToString("s %s %d %d ", is_mip ? "mip" : "bas", + (int)glpsol_num_row, (int)num_col); if (is_mip) { - fprintf(file, "%s", solution_status_char.c_str()); + ss << highsFormatToString("%s", solution_status_char.c_str()); } else { if (info.primal_solution_status == kSolutionStatusNone) { - fprintf(file, "u"); + ss << highsFormatToString("u"); } else if (info.primal_solution_status == kSolutionStatusInfeasible) { - fprintf(file, "i"); + ss << highsFormatToString("i"); } else if (info.primal_solution_status == kSolutionStatusFeasible) { - fprintf(file, "f"); + ss << highsFormatToString("f"); } else { - fprintf(file, "?"); + ss << highsFormatToString("?"); } - fprintf(file, " "); + ss << highsFormatToString(" "); if (info.dual_solution_status == kSolutionStatusNone) { - fprintf(file, "u"); + ss << highsFormatToString("u"); } else if (info.dual_solution_status == kSolutionStatusInfeasible) { - fprintf(file, "i"); + ss << highsFormatToString("i"); } else if (info.dual_solution_status == kSolutionStatusFeasible) { - fprintf(file, "f"); + ss << highsFormatToString("f"); } else { - fprintf(file, "?"); + ss << highsFormatToString("?"); } } double double_value = has_objective ? info.objective_function_value : 0; std::array double_string = highsDoubleToString(double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, " %s\n", double_string.data()); + ss << highsFormatToString(" %s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); } // 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 " - " Upper bound", - have_basis ? "St" : " "); - if (have_dual) fprintf(file, " Marginal"); - fprintf(file, "\n"); - - fprintf(file, - "------ ------------ %s ------------- ------------- " - "-------------", - have_basis ? "--" : " "); - if (have_dual) fprintf(file, " -------------"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + " No. Row name %s Activity Lower bound " + " Upper bound", + have_basis ? "St" : " "); + if (have_dual) ss << highsFormatToString(" Marginal"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "------ ------------ %s ------------- ------------- " + "-------------", + have_basis ? "--" : " "); + if (have_dual) ss << highsFormatToString(" -------------"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } HighsInt row_id = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { row_id++; if (row_id == cost_row_location) { - writeGlpsolCostRow(file, raw, is_mip, row_id, objective_name, + writeGlpsolCostRow(file, log_options, raw, is_mip, row_id, objective_name, info.objective_function_value); row_id++; } + ss.str(std::string()); if (raw) { - fprintf(file, "i %d ", (int)row_id); + ss << highsFormatToString("i %d ", (int)row_id); if (is_mip) { // Complete the line if for a MIP 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()); + ss << highsFormatToString("%s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); continue; } } else { - fprintf(file, "%6d ", (int)row_id); + ss << highsFormatToString("%6d ", (int)row_id); std::string row_name = ""; if (have_row_names) row_name = lp.row_names_[iRow]; if (row_name.length() <= 12) { - fprintf(file, "%-12s ", row_name.c_str()); + ss << highsFormatToString("%-12s ", row_name.c_str()); } else { - fprintf(file, "%s\n%20s", row_name.c_str(), ""); + ss << highsFormatToString("%s\n", row_name.c_str()); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << " "; } } const double lower = lp.row_lower_[iRow]; @@ -758,7 +821,6 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, std::string status_text = " "; std::string status_char = ""; if (have_basis) { - const HighsBasisStatus status = basis.row_status[iRow]; switch (basis.row_status[iRow]) { case HighsBasisStatus::kBasic: status_text = "B "; @@ -783,29 +845,30 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } } if (raw) { - fprintf(file, "%s ", status_char.c_str()); + ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s ", double_string.data()); + ss << highsFormatToString("%s ", double_string.data()); } else { - fprintf(file, "%s ", status_text.c_str()); - fprintf(file, "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); + ss << highsFormatToString("%s ", status_text.c_str()); + ss << highsFormatToString( + "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); if (lower > -kHighsInf) - fprintf(file, "%13.6g ", lower); + ss << highsFormatToString("%13.6g ", lower); else - fprintf(file, "%13s ", ""); + ss << highsFormatToString("%13s ", ""); if (lower != upper && upper < kHighsInf) - fprintf(file, "%13.6g ", upper); + ss << highsFormatToString("%13.6g ", upper); else - fprintf(file, "%13s ", lower == upper ? "=" : ""); + ss << highsFormatToString("%13s ", lower == upper ? "=" : ""); } if (have_dual) { if (raw) { double double_value = solution.row_dual[iRow]; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s", double_string.data()); + ss << highsFormatToString("%s", double_string.data()); } else { // If the row is known to be basic, don't print the dual // value. If there's no basis, row cannot be known to be basic @@ -814,56 +877,67 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, not_basic = basis.row_status[iRow] != HighsBasisStatus::kBasic; if (not_basic) { if (fabs(dual) <= kGlpsolPrintAsZero) - fprintf(file, "%13s", "< eps"); + ss << highsFormatToString("%13s", "< eps"); else - fprintf(file, "%13.6g ", dual); + ss << highsFormatToString("%13.6g ", dual); } } } - fprintf(file, "\n"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (cost_row_location == lp.num_row_ + 1) { row_id++; - writeGlpsolCostRow(file, raw, is_mip, row_id, objective_name, + writeGlpsolCostRow(file, log_options, raw, is_mip, row_id, objective_name, info.objective_function_value); } - if (!raw) fprintf(file, "\n"); + if (!raw) highsFprintfString(file, log_options, "\n"); if (!raw) { - fprintf(file, - " No. Column name %s Activity Lower bound " - " Upper bound", - have_basis ? "St" : " "); - if (have_dual) fprintf(file, " Marginal"); - fprintf(file, "\n"); - fprintf(file, - "------ ------------ %s ------------- ------------- " - "-------------", - have_basis ? "--" : " "); - if (have_dual) fprintf(file, " -------------"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + " No. Column name %s Activity Lower bound " + " Upper bound", + have_basis ? "St" : " "); + if (have_dual) ss << highsFormatToString(" Marginal"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "------ ------------ %s ------------- ------------- " + "-------------", + have_basis ? "--" : " "); + if (have_dual) ss << highsFormatToString(" -------------"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (raw) line_prefix = "j "; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + ss.str(std::string()); if (raw) { - fprintf(file, "%s%d ", line_prefix.c_str(), (int)(iCol + 1)); + ss << highsFormatToString("%s%d ", line_prefix.c_str(), (int)(iCol + 1)); if (is_mip) { 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()); + ss << highsFormatToString("%s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); continue; } } else { - fprintf(file, "%6d ", (int)(iCol + 1)); + ss << highsFormatToString("%6d ", (int)(iCol + 1)); std::string col_name = ""; if (have_col_names) col_name = lp.col_names_[iCol]; if (!have_col_names || col_name.length() <= 12) { - fprintf(file, "%-12s ", !have_col_names ? "" : col_name.c_str()); + ss << highsFormatToString("%-12s ", + !have_col_names ? "" : col_name.c_str()); } else { - fprintf(file, "%s\n%20s", col_name.c_str(), ""); + ss << highsFormatToString("%s\n", col_name.c_str()); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << " "; } } const double lower = lp.col_lower_[iCol]; @@ -873,7 +947,6 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, std::string status_text = " "; std::string status_char = ""; if (have_basis) { - const HighsBasisStatus status = basis.col_status[iCol]; switch (basis.col_status[iCol]) { case HighsBasisStatus::kBasic: status_text = "B "; @@ -901,29 +974,30 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, status_text = "* "; } if (raw) { - fprintf(file, "%s ", status_char.c_str()); + ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s ", double_string.data()); + ss << highsFormatToString("%s ", double_string.data()); } else { - fprintf(file, "%s ", status_text.c_str()); - fprintf(file, "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); + ss << highsFormatToString("%s ", status_text.c_str()); + ss << highsFormatToString( + "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); if (lower > -kHighsInf) - fprintf(file, "%13.6g ", lower); + ss << highsFormatToString("%13.6g ", lower); else - fprintf(file, "%13s ", ""); + ss << highsFormatToString("%13s ", ""); if (lower != upper && upper < kHighsInf) - fprintf(file, "%13.6g ", upper); + ss << highsFormatToString("%13.6g ", upper); else - fprintf(file, "%13s ", lower == upper ? "=" : ""); + ss << highsFormatToString("%13s ", lower == upper ? "=" : ""); } if (have_dual) { if (raw) { double double_value = solution.col_dual[iCol]; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s", double_string.data()); + ss << highsFormatToString("%s", double_string.data()); } else { // If the column is known to be basic, don't print the dual // value. If there's no basis, column cannot be known to be @@ -933,16 +1007,17 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, not_basic = basis.col_status[iCol] != HighsBasisStatus::kBasic; if (not_basic) { if (fabs(dual) <= kGlpsolPrintAsZero) - fprintf(file, "%13s", "< eps"); + ss << highsFormatToString("%13s", "< eps"); else - fprintf(file, "%13.6g ", dual); + ss << highsFormatToString("%13.6g ", dual); } } } - fprintf(file, "\n"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (raw) { - fprintf(file, "e o f\n"); + highsFprintfString(file, log_options, "e o f\n"); return; } HighsPrimalDualErrors errors; @@ -952,13 +1027,14 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, HighsInt relative_error_index; double relative_error_value; getKktFailures(options, model, solution, basis, local_info, errors, true); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); if (is_mip) { - fprintf(file, "Integer feasibility conditions:\n"); + highsFprintfString(file, log_options, "Integer feasibility conditions:\n"); } else { - fprintf(file, "Karush-Kuhn-Tucker optimality conditions:\n"); + highsFprintfString(file, log_options, + "Karush-Kuhn-Tucker optimality conditions:\n"); } - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Primal residual absolute_error_value = errors.max_primal_residual.absolute_value; absolute_error_index = errors.max_primal_residual.absolute_index + 1; @@ -966,17 +1042,25 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, relative_error_index = errors.max_primal_residual.relative_index + 1; if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; - fprintf(file, "KKT.PE: max.abs.err = %.2e on row %d\n", absolute_error_value, - absolute_error_index == 0 ? 0 : (int)absolute_error_index); - fprintf(file, " max.rel.err = %.2e on row %d\n", relative_error_value, - absolute_error_index == 0 ? 0 : (int)relative_error_index); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "PRIMAL SOLUTION IS WRONG"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + "KKT.PE: max.abs.err = %.2e on row %d\n", absolute_error_value, + absolute_error_index == 0 ? 0 : (int)absolute_error_index); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + " max.rel.err = %.2e on row %d\n", relative_error_value, + absolute_error_index == 0 ? 0 : (int)relative_error_index); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality ? "Low quality" + : "PRIMAL SOLUTION IS WRONG"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); // Primal infeasibility absolute_error_value = errors.max_primal_infeasibility.absolute_value; @@ -986,24 +1070,31 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; bool on_col = absolute_error_index > 0 && absolute_error_index <= lp.num_col_; - fprintf(file, "KKT.PB: max.abs.err = %.2e on %s %d\n", absolute_error_value, - on_col ? "column" : "row", - absolute_error_index <= lp.num_col_ - ? (int)absolute_error_index - : (int)(absolute_error_index - lp.num_col_)); + ss.str(std::string()); + ss << highsFormatToString("KKT.PB: max.abs.err = %.2e on %s %d\n", + absolute_error_value, on_col ? "column" : "row", + absolute_error_index <= lp.num_col_ + ? (int)absolute_error_index + : (int)(absolute_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); on_col = relative_error_index > 0 && relative_error_index <= lp.num_col_; - fprintf(file, " max.rel.err = %.2e on %s %d\n", relative_error_value, - on_col ? "column" : "row", - relative_error_index <= lp.num_col_ - ? (int)relative_error_index - : (int)(relative_error_index - lp.num_col_)); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "PRIMAL SOLUTION IS INFEASIBLE"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString(" max.rel.err = %.2e on %s %d\n", + relative_error_value, on_col ? "column" : "row", + relative_error_index <= lp.num_col_ + ? (int)relative_error_index + : (int)(relative_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality + ? "Low quality" + : "PRIMAL SOLUTION IS INFEASIBLE"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); if (have_dual) { // Dual residual @@ -1013,17 +1104,19 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, relative_error_index = errors.max_dual_residual.relative_index + 1; if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; - fprintf(file, "KKT.DE: max.abs.err = %.2e on column %d\n", - absolute_error_value, (int)absolute_error_index); - fprintf(file, " max.rel.err = %.2e on column %d\n", - relative_error_value, (int)relative_error_index); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "DUAL SOLUTION IS WRONG"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString("KKT.DE: max.abs.err = %.2e on column %d\n", + absolute_error_value, (int)absolute_error_index); + ss << highsFormatToString(" max.rel.err = %.2e on column %d\n", + relative_error_value, (int)relative_error_index); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality ? "Low quality" + : "DUAL SOLUTION IS WRONG"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); // Dual infeasibility absolute_error_value = errors.max_dual_infeasibility.absolute_value; @@ -1034,29 +1127,37 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (!relative_error_value) relative_error_index = 0; bool on_col = absolute_error_index > 0 && absolute_error_index <= lp.num_col_; - fprintf(file, "KKT.DB: max.abs.err = %.2e on %s %d\n", absolute_error_value, - on_col ? "column" : "row", - absolute_error_index <= lp.num_col_ - ? (int)absolute_error_index - : (int)(absolute_error_index - lp.num_col_)); + ss.str(std::string()); + ss << highsFormatToString("KKT.DB: max.abs.err = %.2e on %s %d\n", + absolute_error_value, on_col ? "column" : "row", + absolute_error_index <= lp.num_col_ + ? (int)absolute_error_index + : (int)(absolute_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); on_col = relative_error_index > 0 && relative_error_index <= lp.num_col_; - fprintf(file, " max.rel.err = %.2e on %s %d\n", relative_error_value, - on_col ? "column" : "row", - relative_error_index <= lp.num_col_ - ? (int)relative_error_index - : (int)(relative_error_index - lp.num_col_)); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "DUAL SOLUTION IS INFEASIBLE"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString(" max.rel.err = %.2e on %s %d\n", + relative_error_value, on_col ? "column" : "row", + relative_error_index <= lp.num_col_ + ? (int)relative_error_index + : (int)(relative_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality + ? "Low quality" + : "DUAL SOLUTION IS INFEASIBLE"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); } - fprintf(file, "End of output\n"); + highsFprintfString(file, log_options, "End of output\n"); } -void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, +void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution) { const bool have_value = solution.value_valid; const bool have_dual = solution.dual_valid; @@ -1080,44 +1181,59 @@ void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, use_row_status = basis.row_status; } if (!have_value && !have_dual && !have_basis) return; - fprintf(file, + highsFprintfString( + file, log_options, + highsFormatToString( "%" HIGHSINT_FORMAT " %" HIGHSINT_FORMAT " : Number of columns and rows for primal or dual solution " "or basis\n", - lp.num_col_, lp.num_row_); + lp.num_col_, lp.num_row_)); + std::stringstream ss; + ss.str(std::string()); if (have_value) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Primal solution\n"); + ss << highsFormatToString(" Primal solution\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); if (have_dual) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Dual solution\n"); + ss << highsFormatToString(" Dual solution\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); if (have_basis) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Basis\n"); - fprintf(file, "Columns\n"); + ss << highsFormatToString(" Basis\n"); + highsFprintfString(file, log_options, ss.str()); + highsFprintfString(file, log_options, "Columns\n"); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (have_value) fprintf(file, "%.15g ", use_col_value[iCol]); - if (have_dual) fprintf(file, "%.15g ", use_col_dual[iCol]); + ss.str(std::string()); + if (have_value) ss << highsFormatToString("%.15g ", use_col_value[iCol]); + if (have_dual) ss << highsFormatToString("%.15g ", use_col_dual[iCol]); if (have_basis) - fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_col_status[iCol]); - fprintf(file, "\n"); + ss << highsFormatToString("%" HIGHSINT_FORMAT "", + (HighsInt)use_col_status[iCol]); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } - fprintf(file, "Rows\n"); + highsFprintfString(file, log_options, "Rows\n"); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (have_value) fprintf(file, "%.15g ", use_row_value[iRow]); - if (have_dual) fprintf(file, "%.15g ", use_row_dual[iRow]); + ss.str(std::string()); + if (have_value) ss << highsFormatToString("%.15g ", use_row_value[iRow]); + if (have_dual) ss << highsFormatToString("%.15g ", use_row_dual[iRow]); if (have_basis) - fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_row_status[iRow]); - fprintf(file, "\n"); + ss << highsFormatToString("%" HIGHSINT_FORMAT "", + (HighsInt)use_row_status[iRow]); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } } @@ -1233,6 +1349,9 @@ std::string utilModelStatusToString(const HighsModelStatus model_status) { case HighsModelStatus::kModelEmpty: return "Empty"; break; + case HighsModelStatus::kMemoryLimit: + return "Memory limit reached"; + break; case HighsModelStatus::kOptimal: return "Optimal"; break; @@ -1321,6 +1440,8 @@ HighsStatus highsStatusFromHighsModelStatus(HighsModelStatus model_status) { return HighsStatus::kError; case HighsModelStatus::kPostsolveError: return HighsStatus::kError; + case HighsModelStatus::kMemoryLimit: + return HighsStatus::kError; case HighsModelStatus::kModelEmpty: return HighsStatus::kOk; case HighsModelStatus::kOptimal: @@ -1367,7 +1488,7 @@ std::string findModelObjectiveName(const HighsLp* lp, if (!has_objective && hessian) { // Zero cost vector, so only chance of an objective comes from any // Hessian - has_objective = hessian->dim_; + has_objective = (hessian->dim_ != 0); } HighsInt pass = 0; for (;;) { diff --git a/src/lp_data/HighsModelUtils.h b/src/lp_data/HighsModelUtils.h index bb1c71f85d..110e668f5a 100644 --- a/src/lp_data/HighsModelUtils.h +++ b/src/lp_data/HighsModelUtils.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -31,29 +31,33 @@ bool hasNamesWithSpaces(const HighsLogOptions& log_options, const HighsInt num_name, const std::vector& names); void writeModelBoundSolution( - FILE* file, const bool columns, const HighsInt dim, - const std::vector& lower, const std::vector& upper, - const std::vector& names, const bool have_primal, - const std::vector& primal, const bool have_dual, - const std::vector& dual, const bool have_basis, - const std::vector& status, + FILE* file, const HighsLogOptions& log_options, const bool columns, + const HighsInt dim, const std::vector& lower, + const std::vector& upper, const std::vector& names, + const bool have_primal, const std::vector& primal, + const bool have_dual, const std::vector& dual, + const bool have_basis, const std::vector& status, const HighsVarType* integrality = NULL); -void writeModelObjective(FILE* file, const HighsModel& model, +void writeModelObjective(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const std::vector& primal_solution); -void writeLpObjective(FILE* file, const HighsLp& lp, +void writeLpObjective(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution); -void writeObjectiveValue(FILE* file, const double objective_value); +void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, + const double objective_value); -void writePrimalSolution(FILE* file, const HighsLp& lp, +void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, + 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 = false); +void writeModelSolution(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const HighsSolution& solution, + const HighsInfo& info, const bool sparse = false); HighsInt maxNameLength(const HighsInt num_name, const std::vector& names); @@ -68,7 +72,8 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, const HighsModelStatus model_status, const HighsInt style); -void writeGlpsolCostRow(FILE* file, const bool raw, const bool is_mip, +void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, + const bool raw, const bool is_mip, const HighsInt row_id, const std::string objective_name, const double objective_function_value); @@ -78,7 +83,8 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const HighsModelStatus model_status, const HighsInfo& info, const bool raw); -void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, +void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution); HighsBasisStatus checkedVarHighsNonbasicStatus( diff --git a/src/lp_data/HighsOptions.cpp b/src/lp_data/HighsOptions.cpp index 163b1c1d53..2cc08766ba 100644 --- a/src/lp_data/HighsOptions.cpp +++ b/src/lp_data/HighsOptions.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -83,13 +83,14 @@ bool commandLineOffOnOk(const HighsLogOptions& report_log_options, bool commandLineSolverOk(const HighsLogOptions& report_log_options, const string& value) { if (value == kSimplexString || value == kHighsChooseString || - value == kIpmString) + value == kIpmString || value == kPdlpString) return true; - 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()); + highsLogUser(report_log_options, HighsLogType::kWarning, + "Value \"%s\" for solver option is not one of \"%s\", \"%s\", " + "\"%s\" or \"%s\"\n", + value.c_str(), kSimplexString.c_str(), + kHighsChooseString.c_str(), kIpmString.c_str(), + kPdlpString.c_str()); return false; } @@ -146,9 +147,9 @@ OptionStatus checkOptions(const HighsLogOptions& report_log_options, bool* value_pointer = option.value; for (HighsInt check_index = 0; check_index < num_options; check_index++) { if (check_index == index) continue; - OptionRecordBool& check_option = - ((OptionRecordBool*)option_records[check_index])[0]; - if (check_option.type == HighsOptionType::kBool) { + if (option_records[check_index]->type == HighsOptionType::kBool) { + OptionRecordBool& check_option = + ((OptionRecordBool*)option_records[check_index])[0]; if (check_option.value == value_pointer) { highsLogUser(report_log_options, HighsLogType::kError, "checkOptions: Option %" HIGHSINT_FORMAT @@ -170,9 +171,9 @@ OptionStatus checkOptions(const HighsLogOptions& report_log_options, HighsInt* value_pointer = option.value; for (HighsInt check_index = 0; check_index < num_options; check_index++) { if (check_index == index) continue; - OptionRecordInt& check_option = - ((OptionRecordInt*)option_records[check_index])[0]; - if (check_option.type == HighsOptionType::kInt) { + if (option_records[check_index]->type == HighsOptionType::kInt) { + OptionRecordInt& check_option = + ((OptionRecordInt*)option_records[check_index])[0]; if (check_option.value == value_pointer) { highsLogUser(report_log_options, HighsLogType::kError, "checkOptions: Option %" HIGHSINT_FORMAT @@ -195,9 +196,9 @@ OptionStatus checkOptions(const HighsLogOptions& report_log_options, double* value_pointer = option.value; for (HighsInt check_index = 0; check_index < num_options; check_index++) { if (check_index == index) continue; - OptionRecordDouble& check_option = - ((OptionRecordDouble*)option_records[check_index])[0]; - if (check_option.type == HighsOptionType::kDouble) { + if (option_records[check_index]->type == HighsOptionType::kDouble) { + OptionRecordDouble& check_option = + ((OptionRecordDouble*)option_records[check_index])[0]; if (check_option.value == value_pointer) { highsLogUser(report_log_options, HighsLogType::kError, "checkOptions: Option %" HIGHSINT_FORMAT @@ -218,9 +219,9 @@ OptionStatus checkOptions(const HighsLogOptions& report_log_options, std::string* value_pointer = option.value; for (HighsInt check_index = 0; check_index < num_options; check_index++) { if (check_index == index) continue; - OptionRecordString& check_option = - ((OptionRecordString*)option_records[check_index])[0]; - if (check_option.type == HighsOptionType::kString) { + if (option_records[check_index]->type == HighsOptionType::kString) { + OptionRecordString& check_option = + ((OptionRecordString*)option_records[check_index])[0]; if (check_option.value == value_pointer) { highsLogUser(report_log_options, HighsLogType::kError, "checkOptions: Option %" HIGHSINT_FORMAT @@ -470,10 +471,8 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, 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"); + if (value_trim.find_first_not_of("+-0123456789eE") != std::string::npos) + return OptionStatus::kIllegalValue; HighsInt value_int; int scanned_num_char; const char* value_char = value_trim.c_str(); @@ -496,22 +495,29 @@ OptionStatus setLocalOptionValue(const HighsLogOptions& report_log_options, ((OptionRecordInt*)option_records[index])[0], value_int); } else if (type == HighsOptionType::kDouble) { - // 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, - "setLocalOptionValue: Value = \"%s\" converts via atoi as " - "%" HIGHSINT_FORMAT - " " - "so is %g as double, and %g via atof\n", - value_trim.c_str(), value_int, value_int_double, - value_double); + // Check that the string only contains legitimate characters - + // after handling +/- inf + double value_double = 0; + tolower(value_trim); + if (value_trim == "inf" || value_trim == "+inf") { + value_double = kHighsInf; + } else if (value_trim == "-inf") { + value_double = -kHighsInf; + } else { + if (value_trim.find_first_not_of("+-.0123456789eE") != std::string::npos) + return OptionStatus::kIllegalValue; + HighsInt value_int = atoi(value_trim.c_str()); + 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, + "setLocalOptionValue: Value = \"%s\" converts via atoi as " + "%" HIGHSINT_FORMAT + " " + "so is %g as double, and %g via atof\n", + value_trim.c_str(), value_int, value_int_double, + value_double); + } } return setLocalOptionValue(report_log_options, ((OptionRecordDouble*)option_records[index])[0], @@ -813,7 +819,6 @@ HighsStatus writeOptionsToFile(FILE* file, 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 (html_file) { fprintf(file, "\n\n\n\n"); fprintf(file, " HiGHS Options\n"); @@ -839,8 +844,6 @@ HighsStatus writeOptionsToFile(FILE* file, void reportOptions(FILE* file, const std::vector& option_records, 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; diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 7c5df78e00..9a9bb29aa6 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -257,6 +257,7 @@ void reportOption(FILE* file, const OptionRecordString& option, const string kSimplexString = "simplex"; const string kIpmString = "ipm"; +const string kPdlpString = "pdlp"; const HighsInt kKeepNRowsDeleteRows = -1; const HighsInt kKeepNRowsDeleteEntries = 0; @@ -303,6 +304,8 @@ struct HighsOptionsStruct { double objective_bound; double objective_target; HighsInt threads; + HighsInt user_bound_scale; + HighsInt user_cost_scale; HighsInt highs_debug_level; HighsInt highs_analysis_level; HighsInt simplex_strategy; @@ -328,8 +331,20 @@ struct HighsOptionsStruct { // Options for IPM solver HighsInt ipm_iteration_limit; + // Options for PDLP solver + bool pdlp_native_termination; + bool pdlp_scaling; + HighsInt pdlp_iteration_limit; + HighsInt pdlp_e_restart_method; + double pdlp_d_gap_tol; + + // Options for QP solver + HighsInt qp_iteration_limit; + HighsInt qp_nullspace_limit; + // Advanced options HighsInt log_dev_level; + bool log_githash; bool solve_relaxation; bool allow_unbounded_or_infeasible; bool use_implied_bounds_from_presolve; @@ -347,6 +362,7 @@ struct HighsOptionsStruct { HighsInt simplex_price_strategy; HighsInt simplex_unscaled_solution_strategy; HighsInt presolve_reduction_limit; + HighsInt restart_presolve_reduction_limit; HighsInt presolve_substitution_maxfillin; HighsInt presolve_rule_off; bool presolve_rule_logging; @@ -366,6 +382,9 @@ struct HighsOptionsStruct { bool less_infeasible_DSE_check; bool less_infeasible_DSE_choose_row; bool use_original_HFactor_logic; + bool run_centring; + HighsInt max_centring_steps; + double centring_ratio_tolerance; // Options for iCrash bool icrash; @@ -379,6 +398,7 @@ struct HighsOptionsStruct { // Options for MIP solver bool mip_detect_symmetry; + bool mip_allow_restart; HighsInt mip_max_nodes; HighsInt mip_max_stall_nodes; HighsInt mip_max_leaves; @@ -404,6 +424,128 @@ struct HighsOptionsStruct { // Logging callback identifiers HighsLogOptions log_options; virtual ~HighsOptionsStruct() {} + + HighsOptionsStruct() + : presolve(""), + solver(""), + parallel(""), + run_crossover(""), + time_limit(0.0), + solution_file(""), + write_model_file(""), + random_seed(0), + ranging(""), + infinite_cost(0.0), + infinite_bound(0.0), + small_matrix_value(0.0), + large_matrix_value(0.0), + primal_feasibility_tolerance(0.0), + dual_feasibility_tolerance(0.0), + ipm_optimality_tolerance(0.0), + objective_bound(0.0), + objective_target(0.0), + threads(0), + user_bound_scale(0), + user_cost_scale(0), + highs_debug_level(0), + highs_analysis_level(0), + simplex_strategy(0), + simplex_scale_strategy(0), + simplex_crash_strategy(0), + simplex_dual_edge_weight_strategy(0), + simplex_primal_edge_weight_strategy(0), + simplex_iteration_limit(0), + simplex_update_limit(0), + simplex_min_concurrency(0), + simplex_max_concurrency(0), + log_file(""), + write_model_to_file(false), + write_solution_to_file(false), + write_solution_style(0), + glpsol_cost_row_location(0), + output_flag(false), + log_to_console(false), + ipm_iteration_limit(0), + pdlp_native_termination(false), + pdlp_scaling(false), + pdlp_iteration_limit(0), + pdlp_e_restart_method(0), + pdlp_d_gap_tol(0.0), + qp_iteration_limit(0), + qp_nullspace_limit(0), + log_dev_level(0), + log_githash(false), + solve_relaxation(false), + allow_unbounded_or_infeasible(false), + use_implied_bounds_from_presolve(false), + lp_presolve_requires_basis_postsolve(false), + mps_parser_type_free(false), + keep_n_rows(0), + cost_scale_factor(0), + allowed_matrix_scale_factor(0), + allowed_cost_scale_factor(0), + ipx_dualize_strategy(0), + simplex_dualize_strategy(0), + simplex_permute_strategy(0), + max_dual_simplex_cleanup_level(0), + max_dual_simplex_phase1_cleanup_level(0), + simplex_price_strategy(0), + simplex_unscaled_solution_strategy(0), + presolve_reduction_limit(0), + restart_presolve_reduction_limit(0), + presolve_substitution_maxfillin(0), + presolve_rule_off(0), + presolve_rule_logging(false), + simplex_initial_condition_check(false), + no_unnecessary_rebuild_refactor(false), + simplex_initial_condition_tolerance(0.0), + rebuild_refactor_solution_error_tolerance(0.0), + dual_steepest_edge_weight_error_tolerance(0.0), + dual_steepest_edge_weight_log_error_threshold(0.0), + dual_simplex_cost_perturbation_multiplier(0.0), + primal_simplex_bound_perturbation_multiplier(0.0), + dual_simplex_pivot_growth_tolerance(0.0), + presolve_pivot_threshold(0.0), + factor_pivot_threshold(0.0), + factor_pivot_tolerance(0.0), + start_crossover_tolerance(0.0), + less_infeasible_DSE_check(false), + less_infeasible_DSE_choose_row(false), + use_original_HFactor_logic(false), + run_centring(false), + max_centring_steps(0), + centring_ratio_tolerance(0.0), + icrash(false), + icrash_dualize(false), + icrash_strategy(""), + icrash_starting_weight(0.0), + icrash_iterations(0), + icrash_approx_iter(0), + icrash_exact(false), + icrash_breakpoints(false), + mip_detect_symmetry(false), + mip_allow_restart(false), + mip_max_nodes(0), + mip_max_stall_nodes(0), + mip_max_leaves(0), + mip_max_improving_sols(0), + mip_lp_age_limit(0), + mip_pool_age_limit(0), + mip_pool_soft_limit(0), + mip_pscost_minreliable(0), + mip_min_cliquetable_entries_for_parallelism(0), + mip_report_level(0), + mip_feasibility_tolerance(0.0), + mip_rel_gap(0.0), + mip_abs_gap(0.0), + mip_heuristic_effort(0.0), + mip_min_logging_interval(0.0), +#ifdef HIGHS_DEBUGSOL + mip_debug_solution_file(""), +#endif + mip_improving_solution_save(false), + mip_improving_solution_report_sparse(false), + mip_improving_solution_file(""){}; }; // For now, but later change so HiGHS properties are string based so that new @@ -471,8 +613,9 @@ class HighsOptions : public HighsOptionsStruct { record_string = new OptionRecordString( kSolverString, - "Solver option: \"simplex\", \"choose\" or \"ipm\". If " - "\"simplex\"/\"ipm\" is chosen then, for a MIP (QP) the integrality " + "Solver option: \"simplex\", \"choose\", \"ipm\" or \"pdlp\". If " + "\"simplex\"/\"ipm\"/\"pdlp\" is chosen then, for a MIP (QP) the " + "integrality " "constraint (quadratic term) will be ignored", advanced, &solver, kHighsChooseString); records.push_back(record_string); @@ -567,6 +710,16 @@ class HighsOptions : public HighsOptionsStruct { &threads, 0, 0, kHighsIInf); records.push_back(record_int); + record_int = new OptionRecordInt( + "user_bound_scale", "Exponent of power-of-two bound scaling for model", + advanced, &user_bound_scale, -kHighsIInf, 0, kHighsIInf); + records.push_back(record_int); + + record_int = new OptionRecordInt( + "user_cost_scale", "Exponent of power-of-two cost scaling for model", + advanced, &user_cost_scale, -kHighsIInf, 0, kHighsIInf); + records.push_back(record_int); + record_int = new OptionRecordInt("highs_debug_level", "Debugging level in HiGHS", now_advanced, &highs_debug_level, kHighsDebugLevelMin, @@ -748,6 +901,11 @@ class HighsOptions : public HighsOptionsStruct { advanced, &mip_detect_symmetry, true); records.push_back(record_bool); + record_bool = new OptionRecordBool("mip_allow_restart", + "Whether MIP restart is permitted", + advanced, &mip_allow_restart, true); + records.push_back(record_bool); + record_int = new OptionRecordInt("mip_max_nodes", "MIP solver max number of nodes", advanced, &mip_max_nodes, 0, kHighsIInf, kHighsIInf); @@ -781,7 +939,7 @@ class HighsOptions : public HighsOptionsStruct { record_string = new OptionRecordString( "mip_improving_solution_file", "File for reporting improving MIP solutions: not reported for an empty " - "string \"\"", + "string \\\"\\\"", advanced, &mip_improving_solution_file, kHighsFilenameDefault); records.push_back(record_string); @@ -873,8 +1031,46 @@ class HighsOptions : public HighsOptionsStruct { &ipm_iteration_limit, 0, kHighsIInf, kHighsIInf); records.push_back(record_int); + record_bool = new OptionRecordBool( + "pdlp_native_termination", + "Use native termination for PDLP solver: Default = false", advanced, + &pdlp_native_termination, false); + records.push_back(record_bool); + + record_bool = new OptionRecordBool( + "pdlp_scaling", "Scaling option for PDLP solver: Default = true", + advanced, &pdlp_scaling, true); + records.push_back(record_bool); + + record_int = new OptionRecordInt( + "pdlp_iteration_limit", "Iteration limit for PDLP solver", advanced, + &pdlp_iteration_limit, 0, kHighsIInf, kHighsIInf); + records.push_back(record_int); + + record_int = new OptionRecordInt("pdlp_e_restart_method", + "Restart mode for PDLP solver: 0 => none; " + "1 => GPU (default); 2 => CPU ", + advanced, &pdlp_e_restart_method, 0, 1, 2); + records.push_back(record_int); + + record_double = new OptionRecordDouble( + "pdlp_d_gap_tol", + "Duality gap tolerance for PDLP solver: Default = 1e-4", advanced, + &pdlp_d_gap_tol, 1e-12, 1e-4, kHighsInf); + records.push_back(record_double); + + record_int = new OptionRecordInt( + "qp_iteration_limit", "Iteration limit for QP solver", advanced, + &qp_iteration_limit, 0, kHighsIInf, kHighsIInf); + records.push_back(record_int); + + record_int = new OptionRecordInt("qp_nullspace_limit", + "Nullspace limit for QP solver", advanced, + &qp_nullspace_limit, 0, 4000, kHighsIInf); + records.push_back(record_int); + // Fix the number of user settable options - num_user_settable_options_ = records.size(); + num_user_settable_options_ = static_cast(records.size()); // Advanced options advanced = true; @@ -886,6 +1082,10 @@ class HighsOptions : public HighsOptionsStruct { kHighsLogDevLevelMax); records.push_back(record_int); + record_bool = new OptionRecordBool("log_githash", "Log the githash", + advanced, &log_githash, true); + records.push_back(record_bool); + record_bool = new OptionRecordBool( "solve_relaxation", "Solve the relaxation of discrete model components", advanced, &solve_relaxation, false); @@ -1058,6 +1258,13 @@ class HighsOptions : public HighsOptionsStruct { &presolve_reduction_limit, -1, -1, kHighsIInf); records.push_back(record_int); + record_int = new OptionRecordInt( + "restart_presolve_reduction_limit", + "Limit on number of further presolve reductions on restart in MIP " + "solver -1 => no limit, otherwise, must be positive", + advanced, &restart_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); @@ -1109,6 +1316,25 @@ class HighsOptions : public HighsOptionsStruct { &less_infeasible_DSE_choose_row, true); records.push_back(record_bool); + record_bool = + new OptionRecordBool("run_centring", "Perform centring steps or not", + advanced, &run_centring, false); + records.push_back(record_bool); + + record_int = + new OptionRecordInt("max_centring_steps", + "Maximum number of steps to use (default = 5) " + "when computing the analytic centre", + advanced, &max_centring_steps, 0, 5, kHighsIInf); + records.push_back(record_int); + + record_double = new OptionRecordDouble( + "centring_ratio_tolerance", + "Centring stops when the ratio max(x_j*s_j) / min(x_j*s_j) is below " + "this tolerance (default = 100)", + advanced, ¢ring_ratio_tolerance, 0, 100, kHighsInf); + records.push_back(record_double); + // Set up the log_options aliases log_options.clear(); log_options.log_stream = @@ -1119,7 +1345,7 @@ class HighsOptions : public HighsOptionsStruct { } void deleteRecords() { - for (HighsUInt i = 0; i < records.size(); i++) delete records[i]; + for (size_t i = 0; i < records.size(); i++) delete records[i]; } public: diff --git a/src/lp_data/HighsRanging.cpp b/src/lp_data/HighsRanging.cpp index 3c0999952b..a84ec98cd5 100644 --- a/src/lp_data/HighsRanging.cpp +++ b/src/lp_data/HighsRanging.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -92,8 +92,6 @@ HighsStatus getRangingData(HighsRanging& ranging, // Aliases const HighsSimplexInfo& simplex_info = ekk_instance.info_; const SimplexBasis& simplex_basis = ekk_instance.basis_; - const vector& col_scale = use_lp.scale_.col; - const vector& row_scale = use_lp.scale_.row; const vector& value_ = simplex_info.workValue_; const vector& dual_ = simplex_info.workDual_; const vector& cost_ = simplex_info.workCost_; @@ -228,7 +226,6 @@ HighsStatus getRangingData(HighsRanging& ranging, double alpha = dWork_[myk_inc]; ixj_inc[j] = i; axj_inc[j] = alpha; - const double numerator = (alpha < 0 ? dxi_inc[i] : dxi_dec[i]); txj_inc[j] = (alpha < 0 ? dxi_inc[i] : dxi_dec[i]) / -alpha; wxj_inc[j] = (alpha < 0 ? +1 : -1); } diff --git a/src/lp_data/HighsRanging.h b/src/lp_data/HighsRanging.h index 3e073dd349..10d3930598 100644 --- a/src/lp_data/HighsRanging.h +++ b/src/lp_data/HighsRanging.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsRuntimeOptions.h b/src/lp_data/HighsRuntimeOptions.h index 74563d5c1b..c4b7ecc1e0 100644 --- a/src/lp_data/HighsRuntimeOptions.h +++ b/src/lp_data/HighsRuntimeOptions.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -119,7 +119,7 @@ bool loadOptions(const HighsLogOptions& report_log_options, int argc, auto& v = result[kReadSolutionFileString].as>(); if (v.size() > 1) { HighsInt nonEmpty = 0; - for (HighsInt i = 0; i < (HighsInt)v.size(); i++) { + for (size_t i = 0; i < v.size(); i++) { std::string arg = v[i]; if (trim(arg).size() > 0) { nonEmpty++; diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index e14feb5bdd..29e40168a0 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -91,6 +91,11 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, double& sum_dual_infeasibility = highs_info.sum_dual_infeasibilities; + double& max_complementarity_violation = + highs_info.max_complementarity_violation; + double& sum_complementarity_violations = + highs_info.sum_complementarity_violations; + num_primal_infeasibility = kHighsIllegalInfeasibilityCount; max_absolute_primal_infeasibility_value = kHighsIllegalInfeasibilityMeasure; sum_primal_infeasibility = kHighsIllegalInfeasibilityMeasure; @@ -100,13 +105,17 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, num_dual_infeasibility = kHighsIllegalInfeasibilityCount; max_dual_infeasibility_value = kHighsIllegalInfeasibilityMeasure; sum_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; + primal_dual_errors.max_dual_infeasibility.invalidate(); highs_info.dual_solution_status = kSolutionStatusNone; + max_complementarity_violation = kHighsIllegalInfeasibilityMeasure; + sum_complementarity_violations = kHighsIllegalInfeasibilityMeasure; + const bool& have_primal_solution = solution.value_valid; const bool& have_dual_solution = solution.dual_valid; const bool& have_basis = basis.valid; - const bool have_integrality = lp.integrality_.size() > 0; + const bool have_integrality = (lp.integrality_.size() != 0); // Check that there is no dual solution if there's no primal solution assert(have_primal_solution || !have_dual_solution); // Check that there is no basis if there's no dual solution @@ -228,8 +237,6 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, dual_negative_sum.resize(lp.num_col_); } } - HighsInt num_basic_var = 0; - HighsInt num_non_basic_var = 0; // Set status to a value so the compiler doesn't think it might be unassigned. HighsBasisStatus status = HighsBasisStatus::kNonbasic; @@ -268,11 +275,20 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, } // Flip dual according to lp.sense_ dual *= (HighsInt)lp.sense_; - getVariableKktFailures( + + const bool status_value_ok = getVariableKktFailures( primal_feasibility_tolerance, dual_feasibility_tolerance, lower, upper, value, dual, status_pointer, integrality, absolute_primal_infeasibility, relative_primal_infeasibility, dual_infeasibility, value_residual); - // Accumulate primal infeasiblilties + if (!status_value_ok) + highsLogUser(options.log_options, HighsLogType::kError, + "getKktFailures: %s %d status-value error: [%g; %g; %g] has " + "residual %g\n", + iVar < lp.num_col_ ? "Column" : "Row ", + iVar < lp.num_col_ ? int(iVar) : int(iVar - lp.num_col_), + lower, value, upper, value_residual); + assert(status_value_ok); + // Accumulate primal infeasibilities if (absolute_primal_infeasibility > primal_feasibility_tolerance) num_primal_infeasibility++; if (max_absolute_primal_infeasibility_value < @@ -288,7 +304,7 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, sum_primal_infeasibility += absolute_primal_infeasibility; if (have_dual_solution) { - // Accumulate dual infeasiblilties + // Accumulate dual infeasibilities if (dual_infeasibility > dual_feasibility_tolerance) num_dual_infeasibility++; if (max_dual_infeasibility_value < dual_infeasibility) { @@ -299,7 +315,6 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, } if (have_basis) { if (status == HighsBasisStatus::kBasic) { - num_basic_var++; double abs_basic_dual = dual_infeasibility; if (abs_basic_dual > 0) { num_nonzero_basic_duals++; @@ -310,7 +325,6 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, sum_nonzero_basic_duals += abs_basic_dual; } } else { - num_non_basic_var++; double off_bound_nonbasic = value_residual; if (off_bound_nonbasic > 0) num_off_bound_nonbasic++; max_off_bound_nonbasic = @@ -349,6 +363,37 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, } } } + + if (have_dual_solution) { + // Determine the sum of complementarity violations + max_complementarity_violation = 0; + sum_complementarity_violations = 0; + double primal_residual = 0; + for (HighsInt iVar = 0; iVar < lp.num_col_ + lp.num_row_; iVar++) { + const bool is_col = iVar < lp.num_col_; + const HighsInt iRow = iVar - lp.num_col_; + const double primal = + is_col ? solution.col_value[iVar] : solution.row_value[iRow]; + const double dual = + is_col ? solution.col_dual[iVar] : solution.row_dual[iRow]; + const double lower = is_col ? lp.col_lower_[iVar] : lp.row_lower_[iRow]; + const double upper = is_col ? lp.col_upper_[iVar] : lp.row_upper_[iRow]; + if (lower <= -kHighsInf && upper >= kHighsInf) { + // Free + primal_residual = 1; + } else { + const double mid = (lower + upper) * 0.5; + primal_residual = primal < mid ? std::fabs(lower - primal) + : std::fabs(upper - primal); + } + const double dual_residual = std::fabs(dual); + const double complementarity_violation = primal_residual * dual_residual; + sum_complementarity_violations += complementarity_violation; + max_complementarity_violation = + std::max(complementarity_violation, max_complementarity_violation); + } + } + if (get_residuals) { const double large_residual_error = 1e-12; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { @@ -445,7 +490,7 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, // If the basis status is valid, then the numbers of basic and // nonbasic variables are updated, and the extent to which a nonbasic // variable is off its bound is returned. -void getVariableKktFailures(const double primal_feasibility_tolerance, +bool getVariableKktFailures(const double primal_feasibility_tolerance, const double dual_feasibility_tolerance, const double lower, const double upper, const double value, const double dual, @@ -455,7 +500,7 @@ void getVariableKktFailures(const double primal_feasibility_tolerance, double& relative_primal_infeasibility, double& dual_infeasibility, double& value_residual) { - const double middle = (lower + upper) * 0.5; + bool status_value_ok = true; // @primal_infeasibility calculation absolute_primal_infeasibility = 0; relative_primal_infeasibility = 0; @@ -489,11 +534,11 @@ void getVariableKktFailures(const double primal_feasibility_tolerance, // Check that kLower and kUpper are consistent with value and // bounds - for debugging QP basis errors if (*status_pointer == HighsBasisStatus::kLower) { - assert(value >= lower - primal_feasibility_tolerance && - value <= lower + primal_feasibility_tolerance); + status_value_ok = value >= lower - primal_feasibility_tolerance && + value <= lower + primal_feasibility_tolerance; } else if (*status_pointer == HighsBasisStatus::kUpper) { - assert(value >= upper - primal_feasibility_tolerance && - value <= upper + primal_feasibility_tolerance); + status_value_ok = value >= upper - primal_feasibility_tolerance && + value <= upper + primal_feasibility_tolerance; } } if (at_a_bound) { @@ -516,6 +561,7 @@ void getVariableKktFailures(const double primal_feasibility_tolerance, // Off bounds (or free) dual_infeasibility = fabs(dual); } + return status_value_ok; } void HighsError::print(std::string message) { @@ -656,8 +702,6 @@ HighsStatus ipxSolutionToHighsSolution( assert(ipx_num_row == lp.num_row_); double dual_residual_norm = 0; for (HighsInt col = 0; col < lp.num_col_; col++) { - double lower = lp.col_lower_[col]; - double upper = lp.col_upper_[col]; double value = ipx_col_value[col]; if (get_row_activities) { // Accumulate row activities to assign value to free rows @@ -730,7 +774,7 @@ HighsStatus ipxSolutionToHighsSolution( HighsInt num_col_primal_truncations = 0; HighsInt num_col_dual_truncations = 0; HighsInt col = 0, row = 0; - double lower, upper, value, dual, residual; + double lower, upper, value, dual; const HighsInt check_col = -127; const HighsInt check_row = -37; // Truncating to tolerances can lead to infeasibilities by margin @@ -934,6 +978,14 @@ HighsStatus ipxSolutionToHighsSolution( } assert(ipx_row == ipx_num_row); assert(ipx_slack == ipx_num_col); + if (lp.sense_ == ObjSense::kMaximize) { + // Flip dual values since original LP is maximization + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + highs_solution.col_dual[iCol] *= -1; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + highs_solution.row_dual[iRow] *= -1; + } + // Indicate that the primal and dual solution are known highs_solution.value_valid = true; highs_solution.dual_valid = true; @@ -974,8 +1026,6 @@ HighsStatus ipxBasicSolutionToHighsBasicSolution( HighsInt num_basic_variables = 0; for (HighsInt col = 0; col < lp.num_col_; col++) { bool unrecognised = false; - const double lower = lp.col_lower_[col]; - const double upper = lp.col_upper_[col]; if (ipx_col_status[col] == ipx_basic) { // Column is basic highs_basis.col_status[col] = HighsBasisStatus::kBasic; @@ -1172,12 +1222,12 @@ HighsStatus ipxBasicSolutionToHighsBasicSolution( assert(ipx_row == ipx_solution.num_row); assert(ipx_slack == ipx_solution.num_col); - // Flip dual according to lp.sense_ - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - highs_solution.col_dual[iCol] *= (HighsInt)lp.sense_; - } - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - highs_solution.row_dual[iRow] *= (HighsInt)lp.sense_; + if (lp.sense_ == ObjSense::kMaximize) { + // Flip dual values since original LP is maximization + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + highs_solution.col_dual[iCol] *= -1; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + highs_solution.row_dual[iRow] *= -1; } if (num_boxed_rows) @@ -1220,7 +1270,6 @@ HighsStatus formSimplexLpBasisAndFactor(HighsLpSolverObject& solver_object, HighsBasis& basis = solver_object.basis_; HighsOptions& options = solver_object.options_; HEkk& ekk_instance = solver_object.ekk_instance_; - HighsLp& ekk_lp = ekk_instance.lp_; HighsSimplexStatus& ekk_status = ekk_instance.status_; lp.ensureColwise(); // Consider scaling the LP @@ -1354,33 +1403,29 @@ void resetModelStatusAndHighsInfo(HighsModelStatus& model_status, } bool isBasisConsistent(const HighsLp& lp, const HighsBasis& basis) { - bool consistent = true; - consistent = isBasisRightSize(lp, basis) && consistent; - if (consistent) { - HighsInt num_basic_variables = 0; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (basis.col_status[iCol] == HighsBasisStatus::kBasic) - num_basic_variables++; - } - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (basis.row_status[iRow] == HighsBasisStatus::kBasic) - num_basic_variables++; - } - bool right_num_basic_variables = num_basic_variables == lp.num_row_; - consistent = right_num_basic_variables && consistent; + if (!isBasisRightSize(lp, basis)) return false; + + HighsInt num_basic_variables = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (basis.col_status[iCol] == HighsBasisStatus::kBasic) + num_basic_variables++; } - return consistent; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (basis.row_status[iRow] == HighsBasisStatus::kBasic) + num_basic_variables++; + } + return num_basic_variables == lp.num_row_; } bool isPrimalSolutionRightSize(const HighsLp& lp, const HighsSolution& solution) { - return (HighsInt)solution.col_value.size() == lp.num_col_ && - (HighsInt)solution.row_value.size() == lp.num_row_; + return solution.col_value.size() == static_cast(lp.num_col_) && + solution.row_value.size() == static_cast(lp.num_row_); } bool isDualSolutionRightSize(const HighsLp& lp, const HighsSolution& solution) { - return (HighsInt)solution.col_dual.size() == lp.num_col_ && - (HighsInt)solution.row_dual.size() == lp.num_row_; + return solution.col_dual.size() == static_cast(lp.num_col_) && + solution.row_dual.size() == static_cast(lp.num_row_); } bool isSolutionRightSize(const HighsLp& lp, const HighsSolution& solution) { @@ -1389,8 +1434,14 @@ bool isSolutionRightSize(const HighsLp& lp, const HighsSolution& solution) { } bool isBasisRightSize(const HighsLp& lp, const HighsBasis& basis) { - return (HighsInt)basis.col_status.size() == lp.num_col_ && - (HighsInt)basis.row_status.size() == lp.num_row_; + return basis.col_status.size() == static_cast(lp.num_col_) && + basis.row_status.size() == static_cast(lp.num_row_); +} + +bool HighsSolution::hasUndefined() { + for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) + if (this->col_value[iCol] == kHighsUndefined) return true; + return false; } void HighsSolution::invalidate() { diff --git a/src/lp_data/HighsSolution.h b/src/lp_data/HighsSolution.h index 05c84154da..f869b6b0ee 100644 --- a/src/lp_data/HighsSolution.h +++ b/src/lp_data/HighsSolution.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -85,7 +85,7 @@ void getKktFailures(const HighsOptions& options, const HighsLp& lp, HighsPrimalDualErrors& primal_dual_errors, const bool get_residuals = false); -void getVariableKktFailures(const double primal_feasibility_tolerance, +bool getVariableKktFailures(const double primal_feasibility_tolerance, const double dual_feasibility_tolerance, const double lower, const double upper, const double value, const double dual, diff --git a/src/lp_data/HighsSolutionDebug.cpp b/src/lp_data/HighsSolutionDebug.cpp index 75cc3d42d0..818c94c1bf 100644 --- a/src/lp_data/HighsSolutionDebug.cpp +++ b/src/lp_data/HighsSolutionDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -57,7 +57,7 @@ HighsDebugStatus debugHighsSolution(const string message, // // Set up a HighsModelStatus and HighsInfo just to // complete the parameter list.By setting - // check_model_status_and_highs_info to be false they waren't + // check_model_status_and_highs_info to be false they aren't // used. HighsModelStatus dummy_model_status; HighsInfo dummy_highs_info; @@ -152,7 +152,7 @@ HighsDebugStatus debugHighsSolution( error_found = true; highsLogDev(options.log_options, HighsLogType::kError, "debugHighsLpSolution: %" HIGHSINT_FORMAT - " primal infeasiblilities but model status is %s\n", + " primal infeasibilities but model status is %s\n", num_primal_infeasibility, utilModelStatusToString(model_status).c_str()); } @@ -160,7 +160,7 @@ HighsDebugStatus debugHighsSolution( error_found = true; highsLogDev(options.log_options, HighsLogType::kError, "debugHighsLpSolution: %" HIGHSINT_FORMAT - " dual infeasiblilities but model status is %s\n", + " dual infeasibilities but model status is %s\n", num_dual_infeasibility, utilModelStatusToString(model_status).c_str()); } diff --git a/src/lp_data/HighsSolutionDebug.h b/src/lp_data/HighsSolutionDebug.h index 1c73644830..1f30a0bf2e 100644 --- a/src/lp_data/HighsSolutionDebug.h +++ b/src/lp_data/HighsSolutionDebug.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index d55b90ea1f..d839bfb88d 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -14,6 +14,7 @@ #include "ipm/IpxWrapper.h" #include "lp_data/HighsSolutionDebug.h" +#include "pdlp/CupdlpWrapper.h" #include "simplex/HApp.h" // The method below runs simplex or ipx solver on the lp. @@ -37,25 +38,39 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return_status, "assessLp"); if (return_status == HighsStatus::kError) return return_status; } - if (!solver_object.lp_.num_row_) { - // Unconstrained LP so solve directly + if (!solver_object.lp_.num_row_ || solver_object.lp_.a_matrix_.numNz() == 0) { + // LP is unconstrained due to having no rows or a zero constraint + // matrix, so solve directly call_status = solveUnconstrainedLp(solver_object); return_status = interpretCallStatus(options.log_options, call_status, return_status, "solveUnconstrainedLp"); if (return_status == HighsStatus::kError) return return_status; - } else if (options.solver == kIpmString) { - // Use IPM - bool imprecise_solution; - // Use IPX to solve the LP - try { - call_status = solveLpIpx(solver_object); - } catch (const std::exception& exception) { - highsLogDev(options.log_options, HighsLogType::kError, - "Exception %s in solveLpIpx\n", exception.what()); - call_status = HighsStatus::kError; + } else if (options.solver == kIpmString || options.run_centring || + options.solver == kPdlpString) { + // Use IPM or PDLP + if (options.solver == kIpmString || options.run_centring) { + // Use IPX to solve the LP + try { + call_status = solveLpIpx(solver_object); + } catch (const std::exception& exception) { + highsLogDev(options.log_options, HighsLogType::kError, + "Exception %s in solveLpIpx\n", exception.what()); + call_status = HighsStatus::kError; + } + return_status = interpretCallStatus(options.log_options, call_status, + return_status, "solveLpIpx"); + } else { + // Use cuPDLP-C to solve the LP + try { + call_status = solveLpCupdlp(solver_object); + } catch (const std::exception& exception) { + highsLogDev(options.log_options, HighsLogType::kError, + "Exception %s in solveLpCupdlp\n", exception.what()); + call_status = HighsStatus::kError; + } + return_status = interpretCallStatus(options.log_options, call_status, + return_status, "solveLpCupdlp"); } - return_status = interpretCallStatus(options.log_options, call_status, - return_status, "solveLpIpx"); if (return_status == HighsStatus::kError) return return_status; // Non-error return requires a primal solution assert(solver_object.solution_.value_valid); @@ -64,47 +79,108 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { solver_object.lp_.objectiveValue(solver_object.solution_.col_value); getLpKktFailures(options, solver_object.lp_, solver_object.solution_, solver_object.basis_, solver_object.highs_info_); - // Seting the IPM-specific values of (highs_)info_ has been done in - // solveLpIpx - const bool unwelcome_ipx_status = - solver_object.model_status_ == HighsModelStatus::kUnknown || - (solver_object.model_status_ == - HighsModelStatus::kUnboundedOrInfeasible && - !options.allow_unbounded_or_infeasible); - if (unwelcome_ipx_status) { - highsLogUser(options.log_options, HighsLogType::kWarning, - "Unwelcome IPX status of %s: basis is %svalid; solution is " - "%svalid; run_crossover is \"%s\"\n", - utilModelStatusToString(solver_object.model_status_).c_str(), - solver_object.basis_.valid ? "" : "not ", - solver_object.solution_.value_valid ? "" : "not ", - options.run_crossover.c_str()); - if (options.run_crossover != kHighsOffString) { - // IPX has returned a model status that HiGHS would rather - // avoid, so perform simplex clean-up if crossover was allowed. - // - // This is an unusual situation, and the cost will usually be - // acceptable. Worst case is if crossover wasn't run, in which - // case there's no basis to start simplex - // - // ToDo: Check whether simplex can exploit the primal solution returned - // by IPX - highsLogUser(options.log_options, HighsLogType::kWarning, - "IPX solution is imprecise, so clean up with simplex\n"); - // Reset the return status since it will now be determined by - // the outcome of the simplex solve - return_status = HighsStatus::kOk; - call_status = solveLpSimplex(solver_object); - return_status = interpretCallStatus(options.log_options, call_status, - return_status, "solveLpSimplex"); - if (return_status == HighsStatus::kError) return return_status; - if (!isSolutionRightSize(solver_object.lp_, solver_object.solution_)) { - highsLogUser(options.log_options, HighsLogType::kError, - "Inconsistent solution returned from solver\n"); - return HighsStatus::kError; + if (options.solver == kIpmString || options.run_centring) { + // Setting the IPM-specific values of (highs_)info_ has been done in + // solveLpIpx + const bool unwelcome_ipx_status = + solver_object.model_status_ == HighsModelStatus::kUnknown || + (solver_object.model_status_ == + HighsModelStatus::kUnboundedOrInfeasible && + !options.allow_unbounded_or_infeasible); + if (unwelcome_ipx_status) { + // When performing an analytic centre calculation, the setting + // of options.run_crossover is ignored, so simplex clean-up is + // not possible - or desirable, anyway! + highsLogUser( + options.log_options, HighsLogType::kWarning, + "Unwelcome IPX status of %s: basis is %svalid; solution is " + "%svalid; run_crossover is \"%s\"\n", + utilModelStatusToString(solver_object.model_status_).c_str(), + solver_object.basis_.valid ? "" : "not ", + solver_object.solution_.value_valid ? "" : "not ", + options.run_centring ? "off" : options.run_crossover.c_str()); + const bool allow_simplex_cleanup = + options.run_crossover != kHighsOffString && !options.run_centring; + if (allow_simplex_cleanup) { + // IPX has returned a model status that HiGHS would rather + // avoid, so perform simplex clean-up if crossover was allowed. + // + // This is an unusual situation, and the cost will usually be + // acceptable. Worst case is if crossover wasn't run, in which + // case there's no basis to start simplex + // + // ToDo: Check whether simplex can exploit the primal solution + // returned by IPX + highsLogUser(options.log_options, HighsLogType::kWarning, + "IPX solution is imprecise, so clean up with simplex\n"); + // Reset the return status since it will now be determined by + // the outcome of the simplex solve + return_status = HighsStatus::kOk; + call_status = solveLpSimplex(solver_object); + return_status = interpretCallStatus(options.log_options, call_status, + return_status, "solveLpSimplex"); + if (return_status == HighsStatus::kError) return return_status; + if (!isSolutionRightSize(solver_object.lp_, + solver_object.solution_)) { + highsLogUser(options.log_options, HighsLogType::kError, + "Inconsistent solution returned from solver\n"); + return HighsStatus::kError; + } + } // options.run_crossover == kHighsOnString + } // unwelcome_ipx_status + } else { + // PDLP has been used, so check whether claim of optimality + // satisfies the HiGHS criteria + // + // Even when PDLP terminates with primal and dual feasibility + // and duality gap that are within the tolerances supplied by + // HiGHS, the HiGHS primal and dual feasibility tolerances may + // not be satisfied since they are absolute, and in PDLP they + // are relative. Note that, even when only one PDLP row activit + // fails to satisfy the absolute tolerance, the absolute norm + // measure reported by PDLP will not necessarily be the same as + // with HiGHS, since PDLP uses the 2-norm, and HiGHS the + // infinity- and 1-norm + // + // A single small HiGHS primal infeasibility from PDLP can yield + // a significant dual infeasibility, since the variable is + // interpreted as being off its bound so any dual value is an + // infeasibility. Hence, for context, the max and sum of + // complementarity violations are also computed. + const HighsInfo& info = solver_object.highs_info_; + if (solver_object.model_status_ == HighsModelStatus::kOptimal) { + if (info.num_primal_infeasibilities || info.num_dual_infeasibilities) { + if (info.num_primal_infeasibilities) { + highsLogUser(options.log_options, HighsLogType::kWarning, + "PDLP claims optimality, but with num/max/sum %d / " + "%9.4g / %9.4g primal infeasibilities\n", + int(info.num_primal_infeasibilities), + info.max_primal_infeasibility, + info.sum_primal_infeasibilities); + } else if (info.num_dual_infeasibilities) { + highsLogUser(options.log_options, HighsLogType::kWarning, + "PDLP claims optimality, but with num/max/sum %d / " + "%9.4g / %9.4g dual infeasibilities\n", + int(info.num_dual_infeasibilities), + info.max_dual_infeasibility, + info.sum_dual_infeasibilities); + } + highsLogUser(options.log_options, HighsLogType::kWarning, + " and max/sum %9.4g " + "/ %9.4g complementarity violations\n", + info.max_complementarity_violation, + info.sum_complementarity_violations); + highsLogUser( + options.log_options, HighsLogType::kWarning, + " so set model status to \"unknown\"\n"); + solver_object.model_status_ = HighsModelStatus::kUnknown; } - } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } else if (solver_object.model_status_ == + HighsModelStatus::kUnboundedOrInfeasible) { + if (info.num_primal_infeasibilities == 0) + solver_object.model_status_ = HighsModelStatus::kUnbounded; + } + } } else { // Use Simplex call_status = solveLpSimplex(solver_object); @@ -143,14 +219,17 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, resetModelStatusAndHighsInfo(model_status, highs_info); // Check that the LP really is unconstrained! - assert(lp.num_row_ == 0); - if (lp.num_row_ != 0) return HighsStatus::kError; + assert(lp.num_row_ == 0 || lp.a_matrix_.numNz() == 0); + if (lp.num_row_ > 0) { + // LP has rows, but should only be here if the constraint matrix + // is zero + if (lp.a_matrix_.numNz() > 0) return HighsStatus::kError; + } highsLogUser(options.log_options, HighsLogType::kInfo, "Solving an unconstrained LP with %" HIGHSINT_FORMAT " columns\n", lp.num_col_); - solution.col_value.assign(lp.num_col_, 0); solution.col_dual.assign(lp.num_col_, 0); basis.col_status.assign(lp.num_col_, HighsBasisStatus::kNonbasic); @@ -165,8 +244,6 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, // Initialise the objective value calculation. Done using // HighsSolution so offset is vanilla double objective = lp.offset_; - bool infeasible = false; - bool unbounded = false; highs_info.num_primal_infeasibilities = 0; highs_info.max_primal_infeasibility = 0; @@ -175,6 +252,31 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, highs_info.max_dual_infeasibility = 0; highs_info.sum_dual_infeasibilities = 0; + if (lp.num_row_ > 0) { + // Assign primal, dual and basis status for rows, checking for + // infeasibility + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + double primal_infeasibility = 0; + double lower = lp.row_lower_[iRow]; + double upper = lp.row_upper_[iRow]; + if (lower > primal_feasibility_tolerance) { + // Lower bound too large for zero activity + primal_infeasibility = lower; + } else if (upper < -primal_feasibility_tolerance) { + // Upper bound too small for zero activity + primal_infeasibility = -upper; + } + solution.row_value.push_back(0); + solution.row_dual.push_back(0); + basis.row_status.push_back(HighsBasisStatus::kBasic); + if (primal_infeasibility > primal_feasibility_tolerance) + highs_info.num_primal_infeasibilities++; + highs_info.sum_primal_infeasibilities += primal_infeasibility; + highs_info.max_primal_infeasibility = + std::max(primal_infeasibility, highs_info.max_primal_infeasibility); + } + } + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { double cost = lp.col_cost_[iCol]; double dual = (HighsInt)lp.sense_ * cost; @@ -212,7 +314,7 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, dual_infeasibility = std::max(-dual, 0.); } } else if (highs_isInfinity(-lower) && highs_isInfinity(upper)) { - // Free column: set to zero and record dual infeasiblility + // Free column: set to zero and record dual infeasibility value = 0; status = HighsBasisStatus::kZero; dual_infeasibility = std::fabs(dual); @@ -225,7 +327,7 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, dual_infeasibility = 0; } else { // Infinite lower bound so set to upper bound and record dual - // infeasiblility + // infeasibility value = upper; status = HighsBasisStatus::kUpper; dual_infeasibility = dual; @@ -239,7 +341,7 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, dual_infeasibility = 0; } else { // Infinite upper bound so set to lower bound and record dual - // infeasiblility + // infeasibility value = lower; status = HighsBasisStatus::kLower; dual_infeasibility = -dual; @@ -291,3 +393,205 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, return HighsStatus::kOk; } + +void assessExcessiveBoundCost(const HighsLogOptions log_options, + const HighsModel& model) { + auto assessFiniteNonzero = [&](const double value, double& min_value, + double& max_value) { + double abs_value = std::abs(value); + if (abs_value > 0 && abs_value < kHighsInf) { + min_value = std::min(abs_value, min_value); + max_value = std::max(abs_value, max_value); + } + }; + const HighsLp& lp = model.lp_; + double min_finite_col_cost = kHighsInf; + double max_finite_col_cost = -kHighsInf; + double min_finite_col_bound = kHighsInf; + double max_finite_col_bound = -kHighsInf; + double min_finite_row_bound = kHighsInf; + double max_finite_row_bound = -kHighsInf; + double min_matrix_value = kHighsInf; + double max_matrix_value = -kHighsInf; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + assessFiniteNonzero(lp.col_cost_[iCol], min_finite_col_cost, + max_finite_col_cost); + assessFiniteNonzero(lp.col_lower_[iCol], min_finite_col_bound, + max_finite_col_bound); + assessFiniteNonzero(lp.col_upper_[iCol], min_finite_col_bound, + max_finite_col_bound); + } + if (min_finite_col_cost == kHighsInf) min_finite_col_cost = 0; + if (max_finite_col_cost == -kHighsInf) max_finite_col_cost = 0; + if (min_finite_col_bound == kHighsInf) min_finite_col_bound = 0; + if (max_finite_col_bound == -kHighsInf) max_finite_col_bound = 0; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + assessFiniteNonzero(lp.row_lower_[iRow], min_finite_row_bound, + max_finite_row_bound); + assessFiniteNonzero(lp.row_upper_[iRow], min_finite_row_bound, + max_finite_row_bound); + } + if (min_finite_row_bound == kHighsInf) min_finite_row_bound = 0; + if (max_finite_row_bound == -kHighsInf) max_finite_row_bound = 0; + HighsInt num_nz = lp.a_matrix_.numNz(); + for (HighsInt iEl = 0; iEl < num_nz; iEl++) + assessFiniteNonzero(lp.a_matrix_.value_[iEl], min_matrix_value, + max_matrix_value); + + highsLogUser(log_options, HighsLogType::kInfo, "Coefficient ranges:\n"); + if (num_nz) + highsLogUser(log_options, HighsLogType::kInfo, " Matrix [%5.0e, %5.0e]\n", + min_matrix_value, max_matrix_value); + if (lp.num_col_) { + highsLogUser(log_options, HighsLogType::kInfo, " Cost [%5.0e, %5.0e]\n", + min_finite_col_cost, max_finite_col_cost); + highsLogUser(log_options, HighsLogType::kInfo, " Bound [%5.0e, %5.0e]\n", + min_finite_col_bound, max_finite_col_bound); + } + if (lp.num_row_) + highsLogUser(log_options, HighsLogType::kInfo, " RHS [%5.0e, %5.0e]\n", + min_finite_row_bound, max_finite_row_bound); + + // LPs with no columns or no finite nonzero costs will have + // max_finite_col_cost = 0 + assert(max_finite_col_cost >= 0); + if (max_finite_col_cost > kExcessivelyLargeCostValue) { + double user_cost_scale_value = std::pow(2, lp.user_cost_scale_); + // Warn that costs are excessively large, and suggest scaling + double ratio = kExcessivelyLargeCostValue / + (max_finite_col_cost / user_cost_scale_value); + HighsInt suggested_user_cost_scale_setting = std::floor(std::log2(ratio)); + HighsInt suggested_cost_scale_exponent = std::floor(std::log10(ratio)); + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively large costs: consider scaling the costs " + "by 1e%+1d or less, or setting option user_cost_scale to %d or less\n", + lp.user_cost_scale_ ? "User-scaled problem" : "Problem", + int(-suggested_cost_scale_exponent), + int(suggested_user_cost_scale_setting)); + } + // LPs with no columns or no finite nonzero bounds will have + // max_finite_col_bound = 0 + assert(max_finite_col_bound >= 0); + if (max_finite_col_bound > kExcessivelyLargeBoundValue) { + double user_bound_scale_value = std::pow(2, lp.user_bound_scale_); + // Warn that bounds are excessively large, and suggest scaling + double ratio = kExcessivelyLargeBoundValue / + (max_finite_col_bound / user_bound_scale_value); + HighsInt suggested_user_bound_scale = std::floor(std::log2(ratio)); + HighsInt suggested_bound_scale_exponent = std::floor(std::log10(ratio)); + if (lp.isMip()) { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively large bounds: consider scaling the bounds " + "by 1e%+1d or less\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(-suggested_bound_scale_exponent)); + } else { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively large bounds: consider scaling the bounds " + "by 1e%+1d or less, " + "or setting option user_bound_scale to %d or less\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(-suggested_bound_scale_exponent), + int(suggested_user_bound_scale)); + } + } + // LPs with no rows or no finite nonzero bounds will have + // max_finite_row_bound = 0 + assert(max_finite_row_bound >= 0); + if (max_finite_row_bound > kExcessivelyLargeBoundValue) { + double user_bound_scale_value = std::pow(2, lp.user_bound_scale_); + // Warn that bounds are excessively large, and suggest scaling + double ratio = kExcessivelyLargeBoundValue / + (max_finite_row_bound / user_bound_scale_value); + HighsInt suggested_user_bound_scale = std::floor(std::log2(ratio)); + HighsInt suggested_bound_scale_exponent = std::floor(std::log10(ratio)); + if (lp.isMip()) { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively large bounds: consider scaling the bounds " + "by 1e%+1d or less\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(-suggested_bound_scale_exponent)); + } else { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively large bounds: consider scaling the bounds " + "by 1e%+1d or less, " + "or setting option user_bound_scale to %d or less\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(-suggested_bound_scale_exponent), + int(suggested_user_bound_scale)); + } + } + // Now consider warning relating to small maximum costs and bounds + if (max_finite_col_cost > 0 && + max_finite_col_cost < kExcessivelySmallCostValue) { + double user_cost_scale_value = std::pow(2, lp.user_cost_scale_); + // Warn that costs are excessively small, and suggest scaling + double ratio = kExcessivelySmallCostValue / + (max_finite_col_cost / user_cost_scale_value); + HighsInt suggested_user_cost_scale_setting = std::ceil(std::log2(ratio)); + HighsInt suggested_cost_scale_exponent = std::ceil(std::log10(ratio)); + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively small costs: consider scaling the costs up " + "by 1e%+1d or more, " + "or setting option user_cost_scale to %d or more\n", + lp.user_cost_scale_ ? "User-scaled problem" : "Problem", + int(suggested_cost_scale_exponent), + int(suggested_user_cost_scale_setting)); + } + if (max_finite_col_bound > 0 && + max_finite_col_bound < kExcessivelySmallBoundValue) { + double user_bound_scale_value = std::pow(2, lp.user_bound_scale_); + // Warn that bounds are excessively small, and suggest scaling + double ratio = kExcessivelySmallBoundValue / + (max_finite_col_bound / user_bound_scale_value); + HighsInt suggested_user_bound_scale = std::ceil(std::log2(ratio)); + HighsInt suggested_bound_scale_exponent = std::ceil(std::log10(ratio)); + if (lp.isMip()) { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively small bounds: consider scaling the bounds " + "by 1e%+1d or more\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(suggested_bound_scale_exponent)); + } else { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively small bounds: consider scaling the bounds " + "by 1e%+1d or more, " + "or setting option user_bound_scale to %d or more\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(suggested_bound_scale_exponent), int(suggested_user_bound_scale)); + } + } + if (max_finite_row_bound > 0 && + max_finite_row_bound < kExcessivelySmallBoundValue) { + double user_bound_scale_value = std::pow(2, lp.user_bound_scale_); + // Warn that bounds are excessively small, and suggest scaling + double ratio = kExcessivelySmallBoundValue / + (max_finite_row_bound / user_bound_scale_value); + HighsInt suggested_user_bound_scale = std::ceil(std::log2(ratio)); + HighsInt suggested_bound_scale_exponent = std::ceil(std::log10(ratio)); + if (lp.isMip()) { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively small bounds: consider scaling the bounds " + "by 1e%+1d or more\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(suggested_bound_scale_exponent)); + } else { + highsLogUser( + log_options, HighsLogType::kWarning, + "%s has excessively small bounds: consider scaling the bounds " + "by 1e%+1d or more, " + "or setting option user_bound_scale to %d or more\n", + lp.user_bound_scale_ ? "User-scaled problem" : "Problem", + int(suggested_bound_scale_exponent), int(suggested_user_bound_scale)); + } + } +} diff --git a/src/lp_data/HighsSolve.h b/src/lp_data/HighsSolve.h index 1bf38d672a..3bc1170249 100644 --- a/src/lp_data/HighsSolve.h +++ b/src/lp_data/HighsSolve.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -21,4 +21,6 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, HighsModelStatus& model_status, HighsInfo& highs_info, HighsSolution& solution, HighsBasis& basis); +void assessExcessiveBoundCost(const HighsLogOptions log_options, + const HighsModel& model); #endif // LP_DATA_HIGHSSOLVE_H_ diff --git a/src/lp_data/HighsStatus.cpp b/src/lp_data/HighsStatus.cpp index 7bcff56001..1f8644978f 100644 --- a/src/lp_data/HighsStatus.cpp +++ b/src/lp_data/HighsStatus.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/lp_data/HighsStatus.h b/src/lp_data/HighsStatus.h index 3e6c91f516..7cfc0b9d32 100644 --- a/src/lp_data/HighsStatus.h +++ b/src/lp_data/HighsStatus.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/meson.build b/src/meson.build index 341c63a42c..5fb54a97c2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,45 +10,31 @@ conf_data.set('HIGHS_VERSION_PATCH', meson.project_version().split('.')[2]) conf_data.set('ZLIB_FOUND', zlib_dep.found()) + +if is_windows +# There is a bug with threading on windows, see CMakeList.txt:307 +# Also https://github.com/ERGO-Code/HiGHS/issues/1382#issuecomment-1670967735 +# Generally, no point using threading with HiGHs at this point (May 2024) + conf_data.set('HIGHS_NO_DEFAULT_THREADS', + true) +else + conf_data.set('HIGHS_NO_DEFAULT_THREADS', + threads_dep.found()) +endif +# This dependency hasn't been setup.. +#conf_data.set('CUPDLP_CPU', +# cupdlp_cpu.found()) # 64 bit numbers -if host_machine.cpu_family() == 'x86_64' +if host_machine.cpu_family() == 'x86_64' or host_machine.cpu_family() == 'amd64' # Get user's option, if it's not provided, enable highsint64 by default on x86_64 highsint64_opt = get_option('highsint64') conf_data.set('HIGHSINT64', highsint64_opt) else conf_data.set('HIGHSINT64', false) endif -# Commit hash -commit_hash = 'unknown' # Default value -git = find_program('git', required: false) -if git.found() - commit_hash_cmd = run_command(git, 'rev-parse', '--short', - '-C', meson.project_source_root(), 'HEAD', - check: false, # Don't abort on failure - ) - if commit_hash_cmd.returncode() == 0 - commit_hash = commit_hash_cmd.stdout().strip() - endif -endif -conf_data.set_quoted('HIGHS_GITHASH', commit_hash) -# Date -python_getdate = ''' -import datetime -import os -import time -build_date = datetime.datetime.utcfromtimestamp( - int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) -) -build_date_str = build_date.strftime('%Y-%m-%d') -print(build_date_str) -''' -today_cmd = run_command('python3', '-c', - python_getdate, - check: true) -today_str = today_cmd.stdout().strip() conf_data.set_quoted('HIGHS_COMPILATION_DATE', - today_str) + 'deprecated') # Conditional compiler options _mm_pause_code = ''' @@ -96,45 +82,63 @@ else _have_builtin_clz) endif -configure_file( +interim_conf = configure_file( input: 'HConfig.h.meson.in', - output: 'HConfig.h', + output: 'HConfig.h.meson.interim', configuration: conf_data ) +highs_conf_file = vcs_tag( + input: interim_conf, + output: 'HConfig.h', + fallback: 'unknown', + replace_string: '_HIGHS_GITHASH_', # Remember to quote manually +) + _incdirs += include_directories('.') # --------------------- Libraries +_cupdlp_srcs = [ + 'pdlp/cupdlp/cupdlp_cs.c', + 'pdlp/cupdlp/cupdlp_linalg.c', + 'pdlp/cupdlp/cupdlp_proj.c', + 'pdlp/cupdlp/cupdlp_restart.c', + 'pdlp/cupdlp/cupdlp_scaling_cuda.c', + 'pdlp/cupdlp/cupdlp_solver.c', + 'pdlp/cupdlp/cupdlp_step.c', + 'pdlp/cupdlp/cupdlp_utils.c', +] + _basiclu_srcs = [ 'ipm/basiclu/basiclu_factorize.c', - 'ipm/basiclu/basiclu_solve_dense.c', - 'ipm/basiclu/lu_build_factors.c', - 'ipm/basiclu/lu_factorize_bump.c', - 'ipm/basiclu/lu_initialize.c', - 'ipm/basiclu/lu_markowitz.c', - 'ipm/basiclu/lu_setup_bump.c', - 'ipm/basiclu/lu_solve_sparse.c', 'ipm/basiclu/basiclu_get_factors.c', + 'ipm/basiclu/basiclu_initialize.c', + 'ipm/basiclu/basiclu_object.c', + 'ipm/basiclu/basiclu_solve_dense.c', 'ipm/basiclu/basiclu_solve_for_update.c', + 'ipm/basiclu/basiclu_solve_sparse.c', + 'ipm/basiclu/basiclu_update.c', + 'ipm/basiclu/lu_build_factors.c', 'ipm/basiclu/lu_condest.c', + 'ipm/basiclu/lu_dfs.c', + 'ipm/basiclu/lu_factorize_bump.c', 'ipm/basiclu/lu_file.c', + 'ipm/basiclu/lu_garbage_perm.c', + 'ipm/basiclu/lu_initialize.c', 'ipm/basiclu/lu_internal.c', + 'ipm/basiclu/lu_markowitz.c', 'ipm/basiclu/lu_matrix_norm.c', - 'ipm/basiclu/lu_singletons.c', - 'ipm/basiclu/lu_solve_symbolic.c', - 'ipm/basiclu/lu_update.c', - 'ipm/basiclu/basiclu_initialize.c', - 'ipm/basiclu/basiclu_solve_sparse.c', 'ipm/basiclu/lu_pivot.c', - 'ipm/basiclu/lu_solve_dense.c', - 'ipm/basiclu/lu_solve_triangular.c', - 'ipm/basiclu/basiclu_object.c', - 'ipm/basiclu/basiclu_update.c', - 'ipm/basiclu/lu_dfs.c', - 'ipm/basiclu/lu_garbage_perm.c', 'ipm/basiclu/lu_residual_test.c', + 'ipm/basiclu/lu_setup_bump.c', + 'ipm/basiclu/lu_singletons.c', + 'ipm/basiclu/lu_solve_dense.c', 'ipm/basiclu/lu_solve_for_update.c', + 'ipm/basiclu/lu_solve_sparse.c', + 'ipm/basiclu/lu_solve_symbolic.c', + 'ipm/basiclu/lu_solve_triangular.c', + 'ipm/basiclu/lu_update.c', ] _ipx_srcs = [ @@ -173,84 +177,86 @@ _ipx_srcs = [ _srcs = [ '../extern/filereaderlp/reader.cpp', + 'interfaces/highs_c_api.cpp', 'io/Filereader.cpp', - 'io/FilereaderLp.cpp', 'io/FilereaderEms.cpp', + 'io/FilereaderLp.cpp', 'io/FilereaderMps.cpp', - 'io/HighsIO.cpp', 'io/HMPSIO.cpp', 'io/HMpsFF.cpp', + 'io/HighsIO.cpp', 'io/LoadOptions.cpp', + 'ipm/IpxWrapper.cpp', 'lp_data/Highs.cpp', 'lp_data/HighsCallback.cpp', 'lp_data/HighsDebug.cpp', + 'lp_data/HighsDeprecated.cpp', 'lp_data/HighsInfo.cpp', 'lp_data/HighsInfoDebug.cpp', - 'lp_data/HighsDeprecated.cpp', 'lp_data/HighsInterface.cpp', 'lp_data/HighsLp.cpp', 'lp_data/HighsLpUtils.cpp', 'lp_data/HighsModelUtils.cpp', + 'lp_data/HighsOptions.cpp', 'lp_data/HighsRanging.cpp', 'lp_data/HighsSolution.cpp', 'lp_data/HighsSolutionDebug.cpp', 'lp_data/HighsSolve.cpp', 'lp_data/HighsStatus.cpp', - 'lp_data/HighsOptions.cpp', - 'mip/HighsMipSolver.cpp', - 'mip/HighsMipSolverData.cpp', + 'mip/HighsCliqueTable.cpp', + 'mip/HighsConflictPool.cpp', + 'mip/HighsCutGeneration.cpp', + 'mip/HighsCutPool.cpp', + 'mip/HighsDebugSol.cpp', 'mip/HighsDomain.cpp', 'mip/HighsDynamicRowMatrix.cpp', + 'mip/HighsGFkSolve.cpp', + 'mip/HighsImplications.cpp', + 'mip/HighsLpAggregator.cpp', 'mip/HighsLpRelaxation.cpp', - 'mip/HighsSeparation.cpp', - 'mip/HighsSeparator.cpp', - 'mip/HighsTableauSeparator.cpp', + 'mip/HighsMipSolver.cpp', + 'mip/HighsMipSolverData.cpp', 'mip/HighsModkSeparator.cpp', + 'mip/HighsNodeQueue.cpp', + 'mip/HighsObjectiveFunction.cpp', 'mip/HighsPathSeparator.cpp', - 'mip/HighsCutGeneration.cpp', - 'mip/HighsSearch.cpp', - 'mip/HighsConflictPool.cpp', - 'mip/HighsCutPool.cpp', - 'mip/HighsCliqueTable.cpp', - 'mip/HighsGFkSolve.cpp', - 'mip/HighsTransformedLp.cpp', - 'mip/HighsLpAggregator.cpp', - 'mip/HighsDebugSol.cpp', - 'mip/HighsImplications.cpp', 'mip/HighsPrimalHeuristics.cpp', 'mip/HighsPseudocost.cpp', 'mip/HighsRedcostFixing.cpp', - 'mip/HighsNodeQueue.cpp', - 'mip/HighsObjectiveFunction.cpp', + 'mip/HighsSearch.cpp', + 'mip/HighsSeparation.cpp', + 'mip/HighsSeparator.cpp', + 'mip/HighsTableauSeparator.cpp', + 'mip/HighsTransformedLp.cpp', 'model/HighsHessian.cpp', 'model/HighsHessianUtils.cpp', 'model/HighsModel.cpp', 'parallel/HighsTaskExecutor.cpp', + 'pdlp/CupdlpWrapper.cpp', + 'presolve/HPresolve.cpp', + 'presolve/HPresolveAnalysis.cpp', + 'presolve/HighsPostsolveStack.cpp', + 'presolve/HighsSymmetry.cpp', 'presolve/ICrash.cpp', 'presolve/ICrashUtil.cpp', 'presolve/ICrashX.cpp', - 'presolve/HighsPostsolveStack.cpp', - 'presolve/HighsSymmetry.cpp', - 'presolve/HPresolve.cpp', - 'presolve/HPresolveAnalysis.cpp', 'presolve/PresolveComponent.cpp', 'qpsolver/a_asm.cpp', 'qpsolver/a_quass.cpp', 'qpsolver/basis.cpp', + 'qpsolver/perturbation.cpp', 'qpsolver/quass.cpp', 'qpsolver/ratiotest.cpp', 'qpsolver/scaling.cpp', - 'qpsolver/perturbation.cpp', 'simplex/HEkk.cpp', 'simplex/HEkkControl.cpp', 'simplex/HEkkDebug.cpp', - 'simplex/HEkkPrimal.cpp', 'simplex/HEkkDual.cpp', + 'simplex/HEkkDualMulti.cpp', 'simplex/HEkkDualRHS.cpp', 'simplex/HEkkDualRow.cpp', - 'simplex/HEkkDualMulti.cpp', 'simplex/HEkkInterface.cpp', - 'simplex/HighsSimplexAnalysis.cpp', + 'simplex/HEkkPrimal.cpp', 'simplex/HSimplex.cpp', 'simplex/HSimplexDebug.cpp', 'simplex/HSimplexNla.cpp', @@ -258,6 +264,7 @@ _srcs = [ 'simplex/HSimplexNlaFreeze.cpp', 'simplex/HSimplexNlaProductForm.cpp', 'simplex/HSimplexReport.cpp', + 'simplex/HighsSimplexAnalysis.cpp', 'test/DevKkt.cpp', 'test/KktCh2.cpp', 'util/HFactor.cpp', @@ -265,6 +272,8 @@ _srcs = [ 'util/HFactorExtend.cpp', 'util/HFactorRefactor.cpp', 'util/HFactorUtils.cpp', + 'util/HSet.cpp', + 'util/HVectorBase.cpp', 'util/HighsHash.cpp', 'util/HighsLinearSumBounds.cpp', 'util/HighsMatrixPic.cpp', @@ -272,29 +281,42 @@ _srcs = [ 'util/HighsSort.cpp', 'util/HighsSparseMatrix.cpp', 'util/HighsUtils.cpp', - 'util/HSet.cpp', - 'util/HVectorBase.cpp', 'util/stringutil.cpp', - 'interfaces/highs_c_api.cpp', ] highslib_srcs = [ + highs_conf_file, _srcs, - 'ipm/IpxWrapper.cpp', + _cupdlp_srcs, _basiclu_srcs, _ipx_srcs ] +symbol_visibility = 'default' +if get_option('default_library') == 'static' + # Ensure that if we link a static library into a shared library, + # private symbols don't get re-exported. + symbol_visibility = 'inlineshidden' +endif + + +if not get_option('with_pybind11') highslib = library('highs', highslib_srcs, dependencies: _deps, cpp_args: _args, c_args: _args, - link_with: _linkto, include_directories: _incdirs, + gnu_symbol_visibility: symbol_visibility, pic: true, install: true) + +highs_dep = declare_dependency(link_with: highslib, + dependencies: _deps, + include_directories: _incdirs, + ) +endif # --------------- Interfaces @@ -308,7 +330,7 @@ if get_option('with_fortran') _fsrcs, dependencies: _deps, cpp_args: _args, - link_with: [ _linkto, highslib ], + link_with: highslib, include_directories: _incdirs, pic: true, install: true) @@ -324,8 +346,80 @@ if get_option('with_csharp') _cs_srcs, dependencies: _deps, cpp_args: _args, - link_with: [ _linkto, highslib ], + link_with: highslib, include_directories: _incdirs, pic: true, install: true) endif + + +if get_option('with_pybind11') + py = import('python').find_installation(pure: false) + + highslib = library('highs', + highslib_srcs, + dependencies: _deps, + cpp_args: _args, + c_args: _args, + include_directories: _incdirs, + gnu_symbol_visibility: symbol_visibility, + pic: true, + install: true, + install_dir: py.get_install_dir() / 'highspy') + +highs_dep = declare_dependency(link_with: highslib, + dependencies: _deps, + include_directories: _incdirs, + ) + _deps += highs_dep + + pyb11_dep = [ + # py_dep is auto-added for Python >= 3.9, so it can be dropped here when + # that is the minimum supported Python version + py.dependency(), + dependency('pybind11') + ] + _deps += pyb11_dep + + highspy_cpp = files([ + 'highs_bindings.cpp' + ]) + highsoptions_cpp = files([ + 'highs_options.cpp' + ]) + + py.extension_module( + '_core', + sources : highspy_cpp + highs_conf_file, + dependencies: _deps, + cpp_args: _args, + install: true, + subdir: 'highspy', + include_directories: _incdirs, + ) + + py.extension_module( + '_highs_options', + sources : highsoptions_cpp + highs_conf_file, + dependencies: _deps, + cpp_args: _args, + install: true, + subdir: 'highspy', + include_directories: _incdirs, + ) + + highs_py_srcs = files( + 'highspy/__init__.py', + 'highspy/highs.py',) + + py.install_sources(highs_py_srcs, + pure: false, + subdir: 'highspy' + ) + +if is_windows + rootdir = meson.project_source_root() + meson.add_install_script(f'@rootdir@' / 'scripts' / 'post_install_win.bat', '{dest_dir}', '{wheel}') +endif + +endif diff --git a/src/mip/HighsCliqueTable.cpp b/src/mip/HighsCliqueTable.cpp index 34d391c203..54490e08e0 100644 --- a/src/mip/HighsCliqueTable.cpp +++ b/src/mip/HighsCliqueTable.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -15,13 +15,13 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsCutPool.h" #include "mip/HighsDomain.h" #include "mip/HighsMipSolver.h" #include "mip/HighsMipSolverData.h" #include "parallel/HighsCombinable.h" #include "parallel/HighsParallel.h" -#include "pdqsort/pdqsort.h" #include "presolve/HighsPostsolveStack.h" #include "util/HighsSplay.h" @@ -189,7 +189,7 @@ void HighsCliqueTable::bronKerboschRecurse(BronKerboschData& data, if (data.stop()) return; double pivweight = -1.0; - CliqueVar pivot; + CliqueVar pivot{0, 0}; for (HighsInt i = 0; i != Xlen; ++i) { if (X[i].weight(data.sol) > pivweight) { @@ -409,7 +409,7 @@ void HighsCliqueTable::doAddClique(const CliqueVar* cliquevars, freeslots.push_back(cliqueid); return; case 2: - // due to subsitutions the clique became smaller and is now of size + // due to substitutions the clique became smaller and is now of size // two as a result we need to link it to the size two cliqueset // instead of the normal cliqueset assert(cliqueid >= 0 && cliqueid < (HighsInt)cliques.size()); @@ -1164,6 +1164,9 @@ void HighsCliqueTable::extractCliquesFromCut(const HighsMipSolver& mipsolver, coef = globaldom.col_upper_[col] - implcolub; constant = implcolub; } else { + // make sure that upper bound is not infinite to avoid adding VUB + // with coefficient '-kHighsInf' and constant 'kHighsInf' + if (globaldom.col_upper_[col] == kHighsInf) continue; coef = implcolub - globaldom.col_upper_[col]; constant = globaldom.col_upper_[col]; } @@ -1186,6 +1189,9 @@ void HighsCliqueTable::extractCliquesFromCut(const HighsMipSolver& mipsolver, coef = globaldom.col_lower_[col] - implcollb; constant = implcollb; } else { + // make sure that lower bound is not infinite to avoid adding VLB + // with coefficient 'kHighsInf' and constant '-kHighsInf' + if (globaldom.col_lower_[col] == -kHighsInf) continue; coef = implcollb - globaldom.col_lower_[col]; constant = globaldom.col_lower_[col]; } @@ -1367,7 +1373,7 @@ void HighsCliqueTable::extractCliques(HighsMipSolver& mipsolver, } if (!freevar && nbin != 0) { - // printf("extracing cliques from this row:\n"); + // printf("extracting cliques from this row:\n"); // printRow(globaldom, inds.data(), vals.data(), inds.size(), // -kHighsInf, rhs); extractCliques(mipsolver, inds, vals, complementation, rhs, nbin, perm, @@ -1415,7 +1421,7 @@ void HighsCliqueTable::extractCliques(HighsMipSolver& mipsolver, } if (!freevar && nbin != 0) { - // printf("extracing cliques from this row:\n"); + // printf("extracting cliques from this row:\n"); // printRow(globaldom, inds.data(), vals.data(), inds.size(), // -kHighsInf, rhs); extractCliques(mipsolver, inds, vals, complementation, rhs, nbin, perm, @@ -1611,13 +1617,16 @@ void HighsCliqueTable::processInfeasibleVertices(HighsDomain& globaldom) { std::max( HighsInt{10}, (cliques[cliqueid].end - cliques[cliqueid].start) >> 1)) { - HighsInt initSize = cliques[cliqueid].end - cliques[cliqueid].start; clq.assign(cliqueentries.begin() + cliques[cliqueid].start, cliqueentries.begin() + cliques[cliqueid].end); - HighsInt numDel = 0; - for (CliqueVar x : clq) numDel += colDeleted[x.col]; - assert(numDel == cliques[cliqueid].numZeroFixed); + auto computeNumDeleted = [&](const std::vector& clq) { + HighsInt numDel = 0; + for (CliqueVar x : clq) numDel += colDeleted[x.col]; + return numDel; + }; + + assert(computeNumDeleted(clq) == cliques[cliqueid].numZeroFixed); removeClique(cliqueid); clq.erase(std::remove_if(clq.begin(), clq.end(), @@ -1948,7 +1957,6 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, for (HighsInt i = 0; i != initialCliqueSize; ++i) iscandidate[clique[i].index()] = true; - HighsInt node; auto addCands = [&](HighsInt cliqueid) { HighsInt start = cliques[cliqueid].start; HighsInt end = cliques[cliqueid].end; @@ -1986,7 +1994,7 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, clique.erase(clique.begin() + newSize, clique.end()); } - if (initialCliqueSize < (HighsInt)clique.size()) { + if (static_cast(initialCliqueSize) < clique.size()) { // todo, shuffle extension vars? randgen.shuffle(clique.data() + initialCliqueSize, clique.size() - initialCliqueSize); @@ -2013,8 +2021,9 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain, std::remove_if(clique.begin(), clique.end(), [&](CliqueVar v) { return globaldomain.isFixed(v.col) && - int(globaldomain.col_lower_[v.col]) == - (1 - v.val); + static_cast( + globaldomain.col_lower_[v.col]) == + static_cast(1 - v.val); }), clique.end()); } @@ -2060,7 +2069,6 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { for (HighsInt i = 0; i != numclqvars; ++i) iscandidate[clqvars[i].index()] = true; - HighsInt node; auto addCands = [&](HighsInt cliqueid) { HighsInt start = cliques[cliqueid].start; HighsInt end = cliques[cliqueid].end; @@ -2144,7 +2152,6 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { } bool redundant = false; - HighsInt numRemoved = 0; HighsInt dominatingOrigin = kHighsIInf; for (HighsInt cliqueid : cliquehitinds) { HighsInt hits = cliquehits[cliqueid]; @@ -2169,7 +2176,6 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { if (!vHasClq) infeasvertexstack.push_back(v); } } else { - ++numRemoved; removeClique(cliqueid); } } @@ -2185,8 +2191,9 @@ void HighsCliqueTable::runCliqueMerging(HighsDomain& globaldomain) { std::remove_if(extensionvars.begin(), extensionvars.end(), [&](CliqueVar v) { return globaldomain.isFixed(v.col) && - int(globaldomain.col_lower_[v.col]) == - (1 - v.val); + static_cast( + globaldomain.col_lower_[v.col]) == + static_cast(1 - v.val); }), extensionvars.end()); @@ -2285,7 +2292,7 @@ void HighsCliqueTable::buildFrom(const HighsLp* origModel, newCliqueTable.setPresolveFlag(minEntriesForParallelism); HighsInt ncliques = init.cliques.size(); std::vector clqBuffer; - clqBuffer.reserve(2 * origModel->num_col_); + clqBuffer.reserve(2 * static_cast(origModel->num_col_)); for (HighsInt i = 0; i != ncliques; ++i) { if (init.cliques[i].start == -1) continue; diff --git a/src/mip/HighsCliqueTable.h b/src/mip/HighsCliqueTable.h index 2f062306bb..303dbbd46c 100644 --- a/src/mip/HighsCliqueTable.h +++ b/src/mip/HighsCliqueTable.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -158,9 +158,9 @@ class HighsCliqueTable { int64_t numNeighbourhoodQueries; HighsCliqueTable(HighsInt ncols) { - invertedHashList.resize(2 * ncols); - invertedHashListSizeTwo.resize(2 * ncols); - numcliquesvar.resize(2 * ncols, 0); + invertedHashList.resize(2 * static_cast(ncols)); + invertedHashListSizeTwo.resize(2 * static_cast(ncols)); + numcliquesvar.resize(2 * static_cast(ncols), 0); colsubstituted.resize(ncols); colDeleted.resize(ncols, false); nfixings = 0; diff --git a/src/mip/HighsConflictPool.cpp b/src/mip/HighsConflictPool.cpp index 3c7f7cde3d..66721bd26e 100644 --- a/src/mip/HighsConflictPool.cpp +++ b/src/mip/HighsConflictPool.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsConflictPool.h b/src/mip/HighsConflictPool.h index 09be5db530..c7c3078c11 100644 --- a/src/mip/HighsConflictPool.h +++ b/src/mip/HighsConflictPool.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index ce6e475e5d..1417c777b7 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -10,9 +10,9 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsCutGeneration.h" +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsMipSolverData.h" #include "mip/HighsTransformedLp.h" -#include "pdqsort/pdqsort.h" #include "util/HighsIntegers.h" HighsCutGeneration::HighsCutGeneration(const HighsLpRelaxation& lpRelaxation, @@ -58,7 +58,6 @@ bool HighsCutGeneration::determineCover(bool lpSol) { coverweight += vals[j] * upper[j]; } - const auto& nodequeue = lpRelaxation.getMipSolver().mipdata_->nodequeue; // sort the remaining variables by the contribution to the rows activity in // the current solution pdqsort(cover.begin() + coversize, cover.begin() + maxCoverSize, @@ -1133,7 +1132,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, return false; // it can happen that there is an unbounded integer variable during the - // transform call so that the integers are not tranformed to positive values. + // transform call so that the integers are not transformed to positive values. // Now the call to preprocessBaseInequality may have removed the unbounded // integer, e.g. due to a small coefficient value, so that we can still use // the lifted inequalities instead of cmir. We need to make sure, however, @@ -1279,7 +1278,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, lpRelaxation.getMipSolver().mipdata_->debugSolution.checkCut(inds, vals, rowlen, rhs_); // apply cut postprocessing including scaling and removal of small - // coeffiicents + // coefficients if (!postprocessCut()) return false; rhs_ = (double)rhs; vals_.resize(rowlen); @@ -1498,7 +1497,7 @@ bool HighsCutGeneration::finalizeAndAddCut(std::vector& inds_, lpRelaxation.getMipSolver().mipdata_->debugSolution.checkCut(inds, vals, rowlen, rhs_); // apply cut postprocessing including scaling and removal of small - // coeffiicents + // coefficients if (!postprocessCut()) return false; rhs_ = (double)rhs; vals_.resize(rowlen); diff --git a/src/mip/HighsCutGeneration.h b/src/mip/HighsCutGeneration.h index 1e722cb7c4..cc18998c6d 100644 --- a/src/mip/HighsCutGeneration.h +++ b/src/mip/HighsCutGeneration.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsCutPool.cpp b/src/mip/HighsCutPool.cpp index 0f4d9e9289..c8a058986e 100644 --- a/src/mip/HighsCutPool.cpp +++ b/src/mip/HighsCutPool.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -14,10 +14,10 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsDomain.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolverData.h" -#include "pdqsort/pdqsort.h" #include "util/HighsCDouble.h" #include "util/HighsHash.h" @@ -301,7 +301,7 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, }) - efficacious_cuts.begin(); - HighsInt lowerThreshold = 0.05 * efficacious_cuts.size(); + HighsInt lowerThreshold = efficacious_cuts.size() / 20; HighsInt upperThreshold = efficacious_cuts.size() - 1; if (numefficacious <= lowerThreshold) { diff --git a/src/mip/HighsCutPool.h b/src/mip/HighsCutPool.h index 7e63677e0b..1bd97f552c 100644 --- a/src/mip/HighsCutPool.h +++ b/src/mip/HighsCutPool.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -136,7 +136,7 @@ class HighsCutPool { void separateLpCutsAfterRestart(HighsCutSet& cutset); - bool cutIsIntegral(HighsInt cut) const { return rowintegral[cut]; } + bool cutIsIntegral(HighsInt cut) const { return (rowintegral[cut] != 0); } HighsInt getNumCuts() const { return matrix_.getNumRows() - matrix_.getNumDelRows(); diff --git a/src/mip/HighsDebugSol.cpp b/src/mip/HighsDebugSol.cpp index 88352dcb82..2166058c62 100644 --- a/src/mip/HighsDebugSol.cpp +++ b/src/mip/HighsDebugSol.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -176,7 +176,7 @@ void HighsDebugSol::checkRowAggregation(const HighsLp& lp, dbgSol.dual_valid = false; dbgSol.value_valid = true; dbgSol.col_value = debugSolution; - calculateRowValues(lp, dbgSol); + calculateRowValuesQuad(lp, dbgSol); for (HighsInt i = 0; i < Rlen; ++i) { if (Rindex[i] < lp.num_col_) violation += dbgSol.col_value[Rindex[i]] * Rvalue[i]; diff --git a/src/mip/HighsDebugSol.h b/src/mip/HighsDebugSol.h index 65a8a9f85a..ddce632c45 100644 --- a/src/mip/HighsDebugSol.h +++ b/src/mip/HighsDebugSol.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -88,54 +88,47 @@ struct HighsDebugSol { #else struct HighsDebugSol { - HighsDebugSol(HighsMipSolver& mipsolver) {} + HighsDebugSol(HighsMipSolver&) {} void newIncumbentFound() const {} void activate() const {} - void shrink(const std::vector& newColIndex) const {} + void shrink(const std::vector&) const {} - void registerDomain(const HighsDomain& domain) const {} + void registerDomain(const HighsDomain&) const {} - void boundChangeAdded(const HighsDomain& domain, - const HighsDomainChange& domchg, - bool branching = false) const {} + void boundChangeAdded(const HighsDomain&, const HighsDomainChange&, + bool = false) const {} - void boundChangeRemoved(const HighsDomain& domain, - const HighsDomainChange& domchg) const {} + void boundChangeRemoved(const HighsDomain&, const HighsDomainChange&) const {} - void resetDomain(const HighsDomain& domain) const {} + void resetDomain(const HighsDomain&) const {} - void nodePruned(const HighsDomain& localdomain) const {} + void nodePruned(const HighsDomain&) const {} - void checkCut(const HighsInt* Rindex, const double* Rvalue, HighsInt Rlen, - double rhs) const {} + void checkCut(const HighsInt*, const double*, HighsInt, double) const {} - void checkRow(const HighsInt* Rindex, const double* Rvalue, HighsInt Rlen, - double Rlower, double Rupper) const {} + void checkRow(const HighsInt*, const double*, HighsInt, double, + double) const {} - void checkRowAggregation(const HighsLp& lp, const HighsInt* Rindex, - const double* Rvalue, HighsInt Rlen) const {} + void checkRowAggregation(const HighsLp&, const HighsInt*, const double*, + HighsInt) const {} - void checkClique(const HighsCliqueTable::CliqueVar* clq, - HighsInt clqlen) const {} + void checkClique(const HighsCliqueTable::CliqueVar*, HighsInt) const {} - void checkVub(HighsInt col, HighsInt vubcol, double vubcoef, - double vubconstant) const {} + void checkVub(HighsInt, HighsInt, double, double) const {} - void checkVlb(HighsInt col, HighsInt vlbcol, double vlbcoef, - double vlbconstant) const {} + void checkVlb(HighsInt, HighsInt, double, double) const {} void checkConflictReasonFrontier( - const std::set& reasonSideFrontier, - const std::vector& domchgstack) const {} + const std::set&, + const std::vector&) const {} void checkConflictReconvergenceFrontier( - const std::set& - reconvergenceFrontier, - const HighsDomain::ConflictSet::LocalDomChg& reconvDomchgPos, - const std::vector& domchgstack) const {} + const std::set&, + const HighsDomain::ConflictSet::LocalDomChg&, + const std::vector&) const {} }; #endif diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index 214ceda6ba..eb38717812 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -15,10 +15,10 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsConflictPool.h" #include "mip/HighsCutPool.h" #include "mip/HighsMipSolverData.h" -#include "pdqsort/pdqsort.h" static double activityContributionMin(double coef, const double& lb, const double& ub) { @@ -46,6 +46,29 @@ static double activityContributionMax(double coef, const double& lb, } } +static HighsCDouble computeDelta(double val, double oldbound, double newbound, + double inf, HighsInt& numinfs) { + if (oldbound == inf) { + --numinfs; + return static_cast(newbound) * val; + } else if (newbound == inf) { + ++numinfs; + return static_cast(-oldbound) * val; + } else { + return (static_cast(newbound) - + static_cast(oldbound)) * + val; + } +} + +static inline double boundRange(double upper_bound, double lower_bound, + double tolerance, HighsVarType var_type) { + double range = upper_bound - lower_bound; + return range - (var_type == HighsVarType::kContinuous + ? std::max(0.3 * range, 1000.0 * tolerance) + : tolerance); +} + HighsDomain::HighsDomain(HighsMipSolver& mipsolver) : mipsolver(&mipsolver) { col_lower_ = mipsolver.model_->col_lower_; col_upper_ = mipsolver.model_->col_upper_; @@ -150,7 +173,6 @@ void HighsDomain::ConflictPoolPropagation::conflictAdded(HighsInt conflict) { HighsInt numWatched = 0; for (HighsInt i = start; i != end; ++i) { if (domain->isActive(conflictEntries[i])) continue; - HighsInt col = conflictEntries[i].column; HighsInt watchPos = 2 * conflict + numWatched; watchedLiterals_[watchPos].domchg = conflictEntries[i]; linkWatchedLiteral(watchPos); @@ -231,15 +253,12 @@ void HighsDomain::ConflictPoolPropagation::updateActivityLbChange( HighsInt col, double oldbound, double newbound) { assert(!domain->infeasible_); - const std::vector& conflictEntries = - conflictpool_->getConflictEntryVector(); - for (HighsInt i = colLowerWatched_[col]; i != -1; i = watchedLiterals_[i].next) { HighsInt conflict = i >> 1; const HighsDomainChange& domchg = watchedLiterals_[i].domchg; - HighsInt numInactiveDelta = + uint8_t numInactiveDelta = (domchg.boundval > newbound) - (domchg.boundval > oldbound); if (numInactiveDelta != 0) { conflictFlag_[conflict] += numInactiveDelta; @@ -252,9 +271,6 @@ void HighsDomain::ConflictPoolPropagation::updateActivityUbChange( HighsInt col, double oldbound, double newbound) { assert(!domain->infeasible_); - const std::vector& conflictEntries = - conflictpool_->getConflictEntryVector(); - for (HighsInt i = colUpperWatched_[col]; i != -1; i = watchedLiterals_[i].next) { HighsInt conflict = i >> 1; @@ -291,7 +307,6 @@ void HighsDomain::ConflictPoolPropagation::propagateConflict( WatchedLiteral* watched = watchedLiterals_.data() + 2 * conflict; HighsInt inactive[2]; - HighsInt latestactive[2]; HighsInt numInactive = 0; for (HighsInt i = start; i != end; ++i) { if (domain->isActive(entries[i])) continue; @@ -377,14 +392,11 @@ void HighsDomain::CutpoolPropagation::recomputeCapacityThreshold(HighsInt cut) { if (domain->col_upper_[arindex[i]] == domain->col_lower_[arindex[i]]) continue; - double boundRange = - domain->col_upper_[arindex[i]] - domain->col_lower_[arindex[i]]; - - boundRange -= domain->variableType(arindex[i]) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * domain->feastol()) - : domain->feastol(); - - double threshold = std::fabs(arvalue[i]) * boundRange; + double threshold = + std::fabs(arvalue[i]) * boundRange(domain->col_upper_[arindex[i]], + domain->col_lower_[arindex[i]], + domain->feastol(), + domain->variableType(arindex[i])); capacityThreshold_[cut] = std::max({capacityThreshold_[cut], threshold, domain->feastol()}); @@ -469,17 +481,8 @@ void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, cutpool->getMatrix().forEachPositiveColumnEntry( col, [&](HighsInt row, double val) { assert(val > 0); - double deltamin; - - if (oldbound == -kHighsInf) { - --activitycutsinf_[row]; - deltamin = newbound * val; - } else if (newbound == -kHighsInf) { - ++activitycutsinf_[row]; - deltamin = -oldbound * val; - } else { - deltamin = (newbound - oldbound) * val; - } + HighsCDouble deltamin = computeDelta(val, oldbound, newbound, + -kHighsInf, activitycutsinf_[row]); activitycuts_[row] += deltamin; if (deltamin <= 0) { @@ -512,18 +515,8 @@ void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, cutpool->getMatrix().forEachPositiveColumnEntry( col, [&](HighsInt row, double val) { assert(val > 0); - double deltamin; - - if (oldbound == -kHighsInf) { - --activitycutsinf_[row]; - deltamin = newbound * val; - } else if (newbound == -kHighsInf) { - ++activitycutsinf_[row]; - deltamin = -oldbound * val; - } else { - deltamin = (newbound - oldbound) * val; - } - activitycuts_[row] += deltamin; + activitycuts_[row] += computeDelta(val, oldbound, newbound, + -kHighsInf, activitycutsinf_[row]); if (domain->infeasible_reason.index == row) return false; @@ -549,17 +542,8 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, cutpool->getMatrix().forEachNegativeColumnEntry( col, [&](HighsInt row, double val) { assert(val < 0); - double deltamin; - - if (oldbound == kHighsInf) { - --activitycutsinf_[row]; - deltamin = newbound * val; - } else if (newbound == kHighsInf) { - ++activitycutsinf_[row]; - deltamin = -oldbound * val; - } else { - deltamin = (newbound - oldbound) * val; - } + HighsCDouble deltamin = computeDelta(val, oldbound, newbound, kHighsInf, + activitycutsinf_[row]); activitycuts_[row] += deltamin; if (deltamin <= 0) { @@ -590,18 +574,8 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, cutpool->getMatrix().forEachNegativeColumnEntry( col, [&](HighsInt row, double val) { assert(val < 0); - double deltamin; - - if (oldbound == kHighsInf) { - --activitycutsinf_[row]; - deltamin = newbound * val; - } else if (newbound == kHighsInf) { - ++activitycutsinf_[row]; - deltamin = -oldbound * val; - } else { - deltamin = (newbound - oldbound) * val; - } - activitycuts_[row] += deltamin; + activitycuts_[row] += computeDelta(val, oldbound, newbound, kHighsInf, + activitycutsinf_[row]); if (domain->infeasible_reason.index == row) return false; @@ -712,10 +686,6 @@ HighsDomain::ObjectivePropagation::ObjectivePropagation(HighsDomain* domain) } } - double lb = numInfObjLower == 0 - ? double(objectiveLower) + domain->mipsolver->model_->offset_ - : -kHighsInf; - recomputeCapacityThreshold(); debugCheckObjectiveLower(); } @@ -792,12 +762,11 @@ void HighsDomain::ObjectivePropagation::recomputeCapacityThreshold() { for (HighsInt i = partitionStarts[numPartitions]; i < numObjNzs; ++i) { HighsInt col = objNonzeros[i]; - double boundRange = (domain->col_upper_[col] - domain->col_lower_[col]); - boundRange -= domain->variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * domain->feastol()) - : domain->feastol(); - capacityThreshold = - std::max(capacityThreshold, std::fabs(cost[col]) * boundRange); + capacityThreshold = std::max( + capacityThreshold, + std::fabs(cost[col]) * + boundRange(domain->col_upper_[col], domain->col_lower_[col], + domain->feastol(), domain->variableType(col))); } } @@ -805,11 +774,11 @@ void HighsDomain::ObjectivePropagation::updateActivityLbChange( HighsInt col, double oldbound, double newbound) { if (cost[col] <= 0.0) { if (cost[col] != 0.0 && newbound < oldbound) { - double boundRange = domain->col_upper_[col] - newbound; - boundRange -= domain->variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * domain->feastol()) - : domain->feastol(); - capacityThreshold = std::max(capacityThreshold, -cost[col] * boundRange); + capacityThreshold = + std::max(capacityThreshold, + -cost[col] * boundRange(domain->col_upper_[col], newbound, + domain->feastol(), + domain->variableType(col))); isPropagated = false; } debugCheckObjectiveLower(); @@ -833,11 +802,11 @@ void HighsDomain::ObjectivePropagation::updateActivityLbChange( debugCheckObjectiveLower(); if (newbound < oldbound) { - double boundRange = (domain->col_upper_[col] - domain->col_lower_[col]); - boundRange -= domain->variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * domain->feastol()) - : domain->feastol(); - capacityThreshold = std::max(capacityThreshold, cost[col] * boundRange); + capacityThreshold = std::max( + capacityThreshold, + cost[col] * boundRange(domain->col_upper_[col], + domain->col_lower_[col], domain->feastol(), + domain->variableType(col))); } else if (numInfObjLower == 0 && objectiveLower > domain->mipsolver->mipdata_->upper_limit) { domain->infeasible_ = true; @@ -867,7 +836,7 @@ void HighsDomain::ObjectivePropagation::updateActivityLbChange( objectiveLowerContributions[partitionPos].contribution; // update the capacity threshold with the difference of the new highest - // contribution position to the lowest consitribution as the column with + // contribution position to the lowest contribution as the column with // the lowest contribution can be fixed to its bound that yields the // highest objective value. HighsInt bestPos = contributionTree.last(); @@ -926,11 +895,10 @@ void HighsDomain::ObjectivePropagation::updateActivityUbChange( HighsInt col, double oldbound, double newbound) { if (cost[col] >= 0.0) { if (cost[col] != 0.0 && newbound > oldbound) { - double boundRange = newbound - domain->col_lower_[col]; - boundRange -= domain->variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * domain->feastol()) - : domain->feastol(); - capacityThreshold = std::max(capacityThreshold, cost[col] * boundRange); + capacityThreshold = std::max( + capacityThreshold, + cost[col] * boundRange(newbound, domain->col_lower_[col], + domain->feastol(), domain->variableType(col))); isPropagated = false; } debugCheckObjectiveLower(); @@ -954,11 +922,11 @@ void HighsDomain::ObjectivePropagation::updateActivityUbChange( debugCheckObjectiveLower(); if (newbound > oldbound) { - double boundRange = (domain->col_upper_[col] - domain->col_lower_[col]); - boundRange -= domain->variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * domain->feastol()) - : domain->feastol(); - capacityThreshold = std::max(capacityThreshold, -cost[col] * boundRange); + capacityThreshold = std::max( + capacityThreshold, + -cost[col] * boundRange(domain->col_upper_[col], + domain->col_lower_[col], domain->feastol(), + domain->variableType(col))); } else if (numInfObjLower == 0 && objectiveLower > domain->mipsolver->mipdata_->upper_limit) { domain->infeasible_ = true; @@ -988,7 +956,7 @@ void HighsDomain::ObjectivePropagation::updateActivityUbChange( objectiveLowerContributions[partitionPos].contribution; // update the capacity threshold with the difference of the new highest - // contribution position to the lowest consitribution as the column with + // contribution position to the lowest contribution as the column with // the lowest contribution can be fixed to its bound that yields the // highest objective value. HighsInt bestPos = contributionTree.last(); @@ -1001,7 +969,7 @@ void HighsDomain::ObjectivePropagation::updateActivityUbChange( // the new linked column could be the one with the new lowest // contribution so update the capacity threshold to ensure propagation // runs when it can be fixed to the bound that yields the highest - // objective valueu + // objective value capacityThreshold = std::max((oldContribution - objectiveLowerContributions[partitionPos].contribution) * @@ -1299,7 +1267,6 @@ void HighsDomain::computeMinActivity(HighsInt start, HighsInt end, activitymin += contributionmin; } - activitymin.renormalize(); } else { activitymin = 0.0; ninfmin = 0; @@ -1317,9 +1284,8 @@ void HighsDomain::computeMinActivity(HighsInt start, HighsInt end, else activitymin += contributionmin; } - - activitymin.renormalize(); } + activitymin.renormalize(); } void HighsDomain::computeMaxActivity(HighsInt start, HighsInt end, @@ -1345,8 +1311,6 @@ void HighsDomain::computeMaxActivity(HighsInt start, HighsInt end, else activitymax += contributionmin; } - - activitymax.renormalize(); } else { activitymax = 0.0; ninfmax = 0; @@ -1364,9 +1328,8 @@ void HighsDomain::computeMaxActivity(HighsInt start, HighsInt end, else activitymax += contributionmin; } - - activitymax.renormalize(); } + activitymax.renormalize(); } double HighsDomain::adjustedUb(HighsInt col, HighsCDouble boundVal, @@ -1374,13 +1337,10 @@ double HighsDomain::adjustedUb(HighsInt col, HighsCDouble boundVal, double bound; if (mipsolver->variableType(col) != HighsVarType::kContinuous) { - bound = std::floor(double(boundVal + mipsolver->mipdata_->feastol)); - if (bound < col_upper_[col] && - col_upper_[col] - bound > - 1000.0 * mipsolver->mipdata_->feastol * std::fabs(bound)) - accept = true; - else - accept = false; + bound = static_cast(floor(boundVal + mipsolver->mipdata_->feastol)); + accept = bound < col_upper_[col] && + col_upper_[col] - bound > + 1000.0 * mipsolver->mipdata_->feastol * std::fabs(bound); } else { if (std::fabs(double(boundVal) - col_lower_[col]) <= mipsolver->mipdata_->epsilon) @@ -1409,13 +1369,10 @@ double HighsDomain::adjustedLb(HighsInt col, HighsCDouble boundVal, double bound; if (mipsolver->variableType(col) != HighsVarType::kContinuous) { - bound = std::ceil(double(boundVal - mipsolver->mipdata_->feastol)); - if (bound > col_lower_[col] && - bound - col_lower_[col] > - 1000.0 * mipsolver->mipdata_->feastol * std::fabs(bound)) - accept = true; - else - accept = false; + bound = static_cast(ceil(boundVal - mipsolver->mipdata_->feastol)); + accept = bound > col_lower_[col] && + bound - col_lower_[col] > + 1000.0 * mipsolver->mipdata_->feastol * std::fabs(bound); } else { if (std::fabs(col_upper_[col] - double(boundVal)) <= mipsolver->mipdata_->epsilon) @@ -1529,14 +1486,10 @@ HighsInt HighsDomain::propagateRowLower(const HighsInt* Rindex, void HighsDomain::updateThresholdLbChange(HighsInt col, double newbound, double val, double& threshold) { if (newbound != col_upper_[col]) { - double boundRange = (col_upper_[col] - newbound); - - boundRange -= - variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * mipsolver->mipdata_->feastol) - : mipsolver->mipdata_->feastol; - - double thresholdNew = std::fabs(val) * boundRange; + double thresholdNew = + std::fabs(val) * boundRange(col_upper_[col], newbound, + mipsolver->mipdata_->feastol, + variableType(col)); // the new threshold is now the maximum of the new threshold and the current // one @@ -1548,14 +1501,10 @@ void HighsDomain::updateThresholdLbChange(HighsInt col, double newbound, void HighsDomain::updateThresholdUbChange(HighsInt col, double newbound, double val, double& threshold) { if (newbound != col_lower_[col]) { - double boundRange = (newbound - col_lower_[col]); - - boundRange -= - variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * mipsolver->mipdata_->feastol) - : mipsolver->mipdata_->feastol; - - double thresholdNew = std::fabs(val) * boundRange; + double thresholdNew = + std::fabs(val) * boundRange(newbound, col_lower_[col], + mipsolver->mipdata_->feastol, + variableType(col)); // the new threshold is now the maximum of the new threshold and the current // one @@ -1579,16 +1528,9 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamin; - if (oldbound == -kHighsInf) { - --activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == -kHighsInf) { - ++activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamin = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } + HighsCDouble deltamin = + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, -kHighsInf, + activitymininf_[mip->a_matrix_.index_[i]]); activitymin_[mip->a_matrix_.index_[i]] += deltamin; #ifndef NDEBUG @@ -1630,16 +1572,9 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) markPropagate(mip->a_matrix_.index_[i]); } else { - double deltamax; - if (oldbound == -kHighsInf) { - --activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == -kHighsInf) { - ++activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamax = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } + HighsCDouble deltamax = + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, -kHighsInf, + activitymaxinf_[mip->a_matrix_.index_[i]]); activitymax_[mip->a_matrix_.index_[i]] += deltamax; #ifndef NDEBUG @@ -1696,29 +1631,13 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, std::swap(oldbound, newbound); for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamin; - if (oldbound == -kHighsInf) { - --activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == -kHighsInf) { - ++activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamin = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } - activitymin_[mip->a_matrix_.index_[i]] += deltamin; + activitymin_[mip->a_matrix_.index_[i]] += + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, + -kHighsInf, activitymininf_[mip->a_matrix_.index_[i]]); } else { - double deltamax; - if (oldbound == -kHighsInf) { - --activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == -kHighsInf) { - ++activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamax = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } - activitymax_[mip->a_matrix_.index_[i]] += deltamax; + activitymax_[mip->a_matrix_.index_[i]] += + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, + -kHighsInf, activitymaxinf_[mip->a_matrix_.index_[i]]); } } @@ -1748,16 +1667,9 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamax; - if (oldbound == kHighsInf) { - --activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == kHighsInf) { - ++activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamax = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } + HighsCDouble deltamax = + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, kHighsInf, + activitymaxinf_[mip->a_matrix_.index_[i]]); activitymax_[mip->a_matrix_.index_[i]] += deltamax; #ifndef NDEBUG @@ -1802,17 +1714,9 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, // propagateinds_.push_back(mip->a_matrix_.index_[i]); } } else { - double deltamin; - if (oldbound == kHighsInf) { - --activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == kHighsInf) { - ++activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamin = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } - + HighsCDouble deltamin = + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, kHighsInf, + activitymininf_[mip->a_matrix_.index_[i]]); activitymin_[mip->a_matrix_.index_[i]] += deltamin; #ifndef NDEBUG @@ -1872,30 +1776,13 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, std::swap(oldbound, newbound); for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamax; - if (oldbound == kHighsInf) { - --activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == kHighsInf) { - ++activitymaxinf_[mip->a_matrix_.index_[i]]; - deltamax = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamax = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } - activitymax_[mip->a_matrix_.index_[i]] += deltamax; + activitymax_[mip->a_matrix_.index_[i]] += + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, + kHighsInf, activitymaxinf_[mip->a_matrix_.index_[i]]); } else { - double deltamin; - if (oldbound == kHighsInf) { - --activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = newbound * mip->a_matrix_.value_[i]; - } else if (newbound == kHighsInf) { - ++activitymininf_[mip->a_matrix_.index_[i]]; - deltamin = -oldbound * mip->a_matrix_.value_[i]; - } else { - deltamin = (newbound - oldbound) * mip->a_matrix_.value_[i]; - } - - activitymin_[mip->a_matrix_.index_[i]] += deltamin; + activitymin_[mip->a_matrix_.index_[i]] += + computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, + kHighsInf, activitymininf_[mip->a_matrix_.index_[i]]); } } @@ -1920,13 +1807,9 @@ void HighsDomain::recomputeCapacityThreshold(HighsInt row) { if (col_upper_[col] == col_lower_[col]) continue; - double boundRange = col_upper_[col] - col_lower_[col]; - - boundRange -= variableType(col) == HighsVarType::kContinuous - ? std::max(0.3 * boundRange, 1000.0 * feastol()) - : feastol(); - - double threshold = std::fabs(mipsolver->mipdata_->ARvalue_[i]) * boundRange; + double threshold = std::fabs(mipsolver->mipdata_->ARvalue_[i]) * + boundRange(col_upper_[col], col_lower_[col], feastol(), + variableType(col)); capacityThreshold_[row] = std::max({capacityThreshold_[row], threshold, feastol()}); @@ -2174,7 +2057,6 @@ void HighsDomain::setDomainChangeStack( domchgreason_.clear(); branchPos_.clear(); HighsInt stacksize = domchgstack.size(); - HighsInt nextBranchPos = -1; HighsInt k = 0; for (HighsInt branchPos : branchingPositions) { for (; k < branchPos; ++k) { @@ -2193,13 +2075,13 @@ void HighsDomain::setDomainChangeStack( if (k == stacksize) return; // For redundant branching bound changes we need to be more careful due to - // symmetry handling. If these boundchanges are redundant simply because the - // corresponding subtree was enumerated and hence the global bound updated, - // then we still need to keep their status as branching variables for - // computing correct stabilizers. - // They can, however, be safely dropped if they are either strictly - // redundant in the global domain, or if there is already a local bound - // change that makes the branching change redundant. + // symmetry handling. If these bound changes are redundant simply because + // the corresponding subtree was enumerated and hence the global bound + // updated, then we still need to keep their status as branching variables + // for computing correct stabilizers. They can, however, be safely dropped + // if they are either strictly redundant in the global domain, or if there + // is already a local bound change that makes the branching change + // redundant. if (domchgstack[k].boundtype == HighsBoundType::kLower) { if (domchgstack[k].boundval <= col_lower_[domchgstack[k].column]) { if (domchgstack[k].boundval < col_lower_[domchgstack[k].column]) @@ -2418,13 +2300,10 @@ bool HighsDomain::propagate() { if (!propagateinds_.empty()) { propagateinds.swap(propagateinds_); - HighsInt propnnz = 0; HighsInt numproprows = propagateinds.size(); for (HighsInt i = 0; i != numproprows; ++i) { HighsInt row = propagateinds[i]; propagateflags_[row] = 0; - propnnz += mipsolver->mipdata_->ARstart_[i + 1] - - mipsolver->mipdata_->ARstart_[i]; } if (!infeasible_) { @@ -2511,14 +2390,10 @@ bool HighsDomain::propagate() { if (!cutpoolprop.propagatecutinds_.empty()) { propagateinds.swap(cutpoolprop.propagatecutinds_); - HighsInt propnnz = 0; HighsInt numproprows = propagateinds.size(); - for (HighsInt i = 0; i != numproprows; ++i) { HighsInt cut = propagateinds[i]; cutpoolprop.propagatecutflags_[cut] &= 2; - propnnz += cutpoolprop.cutpool->getMatrix().getRowEnd(cut) - - cutpoolprop.cutpool->getMatrix().getRowStart(cut); } if (!infeasible_) { @@ -2646,8 +2521,8 @@ void HighsDomain::conflictAnalyzeReconvergence( proofinds, proofvals, prooflen, proofrhs, double(activitymin))) return; - if (conflictSet.resolvedDomainChanges.size() > - 100 + 0.3 * mipsolver->mipdata_->integral_cols.size()) + if (10 * conflictSet.resolvedDomainChanges.size() > + 1000 + 3 * mipsolver->mipdata_->integral_cols.size()) return; conflictSet.reconvergenceFrontier.insert( @@ -2851,7 +2726,7 @@ bool HighsDomain::ConflictSet::explainBoundChangeGeq( b0 += (1.0 - 10 * localdom.mipsolver->mipdata_->feastol); } else { // for a continuous variable we relax the bound by epsilon to - // accomodate for tiny rounding errors + // accommodate for tiny rounding errors if (domchg.domchg.boundtype == HighsBoundType::kLower) b0 -= localdom.mipsolver->mipdata_->epsilon; else @@ -2961,7 +2836,7 @@ bool HighsDomain::ConflictSet::explainBoundChangeLeq( b0 += (1.0 - 10 * localdom.mipsolver->mipdata_->feastol); } else { // for a continuous variable we relax the bound by epsilon to - // accomodate for tiny rounding errors + // accommodate for tiny rounding errors if (domchg.domchg.boundtype == HighsBoundType::kLower) b0 -= localdom.mipsolver->mipdata_->epsilon; else @@ -3008,8 +2883,8 @@ bool HighsDomain::ConflictSet::resolveLinearGeq(HighsCDouble M, double Mupper, if (covered < -localdom.feastol()) { // there is room for relaxing bounds / dropping unneeded bound changes // from the explanation - HighsInt numRelaxed = 0; - HighsInt numDropped = 0; + // HighsInt numRelaxed = 0; + // HighsInt numDropped = 0; for (HighsInt k = resolvedDomainChanges.size() - 1; k >= 0; --k) { ResolveCandidate& reasonDomchg = resolveBuffer[k]; LocalDomChg& locdomchg = resolvedDomainChanges[k]; @@ -3035,14 +2910,14 @@ bool HighsDomain::ConflictSet::resolveLinearGeq(HighsCDouble M, double Mupper, resolvedDomainChanges.resize(last); M -= reasonDomchg.delta; - ++numDropped; + // ++numDropped; } else { while (relaxLb <= localdom.prevboundval_[locdomchg.pos].first) locdomchg.pos = localdom.prevboundval_[locdomchg.pos].second; // bound can be relaxed M += vals[i] * (relaxLb - lb); - ++numRelaxed; + // ++numRelaxed; } covered = double(M - Mupper); @@ -3057,6 +2932,7 @@ bool HighsDomain::ConflictSet::resolveLinearGeq(HighsCDouble M, double Mupper, relaxUb = std::floor(relaxUb); if (relaxUb - ub <= localdom.feastol()) continue; + locdomchg.domchg.boundval = relaxUb; if (relaxUb - gub >= -localdom.mipsolver->mipdata_->epsilon) { @@ -3066,14 +2942,14 @@ bool HighsDomain::ConflictSet::resolveLinearGeq(HighsCDouble M, double Mupper, resolvedDomainChanges.resize(last); M -= reasonDomchg.delta; - ++numDropped; + // ++numDropped; } else { // bound can be relaxed while (relaxUb >= localdom.prevboundval_[locdomchg.pos].first) locdomchg.pos = localdom.prevboundval_[locdomchg.pos].second; M += vals[i] * (relaxUb - ub); - ++numRelaxed; + // ++numRelaxed; } covered = double(M - Mupper); @@ -3122,8 +2998,8 @@ bool HighsDomain::ConflictSet::resolveLinearLeq(HighsCDouble M, double Mlower, if (covered > localdom.feastol()) { // there is room for relaxing bounds / dropping unneeded bound changes // from the explanation - HighsInt numRelaxed = 0; - HighsInt numDropped = 0; + // HighsInt numRelaxed = 0; + // HighsInt numDropped = 0; for (HighsInt k = resolvedDomainChanges.size() - 1; k >= 0; --k) { ResolveCandidate& reasonDomchg = resolveBuffer[k]; LocalDomChg& locdomchg = resolvedDomainChanges[k]; @@ -3149,14 +3025,14 @@ bool HighsDomain::ConflictSet::resolveLinearLeq(HighsCDouble M, double Mlower, resolvedDomainChanges.resize(last); M -= reasonDomchg.delta; - ++numDropped; + // ++numDropped; } else { // bound can be relaxed while (relaxLb <= localdom.prevboundval_[locdomchg.pos].first) locdomchg.pos = localdom.prevboundval_[locdomchg.pos].second; M += vals[i] * (relaxLb - lb); - ++numRelaxed; + // ++numRelaxed; } covered = double(M - Mlower); @@ -3181,14 +3057,14 @@ bool HighsDomain::ConflictSet::resolveLinearLeq(HighsCDouble M, double Mlower, resolvedDomainChanges.resize(last); M -= reasonDomchg.delta; - ++numDropped; + // ++numDropped; } else { // bound can be relaxed while (relaxUb >= localdom.prevboundval_[locdomchg.pos].first) locdomchg.pos = localdom.prevboundval_[locdomchg.pos].second; M += vals[i] * (relaxUb - ub); - ++numRelaxed; + // ++numRelaxed; } covered = double(M - Mlower); @@ -3837,8 +3713,8 @@ void HighsDomain::ConflictSet::conflictAnalysis( locdomchg.domchg.column); } - if (resolvedDomainChanges.size() > - 100 + 0.3 * localdom.mipsolver->mipdata_->integral_cols.size()) + if (10 * resolvedDomainChanges.size() > + 1000 + 3 * localdom.mipsolver->mipdata_->integral_cols.size()) return; reasonSideFrontier.insert(resolvedDomainChanges.begin(), @@ -3911,8 +3787,8 @@ void HighsDomain::ConflictSet::conflictAnalysis( locdomchg.domchg.column); } - if (resolvedDomainChanges.size() > - 100 + 0.3 * localdom.mipsolver->mipdata_->integral_cols.size()) + if (10 * resolvedDomainChanges.size() > + 1000 + 3 * localdom.mipsolver->mipdata_->integral_cols.size()) return; reasonSideFrontier.insert(resolvedDomainChanges.begin(), diff --git a/src/mip/HighsDomain.h b/src/mip/HighsDomain.h index 70008b5f19..57ff4fe45c 100644 --- a/src/mip/HighsDomain.h +++ b/src/mip/HighsDomain.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -255,7 +255,14 @@ class HighsDomain { std::vector partitionCliqueData; - ObjectivePropagation() = default; + ObjectivePropagation() { + objFunc = nullptr; + cost = nullptr; + objectiveLower = 0.0; + numInfObjLower = 0; + capacityThreshold = 0.0; + isPropagated = false; + } ObjectivePropagation(HighsDomain* domain); bool isActive() const { return domain != nullptr; } @@ -351,6 +358,7 @@ class HighsDomain { conflictPoolPropagation(other.conflictPoolPropagation), infeasible_(other.infeasible_), infeasible_reason(other.infeasible_reason), + infeasible_pos(other.infeasible_pos), colLowerPos_(other.colLowerPos_), colUpperPos_(other.colUpperPos_), branchPos_(other.branchPos_), diff --git a/src/mip/HighsDomainChange.h b/src/mip/HighsDomainChange.h index 1bec80f396..69efc87cde 100644 --- a/src/mip/HighsDomainChange.h +++ b/src/mip/HighsDomainChange.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsDynamicRowMatrix.cpp b/src/mip/HighsDynamicRowMatrix.cpp index 18e2645425..3bbef0285c 100644 --- a/src/mip/HighsDynamicRowMatrix.cpp +++ b/src/mip/HighsDynamicRowMatrix.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsDynamicRowMatrix.h b/src/mip/HighsDynamicRowMatrix.h index 244febf25d..a0aed341ef 100644 --- a/src/mip/HighsDynamicRowMatrix.h +++ b/src/mip/HighsDynamicRowMatrix.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -42,7 +42,7 @@ class HighsDynamicRowMatrix { /// vector of column sizes - /// keep an ordered set ofof free spaces in the row arrays so that they can be + /// keep an ordered set of free spaces in the row arrays so that they can be /// reused efficiently std::set> freespaces_; @@ -52,7 +52,9 @@ class HighsDynamicRowMatrix { public: HighsDynamicRowMatrix(HighsInt ncols); - bool columnsLinked(HighsInt rowindex) const { return colsLinked[rowindex]; } + bool columnsLinked(HighsInt rowindex) const { + return (colsLinked[rowindex] != 0); + } void unlinkColumns(HighsInt rowindex); diff --git a/src/mip/HighsGFkSolve.cpp b/src/mip/HighsGFkSolve.cpp index 241c7368e7..ee14bba189 100644 --- a/src/mip/HighsGFkSolve.cpp +++ b/src/mip/HighsGFkSolve.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsGFkSolve.h b/src/mip/HighsGFkSolve.h index d9b1bb8d19..75519459de 100644 --- a/src/mip/HighsGFkSolve.h +++ b/src/mip/HighsGFkSolve.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -23,7 +23,7 @@ #include "lp_data/HConst.h" -// helper struct to compute the multipicative inverse by using fermats +// helper struct to compute the multiplicative inverse by using fermats // theorem and recursive repeated squaring. // Under the assumption that k is a small prime and an 32bit HighsInt is enough // to hold the number (k-1)^(k-2) good compilers should be able to optimize this @@ -39,7 +39,7 @@ struct HighsGFk; template <> struct HighsGFk<2> { static constexpr unsigned int powk(unsigned int a) { return a * a; } - static constexpr unsigned int inverse(unsigned int a) { return 1; } + static constexpr unsigned int inverse(unsigned int) { return 1; } }; template <> diff --git a/src/mip/HighsImplications.cpp b/src/mip/HighsImplications.cpp index e353dcf1e6..2c909d9f0d 100644 --- a/src/mip/HighsImplications.cpp +++ b/src/mip/HighsImplications.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -10,9 +10,9 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "mip/HighsImplications.h" +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsCliqueTable.h" #include "mip/HighsMipSolverData.h" -#include "pdqsort/pdqsort.h" bool HighsImplications::computeImplications(HighsInt col, bool val) { HighsDomain& globaldomain = mipsolver.mipdata_->domain; @@ -373,6 +373,10 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { void HighsImplications::addVUB(HighsInt col, HighsInt vubcol, double vubcoef, double vubconstant) { + // assume that VUBs do not have infinite coefficients and infinite constant + // terms since such VUBs effectively evaluate to NaN. + assert(std::abs(vubcoef) != kHighsInf || std::abs(vubconstant) != kHighsInf); + VarBound vub{vubcoef, vubconstant}; mipsolver.mipdata_->debugSolution.checkVub(col, vubcol, vubcoef, vubconstant); @@ -396,6 +400,10 @@ void HighsImplications::addVUB(HighsInt col, HighsInt vubcol, double vubcoef, void HighsImplications::addVLB(HighsInt col, HighsInt vlbcol, double vlbcoef, double vlbconstant) { + // assume that VLBs do not have infinite coefficients and infinite constant + // terms since such VLBs effectively evaluate to NaN. + assert(std::abs(vlbcoef) != kHighsInf || std::abs(vlbconstant) != kHighsInf); + VarBound vlb{vlbcoef, vlbconstant}; mipsolver.mipdata_->debugSolution.checkVlb(col, vlbcol, vlbcoef, vlbconstant); @@ -547,7 +555,8 @@ void HighsImplications::separateImpliedBounds( nextCleanupCall -= std::max(HighsInt{0}, numNewEntries); if (nextCleanupCall < 0) { - HighsInt oldNumEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); + // HighsInt oldNumEntries = + // mipsolver.mipdata_->cliquetable.getNumEntries(); mipsolver.mipdata_->cliquetable.runCliqueMerging(globaldomain); // printf("numEntries: %d, beforeMerging: %d\n", // mipsolver.mipdata_->cliquetable.getNumEntries(), oldNumEntries); diff --git a/src/mip/HighsImplications.h b/src/mip/HighsImplications.h index 69328a055b..ac66aaf771 100644 --- a/src/mip/HighsImplications.h +++ b/src/mip/HighsImplications.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -54,7 +54,7 @@ class HighsImplications { std::vector colsubstituted; HighsImplications(const HighsMipSolver& mipsolver) : mipsolver(mipsolver) { HighsInt numcol = mipsolver.numCol(); - implications.resize(2 * numcol); + implications.resize(2 * static_cast(numcol)); colsubstituted.resize(numcol); vubs.resize(numcol); vlbs.resize(numcol); @@ -69,7 +69,7 @@ class HighsImplications { implications.shrink_to_fit(); HighsInt numcol = mipsolver.numCol(); - implications.resize(2 * numcol); + implications.resize(2 * static_cast(numcol)); colsubstituted.resize(numcol); numImplications = 0; vubs.clear(); diff --git a/src/mip/HighsLpAggregator.cpp b/src/mip/HighsLpAggregator.cpp index 59e0cda0e0..432fa22220 100644 --- a/src/mip/HighsLpAggregator.cpp +++ b/src/mip/HighsLpAggregator.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsLpAggregator.h b/src/mip/HighsLpAggregator.h index 811d5a8e71..fc0485c51c 100644 --- a/src/mip/HighsLpAggregator.h +++ b/src/mip/HighsLpAggregator.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 7f3d683fad..0d50457416 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -20,6 +20,69 @@ #include "util/HighsCDouble.h" #include "util/HighsHash.h" +void HighsLpRelaxation::getCutPool(HighsInt& num_col, HighsInt& num_cut, + std::vector& cut_lower, + std::vector& cut_upper, + HighsSparseMatrix& cut_matrix) const { + // NB RESTORE reference + // const HighsLp& lp = lpsolver.getLp(); + HighsLp lp = lpsolver.getLp(); + num_col = lp.num_col_; + HighsInt num_lp_row = lp.num_row_; + HighsInt num_model_row = mipsolver.numRow(); + num_cut = num_lp_row - num_model_row; + cut_lower.resize(num_cut); + cut_upper.resize(num_cut); + // Get a map from row index to cut row index + std::vector cut_row_index; + cut_row_index.assign(num_lp_row, -1); + HighsInt cut_num = 0; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (lprows[iRow].origin != LpRow::Origin::kCutPool) continue; + cut_row_index[iRow] = cut_num; + cut_lower[cut_num] = lp.row_lower_[iRow]; + cut_upper[cut_num] = lp.row_upper_[iRow]; + cut_num++; + } + assert(cut_num == num_cut); + + cut_matrix.num_col_ = lp.num_col_; + cut_matrix.num_row_ = num_cut; + cut_matrix.format_ = MatrixFormat::kRowwise; + + std::vector cut_matrix_length; + cut_matrix_length.assign(num_cut, 0); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { + HighsInt iCut = cut_row_index[lp.a_matrix_.index_[iEl]]; + if (iCut >= 0) cut_matrix_length[iCut]++; + } + } + cut_matrix.start_.resize(num_cut + 1); + cut_matrix.start_[0] = 0; + HighsInt num_cut_nz = 0; + for (HighsInt iCut = 0; iCut < num_cut; iCut++) { + HighsInt length = cut_matrix_length[iCut]; + cut_matrix_length[iCut] = cut_matrix.start_[iCut]; + num_cut_nz += length; + cut_matrix.start_[iCut + 1] = num_cut_nz; + } + cut_matrix.index_.resize(num_cut_nz); + cut_matrix.value_.resize(num_cut_nz); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { + HighsInt iCut = cut_row_index[lp.a_matrix_.index_[iEl]]; + if (iCut >= 0) { + cut_matrix.index_[cut_matrix_length[iCut]] = iCol; + cut_matrix.value_[cut_matrix_length[iCut]] = lp.a_matrix_.value_[iEl]; + cut_matrix_length[iCut]++; + } + } + } +} + void HighsLpRelaxation::LpRow::get(const HighsMipSolver& mipsolver, HighsInt& len, const HighsInt*& inds, const double*& vals) const { @@ -52,7 +115,7 @@ bool HighsLpRelaxation::LpRow::isIntegral( case kCutPool: return mipsolver.mipdata_->cutpool.cutIsIntegral(index); case kModel: - return mipsolver.mipdata_->rowintegral[index]; + return (mipsolver.mipdata_->rowintegral[index] != 0); }; assert(false); @@ -296,7 +359,6 @@ void HighsLpRelaxation::computeBasicDegenerateDuals(double threshold, HighsInt iRow = row_ep.index[i]; const double lb = lp.row_lower_[iRow]; const double ub = lp.row_upper_[iRow]; - const double dual = solution.row_dual[iRow]; double val = sign * row_ep.array[iRow]; if (ub == lb || val > mipsolver.mipdata_->epsilon) { @@ -343,9 +405,9 @@ double HighsLpRelaxation::computeBestEstimate(const HighsPseudocost& ps) const { // fractionality. HighsCDouble increase = 0.0; - double offset = mipsolver.mipdata_->feastol * - std::max(std::abs(objective), 1.0) / - mipsolver.mipdata_->integral_cols.size(); + double offset = + mipsolver.mipdata_->feastol * std::max(std::abs(objective), 1.0) / + static_cast(mipsolver.mipdata_->integral_cols.size()); for (const std::pair& f : fractionalints) { increase += std::min(ps.getPseudocostUp(f.first, f.second, offset), @@ -1063,7 +1125,7 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { // HighsLogType::kWarning, // "LP failed to reliably determine infeasibility\n"); - // printf("error: unreliable infeasiblities, modelstatus = %" + // printf("error: unreliable infeasibilities, modelstatus = %" // HIGHSINT_FORMAT " (scaled // %" HIGHSINT_FORMAT ")\n", // (HighsInt)lpsolver.getModelStatus(), @@ -1255,14 +1317,28 @@ HighsLpRelaxation::Status HighsLpRelaxation::resolveLp(HighsDomain* domain) { std::vector roundsol = sol.col_value; for (const std::pair& fracint : fractionalints) { + // get column index HighsInt col = fracint.first; - + // round based on locks and sign of objective coefficient if (mipsolver.mipdata_->uplocks[col] == 0 && (mipsolver.colCost(col) < 0 || - mipsolver.mipdata_->downlocks[col] != 0)) - roundsol[col] = std::ceil(fracint.second); - else - roundsol[col] = std::floor(fracint.second); + mipsolver.mipdata_->downlocks[col] != 0)) { + // round up + roundsol[col] = std::min( + std::ceil(fracint.second - mipsolver.mipdata_->feastol), + lpsolver.getLp().col_upper_[col] == kHighsInf + ? kHighsInf + : std::floor(lpsolver.getLp().col_upper_[col] + + mipsolver.mipdata_->feastol)); + } else { + // round down + roundsol[col] = std::max( + std::floor(fracint.second + mipsolver.mipdata_->feastol), + lpsolver.getLp().col_lower_[col] == -kHighsInf + ? -kHighsInf + : std::ceil(lpsolver.getLp().col_lower_[col] - + mipsolver.mipdata_->feastol)); + } } const auto& cliquesubst = diff --git a/src/mip/HighsLpRelaxation.h b/src/mip/HighsLpRelaxation.h index 4377367e47..66fefcd061 100644 --- a/src/mip/HighsLpRelaxation.h +++ b/src/mip/HighsLpRelaxation.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -96,6 +96,11 @@ class HighsLpRelaxation { HighsLpRelaxation(const HighsLpRelaxation& other); + void getCutPool(HighsInt& num_col, HighsInt& num_cut, + std::vector& cut_lower, + std::vector& cut_upper, + HighsSparseMatrix& cut_matrix) const; + class Playground { friend class HighsLpRelaxation; HighsLpRelaxation* lp; diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index aaa103cacd..4724f1a33b 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -104,7 +104,8 @@ HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { modelstatus_ = HighsModelStatus::kNotset; - // std::cout << options_mip_->presolve << std::endl; + // Start the solve_clock for the timer that is local to the HighsMipSolver + // instance timer_.start(timer_.solve_clock); improving_solution_file_ = nullptr; if (!submip && options_mip_->mip_improving_solution_file != "") @@ -113,7 +114,16 @@ void HighsMipSolver::run() { mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); mipdata_->init(); - mipdata_->runPresolve(); + mipdata_->runPresolve(options_mip_->presolve_reduction_limit); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->runPresolve\n", + timer_.read(timer_.solve_clock)); + // Identify whether time limit has been reached (in presolve) + if (modelstatus_ == HighsModelStatus::kNotset && + timer_.read(timer_.solve_clock) >= options_mip_->time_limit) + modelstatus_ = HighsModelStatus::kTimeLimit; + if (modelstatus_ != HighsModelStatus::kNotset) { highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "Presolve: %s\n", @@ -121,16 +131,40 @@ void HighsMipSolver::run() { if (modelstatus_ == HighsModelStatus::kOptimal) { mipdata_->lower_bound = 0; mipdata_->upper_bound = 0; - mipdata_->transformNewIncumbent(std::vector()); + mipdata_->transformNewIntegerFeasibleSolution(std::vector()); + mipdata_->saveReportMipSolution(); } cleanupSolve(); return; } + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - reached mipdata_->runSetup()\n", + timer_.read(timer_.solve_clock)); mipdata_->runSetup(); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->runSetup()\n", + timer_.read(timer_.solve_clock)); restart: if (modelstatus_ == HighsModelStatus::kNotset) { + // Check limits have not been reached before evaluating root node + if (mipdata_->checkLimits()) { + cleanupSolve(); + return; + } + if (!submip) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - reached mipdata_->evaluateRootNode()\n", + timer_.read(timer_.solve_clock)); mipdata_->evaluateRootNode(); + if (!submip) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->evaluateRootNode()\n", + timer_.read(timer_.solve_clock)); // age 5 times to remove stored but never violated cuts after root // separation mipdata_->cutpool.performAging(); @@ -139,7 +173,7 @@ void HighsMipSolver::run() { mipdata_->cutpool.performAging(); mipdata_->cutpool.performAging(); } - if (mipdata_->nodequeue.empty()) { + if (mipdata_->nodequeue.empty() || mipdata_->checkLimits()) { cleanupSolve(); return; } @@ -173,7 +207,7 @@ void HighsMipSolver::run() { HighsInt iterlimit = 10 * std::max(mipdata_->lp.getAvgSolveIters(), mipdata_->avgrootlpiters); iterlimit = std::max({HighsInt{10000}, iterlimit, - HighsInt(1.5 * mipdata_->firstrootlpiters)}); + HighsInt((3 * mipdata_->firstrootlpiters) / 2)}); mipdata_->lp.setIterationLimit(iterlimit); @@ -235,7 +269,7 @@ void HighsMipSolver::run() { search.flushStatistics(); mipdata_->printDisplayLine(); - // printf("continue plunging due to good esitmate\n"); + // printf("continue plunging due to good estimate\n"); } search.openNodesToQueue(mipdata_->nodequeue); search.flushStatistics(); @@ -335,13 +369,19 @@ void HighsMipSolver::run() { lowerBoundLastCheck = mipdata_->lower_bound; } - int64_t minHugeTreeOffset = - (mipdata_->num_leaves - mipdata_->num_leaves_before_run) * 1e-3; - int64_t minHugeTreeEstim = HighsIntegers::nearestInteger( - activeIntegerRatio * (10 + minHugeTreeOffset) * - std::pow(1.5, nTreeRestarts)); + // Possibly prevent restart - necessary for debugging presolve + // errors: see #1553 + if (options_mip_->mip_allow_restart) { + int64_t minHugeTreeOffset = + (mipdata_->num_leaves - mipdata_->num_leaves_before_run) / 1000; + int64_t minHugeTreeEstim = HighsIntegers::nearestInteger( + activeIntegerRatio * (10 + minHugeTreeOffset) * + std::pow(1.5, nTreeRestarts)); - doRestart = numHugeTreeEstim >= minHugeTreeEstim; + doRestart = numHugeTreeEstim >= minHugeTreeEstim; + } else { + doRestart = false; + } } else { // count restart due to many fixings within the first 1000 nodes as // root restart @@ -614,10 +654,14 @@ void HighsMipSolver::cleanupSolve() { assert(modelstatus_ != HighsModelStatus::kNotset); } -void HighsMipSolver::runPresolve() { +void HighsMipSolver::runPresolve(const HighsInt presolve_reduction_limit) { + // Start the solve_clock for the timer that is local to the HighsMipSolver + // instance + assert(!timer_.running(timer_.solve_clock)); + timer_.start(timer_.solve_clock); mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); mipdata_->init(); - mipdata_->runPresolve(); + mipdata_->runPresolve(presolve_reduction_limit); } const HighsLp& HighsMipSolver::getPresolvedModel() const { @@ -631,3 +675,26 @@ HighsPresolveStatus HighsMipSolver::getPresolveStatus() const { presolve::HighsPostsolveStack HighsMipSolver::getPostsolveStack() const { return mipdata_->postSolveStack; } + +void HighsMipSolver::callbackGetCutPool() const { + assert(callback_->user_callback); + assert(callback_->callbackActive(kCallbackMipGetCutPool)); + HighsCallbackDataOut& data_out = callback_->data_out; + + std::vector cut_lower; + std::vector cut_upper; + HighsSparseMatrix cut_matrix; + + mipdata_->lp.getCutPool(data_out.cutpool_num_col, data_out.cutpool_num_cut, + cut_lower, cut_upper, cut_matrix); + + data_out.cutpool_num_nz = cut_matrix.numNz(); + data_out.cutpool_start = cut_matrix.start_.data(); + data_out.cutpool_index = cut_matrix.index_.data(); + data_out.cutpool_value = cut_matrix.value_.data(); + data_out.cutpool_lower = cut_lower.data(); + data_out.cutpool_upper = cut_upper.data(); + callback_->user_callback(kCallbackMipGetCutPool, "MIP cut pool", + &callback_->data_out, &callback_->data_in, + callback_->user_callback_data); +} diff --git a/src/mip/HighsMipSolver.h b/src/mip/HighsMipSolver.h index 46b2f63177..4114019ec7 100644 --- a/src/mip/HighsMipSolver.h +++ b/src/mip/HighsMipSolver.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -94,10 +94,12 @@ class HighsMipSolver { mutable HighsTimer timer_; void cleanupSolve(); - void runPresolve(); + void runPresolve(const HighsInt presolve_reduction_limit); const HighsLp& getPresolvedModel() const; HighsPresolveStatus getPresolveStatus() const; presolve::HighsPostsolveStack getPostsolveStack() const; + + void callbackGetCutPool() const; }; #endif diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 1e170a34c9..9dcd8129b6 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -13,11 +13,11 @@ #include // #include "lp_data/HighsLpUtils.h" +#include "../extern/pdqsort/pdqsort.h" #include "lp_data/HighsModelUtils.h" #include "mip/HighsPseudocost.h" #include "mip/HighsRedcostFixing.h" #include "parallel/HighsParallel.h" -#include "pdqsort/pdqsort.h" #include "presolve/HPresolve.h" #include "util/HighsIntegers.h" @@ -183,17 +183,17 @@ void HighsMipSolverData::finishSymmetryDetection( "No symmetry present\n\n"); } else if (symmetries.orbitopes.size() == 0) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, - "Found %" HIGHSINT_FORMAT " generators\n\n", + "Found %" HIGHSINT_FORMAT " generator(s)\n\n", symmetries.numGenerators); } else { if (symmetries.numPerms != 0) { - highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, - "Found %" HIGHSINT_FORMAT " generators and %" HIGHSINT_FORMAT - " full orbitope(s) acting on %" HIGHSINT_FORMAT - " columns\n\n", - symmetries.numPerms, (HighsInt)symmetries.orbitopes.size(), - (HighsInt)symmetries.columnToOrbitope.size()); + highsLogUser( + mipsolver.options_mip_->log_options, HighsLogType::kInfo, + "Found %" HIGHSINT_FORMAT " generator(s) and %" HIGHSINT_FORMAT + " full orbitope(s) acting on %" HIGHSINT_FORMAT " columns\n\n", + symmetries.numPerms, (HighsInt)symmetries.orbitopes.size(), + (HighsInt)symmetries.columnToOrbitope.size()); } else { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "Found %" HIGHSINT_FORMAT @@ -296,7 +296,7 @@ bool HighsMipSolverData::moreHeuristicsAllowed() const { // since heuristics help most in the beginning of the search, we want to // spent the time we have for heuristics in the first 80% of the tree // exploration. Additionally we want to spent the proportional effort - // of heuristics that is allowed in the the first 30% of tree exploration as + // of heuristics that is allowed in the first 30% of tree exploration as // fast as possible, which is why we have the max(0.3/0.8,...). // Hence, in the first 30% of the tree exploration we allow to spent all // effort available for heuristics in that part of the search as early as @@ -392,7 +392,7 @@ void HighsMipSolverData::init() { dispfreq = 100; } -void HighsMipSolverData::runPresolve() { +void HighsMipSolverData::runPresolve(const HighsInt presolve_reduction_limit) { #ifdef HIGHS_DEBUGSOL bool debugSolActive = false; std::swap(debugSolution.debugSolActive, debugSolActive); @@ -400,9 +400,13 @@ void HighsMipSolverData::runPresolve() { mipsolver.timer_.start(mipsolver.timer_.presolve_clock); presolve::HPresolve presolve; - presolve.setInput(mipsolver); - mipsolver.modelstatus_ = presolve.run(postSolveStack); - presolve_status = presolve.getPresolveStatus(); + if (!presolve.okSetInput(mipsolver, presolve_reduction_limit)) { + mipsolver.modelstatus_ = HighsModelStatus::kMemoryLimit; + presolve_status = HighsPresolveStatus::kOutOfMemory; + } else { + mipsolver.modelstatus_ = presolve.run(postSolveStack); + presolve_status = presolve.getPresolveStatus(); + } mipsolver.timer_.stop(mipsolver.timer_.presolve_clock); #ifdef HIGHS_DEBUGSOL @@ -454,6 +458,16 @@ void HighsMipSolverData::runSetup() { nodequeue.setOptimalityLimit(optimality_limit); } } + if (!mipsolver.submip && feasible && mipsolver.callback_->user_callback && + mipsolver.callback_->active[kCallbackMipSolution]) { + assert(!mipsolver.submip); + mipsolver.callback_->clearHighsCallbackDataOut(); + mipsolver.callback_->data_out.mip_solution = mipsolver.solution_.data(); + const bool interrupt = interruptFromCallbackWithData( + kCallbackMipSolution, mipsolver.solution_objective_, + "Feasible solution"); + assert(!interrupt); + } } if (mipsolver.numCol() == 0) addIncumbent(std::vector(), 0, 'P'); @@ -573,13 +587,26 @@ void HighsMipSolverData::runSetup() { for (HighsInt i = 0; i != mipsolver.numCol(); ++i) { switch (mipsolver.variableType(i)) { case HighsVarType::kContinuous: + if (domain.isFixed(i)) continue; continuous_cols.push_back(i); break; case HighsVarType::kImplicitInteger: + if (domain.isFixed(i)) continue; implint_cols.push_back(i); integral_cols.push_back(i); break; case HighsVarType::kInteger: + if (domain.isFixed(i)) { + if (std::abs(domain.col_lower_[i] - + std::floor(domain.col_lower_[i] + 0.5)) > feastol) { + // integer variable is fixed to a fractional value -> infeasible + mipsolver.modelstatus_ = HighsModelStatus::kInfeasible; + lower_bound = kHighsInf; + pruned_treeweight = 1.0; + return; + } + continue; + } integer_cols.push_back(i); integral_cols.push_back(i); maxTreeSizeLog2 += (HighsInt)std::ceil( @@ -595,8 +622,9 @@ void HighsMipSolverData::runSetup() { // and I would have used the logical to begin with. // // Hence any compiler warning can be ignored safely - numBin += ((mipsolver.model_->col_lower_[i] == 0.0) & - (mipsolver.model_->col_upper_[i] == 1.0)); + numBin += + (static_cast(mipsolver.model_->col_lower_[i] == 0.0) & + static_cast(mipsolver.model_->col_upper_[i] == 1.0)); break; case HighsVarType::kSemiContinuous: case HighsVarType::kSemiInteger: @@ -659,8 +687,9 @@ void HighsMipSolverData::runSetup() { "\n"); } -double HighsMipSolverData::transformNewIncumbent( - const std::vector& sol) { +double HighsMipSolverData::transformNewIntegerFeasibleSolution( + const std::vector& sol, + const bool possibly_store_as_new_incumbent) { HighsSolution solution; solution.col_value = sol; solution.value_valid = true; @@ -681,11 +710,12 @@ double HighsMipSolverData::transformNewIncumbent( double row_violation_ = 0; double integrality_violation_ = 0; - // obj is the actual objective of the MIP - including the offset, - // and independent of objective sense + // Compute to quad precision the objective function value of the MIP + // being solved - including the offset, and independent of objective + // sense // - // ToDO Give it a more meaningful name! - HighsCDouble obj = mipsolver.orig_model_->offset_; + HighsCDouble mipsolver_quad_precision_objective_value = + mipsolver.orig_model_->offset_; if (kAllowDeveloperAssert) assert((HighsInt)solution.col_value.size() == mipsolver.orig_model_->num_col_); @@ -695,7 +725,8 @@ double HighsMipSolverData::transformNewIncumbent( 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; + mipsolver_quad_precision_objective_value += + mipsolver.orig_model_->col_cost_[i] * value; if (mipsolver.orig_model_->integrality_[i] == HighsVarType::kInteger) { double intval = std::floor(value + 0.5); @@ -791,90 +822,110 @@ double HighsMipSolverData::transformNewIncumbent( goto try_again; } } - // store the solution as incumbent in the original space if there is no - // solution or if it is feasible - if (feasible) { - // if (!allow_try_again) - // printf("repaired solution with value %g\n", double(obj)); - // store - mipsolver.row_violation_ = row_violation_; - mipsolver.bound_violation_ = bound_violation_; - mipsolver.integrality_violation_ = integrality_violation_; - mipsolver.solution_ = std::move(solution.col_value); - mipsolver.solution_objective_ = double(obj); - } else { - bool currentFeasible = - mipsolver.solution_objective_ != kHighsInf && - mipsolver.bound_violation_ <= - mipsolver.options_mip_->mip_feasibility_tolerance && - mipsolver.integrality_violation_ <= - mipsolver.options_mip_->mip_feasibility_tolerance && - mipsolver.row_violation_ <= - mipsolver.options_mip_->mip_feasibility_tolerance; - // 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 + // Get a double precision version of the objective function value of + // the MIP being solved + const double mipsolver_objective_value = + double(mipsolver_quad_precision_objective_value); + // Possible MIP solution callback + if (!mipsolver.submip && feasible && mipsolver.callback_->user_callback && + mipsolver.callback_->active[kCallbackMipSolution]) { + mipsolver.callback_->clearHighsCallbackDataOut(); + mipsolver.callback_->data_out.mip_solution = solution.col_value.data(); + const bool interrupt = interruptFromCallbackWithData( + kCallbackMipSolution, mipsolver_objective_value, "Feasible solution"); + assert(!interrupt); + } + + if (possibly_store_as_new_incumbent) { + // Store the solution as incumbent in the original space if there + // is no solution or if it is feasible + if (feasible) { + // if (!allow_try_again) + // printf("repaired solution with value %g\n", + // mipsolver_objective_value); + // store mipsolver.row_violation_ = row_violation_; mipsolver.bound_violation_ = bound_violation_; mipsolver.integrality_violation_ = integrality_violation_; mipsolver.solution_ = std::move(solution.col_value); - mipsolver.solution_objective_ = double(obj); - } + mipsolver.solution_objective_ = mipsolver_objective_value; + } else { + bool currentFeasible = + mipsolver.solution_objective_ != kHighsInf && + mipsolver.bound_violation_ <= + mipsolver.options_mip_->mip_feasibility_tolerance && + mipsolver.integrality_violation_ <= + mipsolver.options_mip_->mip_feasibility_tolerance && + mipsolver.row_violation_ <= + mipsolver.options_mip_->mip_feasibility_tolerance; + // 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", + mipsolver_objective_value, 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); + } - // return infinity so that it is not used for bounding - return kHighsInf; - } + if (!currentFeasible) { + // if the current incumbent is non existent or also not feasible we + // still store the new one + mipsolver.row_violation_ = row_violation_; + mipsolver.bound_violation_ = bound_violation_; + mipsolver.integrality_violation_ = integrality_violation_; + mipsolver.solution_ = std::move(solution.col_value); + mipsolver.solution_objective_ = mipsolver_objective_value; + } + // return infinity so that it is not used for bounding + return kHighsInf; + } + } // return the objective value in the transformed space if (mipsolver.orig_model_->sense_ == ObjSense::kMaximize) - return -double(obj + mipsolver.model_->offset_); + return -double(mipsolver_quad_precision_objective_value + + mipsolver.model_->offset_); - return double(obj - mipsolver.model_->offset_); + return double(mipsolver_quad_precision_objective_value - + mipsolver.model_->offset_); } double HighsMipSolverData::percentageInactiveIntegers() const { @@ -944,7 +995,30 @@ void HighsMipSolverData::performRestart() { nodequeue.clear(); globalOrbits.reset(); - runPresolve(); + // Need to be able to set presolve reduction limit separately when + // restarting - so that bugs in presolve restart can be investigated + // independently (see #1553) + // + // However, when restarting, presolve is (naturally) applied to the + // presolved problem, so have to control the number of _further_ + // presolve reductions + // + // The number of further presolve reductions must be positive, + // otherwise the MIP solver cycles, hence + // restart_presolve_reduction_limit cannot be zero + // + // Although postSolveStack.numReductions() is size_t, it makes no + // sense to use presolve_reduction_limit when the number of + // reductions is vast + HighsInt num_reductions = HighsInt(postSolveStack.numReductions()); + HighsInt restart_presolve_reduction_limit = + mipsolver.options_mip_->restart_presolve_reduction_limit; + assert(restart_presolve_reduction_limit); + HighsInt further_presolve_reduction_limit = + restart_presolve_reduction_limit >= 0 + ? num_reductions + restart_presolve_reduction_limit + : -1; + runPresolve(further_presolve_reduction_limit); if (mipsolver.modelstatus_ != HighsModelStatus::kNotset) { // transform the objective limit to the current model @@ -953,7 +1027,8 @@ void HighsMipSolverData::performRestart() { if (mipsolver.modelstatus_ == HighsModelStatus::kOptimal) { mipsolver.mipdata_->upper_bound = 0; - mipsolver.mipdata_->transformNewIncumbent(std::vector()); + mipsolver.mipdata_->transformNewIntegerFeasibleSolution( + std::vector()); } else upper_bound -= mipsolver.model_->offset_; @@ -1006,8 +1081,30 @@ const std::vector& HighsMipSolverData::getSolution() const { bool HighsMipSolverData::addIncumbent(const std::vector& sol, double solobj, char source) { - if (solobj < upper_bound) { - solobj = transformNewIncumbent(sol); + const bool execute_mip_solution_callback = + !mipsolver.submip && + (mipsolver.callback_->user_callback + ? mipsolver.callback_->active[kCallbackMipSolution] + : false); + // Determine whether the potential new incumbent should be + // transformed + // + // Happens if solobj improves on the upper bound or the MIP solution + // callback is active + const bool possibly_store_as_new_incumbent = solobj < upper_bound; + const bool get_transformed_solution = + possibly_store_as_new_incumbent || execute_mip_solution_callback; + // Get the transformed objective and solution if required + const double transformed_solobj = + get_transformed_solution ? transformNewIntegerFeasibleSolution( + sol, possibly_store_as_new_incumbent) + : 0; + + if (possibly_store_as_new_incumbent) { + // #1463 use pre-computed transformed_solobj + solobj = transformed_solobj; + // solobj = transformNewIntegerFeasibleSolution(sol); + if (solobj >= upper_bound) return false; upper_bound = solobj; incumbent = sol; @@ -1106,10 +1203,10 @@ void HighsMipSolverData::printDisplayLine(char first) { // MIP logging method // // Note that if the original problem is a maximization, the cost - // coefficients are ngated so that the MIP solver only solves a + // coefficients are negated so that the MIP solver only solves a // minimization. Hence, in preparing to print the display line, the // dual bound (lb) is always less than the primal bound (ub). When - // printed, the sense of the optimizaiton is applied so that the + // printed, the sense of the optimization is applied so that the // values printed correspond to the original objective. // No point in computing all the logging values if logging is off @@ -1225,8 +1322,8 @@ void HighsMipSolverData::printDisplayLine(char first) { assert(mip_rel_gap == gap); // Possibly interrupt from MIP logging callback mipsolver.callback_->clearHighsCallbackDataOut(); - const bool interrupt = - interruptFromCallbackWithData(kCallbackMipLogging, "MIP logging"); + const bool interrupt = interruptFromCallbackWithData( + kCallbackMipLogging, mipsolver.solution_objective_, "MIP logging"); assert(!interrupt); } @@ -1434,7 +1531,8 @@ void HighsMipSolverData::evaluateRootNode() { rootlpsolobj = firstlpsolobj; removeFixedIndices(); - if (mipsolver.options_mip_->presolve != kHighsOffString) { + if (mipsolver.options_mip_->mip_allow_restart && + mipsolver.options_mip_->presolve != kHighsOffString) { double fixingRate = percentageInactiveIntegers(); if (fixingRate >= 10.0) { tg.cancel(); @@ -1573,13 +1671,16 @@ void HighsMipSolverData::evaluateRootNode() { } printDisplayLine(); + // Possible cut extraction callback + if (!mipsolver.submip && mipsolver.callback_->user_callback && + mipsolver.callback_->callbackActive(kCallbackMipGetCutPool)) + mipsolver.callbackGetCutPool(); if (checkLimits()) return; do { if (rootlpsol.empty()) break; if (upper_limit != kHighsInf && !moreHeuristicsAllowed()) break; - double oldLimit = upper_limit; heuristics.rootReducedCost(); heuristics.flushStatistics(); @@ -1658,7 +1759,7 @@ void HighsMipSolverData::evaluateRootNode() { printDisplayLine(); if (lower_bound <= upper_limit) { - if (!mipsolver.submip && + if (!mipsolver.submip && mipsolver.options_mip_->mip_allow_restart && mipsolver.options_mip_->presolve != kHighsOffString) { if (!analyticCenterComputed) finishAnalyticCenterComputation(tg); double fixingRate = percentageInactiveIntegers(); @@ -1698,6 +1799,7 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { if (!mipsolver.submip && mipsolver.callback_->user_callback) { mipsolver.callback_->clearHighsCallbackDataOut(); if (interruptFromCallbackWithData(kCallbackMipInterrupt, + mipsolver.solution_objective_, "MIP check limits")) { if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { highsLogDev(options.log_options, HighsLogType::kInfo, @@ -1768,6 +1870,8 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { return true; } + // const double time = mipsolver.timer_.read(mipsolver.timer_.solve_clock); + // printf("checkLimits: time = %g\n", time); if (mipsolver.timer_.read(mipsolver.timer_.solve_clock) >= options.time_limit) { if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { @@ -1820,24 +1924,13 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { if (mipsolver.submip) return; if (non_improving) return; - /* - printf( - "%7s dimension(%d, %d) " - "%4simproving solution: numImprovingSols = %4d; Limits (%11.4g, " - "%11.4g); Objective = %11.4g\n", - mipsolver.submip ? "Sub-MIP" : "MIP ", mipsolver.model_->num_col_, - mipsolver.model_->num_row_, non_improving ? "non-" : "", - int(numImprovingSols), new_upper_limit, upper_limit, - mipsolver.solution_objective_); - */ if (mipsolver.callback_->user_callback) { if (mipsolver.callback_->active[kCallbackMipImprovingSolution]) { mipsolver.callback_->clearHighsCallbackDataOut(); - mipsolver.callback_->data_out.objective_function_value = - mipsolver.solution_objective_; mipsolver.callback_->data_out.mip_solution = mipsolver.solution_.data(); const bool interrupt = interruptFromCallbackWithData( - kCallbackMipImprovingSolution, "Improving solution"); + kCallbackMipImprovingSolution, mipsolver.solution_objective_, + "Improving solution"); assert(!interrupt); } } @@ -1850,9 +1943,11 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { } FILE* file = mipsolver.improving_solution_file_; if (file) { - writeLpObjective(file, *(mipsolver.orig_model_), mipsolver.solution_); + writeLpObjective(file, mipsolver.options_mip_->log_options, + *(mipsolver.orig_model_), mipsolver.solution_); writePrimalSolution( - file, *(mipsolver.orig_model_), mipsolver.solution_, + file, mipsolver.options_mip_->log_options, *(mipsolver.orig_model_), + mipsolver.solution_, mipsolver.options_mip_->mip_improving_solution_report_sparse); } } @@ -1889,8 +1984,13 @@ void HighsMipSolverData::limitsToBounds(double& dual_bound, } } +// Interface to callbackAction, with mipsolver_objective_value since +// incumbent value (mipsolver.solution_objective_) is not right for +// callback_type = kCallbackMipSolution + bool HighsMipSolverData::interruptFromCallbackWithData( - const int callback_type, const std::string message) const { + const int callback_type, const double mipsolver_objective_value, + const std::string message) const { if (!mipsolver.callback_->callbackActive(callback_type)) return false; assert(!mipsolver.submip); @@ -1901,10 +2001,15 @@ bool HighsMipSolverData::interruptFromCallbackWithData( mipsolver.callback_->data_out.running_time = mipsolver.timer_.read(mipsolver.timer_.solve_clock); mipsolver.callback_->data_out.objective_function_value = - mipsolver.solution_objective_; + mipsolver_objective_value; mipsolver.callback_->data_out.mip_node_count = mipsolver.mipdata_->num_nodes; + mipsolver.callback_->data_out.mip_total_lp_iterations = + mipsolver.mipdata_->total_lp_iterations; mipsolver.callback_->data_out.mip_primal_bound = primal_bound; mipsolver.callback_->data_out.mip_dual_bound = dual_bound; - mipsolver.callback_->data_out.mip_gap = mip_rel_gap; + // Option mip_rel_gap, and mip_gap in HighsInfo, are both fractions, + // whereas mip_rel_gap in logging output (mimicked by + // limitsToBounds) gives a percentage, so convert it a fraction + mipsolver.callback_->data_out.mip_gap = 1e-2 * mip_rel_gap; return mipsolver.callback_->callbackAction(callback_type, message); } diff --git a/src/mip/HighsMipSolverData.h b/src/mip/HighsMipSolverData.h index 8221c20f66..cbf697fcfc 100644 --- a/src/mip/HighsMipSolverData.h +++ b/src/mip/HighsMipSolverData.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -128,6 +128,46 @@ struct HighsMipSolverData { implications(mipsolver), heuristics(mipsolver), objectiveFunction(mipsolver), + presolve_status(HighsPresolveStatus::kNotSet), + cliquesExtracted(false), + rowMatrixSet(false), + analyticCenterComputed(false), + analyticCenterStatus(HighsModelStatus::kNotset), + detectSymmetries(false), + numRestarts(0), + numRestartsRoot(0), + numCliqueEntriesAfterPresolve(0), + numCliqueEntriesAfterFirstPresolve(0), + feastol(0.0), + epsilon(0.0), + heuristic_effort(0.0), + dispfreq(0), + firstlpsolobj(-kHighsInf), + rootlpsolobj(-kHighsInf), + numintegercols(0), + maxTreeSizeLog2(0), + pruned_treeweight(0), + avgrootlpiters(0.0), + last_disptime(0.0), + firstrootlpiters(0), + num_nodes(0), + num_leaves(0), + num_leaves_before_run(0), + num_nodes_before_run(0), + total_lp_iterations(0), + heuristic_lp_iterations(0), + sepa_lp_iterations(0), + sb_lp_iterations(0), + total_lp_iterations_before_run(0), + heuristic_lp_iterations_before_run(0), + sepa_lp_iterations_before_run(0), + sb_lp_iterations_before_run(0), + num_disp_lines(0), + numImprovingSols(0), + lower_bound(-kHighsInf), + upper_bound(kHighsInf), + upper_limit(kHighsInf), + optimality_limit(kHighsInf), debugSolution(mipsolver) { domain.addCutpool(cutpool); domain.addConflictPool(conflictPool); @@ -156,11 +196,13 @@ struct HighsMipSolverData { void init(); void basisTransfer(); void checkObjIntegrality(); - void runPresolve(); + void runPresolve(const HighsInt presolve_reduction_limit); void setupDomainPropagation(); - void saveReportMipSolution(const double new_upper_limit); + void saveReportMipSolution(const double new_upper_limit = -kHighsInf); void runSetup(); - double transformNewIncumbent(const std::vector& sol); + double transformNewIntegerFeasibleSolution( + const std::vector& sol, + const bool possibly_store_as_new_incumbent = true); double percentageInactiveIntegers() const; void performRestart(); bool checkSolution(const std::vector& solution) const; @@ -187,6 +229,7 @@ struct HighsMipSolverData { void limitsToBounds(double& dual_bound, double& primal_bound, double& mip_rel_gap) const; bool interruptFromCallbackWithData(const int callback_type, + const double mipsolver_objective_value, const std::string message = "") const; }; diff --git a/src/mip/HighsModkSeparator.cpp b/src/mip/HighsModkSeparator.cpp index 390cda504b..96be0c732a 100644 --- a/src/mip/HighsModkSeparator.cpp +++ b/src/mip/HighsModkSeparator.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -15,13 +15,13 @@ #include +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsCutGeneration.h" #include "mip/HighsGFkSolve.h" #include "mip/HighsLpAggregator.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolverData.h" #include "mip/HighsTransformedLp.h" -#include "pdqsort/pdqsort.h" #include "util/HighsHash.h" #include "util/HighsIntegers.h" diff --git a/src/mip/HighsModkSeparator.h b/src/mip/HighsModkSeparator.h index b899d4a3f6..8e6eb88bac 100644 --- a/src/mip/HighsModkSeparator.h +++ b/src/mip/HighsModkSeparator.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -20,7 +20,7 @@ * cut. * * If a row contains continuous variables that sit at zero after bound - * substitution, then those rows are included in the congurence system, as the + * substitution, then those rows are included in the congruence system, as the * presence of such variables does not reduce the cuts violation when applying * the MIR procedure. In order to handle their presence the row must simply be * scaled, such that all integer variables that have a non-zero solution value diff --git a/src/mip/HighsNodeQueue.cpp b/src/mip/HighsNodeQueue.cpp index fa762b8031..2f2bea0346 100644 --- a/src/mip/HighsNodeQueue.cpp +++ b/src/mip/HighsNodeQueue.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsNodeQueue.h b/src/mip/HighsNodeQueue.h index 3417310d85..8dd3e4a86d 100644 --- a/src/mip/HighsNodeQueue.h +++ b/src/mip/HighsNodeQueue.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -106,8 +106,8 @@ class HighsNodeQueue { reinterpret_cast(state->freeListHead)->next; } else { ptr = reinterpret_cast(state->currChunkStart); - state->currChunkStart += sizeof(FreelistNode); - if (state->currChunkStart > state->currChunkEnd) { + if (!ptr || state->currChunkStart + sizeof(FreelistNode) > + state->currChunkEnd) { auto newChunk = new Chunk; newChunk->next = state->chunkListHead; state->chunkListHead = newChunk; @@ -116,6 +116,8 @@ class HighsNodeQueue { state->currChunkStart + sizeof(newChunk->storage); ptr = reinterpret_cast(state->currChunkStart); state->currChunkStart += sizeof(FreelistNode); + } else { + state->currChunkStart += sizeof(FreelistNode); } } return ptr; diff --git a/src/mip/HighsObjectiveFunction.cpp b/src/mip/HighsObjectiveFunction.cpp index 3e396a7033..09c288cb3c 100644 --- a/src/mip/HighsObjectiveFunction.cpp +++ b/src/mip/HighsObjectiveFunction.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -13,11 +13,11 @@ #include +#include "../extern/pdqsort/pdqsort.h" #include "lp_data/HighsLp.h" #include "mip/HighsCliqueTable.h" #include "mip/HighsDomain.h" #include "mip/HighsMipSolverData.h" -#include "pdqsort/pdqsort.h" #include "util/HighsIntegers.h" HighsObjectiveFunction::HighsObjectiveFunction(const HighsMipSolver& mipsolver) diff --git a/src/mip/HighsObjectiveFunction.h b/src/mip/HighsObjectiveFunction.h index 78bcaf0757..7ba61c6d07 100644 --- a/src/mip/HighsObjectiveFunction.h +++ b/src/mip/HighsObjectiveFunction.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsPathSeparator.cpp b/src/mip/HighsPathSeparator.cpp index 2923fa2d70..83ab962a3d 100644 --- a/src/mip/HighsPathSeparator.cpp +++ b/src/mip/HighsPathSeparator.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -80,33 +80,33 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, // rows so that we can always substitute such columns away using this equation // and block the equation from being used as a start row for (HighsInt i = 0; i != lp.num_row_; ++i) { - if (rowtype[i] == RowType::kEq && numContinuous[i] == 1) { - HighsInt len; - const HighsInt* rowinds; - const double* rowvals; - - lpRelaxation.getRow(i, len, rowinds, rowvals); - - HighsInt j; - for (j = 0; j != len; ++j) { - if (mip.variableType(rowinds[j]) != HighsVarType::kContinuous) continue; - if (transLp.boundDistance(rowinds[j]) == 0.0) continue; - - break; - } - - HighsInt col = rowinds[j]; - double val = rowvals[j]; + if (rowtype[i] != RowType::kEq || numContinuous[i] != 1) continue; + + HighsInt len; + const HighsInt* rowinds; + const double* rowvals; + lpRelaxation.getRow(i, len, rowinds, rowvals); + + // find continuous variable + HighsInt col = -1; + double val = 0.0; + for (HighsInt j = 0; j != len; ++j) { + if (mip.variableType(rowinds[j]) != HighsVarType::kContinuous) continue; + if (transLp.boundDistance(rowinds[j]) == 0.0) continue; + col = rowinds[j]; + val = rowvals[j]; + break; + } - assert(mip.variableType(rowinds[j]) == HighsVarType::kContinuous); - assert(transLp.boundDistance(col) > 0.0); + assert(col != -1); + assert(mip.variableType(col) == HighsVarType::kContinuous); + assert(transLp.boundDistance(col) > 0.0); - if (colSubstitutions[col].first != -1) continue; + if (colSubstitutions[col].first != -1) continue; - colSubstitutions[col].first = i; - colSubstitutions[col].second = val; - rowtype[i] = RowType::kUnusuable; - } + colSubstitutions[col].first = i; + colSubstitutions[col].second = val; + rowtype[i] = RowType::kUnusuable; } // for each continuous variable with nonzero transformed solution value @@ -224,6 +224,78 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, return w <= maxWeight && w >= minWeight; }; + auto skipCol = + [&](const HighsInt& col, + const std::vector>& colArcs, + const std::vector>& arcRows, + const std::vector>& otherColArcs, + const std::vector>& otherArcRows) { + if (currPathLen == 1 && !tryNegatedScale) { + if (colArcs[col].second - colArcs[col].first <= currPathLen) { + for (HighsInt k = colArcs[col].first; k < colArcs[col].second; + ++k) { + if (arcRows[k].first != i) { + tryNegatedScale = true; + break; + } + } + } else + tryNegatedScale = true; + } + + if (otherColArcs[col].first == otherColArcs[col].second) + return true; + if (otherColArcs[col].second - otherColArcs[col].first <= + currPathLen) { + for (HighsInt k = otherColArcs[col].first; + k < otherColArcs[col].second; ++k) { + if (!isRowInCurrentPath(otherArcRows[k].first)) return false; + } + return true; + } + return false; + }; + + auto findRow = + [&](const HighsInt& bestArcCol, const double& val, + const std::vector>& colArcs, + const std::vector>& arcRows, + HighsInt& row, double& weight) { + HighsInt arcRow = randgen.integer(colArcs[bestArcCol].first, + colArcs[bestArcCol].second); + HighsInt r = arcRows[arcRow].first; + double w = -val / arcRows[arcRow].second; + if (!isRowInCurrentPath(r) && checkWeight(w)) { + row = r; + weight = w; + return true; + } + + for (HighsInt nextRow = arcRow + 1; + nextRow < colArcs[bestArcCol].second; ++nextRow) { + r = arcRows[nextRow].first; + w = -val / arcRows[nextRow].second; + if (!isRowInCurrentPath(r) && checkWeight(w)) { + row = r; + weight = w; + return true; + } + } + + for (HighsInt nextRow = colArcs[bestArcCol].first; nextRow < arcRow; + ++nextRow) { + r = arcRows[nextRow].first; + w = -val / arcRows[nextRow].second; + if (!isRowInCurrentPath(r) && checkWeight(w)) { + row = r; + weight = w; + return true; + } + } + + return false; + }; + aggregatedPath.clear(); while (currPathLen != maxPathLen) { @@ -255,33 +327,9 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, if (addedSubstitutionRows) continue; if (baseRowVals[j] < 0) { - if (currPathLen == 1 && !tryNegatedScale) { - if (colOutArcs[col].second - colOutArcs[col].first <= - currPathLen) { - for (HighsInt k = colOutArcs[col].first; - k < colOutArcs[col].second; ++k) { - if (outArcRows[k].first != i) { - tryNegatedScale = true; - break; - } - } - } else - tryNegatedScale = true; - } + if (skipCol(col, colOutArcs, outArcRows, colInArcs, inArcRows)) + continue; - if (colInArcs[col].first == colInArcs[col].second) continue; - if (colInArcs[col].second - colInArcs[col].first <= currPathLen) { - bool haveRow = false; - for (HighsInt k = colInArcs[col].first; k < colInArcs[col].second; - ++k) { - if (!isRowInCurrentPath(inArcRows[k].first)) { - haveRow = true; - break; - } - } - - if (!haveRow) continue; - } if (bestOutArcCol == -1 || transLp.boundDistance(col) > outArcColBoundDist) { bestOutArcCol = col; @@ -289,32 +337,9 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, outArcColBoundDist = transLp.boundDistance(col); } } else { - if (currPathLen == 1 && !tryNegatedScale) { - if (colInArcs[col].second - colInArcs[col].first <= currPathLen) { - for (HighsInt k = colInArcs[col].first; - k < colInArcs[col].second; ++k) { - if (inArcRows[k].first != i) { - tryNegatedScale = true; - break; - } - } - } else - tryNegatedScale = true; - } - - if (colOutArcs[col].first == colOutArcs[col].second) continue; - if (colOutArcs[col].second - colOutArcs[col].first <= currPathLen) { - bool haveRow = false; - for (HighsInt k = colOutArcs[col].first; - k < colOutArcs[col].second; ++k) { - if (!isRowInCurrentPath(outArcRows[k].first)) { - haveRow = true; - break; - } - } + if (skipCol(col, colInArcs, inArcRows, colOutArcs, outArcRows)) + continue; - if (!haveRow) continue; - } if (bestInArcCol == -1 || transLp.boundDistance(col) > inArcColBoundDist) { bestInArcCol = col; @@ -341,77 +366,30 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, if (success || (bestOutArcCol == -1 && bestInArcCol == -1)) break; // we prefer to use an out edge if the bound distances are equal in - // feasibility tolerance otherwise we choose an inArc. This tie breaking - // is arbitrary, but we should direct the substitution to prefer one - // direction to increase diversity. + // feasibility tolerance otherwise we choose an inArc. This tie + // breaking is arbitrary, but we should direct the substitution to + // prefer one direction to increase diversity. + HighsInt row = -1; + double weight = 0.0; if (bestInArcCol == -1 || (bestOutArcCol != -1 && outArcColBoundDist >= inArcColBoundDist - mip.mipdata_->feastol)) { - HighsInt inArcRow = randgen.integer(colInArcs[bestOutArcCol].first, - colInArcs[bestOutArcCol].second); - - HighsInt row = inArcRows[inArcRow].first; - double weight = -outArcColVal / inArcRows[inArcRow].second; - - if (isRowInCurrentPath(row) || !checkWeight(weight)) { - bool foundRow = false; - for (HighsInt nextRow = inArcRow + 1; - nextRow < colInArcs[bestOutArcCol].second && !foundRow; - ++nextRow) { - row = inArcRows[nextRow].first; - weight = -outArcColVal / inArcRows[nextRow].second; - foundRow = !isRowInCurrentPath(row) && checkWeight(weight); - } - - for (HighsInt nextRow = colInArcs[bestOutArcCol].first; - nextRow < inArcRow && !foundRow; ++nextRow) { - row = inArcRows[nextRow].first; - weight = -outArcColVal / inArcRows[nextRow].second; - foundRow = !isRowInCurrentPath(row) && checkWeight(weight); - } - - if (!foundRow) { - if (bestInArcCol == -1) - break; - else - goto check_out_arc_col; - } + if (!findRow(bestOutArcCol, outArcColVal, colInArcs, inArcRows, row, + weight)) { + if (bestInArcCol == -1) + break; + else if (!findRow(bestInArcCol, inArcColVal, colOutArcs, outArcRows, + row, weight)) + break; } - - currentPath[currPathLen] = row; - lpAggregator.addRow(row, weight); } else { - check_out_arc_col: - HighsInt outArcRow = randgen.integer(colOutArcs[bestInArcCol].first, - colOutArcs[bestInArcCol].second); - - HighsInt row = outArcRows[outArcRow].first; - double weight = -inArcColVal / outArcRows[outArcRow].second; - - if (isRowInCurrentPath(row) || !checkWeight(weight)) { - bool foundRow = false; - for (HighsInt nextRow = outArcRow + 1; - nextRow < colOutArcs[bestInArcCol].second && !foundRow; - ++nextRow) { - row = outArcRows[nextRow].first; - weight = -inArcColVal / outArcRows[nextRow].second; - foundRow = !isRowInCurrentPath(row) && checkWeight(weight); - } - - for (HighsInt nextRow = colOutArcs[bestInArcCol].first; - nextRow < outArcRow && !foundRow; ++nextRow) { - row = outArcRows[nextRow].first; - weight = -inArcColVal / outArcRows[nextRow].second; - foundRow = !isRowInCurrentPath(row) && checkWeight(weight); - } - - if (!foundRow) break; - } - - currentPath[currPathLen] = row; - lpAggregator.addRow(row, weight); + if (!findRow(bestInArcCol, inArcColVal, colOutArcs, outArcRows, row, + weight)) + break; } + currentPath[currPathLen] = row; + lpAggregator.addRow(row, weight); ++currPathLen; } diff --git a/src/mip/HighsPathSeparator.h b/src/mip/HighsPathSeparator.h index 5e54c47d35..5d2c9947fe 100644 --- a/src/mip/HighsPathSeparator.h +++ b/src/mip/HighsPathSeparator.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -10,7 +10,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file mip/HighsPathSeparator.h * @brief Class for separating cuts from heuristically aggregating rows from the - * LP to indetify path's in a network + * LP to identify path's in a network * */ diff --git a/src/mip/HighsPrimalHeuristics.cpp b/src/mip/HighsPrimalHeuristics.cpp index 94b4cf885e..db39f498d0 100644 --- a/src/mip/HighsPrimalHeuristics.cpp +++ b/src/mip/HighsPrimalHeuristics.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -13,6 +13,7 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "io/HighsIO.h" #include "lp_data/HConst.h" #include "lp_data/HighsLpUtils.h" @@ -20,7 +21,6 @@ #include "mip/HighsDomainChange.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolverData.h" -#include "pdqsort/pdqsort.h" #include "util/HighsHash.h" #include "util/HighsIntegers.h" @@ -226,7 +226,9 @@ class HeuristicNeighbourhood { if (localdom.isFixed(col)) fixedCols.insert(col); } - return numTotal ? fixedCols.size() / (double)numTotal : 0.0; + return numTotal ? static_cast(fixedCols.size()) / + static_cast(numTotal) + : 0.0; } void backtracked() { @@ -238,7 +240,7 @@ class HeuristicNeighbourhood { void HighsPrimalHeuristics::rootReducedCost() { std::vector> lurkingBounds = mipsolver.mipdata_->redcostfixing.getLurkingBounds(mipsolver); - if (lurkingBounds.size() < 0.1 * mipsolver.mipdata_->integral_cols.size()) + if (10 * lurkingBounds.size() < mipsolver.mipdata_->integral_cols.size()) return; pdqsort(lurkingBounds.begin(), lurkingBounds.end(), [](const std::pair& a, @@ -290,7 +292,7 @@ void HighsPrimalHeuristics::rootReducedCost() { localdom.col_lower_, localdom.col_upper_, 500, // std::max(50, int(0.05 * // (mipsolver.mipdata_->num_leaves))), - 200 + int(0.05 * (mipsolver.mipdata_->num_nodes)), 12); + 200 + mipsolver.mipdata_->num_nodes / 20, 12); } void HighsPrimalHeuristics::RENS(const std::vector& tmp) { @@ -512,7 +514,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { localdom.col_lower_, localdom.col_upper_, 500, // std::max(50, int(0.05 * // (mipsolver.mipdata_->num_leaves))), - 200 + int(0.05 * (mipsolver.mipdata_->num_nodes)), 12)) { + 200 + mipsolver.mipdata_->num_nodes / 20, 12)) { int64_t new_lp_iterations = lp_iterations + heur.getLocalLpIterations(); if (new_lp_iterations + mipsolver.mipdata_->heuristic_lp_iterations > 100000 + ((mipsolver.mipdata_->total_lp_iterations - @@ -529,7 +531,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { return; } maxfixingrate = fixingrate * 0.5; - // printf("infeasible in in root node, trying with lower fixing rate %g\n", + // printf("infeasible in root node, trying with lower fixing rate %g\n", // maxfixingrate); goto retry; } @@ -633,7 +635,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // reinforce direction of this solution away from root // solution if the change is at least 0.4 // otherwise take the direction where the objective gets worse - // if objcetive is zero round to nearest integer + // if objective is zero round to nearest integer double rootchange = fracval - mipsolver.mipdata_->rootlpsol[col]; if (rootchange >= 0.4) fixval = std::ceil(fracval); @@ -667,7 +669,6 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { if (std::abs(currlpsol[i] - mipsolver.mipdata_->incumbent[i]) <= mipsolver.mipdata_->feastol) { double fixval = HighsIntegers::nearestInteger(currlpsol[i]); - HighsInt oldNumBranched = numBranched; if (localdom.col_lower_[i] < fixval) { ++numBranched; heur.branchUpwards(i, fixval, fixval - 0.5); @@ -705,7 +706,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { } if (fixingrate >= minfixingrate) - break; // if the RINS neigborhood achieved a high enough fixing rate + break; // if the RINS neighbourhood achieved a high enough fixing rate // by itself we stop here fixcandend = heurlp.getFractionalIntegers().end(); // now sort the variables by their distance towards the value they will @@ -800,7 +801,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { localdom.col_lower_, localdom.col_upper_, 500, // std::max(50, int(0.05 * // (mipsolver.mipdata_->num_leaves))), - 200 + int(0.05 * (mipsolver.mipdata_->num_nodes)), 12)) { + 200 + mipsolver.mipdata_->num_nodes / 20, 12)) { int64_t new_lp_iterations = lp_iterations + heur.getLocalLpIterations(); if (new_lp_iterations + mipsolver.mipdata_->heuristic_lp_iterations > 100000 + ((mipsolver.mipdata_->total_lp_iterations - @@ -816,7 +817,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { lp_iterations = new_lp_iterations; return; } - // printf("infeasible in in root node, trying with lower fixing rate\n"); + // printf("infeasible in root node, trying with lower fixing rate\n"); maxfixingrate = fixingrate * 0.5; goto retry; } @@ -979,7 +980,7 @@ void HighsPrimalHeuristics::randomizedRounding( lprelax.getLpSolver().changeColsBounds(0, mipsolver.numCol() - 1, localdom.col_lower_.data(), localdom.col_upper_.data()); - if (intcols.size() / (double)mipsolver.numCol() >= 0.2) + if ((5 * intcols.size()) / mipsolver.numCol() >= 1) lprelax.getLpSolver().setOptionValue("presolve", "on"); else lprelax.getLpSolver().setBasis( diff --git a/src/mip/HighsPrimalHeuristics.h b/src/mip/HighsPrimalHeuristics.h index 31792c5de6..cba4f71b25 100644 --- a/src/mip/HighsPrimalHeuristics.h +++ b/src/mip/HighsPrimalHeuristics.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsPseudocost.cpp b/src/mip/HighsPseudocost.cpp index b88cf2321c..b72c3ff5f3 100644 --- a/src/mip/HighsPseudocost.cpp +++ b/src/mip/HighsPseudocost.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsPseudocost.h b/src/mip/HighsPseudocost.h index def030bf34..9d7a70b35c 100644 --- a/src/mip/HighsPseudocost.h +++ b/src/mip/HighsPseudocost.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -154,7 +154,7 @@ class HighsPseudocost { d = unit_gain - cost_total; ++nsamplestotal; - cost_total += d / nsamplestotal; + cost_total += d / static_cast(nsamplestotal); } else { double unit_gain = -objdelta / delta; double d = unit_gain - pseudocostdown[col]; @@ -163,7 +163,7 @@ class HighsPseudocost { d = unit_gain - cost_total; ++nsamplestotal; - cost_total += d / nsamplestotal; + cost_total += d / static_cast(nsamplestotal); } } @@ -171,7 +171,7 @@ class HighsPseudocost { bool upbranch) { double d = ninferences - inferences_total; ++ninferencestotal; - inferences_total += d / ninferencestotal; + inferences_total += d / static_cast(ninferencestotal); if (upbranch) { d = ninferences - inferencesup[col]; ninferencesup[col] += 1; @@ -202,9 +202,10 @@ class HighsPseudocost { double cost; if (nsamplesup[col] == 0 || nsamplesup[col] < minreliable) { - double weightPs = nsamplesup[col] == 0 - ? 0 - : 0.9 + 0.1 * nsamplesup[col] / (double)minreliable; + double weightPs = + nsamplesup[col] == 0 + ? 0 + : 0.9 + 0.1 * nsamplesup[col] / static_cast(minreliable); cost = weightPs * pseudocostup[col]; cost += (1.0 - weightPs) * getAvgPseudocost(); } else @@ -217,9 +218,10 @@ class HighsPseudocost { double cost; if (nsamplesdown[col] == 0 || nsamplesdown[col] < minreliable) { - double weightPs = nsamplesdown[col] == 0 ? 0 - : 0.9 + 0.1 * nsamplesdown[col] / - (double)minreliable; + double weightPs = nsamplesdown[col] == 0 + ? 0 + : 0.9 + 0.1 * nsamplesdown[col] / + static_cast(minreliable); cost = weightPs * pseudocostdown[col]; cost += (1.0 - weightPs) * getAvgPseudocost(); } else @@ -257,12 +259,15 @@ class HighsPseudocost { double cutOffScoreUp = ncutoffsup[col] / - std::max(1.0, double(ncutoffsup[col] + nsamplesup[col])); + std::max(1.0, static_cast(ncutoffsup[col]) + + static_cast(nsamplesup[col])); double cutOffScoreDown = ncutoffsdown[col] / - std::max(1.0, double(ncutoffsdown[col] + nsamplesdown[col])); - double avgCutoffs = - ncutoffstotal / std::max(1.0, double(ncutoffstotal + nsamplestotal)); + std::max(1.0, static_cast(ncutoffsdown[col]) + + static_cast(nsamplesdown[col])); + double avgCutoffs = static_cast(ncutoffstotal) / + std::max(1.0, static_cast(ncutoffstotal) + + static_cast(nsamplestotal)); double cutoffScore = std::max(cutOffScoreUp, 1e-6) * std::max(cutOffScoreDown, 1e-6) / @@ -271,7 +276,8 @@ class HighsPseudocost { double conflictScoreUp = conflictscoreup[col] / conflict_weight; double conflictScoreDown = conflictscoredown[col] / conflict_weight; double conflictScoreAvg = - conflict_avg_score / (conflict_weight * conflictscoreup.size()); + conflict_avg_score / + (conflict_weight * static_cast(conflictscoreup.size())); double conflictScore = std::max(conflictScoreUp, 1e-6) * std::max(conflictScoreDown, 1e-6) / std::max(1e-6, conflictScoreAvg * conflictScoreAvg); @@ -297,15 +303,18 @@ class HighsPseudocost { double cutOffScoreUp = ncutoffsup[col] / - std::max(1.0, double(ncutoffsup[col] + nsamplesup[col])); - double avgCutoffs = - ncutoffstotal / std::max(1.0, double(ncutoffstotal + nsamplestotal)); + std::max(1.0, static_cast(ncutoffsup[col]) + + static_cast(nsamplesup[col])); + double avgCutoffs = static_cast(ncutoffstotal) / + std::max(1.0, static_cast(ncutoffstotal) + + static_cast(nsamplestotal)); double cutoffScore = cutOffScoreUp / std::max(1e-6, avgCutoffs); double conflictScoreUp = conflictscoreup[col] / conflict_weight; double conflictScoreAvg = - conflict_avg_score / (conflict_weight * conflictscoreup.size()); + conflict_avg_score / + (conflict_weight * static_cast(conflictscoreup.size())); double conflictScore = conflictScoreUp / std::max(1e-6, conflictScoreAvg); auto mapScore = [](double score) { return 1.0 - 1.0 / (1.0 + score); }; @@ -323,15 +332,18 @@ class HighsPseudocost { double cutOffScoreDown = ncutoffsdown[col] / - std::max(1.0, double(ncutoffsdown[col] + nsamplesdown[col])); - double avgCutoffs = - ncutoffstotal / std::max(1.0, double(ncutoffstotal + nsamplestotal)); + std::max(1.0, static_cast(ncutoffsdown[col]) + + static_cast(nsamplesdown[col])); + double avgCutoffs = static_cast(ncutoffstotal) / + std::max(1.0, static_cast(ncutoffstotal) + + static_cast(nsamplestotal)); double cutoffScore = cutOffScoreDown / std::max(1e-6, avgCutoffs); double conflictScoreDown = conflictscoredown[col] / conflict_weight; double conflictScoreAvg = - conflict_avg_score / (conflict_weight * conflictscoredown.size()); + conflict_avg_score / + (conflict_weight * static_cast(conflictscoredown.size())); double conflictScore = conflictScoreDown / std::max(1e-6, conflictScoreAvg); auto mapScore = [](double score) { return 1.0 - 1.0 / (1.0 + score); }; diff --git a/src/mip/HighsRedcostFixing.cpp b/src/mip/HighsRedcostFixing.cpp index 80fe4adcbb..14cdcfc62a 100644 --- a/src/mip/HighsRedcostFixing.cpp +++ b/src/mip/HighsRedcostFixing.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsRedcostFixing.h b/src/mip/HighsRedcostFixing.h index 3984721b49..f0b9686054 100644 --- a/src/mip/HighsRedcostFixing.h +++ b/src/mip/HighsRedcostFixing.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsSearch.cpp b/src/mip/HighsSearch.cpp index f6fbf501e1..4cab953b03 100644 --- a/src/mip/HighsSearch.cpp +++ b/src/mip/HighsSearch.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -247,7 +247,6 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, double& upNodeLb) { assert(!lp->getFractionalIntegers().empty()); - static constexpr HighsInt basisstart_threshold = 20; std::vector upscore; std::vector downscore; std::vector upscorereliable; @@ -939,7 +938,6 @@ void HighsSearch::installNode(HighsNodeQueue::OpenNode&& node) { // if global orbits have been computed we check whether they are still valid // in this node const auto& domchgstack = localdom.getDomainChangeStack(); - const auto& branchpos = localdom.getBranchingPositions(); for (HighsInt i : localdom.getBranchingPositions()) { HighsInt col = domchgstack[i].column; if (mipsolver.mipdata_->symmetries.columnPosition[col] == -1) continue; @@ -1133,7 +1131,7 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { } } } else if (lp->getObjective() > getCutoffBound()) { - // the LP is not solved to dual feasibilty due to scaling/numerics + // the LP is not solved to dual feasibility due to scaling/numerics // therefore we compute a conflict constraint as if the LP was bound // exceeding and propagate the local domain again. The lp relaxation // class will take care to consider the dual multipliers with an @@ -1819,7 +1817,7 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { nodelb = std::max(nodelb, localdom.getObjectiveLowerBound()); bool nodeToQueue = nodelb > mipsolver.mipdata_->optimality_limit; - // we check if switching to the other branch of an anchestor yields a higher + // we check if switching to the other branch of an ancestor yields a higher // additive branch score than staying in this node and if so we postpone the // node and put it to the queue to backtrack further. if (!nodeToQueue) { diff --git a/src/mip/HighsSearch.h b/src/mip/HighsSearch.h index 65e7287df2..c9c665e21a 100644 --- a/src/mip/HighsSearch.h +++ b/src/mip/HighsSearch.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -96,6 +96,7 @@ class HighsSearch { std::shared_ptr stabilizerOrbits = nullptr) : lower_bound(parentlb), estimate(parentestimate), + branching_point(0.0), lp_objective(-kHighsInf), other_child_lb(parentlb), nodeBasis(std::move(parentBasis)), diff --git a/src/mip/HighsSeparation.cpp b/src/mip/HighsSeparation.cpp index 29ba58c162..29bd860355 100644 --- a/src/mip/HighsSeparation.cpp +++ b/src/mip/HighsSeparation.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsSeparation.h b/src/mip/HighsSeparation.h index b7634d4633..fa0c8e842e 100644 --- a/src/mip/HighsSeparation.h +++ b/src/mip/HighsSeparation.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsSeparator.cpp b/src/mip/HighsSeparator.cpp index ab9a14d382..4ced13e54e 100644 --- a/src/mip/HighsSeparator.cpp +++ b/src/mip/HighsSeparator.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsSeparator.h b/src/mip/HighsSeparator.h index 1607f5de93..b9a91dca42 100644 --- a/src/mip/HighsSeparator.h +++ b/src/mip/HighsSeparator.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsTableauSeparator.cpp b/src/mip/HighsTableauSeparator.cpp index 86d47dd77b..5e9ab01bc6 100644 --- a/src/mip/HighsTableauSeparator.cpp +++ b/src/mip/HighsTableauSeparator.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -15,12 +15,12 @@ #include +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsCutGeneration.h" #include "mip/HighsLpAggregator.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolverData.h" #include "mip/HighsTransformedLp.h" -#include "pdqsort/pdqsort.h" struct FractionalInteger { double fractionality; @@ -88,8 +88,9 @@ void HighsTableauSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, if (fractionalBasisvars.empty()) return; int64_t maxTries = 5000 + getNumCalls() * 50 + - int64_t(0.1 * (mip.mipdata_->total_lp_iterations - - mip.mipdata_->heuristic_lp_iterations)); + (mip.mipdata_->total_lp_iterations - + mip.mipdata_->heuristic_lp_iterations) / + 10; if (numTries >= maxTries) return; maxTries -= numTries; @@ -145,7 +146,6 @@ void HighsTableauSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, numTries += fractionalBasisvars.size(); for (auto& fracvar : fractionalBasisvars) { - HighsInt i = fracvar.basisIndex; if (lpSolver.getBasisInverseRowSparse(fracvar.basisIndex, rowEpBuffer) != HighsStatus::kOk) continue; @@ -207,8 +207,8 @@ void HighsTableauSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, lpAggregator.getCurrentAggregation(baseRowInds, baseRowVals, false); - if (baseRowInds.size() - fracvar.row_ep.size() > - 1000 + 0.1 * mip.numCol()) { + if (10 * (baseRowInds.size() - fracvar.row_ep.size()) > + 10000 + static_cast(mip.numCol())) { lpAggregator.clear(); continue; } diff --git a/src/mip/HighsTableauSeparator.h b/src/mip/HighsTableauSeparator.h index 7fed2d88ee..c5e042892f 100644 --- a/src/mip/HighsTableauSeparator.h +++ b/src/mip/HighsTableauSeparator.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 3c95f4f44e..16b529a4cb 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/mip/HighsTransformedLp.h b/src/mip/HighsTransformedLp.h index 90aa59da68..f43de55171 100644 --- a/src/mip/HighsTransformedLp.h +++ b/src/mip/HighsTransformedLp.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/model/HighsHessian.cpp b/src/model/HighsHessian.cpp index 5de783017d..6e57a487df 100644 --- a/src/model/HighsHessian.cpp +++ b/src/model/HighsHessian.cpp @@ -40,6 +40,110 @@ void HighsHessian::exactResize() { } } +void HighsHessian::deleteCols(const HighsIndexCollection& index_collection) { + if (this->dim_ == 0) return; + // Can't handle non-triangular matrices yet + assert(this->format_ == HessianFormat::kTriangular); + assert(ok(index_collection)); + HighsInt from_k; + HighsInt to_k; + limits(index_collection, from_k, to_k); + if (from_k > to_k) return; + HighsInt delete_from_col; + HighsInt delete_to_col; + HighsInt keep_from_col; + HighsInt keep_to_col = -1; + HighsInt current_set_entry = 0; + + // Initial pass creates a look-up to for the new index of columns + // being retained, and -1 for columns being deleted + std::vector new_index; + new_index.assign(this->dim_, -1); + HighsInt new_dim = 0; + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, delete_from_col, delete_to_col, + keep_from_col, keep_to_col, current_set_entry); + if (k == from_k) { + // Account for the initial columns being kept + for (HighsInt iCol = 0; iCol < delete_from_col; iCol++) + new_index[iCol] = new_dim++; + } + for (HighsInt iCol = keep_from_col; iCol <= keep_to_col; iCol++) + new_index[iCol] = new_dim++; + // When using a mask, to_k = this->dim_, but consecutive + // keep/delete entries are accumulated, so may not need all passes + if (keep_to_col >= this->dim_ - 1) break; + } + assert(new_dim < this->dim_); + // Now perform the pass that deletes rows/columns + keep_to_col = -1; + current_set_entry = 0; + // Have to accumulate new number of entries from the outset, as + // entries may be lost from columns being kept before any are + // deleted. Also keep a count of the number of nonzeros, in case the new + HighsInt check_new_dim = new_dim; + new_dim = 0; + HighsInt new_num_nz = 0; + HighsInt new_num_entries = 0; + std::vector save_start = this->start_; + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, delete_from_col, delete_to_col, + keep_from_col, keep_to_col, current_set_entry); + if (k == from_k) { + // Account for the initial columns being kept + for (HighsInt iCol = 0; iCol < delete_from_col; iCol++) { + assert(new_index[iCol] >= 0); + for (HighsInt iEl = save_start[iCol]; iEl < save_start[iCol + 1]; + iEl++) { + HighsInt iRow = new_index[this->index_[iEl]]; + if (iRow < 0) continue; + this->index_[new_num_entries] = iRow; + this->value_[new_num_entries] = this->value_[iEl]; + if (this->value_[new_num_entries]) new_num_nz++; + new_num_entries++; + } + new_dim++; + this->start_[new_dim] = new_num_entries; + } + assert(new_dim == delete_from_col); + } + for (HighsInt iCol = keep_from_col; iCol <= keep_to_col; iCol++) { + assert(new_index[iCol] >= 0); + for (HighsInt iEl = save_start[iCol]; iEl < save_start[iCol + 1]; iEl++) { + HighsInt iRow = new_index[this->index_[iEl]]; + if (iRow < 0) continue; + this->index_[new_num_entries] = iRow; + this->value_[new_num_entries] = this->value_[iEl]; + if (this->value_[new_num_entries]) new_num_nz++; + new_num_entries++; + } + new_dim++; + this->start_[new_dim] = new_num_entries; + } + if (keep_to_col >= this->dim_ - 1) break; + } + assert(new_dim == check_new_dim); + this->dim_ = new_dim; + if (!new_num_nz) { + this->clear(); + } else { + this->exactResize(); + } +} + +bool HighsHessian::scaleOk(const HighsInt hessian_scale, + const double small_matrix_value, + const double large_matrix_value) const { + if (!this->dim_) return true; + double hessian_scale_value = std::pow(2, hessian_scale); + for (HighsInt iEl = 0; iEl < this->start_[this->dim_]; iEl++) { + double abs_new_value = std::abs(this->value_[iEl] * hessian_scale_value); + if (abs_new_value >= large_matrix_value) return false; + if (abs_new_value <= small_matrix_value) return false; + } + return true; +} + HighsInt HighsHessian::numNz() const { assert(this->formatOk()); assert((HighsInt)this->start_.size() >= this->dim_ + 1); diff --git a/src/model/HighsHessian.h b/src/model/HighsHessian.h index 3002f561ec..4899893b04 100644 --- a/src/model/HighsHessian.h +++ b/src/model/HighsHessian.h @@ -20,6 +20,7 @@ #include "lp_data/HConst.h" #include "util/HighsCDouble.h" +#include "util/HighsUtils.h" // class HighsHessian; @@ -37,12 +38,16 @@ class HighsHessian { double objectiveValue(const std::vector& solution) const; HighsCDouble objectiveCDoubleValue(const std::vector& solution) const; void exactResize(); + void deleteCols(const HighsIndexCollection& index_collection); void clear(); bool formatOk() const { return (this->format_ == HessianFormat::kTriangular || this->format_ == HessianFormat::kSquare); }; + bool scaleOk(const HighsInt cost_scale, const double small_matrix_value, + const double large_matrix_value) const; HighsInt numNz() const; + void print() const; }; diff --git a/src/model/HighsHessianUtils.cpp b/src/model/HighsHessianUtils.cpp index bd1ad3b132..6e809cc759 100644 --- a/src/model/HighsHessianUtils.cpp +++ b/src/model/HighsHessianUtils.cpp @@ -39,7 +39,10 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { if (return_status == HighsStatus::kError) return return_status; // If the Hessian has no columns there is nothing left to test - if (hessian.dim_ == 0) return HighsStatus::kOk; + if (hessian.dim_ == 0) { + hessian.clear(); + return HighsStatus::kOk; + } // Assess the Hessian matrix // @@ -221,7 +224,6 @@ HighsStatus extractTriangularHessian(const HighsOptions& options, const HighsInt dim = hessian.dim_; HighsInt nnz = 0; for (HighsInt iCol = 0; iCol < dim; iCol++) { - double diagonal_value = 0; const HighsInt nnz0 = nnz; for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1]; iEl++) { @@ -473,6 +475,24 @@ HighsStatus normaliseHessian(const HighsOptions& options, return return_status; } +void completeHessian(const HighsInt full_dim, HighsHessian& hessian) { + // Ensure that any non-zero Hessian of dimension less than the + // number of columns in the model is completed with explicit zero + // diagonal entries + assert(hessian.dim_ <= full_dim); + if (hessian.dim_ == full_dim) return; + HighsInt nnz = hessian.numNz(); + hessian.exactResize(); + for (HighsInt iCol = hessian.dim_; iCol < full_dim; iCol++) { + hessian.index_.push_back(iCol); + hessian.value_.push_back(0); + nnz++; + hessian.start_.push_back(nnz); + } + hessian.dim_ = full_dim; + assert(HighsInt(hessian.start_.size()) == hessian.dim_ + 1); +} + void reportHessian(const HighsLogOptions& log_options, const HighsInt dim, const HighsInt num_nz, const HighsInt* start, const HighsInt* index, const double* value) { diff --git a/src/model/HighsHessianUtils.h b/src/model/HighsHessianUtils.h index cd76d64bd9..032b71b20a 100644 --- a/src/model/HighsHessianUtils.h +++ b/src/model/HighsHessianUtils.h @@ -41,6 +41,8 @@ HighsStatus extractTriangularHessian(const HighsOptions& options, void triangularToSquareHessian(const HighsHessian& hessian, vector& start, vector& index, vector& value); +void completeHessian(const HighsInt full_dim, HighsHessian& hessian); + void reportHessian(const HighsLogOptions& log_options, const HighsInt dim, const HighsInt num_nz, const HighsInt* start, const HighsInt* index, const double* value); diff --git a/src/model/HighsModel.cpp b/src/model/HighsModel.cpp index ccc1f159e4..70bbf3cf28 100644 --- a/src/model/HighsModel.cpp +++ b/src/model/HighsModel.cpp @@ -29,6 +29,33 @@ bool HighsModel::equalButForNames(const HighsModel& model) const { return equal; } +bool HighsModel::userCostScaleOk(const HighsInt user_cost_scale, + const double small_matrix_value, + const double large_matrix_value, + const double infinite_cost) const { + const HighsInt dl_user_cost_scale = + user_cost_scale - this->lp_.user_cost_scale_; + if (!dl_user_cost_scale) return true; + if (this->hessian_.dim_ && + !this->hessian_.scaleOk(dl_user_cost_scale, small_matrix_value, + large_matrix_value)) + return false; + return this->lp_.userCostScaleOk(user_cost_scale, infinite_cost); +} + +void HighsModel::userCostScale(const HighsInt user_cost_scale) { + const HighsInt dl_user_cost_scale = + user_cost_scale - this->lp_.user_cost_scale_; + if (!dl_user_cost_scale) return; + double dl_user_cost_scale_value = std::pow(2, dl_user_cost_scale); + if (this->hessian_.dim_) { + for (HighsInt iEl = 0; iEl < this->hessian_.start_[this->hessian_.dim_]; + iEl++) + this->hessian_.value_[iEl] *= dl_user_cost_scale_value; + } + this->lp_.userCostScale(user_cost_scale); +} + void HighsModel::clear() { this->lp_.clear(); this->hessian_.clear(); diff --git a/src/model/HighsModel.h b/src/model/HighsModel.h index c8a60ef218..114889a607 100644 --- a/src/model/HighsModel.h +++ b/src/model/HighsModel.h @@ -29,7 +29,7 @@ class HighsModel { HighsHessian hessian_; bool operator==(const HighsModel& model) const; bool equalButForNames(const HighsModel& model) const; - bool isQp() const { return this->hessian_.dim_; } + bool isQp() const { return (this->hessian_.dim_ != 0); } bool isMip() const { return this->lp_.isMip(); } bool isEmpty() const { return (this->lp_.num_col_ == 0 && this->lp_.num_row_ == 0); @@ -38,6 +38,11 @@ class HighsModel { return this->lp_.needsMods(infinite_cost); } bool hasMods() const { return this->lp_.hasMods(); } + bool userCostScaleOk(const HighsInt user_cost_scale, + const double small_matrix_value, + const double large_matrix_value, + const double infinite_cost) const; + void userCostScale(const HighsInt user_cost_scale); void clear(); double objectiveValue(const std::vector& solution) const; void objectiveGradient(const std::vector& solution, diff --git a/src/parallel/HighsMutex.h b/src/parallel/HighsMutex.h index cd39788ace..34d8b95541 100644 --- a/src/parallel/HighsMutex.h +++ b/src/parallel/HighsMutex.h @@ -114,7 +114,8 @@ class HighsMutex { } void unlock() { - unsigned int prevState = state.fetch_add(-1, std::memory_order_release); + unsigned int prevState = state.fetch_add( + std::numeric_limits::max(), std::memory_order_release); if (prevState != 1) { unsigned int notifyWorkerId = (prevState >> 1) - 1; diff --git a/src/parallel/HighsParallel.h b/src/parallel/HighsParallel.h index 00b49302d6..270513e466 100644 --- a/src/parallel/HighsParallel.h +++ b/src/parallel/HighsParallel.h @@ -23,8 +23,13 @@ namespace parallel { using mutex = HighsMutex; inline void initialize_scheduler(int numThreads = 0) { - if (numThreads == 0) + if (numThreads == 0) { +#ifdef HIGHS_NO_DEFAULT_THREADS + numThreads = 1; +#else numThreads = (std::thread::hardware_concurrency() + 1) / 2; +#endif + } HighsTaskExecutor::initialize(numThreads); } @@ -125,4 +130,4 @@ void for_each(HighsInt start, HighsInt end, F&& f, HighsInt grainSize = 1) { } // namespace highs -#endif \ No newline at end of file +#endif diff --git a/src/parallel/HighsTask.h b/src/parallel/HighsTask.h index 43b8399d7b..b4b3f5838a 100644 --- a/src/parallel/HighsTask.h +++ b/src/parallel/HighsTask.h @@ -109,8 +109,7 @@ class HighsTask { } void cancel() { - uintptr_t state = - metadata.stealer.fetch_or(kCancelFlag, std::memory_order_release); + metadata.stealer.fetch_or(kCancelFlag, std::memory_order_release); } /// run task as owner, if not cancelled diff --git a/src/pdlp/CupdlpWrapper.cpp b/src/pdlp/CupdlpWrapper.cpp new file mode 100644 index 0000000000..cccf37705a --- /dev/null +++ b/src/pdlp/CupdlpWrapper.cpp @@ -0,0 +1,688 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file pdlp/CupdlpWrapper.cpp + * @brief + * @author Julian Hall + */ +#include "pdlp/CupdlpWrapper.h" + +void getUserParamsFromOptions(const HighsOptions& options, + cupdlp_bool* ifChangeIntParam, + cupdlp_int* intParam, + cupdlp_bool* ifChangeFloatParam, + cupdlp_float* floatParam); + +void analysePdlpSolution(const HighsOptions& options, const HighsLp& lp, + const HighsSolution& highs_solution); + +HighsStatus solveLpCupdlp(HighsLpSolverObject& solver_object) { + return solveLpCupdlp(solver_object.options_, solver_object.timer_, + solver_object.lp_, solver_object.basis_, + solver_object.solution_, solver_object.model_status_, + solver_object.highs_info_, solver_object.callback_); +} + +HighsStatus solveLpCupdlp(const HighsOptions& options, HighsTimer& timer, + const HighsLp& lp, HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsModelStatus& model_status, HighsInfo& highs_info, + HighsCallback& callback) { + // Indicate that there is no valid primal solution, dual solution or basis + highs_basis.valid = false; + highs_solution.value_valid = false; + highs_solution.dual_valid = false; + // Indicate that no imprecise solution has (yet) been found + resetModelStatusAndHighsInfo(model_status, highs_info); + + char* fp = nullptr; + char* fp_sol = nullptr; + + int nCols; + int nRows; + int nEqs; + int nCols_origin; + cupdlp_bool ifSaveSol = false; + cupdlp_bool ifPresolve = false; + + int nnz = 0; + double* rhs = NULL; + double* cost = NULL; + + cupdlp_float* lower = NULL; + cupdlp_float* upper = NULL; + + // ------------------------- + int *csc_beg = NULL, *csc_idx = NULL; + double* csc_val = NULL; + double offset = + 0.0; // true objVal = sig * c'x - offset, sig = 1 (min) or -1 (max) + double sense_origin = 1; // 1 (min) or -1 (max) + int* constraint_new_idx = NULL; + cupdlp_float* x_origin = cupdlp_NULL; + cupdlp_float* y_origin = cupdlp_NULL; + + void* model = NULL; + void* presolvedmodel = NULL; + void* model2solve = NULL; + + // WIP on zbook? + // + // HighsInt size_of_CUPDLPscaling = sizeof(CUPDLPscaling); + // + CUPDLPscaling* scaling = (CUPDLPscaling*)cupdlp_malloc(sizeof(CUPDLPscaling)); + + // WIP on zbook? + // + // printf("size_of_CUPDLPscaling = %d\n", size_of_CUPDLPscaling); + // scaling->ifRuizScaling = 1; + // printf("scaling->ifRuizScaling = %d\n", scaling->ifRuizScaling); + + // claim solvers variables + // prepare pointers + CUPDLP_MATRIX_FORMAT src_matrix_format = CSC; + CUPDLP_MATRIX_FORMAT dst_matrix_format = CSR_CSC; + CUPDLPcsc* csc_cpu = cupdlp_NULL; + CUPDLPproblem* prob = cupdlp_NULL; + + // load parameters + + // set solver parameters + cupdlp_bool ifChangeIntParam[N_INT_USER_PARAM] = {false}; + cupdlp_int intParam[N_INT_USER_PARAM] = {0}; + cupdlp_bool ifChangeFloatParam[N_FLOAT_USER_PARAM] = {false}; + cupdlp_float floatParam[N_FLOAT_USER_PARAM] = {0.0}; + + // Transfer from options + getUserParamsFromOptions(options, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam); + + std::vector constraint_type(lp.num_row_); + + formulateLP_highs(lp, &cost, &nCols, &nRows, &nnz, &nEqs, &csc_beg, &csc_idx, + &csc_val, &rhs, &lower, &upper, &offset, &sense_origin, + &nCols_origin, &constraint_new_idx, constraint_type.data()); + + const cupdlp_int local_log_level = getCupdlpLogLevel(options); + if (local_log_level) cupdlp_printf("Solving with cuPDLP-C\n"); + + H_Init_Scaling(local_log_level, scaling, nCols, nRows, cost, rhs); + cupdlp_int ifScaling = 1; + + CUPDLPwork* w = cupdlp_NULL; + cupdlp_init_work(w, 1); + + problem_create(&prob); + + // currently, only supprot that input matrix is CSC, and store both CSC and + // CSR + csc_create(&csc_cpu); + csc_cpu->nRows = nRows; + csc_cpu->nCols = nCols; + csc_cpu->nMatElem = nnz; + csc_cpu->colMatBeg = (int*)malloc((1 + nCols) * sizeof(int)); + csc_cpu->colMatIdx = (int*)malloc(nnz * sizeof(int)); + csc_cpu->colMatElem = (double*)malloc(nnz * sizeof(double)); + memcpy(csc_cpu->colMatBeg, csc_beg, (nCols + 1) * sizeof(int)); + memcpy(csc_cpu->colMatIdx, csc_idx, nnz * sizeof(int)); + memcpy(csc_cpu->colMatElem, csc_val, nnz * sizeof(double)); + + cupdlp_float scaling_time = getTimeStamp(); + H_PDHG_Scale_Data_cuda(local_log_level, csc_cpu, ifScaling, scaling, cost, + lower, upper, rhs); + scaling_time = getTimeStamp() - scaling_time; + + cupdlp_float alloc_matrix_time = 0.0; + cupdlp_float copy_vec_time = 0.0; + + problem_alloc(prob, nRows, nCols, nEqs, cost, offset, sense_origin, csc_cpu, + src_matrix_format, dst_matrix_format, rhs, lower, upper, + &alloc_matrix_time, ©_vec_time); + + w->problem = prob; + w->scaling = scaling; + PDHG_Alloc(w); + w->timers->dScalingTime = scaling_time; + w->timers->dPresolveTime = 0; // presolve_time; + cupdlp_copy_vec(w->rowScale, scaling->rowScale, cupdlp_float, nRows); + cupdlp_copy_vec(w->colScale, scaling->colScale, cupdlp_float, nCols); + + // CUPDLP_CALL(LP_SolvePDHG(prob, cupdlp_NULL, cupdlp_NULL, cupdlp_NULL, + // cupdlp_NULL)); + // CUPDLP_CALL(LP_SolvePDHG(prob, ifChangeIntParam, intParam, + // ifChangeFloatParam, floatParam, fp)); + + cupdlp_init_double(x_origin, nCols_origin); + cupdlp_init_double(y_origin, nRows); + // Resize the highs_solution so cuPDLP-c can use it internally + highs_solution.col_value.resize(lp.num_col_); + highs_solution.row_value.resize(lp.num_row_); + highs_solution.col_dual.resize(lp.num_col_); + highs_solution.row_dual.resize(lp.num_row_); + int value_valid = 0; + int dual_valid = 0; + int pdlp_model_status = 0; + cupdlp_int pdlp_num_iter = 0; + + cupdlp_retcode retcode = LP_SolvePDHG( + w, ifChangeIntParam, intParam, ifChangeFloatParam, floatParam, fp, + nCols_origin, highs_solution.col_value.data(), + highs_solution.col_dual.data(), highs_solution.row_value.data(), + highs_solution.row_dual.data(), &value_valid, &dual_valid, ifSaveSol, + fp_sol, constraint_new_idx, constraint_type.data(), &pdlp_model_status, + &pdlp_num_iter); + highs_info.pdlp_iteration_count = pdlp_num_iter; + + model_status = HighsModelStatus::kUnknown; + if (retcode != RETCODE_OK) return HighsStatus::kError; + + highs_solution.value_valid = value_valid; + highs_solution.dual_valid = dual_valid; + + if (pdlp_model_status == OPTIMAL) { + model_status = HighsModelStatus::kOptimal; + } else if (pdlp_model_status == INFEASIBLE) { + model_status = HighsModelStatus::kInfeasible; + } else if (pdlp_model_status == UNBOUNDED) { + model_status = HighsModelStatus::kUnbounded; + } else if (pdlp_model_status == INFEASIBLE_OR_UNBOUNDED) { + model_status = HighsModelStatus::kUnboundedOrInfeasible; + } else if (pdlp_model_status == TIMELIMIT_OR_ITERLIMIT) { + model_status = pdlp_num_iter >= intParam[N_ITER_LIM] - 1 + ? HighsModelStatus::kIterationLimit + : HighsModelStatus::kTimeLimit; + } else if (pdlp_model_status == FEASIBLE) { + assert(111 == 666); + model_status = HighsModelStatus::kUnknown; + } else { + assert(111 == 777); + } +#if CUPDLP_DEBUG + analysePdlpSolution(options, lp, highs_solution); +#endif + return HighsStatus::kOk; +} + +int formulateLP_highs(const HighsLp& lp, double** cost, int* nCols, int* nRows, + int* nnz, int* nEqs, int** csc_beg, int** csc_idx, + double** csc_val, double** rhs, double** lower, + double** upper, double* offset, double* sense_origin, + int* nCols_origin, int** constraint_new_idx, + int* constraint_type) { + int retcode = 0; + + // problem size for malloc + int nCols_clp = lp.num_col_; + int nRows_clp = lp.num_row_; + int nnz_clp = lp.a_matrix_.start_[lp.num_col_]; + *nCols_origin = nCols_clp; + *nRows = nRows_clp; // need not recalculate + *nCols = nCols_clp; // need recalculate + *nEqs = 0; // need recalculate + *nnz = nnz_clp; // need recalculate + *offset = lp.offset_; // need not recalculate + if (lp.sense_ == ObjSense::kMinimize) { + *sense_origin = 1.0; + } else if (lp.sense_ == ObjSense::kMaximize) { + *sense_origin = -1.0; + } + + const double* lhs_clp = lp.row_lower_.data(); + const double* rhs_clp = lp.row_upper_.data(); + const HighsInt* A_csc_beg = lp.a_matrix_.start_.data(); + const HighsInt* A_csc_idx = lp.a_matrix_.index_.data(); + const double* A_csc_val = lp.a_matrix_.value_.data(); + int has_lower, has_upper; + + cupdlp_init_int(*constraint_new_idx, *nRows); + + // recalculate nRows and nnz for Ax - z = 0 + for (int i = 0; i < nRows_clp; i++) { + has_lower = lhs_clp[i] > -1e20; + has_upper = rhs_clp[i] < 1e20; + + // count number of equations and rows + if (has_lower && has_upper && lhs_clp[i] == rhs_clp[i]) { + constraint_type[i] = EQ; + (*nEqs)++; + } else if (has_lower && !has_upper) { + constraint_type[i] = GEQ; + } else if (!has_lower && has_upper) { + constraint_type[i] = LEQ; + } else if (has_lower && has_upper) { + constraint_type[i] = BOUND; + (*nCols)++; + (*nnz)++; + (*nEqs)++; + } else { + // printf("Error: constraint %d has no lower and upper bound\n", i); + // retcode = 1; + // goto exit_cleanup; + + // what if regard free as bounded + printf("Warning: constraint %d has no lower and upper bound\n", i); + constraint_type[i] = BOUND; + (*nCols)++; + (*nnz)++; + (*nEqs)++; + } + } + + // allocate memory + cupdlp_init_double(*cost, *nCols); + cupdlp_init_double(*lower, *nCols); + cupdlp_init_double(*upper, *nCols); + cupdlp_init_int(*csc_beg, *nCols + 1); + cupdlp_init_int(*csc_idx, *nnz); + cupdlp_init_double(*csc_val, *nnz); + cupdlp_init_double(*rhs, *nRows); + + // cost, lower, upper + for (int i = 0; i < nCols_clp; i++) { + (*cost)[i] = lp.col_cost_[i] * (*sense_origin); + (*lower)[i] = lp.col_lower_[i]; + + (*upper)[i] = lp.col_upper_[i]; + } + // slack costs + for (int i = nCols_clp; i < *nCols; i++) { + (*cost)[i] = 0.0; + } + // slack bounds + for (int i = 0, j = nCols_clp; i < *nRows; i++) { + if (constraint_type[i] == BOUND) { + (*lower)[j] = lhs_clp[i]; + (*upper)[j] = rhs_clp[i]; + j++; + } + } + + for (int i = 0; i < *nCols; i++) { + if ((*lower)[i] < -1e20) (*lower)[i] = -INFINITY; + if ((*upper)[i] > 1e20) (*upper)[i] = INFINITY; + } + + // permute LP rhs + // EQ or BOUND first + for (int i = 0, j = 0; i < *nRows; i++) { + if (constraint_type[i] == EQ) { + (*rhs)[j] = lhs_clp[i]; + (*constraint_new_idx)[i] = j; + j++; + } else if (constraint_type[i] == BOUND) { + (*rhs)[j] = 0.0; + (*constraint_new_idx)[i] = j; + j++; + } + } + // then LEQ or GEQ + for (int i = 0, j = *nEqs; i < *nRows; i++) { + if (constraint_type[i] == LEQ) { + (*rhs)[j] = -rhs_clp[i]; // multiply -1 + (*constraint_new_idx)[i] = j; + j++; + } else if (constraint_type[i] == GEQ) { + (*rhs)[j] = lhs_clp[i]; + (*constraint_new_idx)[i] = j; + j++; + } + } + + // formulate and permute LP matrix + // beg remains the same + for (int i = 0; i < nCols_clp + 1; i++) (*csc_beg)[i] = A_csc_beg[i]; + for (int i = nCols_clp + 1; i < *nCols + 1; i++) + (*csc_beg)[i] = (*csc_beg)[i - 1] + 1; + + // row idx changes + for (int i = 0, k = 0; i < nCols_clp; i++) { + // same order as in rhs + // EQ or BOUND first + for (int j = (*csc_beg)[i]; j < (*csc_beg)[i + 1]; j++) { + if (constraint_type[A_csc_idx[j]] == EQ || + constraint_type[A_csc_idx[j]] == BOUND) { + (*csc_idx)[k] = (*constraint_new_idx)[A_csc_idx[j]]; + (*csc_val)[k] = A_csc_val[j]; + k++; + } + } + // then LEQ or GEQ + for (int j = (*csc_beg)[i]; j < (*csc_beg)[i + 1]; j++) { + if (constraint_type[A_csc_idx[j]] == LEQ) { + (*csc_idx)[k] = (*constraint_new_idx)[A_csc_idx[j]]; + (*csc_val)[k] = -A_csc_val[j]; // multiply -1 + k++; + } else if (constraint_type[A_csc_idx[j]] == GEQ) { + (*csc_idx)[k] = (*constraint_new_idx)[A_csc_idx[j]]; + (*csc_val)[k] = A_csc_val[j]; + k++; + } + } + } + + // slacks for BOUND + for (int i = 0, j = nCols_clp; i < *nRows; i++) { + if (constraint_type[i] == BOUND) { + (*csc_idx)[(*csc_beg)[j]] = (*constraint_new_idx)[i]; + (*csc_val)[(*csc_beg)[j]] = -1.0; + j++; + } + } + + return retcode; +} + +cupdlp_retcode problem_create(CUPDLPproblem** prob) { + cupdlp_retcode retcode = RETCODE_OK; + + cupdlp_init_problem(*prob, 1); + + return retcode; +} + +// cupdlp_retcode csc_create(CUPDLPcsc **csc_cpu) { +// cupdlp_retcode retcode = RETCODE_OK; +// +// cupdlp_init_csc_cpu(*csc_cpu, 1); +// +// return retcode; +// } + +cupdlp_retcode data_alloc(CUPDLPdata* data, cupdlp_int nRows, cupdlp_int nCols, + void* matrix, CUPDLP_MATRIX_FORMAT src_matrix_format, + CUPDLP_MATRIX_FORMAT dst_matrix_format) { + cupdlp_retcode retcode = RETCODE_OK; + + data->nRows = nRows; + data->nCols = nCols; + data->matrix_format = dst_matrix_format; + data->dense_matrix = cupdlp_NULL; + data->csr_matrix = cupdlp_NULL; + data->csc_matrix = cupdlp_NULL; + data->device = CPU; + + switch (dst_matrix_format) { + case DENSE: + dense_create(&data->dense_matrix); + dense_alloc_matrix(data->dense_matrix, nRows, nCols, matrix, + src_matrix_format); + break; + case CSR: + csr_create(&data->csr_matrix); + csr_alloc_matrix(data->csr_matrix, nRows, nCols, matrix, + src_matrix_format); + break; + case CSC: + csc_create(&data->csc_matrix); + csc_alloc_matrix(data->csc_matrix, nRows, nCols, matrix, + src_matrix_format); + break; + case CSR_CSC: + csc_create(&data->csc_matrix); + csc_alloc_matrix(data->csc_matrix, nRows, nCols, matrix, + src_matrix_format); + csr_create(&data->csr_matrix); + csr_alloc_matrix(data->csr_matrix, nRows, nCols, matrix, + src_matrix_format); + break; + default: + break; + } + // currently, only supprot that input matrix is CSC, and store both CSC and + // CSR data->csc_matrix = matrix; + + return retcode; +} + +cupdlp_retcode problem_alloc( + CUPDLPproblem* prob, cupdlp_int nRows, cupdlp_int nCols, cupdlp_int nEqs, + cupdlp_float* cost, cupdlp_float offset, cupdlp_float sense_origin, + void* matrix, CUPDLP_MATRIX_FORMAT src_matrix_format, + CUPDLP_MATRIX_FORMAT dst_matrix_format, cupdlp_float* rhs, + cupdlp_float* lower, cupdlp_float* upper, cupdlp_float* alloc_matrix_time, + cupdlp_float* copy_vec_time) { + cupdlp_retcode retcode = RETCODE_OK; + prob->nRows = nRows; + prob->nCols = nCols; + prob->nEqs = nEqs; + prob->data = cupdlp_NULL; + prob->cost = cupdlp_NULL; + prob->offset = offset; + prob->sense_origin = sense_origin; + prob->rhs = cupdlp_NULL; + prob->lower = cupdlp_NULL; + prob->upper = cupdlp_NULL; + + cupdlp_float begin = getTimeStamp(); + + cupdlp_init_data(prob->data, 1); + cupdlp_init_vec_double(prob->cost, nCols); + cupdlp_init_vec_double(prob->rhs, nRows); + cupdlp_init_vec_double(prob->lower, nCols); + cupdlp_init_vec_double(prob->upper, nCols); + cupdlp_init_zero_vec_double(prob->hasLower, nCols); + cupdlp_init_zero_vec_double(prob->hasUpper, nCols); + + data_alloc(prob->data, nRows, nCols, matrix, src_matrix_format, + dst_matrix_format); + *alloc_matrix_time = getTimeStamp() - begin; + + prob->data->csc_matrix->MatElemNormInf = + infNorm(((CUPDLPcsc*)matrix)->colMatElem, ((CUPDLPcsc*)matrix)->nMatElem); + + begin = getTimeStamp(); + cupdlp_copy_vec(prob->cost, cost, cupdlp_float, nCols); + cupdlp_copy_vec(prob->rhs, rhs, cupdlp_float, nRows); + cupdlp_copy_vec(prob->lower, lower, cupdlp_float, nCols); + cupdlp_copy_vec(prob->upper, upper, cupdlp_float, nCols); + *copy_vec_time = getTimeStamp() - begin; + + // Keep + cupdlp_haslb(prob->hasLower, prob->lower, -INFINITY, nCols); + cupdlp_hasub(prob->hasUpper, prob->upper, +INFINITY, nCols); + + // TODO: cal dMaxCost, dMaxRhs, dMaxRowBound + + return retcode; +} + +// ToDo: Why can linker not pick up infNorm, cupdlp_haslb and +// cupdlp_hasub from pdlp/cupdlp/cupdlp_linalg.c? +double infNorm(double* x, cupdlp_int n) { + double norm = 0; + for (HighsInt iX = 0; iX < n; iX++) norm = std::max(std::fabs(x[iX]), norm); + return norm; +} +void cupdlp_haslb(cupdlp_float* haslb, const cupdlp_float* lb, + const cupdlp_float bound, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + haslb[i] = lb[i] > bound ? 1.0 : 0.0; + } +} + +void cupdlp_hasub(cupdlp_float* hasub, const cupdlp_float* ub, + const cupdlp_float bound, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + hasub[i] = ub[i] < bound ? 1.0 : 0.0; + } +} + +void getUserParamsFromOptions(const HighsOptions& options, + cupdlp_bool* ifChangeIntParam, + cupdlp_int* intParam, + cupdlp_bool* ifChangeFloatParam, + cupdlp_float* floatParam) { + for (cupdlp_int i = 0; i < N_INT_USER_PARAM; ++i) ifChangeIntParam[i] = false; + for (cupdlp_int i = 0; i < N_FLOAT_USER_PARAM; ++i) + ifChangeFloatParam[i] = false; + // Assume all PDLP-related options in HiGHS cause changes + ifChangeIntParam[N_ITER_LIM] = true; + // If HiGHS is using 64-bit integers, then the default value of + // options.pdlp_iteration_limit is kHighsIInf, so copying this to + // intParam[N_ITER_LIM] will overflow. + intParam[N_ITER_LIM] = cupdlp_int(options.pdlp_iteration_limit > kHighsIInf32 + ? kHighsIInf32 + : options.pdlp_iteration_limit); + // + ifChangeIntParam[N_LOG_LEVEL] = true; + intParam[N_LOG_LEVEL] = getCupdlpLogLevel(options); + // + ifChangeIntParam[IF_SCALING] = true; + intParam[IF_SCALING] = options.pdlp_scaling ? 1 : 0; + // + ifChangeFloatParam[D_PRIMAL_TOL] = true; + floatParam[D_PRIMAL_TOL] = options.primal_feasibility_tolerance; + // + ifChangeFloatParam[D_DUAL_TOL] = true; + floatParam[D_DUAL_TOL] = options.dual_feasibility_tolerance; + // + ifChangeFloatParam[D_GAP_TOL] = true; + floatParam[D_GAP_TOL] = options.pdlp_d_gap_tol; + // + ifChangeFloatParam[D_TIME_LIM] = true; + floatParam[D_TIME_LIM] = options.time_limit; + // + ifChangeIntParam[E_RESTART_METHOD] = true; + intParam[E_RESTART_METHOD] = int(options.pdlp_e_restart_method); + // + ifChangeIntParam[I_INF_NORM_ABS_LOCAL_TERMINATION] = true; + intParam[I_INF_NORM_ABS_LOCAL_TERMINATION] = !options.pdlp_native_termination; +} + +void analysePdlpSolution(const HighsOptions& options, const HighsLp& lp, + const HighsSolution& highs_solution) { + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf("x[%2d] = %11.5g\n", int(iCol), highs_solution.col_value[iCol]); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + printf("y[%2d] = %11.5g\n", int(iRow), highs_solution.row_dual[iRow]); + } + + HighsInt num_primal_infeasibility = 0; + HighsInt num_dual_infeasibility = 0; + double max_primal_infeasibility = 0; + double max_dual_infeasibility = 0; + double sum_primal_infeasibility = 0; + double sum_dual_infeasibility = 0; + const double primal_feasibility_tolerance = + options.primal_feasibility_tolerance; + const double dual_feasibility_tolerance = options.dual_feasibility_tolerance; + double lower; + double upper; + double value; + double dual; + // lambda for computing infeasibilities + auto updateInfeasibilities = [&]() { + double primal_infeasibility = 0; + double dual_infeasibility = 0; + // @primal_infeasibility calculation + if (value < lower - primal_feasibility_tolerance) { + // Below lower + primal_infeasibility = lower - value; + } else if (value > upper + primal_feasibility_tolerance) { + // Above upper + primal_infeasibility = value - upper; + } + double value_residual = + std::min(std::fabs(lower - value), std::fabs(value - upper)); + bool at_a_bound = value_residual <= primal_feasibility_tolerance; + if (at_a_bound) { + // At a bound + double middle = (lower + upper) * 0.5; + if (lower < upper) { + // Non-fixed variable + if (value < middle) { + // At lower + dual_infeasibility = std::max(-dual, 0.); + } else { + // At upper + dual_infeasibility = std::max(dual, 0.); + } + } else { + // Fixed variable + dual_infeasibility = 0; + } + } else { + // Off bounds (or free) + dual_infeasibility = fabs(dual); + } + // Accumulate primal infeasibilities + if (primal_infeasibility > primal_feasibility_tolerance) + num_primal_infeasibility++; + max_primal_infeasibility = + std::max(primal_infeasibility, max_primal_infeasibility); + sum_primal_infeasibility += primal_infeasibility; + // Accumulate dual infeasibilities + if (dual_infeasibility > dual_feasibility_tolerance) + num_dual_infeasibility++; + max_dual_infeasibility = + std::max(dual_infeasibility, max_dual_infeasibility); + sum_dual_infeasibility += dual_infeasibility; + }; + + // Apply the model sense, as PDLP will have done this + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + lower = lp.col_lower_[iCol]; + upper = lp.col_upper_[iCol]; + value = highs_solution.col_value[iCol]; + dual = int(lp.sense_) * highs_solution.col_dual[iCol]; + updateInfeasibilities(); + } + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + lower = lp.row_lower_[iRow]; + upper = lp.row_upper_[iRow]; + value = highs_solution.row_value[iRow]; + dual = int(lp.sense_) * highs_solution.row_dual[iRow]; + updateInfeasibilities(); + } + // + // Determine the sum of complementary violations + double max_complementary_violation = 0; + for (HighsInt iVar = 0; iVar < lp.num_col_ + lp.num_row_; iVar++) { + const bool is_col = iVar < lp.num_col_; + const HighsInt iRow = iVar - lp.num_col_; + const double primal = is_col ? highs_solution.col_value[iVar] + : highs_solution.row_value[iRow]; + const double dual = + is_col ? highs_solution.col_dual[iVar] : highs_solution.row_dual[iRow]; + const double lower = is_col ? lp.col_lower_[iVar] : lp.row_lower_[iRow]; + const double upper = is_col ? lp.col_upper_[iVar] : lp.row_upper_[iRow]; + const double mid = (lower + upper) * 0.5; + const double primal_residual = + primal < mid ? std::fabs(lower - primal) : std::fabs(upper - primal); + const double dual_residual = std::fabs(dual); + const double complementary_violation = primal_residual * dual_residual; + max_complementary_violation = + std::max(complementary_violation, max_complementary_violation); + printf( + "%s %2d [%11.5g, %11.5g, %11.5g] has (primal_residual, dual) values " + "(%11.6g, %11.6g) so complementary_violation = %11.6g\n", + is_col ? "Column" : "Row ", is_col ? int(iVar) : int(iRow), lower, + primal, upper, primal_residual, dual_residual, complementary_violation); + } + printf("PDLP max complementary violation = %g\n", + max_complementary_violation); + printf(" primal infeasibilities (%d, %11.6g, %11.6g)\n", + int(num_primal_infeasibility), sum_primal_infeasibility, + max_primal_infeasibility); + printf(" dual infeasibilities (%d, %11.6g, %11.6g)\n", + int(num_dual_infeasibility), sum_dual_infeasibility, + max_dual_infeasibility); +} + +cupdlp_int getCupdlpLogLevel(const HighsOptions& options) { + if (options.output_flag) { + if (options.log_dev_level) { + return 2; + } else { + return 1; + } + } + return 0; +} diff --git a/src/pdlp/CupdlpWrapper.h b/src/pdlp/CupdlpWrapper.h new file mode 100644 index 0000000000..4444c955ce --- /dev/null +++ b/src/pdlp/CupdlpWrapper.h @@ -0,0 +1,93 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file pdlp/CupdlpWrapper.h + * @brief + */ +#ifndef PDLP_CUPDLP_WRAPPER_H_ +#define PDLP_CUPDLP_WRAPPER_H_ + +#include +#include + +#include "lp_data/HighsSolution.h" +#include "pdlp/cupdlp/cupdlp.h" + +typedef enum CONSTRAINT_TYPE { EQ = 0, LEQ, GEQ, BOUND } constraint_type; + +#define cupdlp_init_int(var, size) \ + { (var) = (int*)malloc((size) * sizeof(int)); } + +#define cupdlp_init_double(var, size) \ + { (var) = (double*)malloc((size) * sizeof(double)); } + +#define cupdlp_init_work(var, size) \ + { (var) = (CUPDLPwork*)malloc((size) * sizeof(CUPDLPwork)); } + +#define cupdlp_init_problem(var, size) \ + { (var) = (CUPDLPproblem*)malloc((size) * sizeof(CUPDLPproblem)); } + +#define cupdlp_init_data(var, size) \ + { (var) = (CUPDLPdata*)malloc((size) * sizeof(CUPDLPdata)); } + +#define cupdlp_init_vec_double(var, size) \ + { (var) = (double*)malloc((size) * sizeof(double)); } + +#define cupdlp_init_zero_vec_double(var, size) \ + { (var) = (double*)calloc(size, sizeof(double)); } + +#define cupdlp_copy_vec(dst, src, type, size) \ + memcpy(dst, src, sizeof(type) * (size)) + +/* #define cupdlp_init_csc_cpu(var, size) \ */ +/* {\ */ +/* (var) = (CUPDLPcsc*)malloc((size) * sizeof(CUPDLPcsc));\ */ +/* } */ + +cupdlp_retcode problem_create(CUPDLPproblem** prob); +// cupdlp_retcode csc_create(CUPDLPcsc **csc_cpu); + +cupdlp_retcode problem_alloc( + CUPDLPproblem* prob, cupdlp_int nRows, cupdlp_int nCols, cupdlp_int nEqs, + cupdlp_float* cost, cupdlp_float offset, cupdlp_float sign_origin, + void* matrix, CUPDLP_MATRIX_FORMAT src_matrix_format, + CUPDLP_MATRIX_FORMAT dst_matrix_format, cupdlp_float* rhs, + cupdlp_float* lower, cupdlp_float* upper, cupdlp_float* alloc_matrix_time, + cupdlp_float* copy_vec_time); + +cupdlp_retcode data_alloc(CUPDLPdata* data, cupdlp_int nRows, cupdlp_int nCols, + void* matrix, CUPDLP_MATRIX_FORMAT src_matrix_format, + CUPDLP_MATRIX_FORMAT dst_matrix_format); + +double infNorm(double* x, cupdlp_int n); + +void cupdlp_haslb(cupdlp_float* haslb, const cupdlp_float* lb, + const cupdlp_float bound, const cupdlp_int len); + +void cupdlp_hasub(cupdlp_float* hasub, const cupdlp_float* ub, + const cupdlp_float bound, const cupdlp_int len); + +HighsStatus solveLpCupdlp(HighsLpSolverObject& solver_object); + +HighsStatus solveLpCupdlp(const HighsOptions& options, HighsTimer& timer, + const HighsLp& lp, HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsModelStatus& model_status, HighsInfo& highs_info, + HighsCallback& callback); +int formulateLP_highs(const HighsLp& lp, double** cost, int* nCols, int* nRows, + int* nnz, int* nEqs, int** csc_beg, int** csc_idx, + double** csc_val, double** rhs, double** lower, + double** upper, double* offset, double* sign_origin, + int* nCols_origin, int** constraint_new_idx, + int* constraint_type); + +cupdlp_int getCupdlpLogLevel(const HighsOptions& options); + +#endif diff --git a/src/pdlp/cupdlp/Diff b/src/pdlp/cupdlp/Diff new file mode 100755 index 0000000000..4df14f428e --- /dev/null +++ b/src/pdlp/cupdlp/Diff @@ -0,0 +1,12 @@ +diff cupdlp_restart.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_restart.h +diff cupdlp_restart.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_restart.c +diff cupdlp_proj.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_proj.h +diff cupdlp_proj.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_proj.c +diff cupdlp_cs.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_cs.h +diff cupdlp_cs.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_cs.c +diff cupdlp_scaling_cuda.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_scaling_cuda.h +diff cupdlp_scaling_cuda.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_scaling_cuda.c +diff cupdlp_solver.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_solver.h +diff cupdlp_solver.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_solver.c +diff cupdlp_utils.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_utils.h +diff cupdlp_step.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_step.h diff --git a/src/pdlp/cupdlp/Meld b/src/pdlp/cupdlp/Meld new file mode 100755 index 0000000000..f49c14e1bc --- /dev/null +++ b/src/pdlp/cupdlp/Meld @@ -0,0 +1,7 @@ +meld cupdlp.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp.h +meld glbopts.h /home/jajhall/cuPDLP-C/cupdlp/glbopts.h +meld cupdlp_defs.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_defs.h +meld cupdlp_step.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_step.c +meld cupdlp_utils.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_utils.c +meld cupdlp_linalg.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_linalg.h +meld cupdlp_linalg.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_linalg.c diff --git a/src/pdlp/cupdlp/Merge b/src/pdlp/cupdlp/Merge new file mode 100755 index 0000000000..644952a955 --- /dev/null +++ b/src/pdlp/cupdlp/Merge @@ -0,0 +1,2 @@ +merge cupdlp.h glbopts.h cupdlp_defs.h cupdlp_step.c cupdlp_utils.c cupdlp_linalg.h cupdlp_linalg.c > merge.c +merge /home/jajhall/cuPDLP-C/cupdlp/cupdlp.h /home/jajhall/cuPDLP-C/cupdlp/glbopts.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_defs.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_step.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_utils.c /home/jajhall/cuPDLP-C/cupdlp/cupdlp_linalg.h /home/jajhall/cuPDLP-C/cupdlp/cupdlp_linalg.c > cuPDLP-C_merge.c diff --git a/src/pdlp/cupdlp/README.md b/src/pdlp/cupdlp/README.md new file mode 100644 index 0000000000..f03ba54b03 --- /dev/null +++ b/src/pdlp/cupdlp/README.md @@ -0,0 +1,121 @@ +# cuPDLP-C observations + +This directory contains files from [cuPDLP-C v0.3.0](https://github.com/COPT-Public/cuPDLP-C/tree/v0.3.0). Below are some issues experienced when integrating them into HiGHS. + +## Termination of cuPDLP-C + +cuPDLP-C terminates when either the current or averaged iterates satisfy primal/dual feasibility, using a 2-norm measure relative to the size of the RHS/costs, and after scaling the LP. + +HiGHS assesses primal/dual feasibility using a infinity-norm absolute measure for the unscaled LP. Thus the cuPDLP-C result frequently fails to satisfy HiGHS primal/dual feasibility. To get around this partially, `iInfNormAbsLocalTermination` has been introduced into cuPDLP-C. + +By default, `iInfNormAbsLocalTermination` is false, so that the original cuPDLP-C termination criteria are used. + +When `iInfNormAbsLocalTermination` is true, cuPDLP-C terminates only when primal/dual feasibility is satisfied for the infinity-norm absolute measure of the current iterate, so that HiGHS primal/dual feasibility is satisfied. + +However, the cuPDLP-C scaling may still result in the HiGHS tolerances not being satisfied. Users can inspect `HighsInfo` values for the maximum and sum of infeasibilities, and the new `HighsInfo` values measuring the maximum and sum of complementarity violations. + +## Preprocessing issue + +The following line is not recognised by g++, + +> #if !(CUPDLP_CPU) + +so I've had to replace all ocurrences by + +> #ifndef CUPDLP_CPU + +This yields a compiler warning about "extra tokens at end of #ifndef +directive" in the case of the following, but it's not a problem for +now, as CUPDLP_CPU is set + +> #ifndef CUPDLP_CPU & USE_KERNELS + +## cmake issues + +CUPDLP_CPU and CUPDLP_DEBUG should both set when building. However, they are not recognised so are forced by the following lines in cupdlp_defs.h + +#define CUPDLP_CPU +#define CUPDLP_DEBUG (1) + +## Use of macro definitions within C++ + +When definitions in [glbopts.h](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/cupdlp/glbopts.h) such as the following are used in [CupdlpWrapper.cpp](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/CupdlpWrapper.cpp) there is a g++ compiler error, because `typeof` isn't recognised + +> #define CUPDLP_INIT(var, size) \ + { \ + (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } + +Hence there is a set of type-specific definitions in `CupdlpWrapper.h`, such as + +>#define cupdlp_init_double(var, size)\ + {\ + (var) = (double*)malloc((size) * sizeof(double));\ + } + +## C methods not picked up by g++ + +Three methods +* `double infNorm(double *x, cupdlp_int n);` +* `void cupdlp_haslb(cupdlp_float *haslb, const cupdlp_float *lb, const cupdlp_float bound, const cupdlp_int len);` +* `void cupdlp_hasub(cupdlp_float *hasub, const cupdlp_float *ub, const cupdlp_float bound, const cupdlp_int len);` + +are declared in [cupdlp_linalg.h](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/cupdlp/cupdlp_linalg.h) and defined in [cupdlp_linalg.c](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/cupdlp/cupdlp_linalg.c) but not picked up by g++. Hence duplicate methods are declared and defined in [CupdlpWrapper.h](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/CupdlpWrapper.h) and [CupdlpWrapper.cpp](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/CupdlpWrapper.cpp). + +## Use of macro definitions within C + +Although the macro definitions in [glbopts.h](https://github.com/ERGO-Code/HiGHS/blob/add-pdlp/src/pdlp/cupdlp/glbopts.h) are fine when used in C under Linux, they cause the following compiler errors on Windows. + +> error C2146: syntax error: missing ';' before identifier 'calloc' (or 'malloc') + +In HiGHS, all the macros using `typeof` have been replaced by multiple type-specific macros + +## Problem with sys/time.h + +The HiGHS branch add-pdlp compiles and runs fine on @jajhall's Linux machine, but CI tests on GitHub fail utterly due to `sys/time.h` not being found. Until this is fixed, or HiGHS passes its own timer for use within `cuPDLP-c`, timing within `cuPDLP-c` can be disabled using the compiler directive `CUPDLP_TIMER`. By default this is defined, so the `cuPDLP-c` is retained. + +## Controlling the `cuPDLP-c` logging + +As a research code, `cuPDLP-c` naturally produces a lot of logging output. HiGHS must be able to run with less logging output, or completely silently. This is achieved using the `nLogLevel` parameter in `cuPDLP-c`. + +By default, `nLogLevel` is 2, so all the original `cuPDLP-c` logging is produced. + +* If `nLogLevel` is 1, then the `cuPDLP-c` logging is less verbose +* If `nLogLevel` is 0, then there is no `cuPDLP-c` logging + +A related issue is the use of `fp` and `fp_sol`. HiGHS won't be using these, so sets them to null pointers. `cuPDLP-c` already doesn't print the solution if `fp_sol` is a null pointer, so the call to `writeJson(fp, pdhg);` is now conditional on `if (fp)`. + +## Handling infeasible or unbounded problems + +`cuPDLP-c` now terminates with status `INFEASIBLE_OR_UNBOUNDED` for the infeasible and unbounded LPs in unit tests `pdlp-infeasible-lp` and `pdlp-unbounded-lp` in `highs/check/TestPdlp.cpp`. In the case of the unbounded LP, PDLP identifies a primal feasible point, so unboundedness can be deduced. This is done in `HighsSolve.cpp:131. + +## Returning the iteration count + +The `cuPDLP-c` iteration count is held in `pdhg->timers->nIter`, but `pdhg` is destroyed in `LP_SolvePDHG`, so `cupdlp_int* num_iter` has been added to the parameter list of this method. + +## To be done + +- Make CupldlpWrapper.cpp look more like C++ than C + ++## Using a GPU ++ ++### Install CUDA ++ ++* sudo apt update && sudo apt upgrade ++* sudo apt autoremove nvidia* --purge ++* sudo apt update && sudo apt upgrade ++* nvcc --version ++* sudo apt install nvidia-cuda-toolkit ++ ++### Building PDLP ++ ++export HIGHS_HOME=/home/jajhall/install ++export CUDA_HOME=/usr/lib/cuda ++ ++ ++ ++ diff --git a/src/pdlp/cupdlp/cupdlp.h b/src/pdlp/cupdlp/cupdlp.h new file mode 100644 index 0000000000..5ba569e6d4 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp.h @@ -0,0 +1,16 @@ + +#ifndef CUPDLP_H +#define CUPDLP_H + +#include "cupdlp_cs.h" +#include "cupdlp_defs.h" +#include "cupdlp_linalg.h" +#include "cupdlp_proj.h" +#include "cupdlp_restart.h" +#include "cupdlp_scaling_cuda.h" +#include "cupdlp_solver.h" +#include "cupdlp_step.h" +#include "cupdlp_utils.h" +#include "glbopts.h" + +#endif diff --git a/src/pdlp/cupdlp/cupdlp_cs.c b/src/pdlp/cupdlp/cupdlp_cs.c new file mode 100644 index 0000000000..cf10cf78cb --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_cs.c @@ -0,0 +1,211 @@ +#include "cupdlp_cs.h" + +/* CSparse routines for reading inputs. Referenced from Tim Davis Suite Sparse + */ +void *cupdlp_dcs_malloc(int n, size_t size) { + return (malloc(MAX(n, 1) * size)); +} + +/* wrapper for calloc */ +void *cupdlp_dcs_calloc(int n, size_t size) { + return (calloc(MAX(n, 1), size)); +} + +/* wrapper for free */ +void *cupdlp_dcs_free(void *p) { + if (p) free(p); /* free p if it is not already NULL */ + return (NULL); /* return NULL to simplify the use of dcupdlp_dcs_free */ +} + +/* wrapper for realloc */ +void *cupdlp_dcs_realloc(void *p, int n, size_t size, int *ok) { + void *pnew; + pnew = realloc(p, MAX(n, 1) * size); /* realloc the block */ + *ok = (pnew != NULL); /* realloc fails if pnew is NULL */ + return ((*ok) ? pnew : p); /* return original p if failure */ +} + +cupdlp_dcs *cupdlp_dcs_spalloc(int m, int n, int nzmax, int values, + int triplet) { + cupdlp_dcs *A = cupdlp_dcs_calloc( + 1, sizeof(cupdlp_dcs)); /* allocate the cupdlp_dcs struct */ + if (!A) return (NULL); /* out of memory */ + A->m = m; /* define dimensions and nzmax */ + A->n = n; + A->nzmax = nzmax = MAX(nzmax, 1); + A->nz = triplet ? 0 : -1; /* allocate triplet or comp.col */ + A->p = cupdlp_dcs_malloc(triplet ? nzmax : n + 1, sizeof(int)); + A->i = cupdlp_dcs_malloc(nzmax, sizeof(int)); + A->x = values ? cupdlp_dcs_malloc(nzmax, sizeof(double)) : NULL; + return ((!A->p || !A->i || (values && !A->x)) ? cupdlp_dcs_spfree(A) : A); +} + +/* change the max # of entries sparse matrix */ +int cupdlp_dcs_sprealloc(cupdlp_dcs *A, int nzmax) { + int ok, oki, okj = 1, okx = 1; + if (!A) return (0); + if (nzmax <= 0) nzmax = IS_CSC(A) ? (A->p[A->n]) : A->nz; + nzmax = MAX(nzmax, 1); + A->i = cupdlp_dcs_realloc(A->i, nzmax, sizeof(int), &oki); + if (IS_TRIPLET(A)) A->p = cupdlp_dcs_realloc(A->p, nzmax, sizeof(int), &okj); + if (A->x) A->x = cupdlp_dcs_realloc(A->x, nzmax, sizeof(double), &okx); + ok = (oki && okj && okx); + if (ok) A->nzmax = nzmax; + return (ok); +} + +/* free a sparse matrix */ +cupdlp_dcs *cupdlp_dcs_spfree(cupdlp_dcs *A) { + if (!A) return (NULL); /* do nothing if A already NULL */ + cupdlp_dcs_free(A->p); + cupdlp_dcs_free(A->i); + cupdlp_dcs_free(A->x); + return ((cupdlp_dcs *)cupdlp_dcs_free( + A)); /* free the cupdlp_dcs struct and return NULL */ +} + +/* free workspace and return a sparse matrix result */ +cupdlp_dcs *cupdlp_dcs_done(cupdlp_dcs *C, void *w, void *x, int ok) { + cupdlp_dcs_free(w); /* free workspace */ + cupdlp_dcs_free(x); + return (ok ? C + : cupdlp_dcs_spfree(C)); /* return result if OK, else free it */ +} + +double cupdlp_dcs_cumsum(int *p, int *c, int n) { + int i, nz = 0; + double nz2 = 0; + if (!p || !c) return (-1); /* check inputs */ + for (i = 0; i < n; i++) { + p[i] = nz; + nz += c[i]; + nz2 += c[i]; /* also in double to avoid int overflow */ + c[i] = p[i]; /* also copy p[0..n-1] back into c[0..n-1]*/ + } + p[n] = nz; + return (nz2); /* return sum (c [0..n-1]) */ +} + +/* add an entry to a triplet matrix; return 1 if ok, 0 otherwise */ +int cupdlp_dcs_entry(cupdlp_dcs *T, int i, int j, double x) { + if (!IS_TRIPLET(T) || i < 0 || j < 0) return (0); /* check inputs */ + if (T->nz >= T->nzmax && !cupdlp_dcs_sprealloc(T, 2 * (T->nzmax))) return (0); + if (T->x) T->x[T->nz] = x; + T->i[T->nz] = i; + T->p[T->nz++] = j; + T->m = MAX(T->m, i + 1); + T->n = MAX(T->n, j + 1); + return (1); +} + +cupdlp_dcs *cupdlp_dcs_compress(const cupdlp_dcs *T) { + int m, n, nz, p, k, *Cp, *Ci, *w, *Ti, *Tj; + double *Cx, *Tx; + cupdlp_dcs *C; + if (!IS_TRIPLET(T)) return (NULL); /* check inputs */ + m = T->m; + n = T->n; + Ti = T->i; + Tj = T->p; + Tx = T->x; + nz = T->nz; + C = cupdlp_dcs_spalloc(m, n, nz, Tx != NULL, 0); /* allocate result */ + w = cupdlp_dcs_calloc(n, sizeof(int)); /* get workspace */ + if (!C || !w) return (cupdlp_dcs_done(C, w, NULL, 0)); /* out of memory */ + Cp = C->p; + Ci = C->i; + Cx = C->x; + for (k = 0; k < nz; k++) w[Tj[k]]++; /* column counts */ + cupdlp_dcs_cumsum(Cp, w, n); /* column pointers */ + for (k = 0; k < nz; k++) { + Ci[p = w[Tj[k]]++] = Ti[k]; /* A(i,j) is the pth entry in C */ + if (Cx) Cx[p] = Tx[k]; + } + return (cupdlp_dcs_done(C, w, NULL, 1)); /* success; free w and return C */ +} + +double cupdlp_dcs_norm(const cupdlp_dcs *A) { + int p, j, n, *Ap; + double *Ax; + double nrm = 0, s; + if (!IS_CSC(A) || !A->x) return (-1); /* check inputs */ + n = A->n; + Ap = A->p; + Ax = A->x; + for (j = 0; j < n; j++) { + for (s = 0, p = Ap[j]; p < Ap[j + 1]; p++) s += fabs(Ax[p]); + nrm = MAX(nrm, s); + } + return (nrm); +} + +int cupdlp_dcs_print(const cupdlp_dcs *A, int brief) { + int p, j, m, n, nzmax, nz, *Ap, *Ai; + double *Ax; + if (!A) { + printf("(null)\n"); + return (0); + } + m = A->m; + n = A->n; + Ap = A->p; + Ai = A->i; + Ax = A->x; + nzmax = A->nzmax; + nz = A->nz; + if (nz < 0) { + printf("%g-by-%g, nzmax: %g nnz: %g, 1-norm: %g\n", (double)m, (double)n, + (double)nzmax, (double)(Ap[n]), cupdlp_dcs_norm(A)); + for (j = 0; j < n; j++) { + printf(" col %g : locations %g to %g\n", (double)j, (double)(Ap[j]), + (double)(Ap[j + 1] - 1)); + for (p = Ap[j]; p < Ap[j + 1]; p++) { + printf(" %g : ", (double)(Ai[p])); + printf("%50.50e \n", Ax ? Ax[p] : 1); + if (brief && p > 20) { + printf(" ...\n"); + return (1); + } + } + } + } else { + printf("triplet: %g-by-%g, nzmax: %g nnz: %g\n", (double)m, (double)n, + (double)nzmax, (double)nz); + for (p = 0; p < nz; p++) { + printf(" %g %g : ", (double)(Ai[p]), (double)(Ap[p])); + printf("%g\n", Ax ? Ax[p] : 1); + if (brief && p > 20) { + printf(" ...\n"); + return (1); + } + } + } + return (1); +} + +cupdlp_dcs *cupdlp_dcs_transpose(const cupdlp_dcs *A, int values) { + cupdlp_int p, q, j, *Cp, *Ci, n, m, *Ap, *Ai, *w; + double *Cx, *Ax; + cupdlp_dcs *C; + if (!IS_CSC(A)) return (NULL); /* check inputs */ + m = A->m; + n = A->n; + Ap = A->p; + Ai = A->i; + Ax = A->x; + C = cupdlp_dcs_spalloc(n, m, Ap[n], values && Ax, 0); /* allocate result */ + w = cupdlp_calloc(m, sizeof(cupdlp_int)); /* get workspace */ + if (!C || !w) return (cupdlp_dcs_done(C, w, NULL, 0)); /* out of memory */ + Cp = C->p; + Ci = C->i; + Cx = C->x; + for (p = 0; p < Ap[n]; p++) w[Ai[p]]++; /* row counts */ + cupdlp_dcs_cumsum(Cp, w, m); /* row pointers */ + for (j = 0; j < n; j++) { + for (p = Ap[j]; p < Ap[j + 1]; p++) { + Ci[q = w[Ai[p]]++] = j; /* place A(i,j) as entry C(j,i) */ + if (Cx) Cx[q] = Ax[p]; + } + } + return (cupdlp_dcs_done(C, w, NULL, 1)); /* success; free w and return C */ +} diff --git a/src/pdlp/cupdlp/cupdlp_cs.h b/src/pdlp/cupdlp/cupdlp_cs.h new file mode 100644 index 0000000000..2902010eb1 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_cs.h @@ -0,0 +1,41 @@ +#ifndef CUPDLP_CS_H +#define CUPDLP_CS_H + +#include "cupdlp_defs.h" + +/* sparse matrix in column-oriented form used in reading mps*/ +typedef struct cupdlp_cs_sparse { + int nzmax; + int m; /* number of rows */ + int n; /* number of columns */ + int *p; /* column pointers (size n+1) or col indices (size nzmax) */ + int *i; /* row indices, size nzmax */ + double *x; /* numerical values, size nzmax */ + int nz; /* # of entries in triplet matrix, -1 for compressed-col */ +} cupdlp_dcs; + +int cupdlp_dcs_entry(cupdlp_dcs *T, int i, int j, double x); +cupdlp_dcs *cupdlp_dcs_compress(const cupdlp_dcs *T); +double cupdlp_dcs_norm(const cupdlp_dcs *A); +int cupdlp_dcs_print(const cupdlp_dcs *A, int brief); + +/* utilities */ +void *_dcs_calloc(int n, size_t size); +void *cupdlp_dcs_free(void *p); +void *cupdlp_dcs_realloc(void *p, int n, size_t size, int *ok); +cupdlp_dcs *cupdlp_dcs_spalloc(int m, int n, int nzmax, int values, int t); +cupdlp_dcs *cupdlp_dcs_spfree(cupdlp_dcs *A); +int cupdlp_dcs_sprealloc(cupdlp_dcs *A, int nzmax); +void *cupdlp_dcs_malloc(int n, size_t size); + +/* utilities */ +double cupdlp_dcs_cumsum(int *p, int *c, int n); +cupdlp_dcs *cupdlp_dcs_done(cupdlp_dcs *C, void *w, void *x, int ok); +int *cupdlp_dcs_idone(int *p, cupdlp_dcs *C, void *w, int ok); +cupdlp_dcs *cupdlp_dcs_transpose(const cupdlp_dcs *A, int values); + +#define IS_CSC(A) (A && (A->nz == -1)) +#define IS_TRIPLET(A) (A && (A->nz >= 0)) +/*--------------------------------------------------------------------------*/ + +#endif diff --git a/src/pdlp/cupdlp/cupdlp_defs.h b/src/pdlp/cupdlp/cupdlp_defs.h new file mode 100644 index 0000000000..068c150935 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_defs.h @@ -0,0 +1,423 @@ +#ifndef CUPDLP_H_GUARD +#define CUPDLP_H_GUARD + +#define CUPDLP_CPU +#define CUPDLP_DEBUG (0) +#define CUPDLP_TIMER + +#ifndef CUPDLP_CPU +#include "cuda/cupdlp_cuda_kernels.cuh" +#include "cuda/cupdlp_cudalinalg.cuh" +#endif +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "glbopts.h" + +#define PDHG_USE_TIMERS (1) +#define USE_MY_BLAS (1) +#define USE_KERNELS (1) + +#define PDHG_STEPSIZE_REDUCTION_EXP \ + (0.3) // Parameters in PDLP adaptive linesearch +#define PDHG_STEPSIZE_GROWTH_EXP (0.6) +#define PDHG_USE_TIMERS (1) +#define PDHG_DISPLAY_TERMINATION_CHECK (1) + +#define PDHG_PROJECT_INITIAL_X (0) +#define SHOW_DEFINE(x) printf("%s=%s\n", #x, STR(x)) + +#define CUPDLP_DEBUG_INTERVAL (40) +#define CUPDLP_RELEASE_INTERVAL (40) +#define CUPDLP_DUMP_ITERATES_STATS (1) +#define CUPDLP_DUMP_LINESEARCH_STATS (1) +#define CUPDLP_INEXACT_EPS (1e-4) + +typedef struct CUPDLP_CUDA_DENSE_VEC CUPDLPvec; +typedef struct CUPDLP_DENSE_MATRIX CUPDLPdense; +typedef struct CUPDLP_CSR_MATRIX CUPDLPcsr; +typedef struct CUPDLP_CSC_MATRIX CUPDLPcsc; +typedef struct CUPDLP_DATA CUPDLPdata; +typedef struct CUPDLP_SETTINGS CUPDLPsettings; +typedef struct CUPDLP_PROBLEM CUPDLPproblem; +typedef struct CUPDLP_RES_OBJ CUPDLPresobj; +typedef struct CUPDLP_ITERATES CUPDLPiterates; +typedef struct CUPDLP_STEPSIZE CUPDLPstepsize; +typedef struct CUPDLP_SCALING CUPDLPscaling; +typedef struct CUPDLP_TIMERS CUPDLPtimers; +typedef struct CUPDLP_WORK CUPDLPwork; +typedef cupdlp_int cupdlp_retcode; + +typedef enum { + OPTIMAL = 0, + INFEASIBLE, + UNBOUNDED, + INFEASIBLE_OR_UNBOUNDED, + TIMELIMIT_OR_ITERLIMIT, + FEASIBLE, +} termination_code; + +typedef enum { + LAST_ITERATE = 0, + AVERAGE_ITERATE, +} termination_iterate; + +typedef enum { + PDHG_FIXED_LINESEARCH = 0, + PDHG_MALITSKY_POCK_LINESEARCH, + PDHG_ADAPTIVE_LINESEARCH +} pdhg_linesearch; + +typedef enum { + PDHG_WITHOUT_RESTART = 0, + PDHG_GPU_RESTART, + PDHG_CPU_RESTART, +} pdhg_restart; + +typedef enum { + CPU = 0, + SINGLE_GPU, + MULTI_GPU, +} CUPDLP_DEVICE; + +typedef enum { + DENSE = 0, + CSR, + CSC, + CSR_CSC, +} CUPDLP_MATRIX_FORMAT; + +typedef enum { + N_ITER_LIM = 0, + IF_SCALING, + I_SCALING_METHOD, + E_LINE_SEARCH_METHOD, + E_RESTART_METHOD, + IF_RUIZ_SCALING, + IF_L2_SCALING, + IF_PC_SCALING, + N_LOG_LEVEL, + N_LOG_INTERVAL, + IF_PRESOLVE, + I_INF_NORM_ABS_LOCAL_TERMINATION, + N_INT_USER_PARAM +} CUPDLP_INT_USER_PARAM_INDEX; + //#define N_INT_USER_PARAM 12 +typedef enum { + D_SCALING_LIMIT = 0, + D_PRIMAL_TOL, + D_DUAL_TOL, + D_GAP_TOL, + D_FEAS_TOL, + D_TIME_LIM, + N_FLOAT_USER_PARAM +} CUPDLP_FLOAT_USER_PARAM_INDEX; + //#define N_FLOAT_USER_PARAM 6 + +// used in sparse matrix-dense vector multiplication +struct CUPDLP_CUDA_DENSE_VEC { + cupdlp_int len; + cupdlp_float *data; +#ifndef CUPDLP_CPU + cusparseDnVecDescr_t cuda_vec; +#endif +}; + +struct CUPDLP_DENSE_MATRIX { + cupdlp_int nRows; + cupdlp_int nCols; + cupdlp_float *data; +}; + +struct CUPDLP_CSR_MATRIX { + cupdlp_int nRows; + cupdlp_int nCols; + cupdlp_int nMatElem; + cupdlp_int *rowMatBeg; + cupdlp_int *rowMatIdx; + cupdlp_float *rowMatElem; +#ifndef CUPDLP_CPU + // Pointers to GPU vectors + cusparseSpMatDescr_t cuda_csr; +#endif +}; + +struct CUPDLP_CSC_MATRIX { + cupdlp_int nRows; + cupdlp_int nCols; + cupdlp_int nMatElem; + cupdlp_int *colMatBeg; + cupdlp_int *colMatIdx; + cupdlp_float *colMatElem; + + // Used to avoid implementing NormInf on cuda + cupdlp_float MatElemNormInf; +#ifndef CUPDLP_CPU + // Pointers to GPU vectors + cusparseSpMatDescr_t cuda_csc; +#endif +}; + +struct CUPDLP_DATA { + cupdlp_int nRows; + cupdlp_int nCols; + CUPDLP_MATRIX_FORMAT matrix_format; + CUPDLPdense *dense_matrix; + CUPDLPcsr *csr_matrix; + CUPDLPcsc *csc_matrix; + CUPDLP_DEVICE device; +}; + +struct CUPDLP_SETTINGS { + // scaling + cupdlp_int ifScaling; + cupdlp_int iScalingMethod; + cupdlp_float dScalingLimit; + + // termination criteria + cupdlp_float dPrimalTol; + cupdlp_float dDualTol; + cupdlp_float dGapTol; + cupdlp_int iInfNormAbsLocalTermination; + + // max iter and time + cupdlp_int nIterLim; + cupdlp_float dTimeLim; + + // Logging + cupdlp_int nLogLevel; + cupdlp_int nLogInterval; + + // restart + pdhg_restart eRestartMethod; +}; + +// some elements are duplicated from CUPDLP_DATA +struct CUPDLP_PROBLEM { + /* Copy of LP problem with permuted columns. */ + CUPDLPdata *data; + // cupdlp_int nMatElem; + // cupdlp_int *colMatBeg; + // cupdlp_int *colMatIdx; + // cupdlp_float *colMatElem; + // cupdlp_int *rowMatBeg; + // cupdlp_int *rowMatIdx; + // cupdlp_float *rowMatElem; + cupdlp_float *lower; + cupdlp_float *upper; + cupdlp_float *cost; // cost for minimization + cupdlp_float *rhs; + cupdlp_float dMaxCost; + cupdlp_float dMaxRhs; + cupdlp_float dMaxRowBound; + cupdlp_int nRows; + cupdlp_int nCols; + cupdlp_int nEqs; + cupdlp_float *hasLower; + cupdlp_float *hasUpper; + cupdlp_float + offset; // true objVal = c'x * sig + offset, sig = 1 (min) or -1 (max) + cupdlp_float sense_origin; // sig = 1 (min) or -1 (max) +}; + +struct CUPDLP_RES_OBJ { + /* residuals and objectives */ + cupdlp_float dFeasTol; + cupdlp_float dPrimalObj; + cupdlp_float dDualObj; + cupdlp_float dDualityGap; + cupdlp_float dComplementarity; + cupdlp_float dPrimalFeas; + cupdlp_float dDualFeas; + cupdlp_float dRelObjGap; + cupdlp_float *primalResidual; + cupdlp_float *dualResidual; + cupdlp_float *dSlackPos; + cupdlp_float *dSlackNeg; + cupdlp_float *dSlackPosAverage; + cupdlp_float *dSlackNegAverage; + cupdlp_float *dLowerFiltered; + cupdlp_float *dUpperFiltered; + + /* for infeasibility detection */ + termination_code primalCode; + termination_code dualCode; + termination_iterate termInfeasIterate; + + cupdlp_float dPrimalInfeasObj; + cupdlp_float dDualInfeasObj; + cupdlp_float dPrimalInfeasRes; + cupdlp_float dDualInfeasRes; + + cupdlp_float dPrimalInfeasObjAverage; + cupdlp_float dDualInfeasObjAverage; + cupdlp_float dPrimalInfeasResAverage; + cupdlp_float dDualInfeasResAverage; + + // buffers + cupdlp_float *primalInfeasRay; // x / norm(x) + cupdlp_float *primalInfeasConstr; // [Ax, min(Gx, 0)] + cupdlp_float *primalInfeasBound; // primal bound violation + cupdlp_float *dualInfeasRay; // y / norm(y, lbd) + cupdlp_float *dualInfeasLbRay; // lbd^+ / norm(y, lbd) + cupdlp_float *dualInfeasUbRay; // lbd^- / norm(y, lbd) + cupdlp_float *dualInfeasConstr; // ATy1 + GTy2 + lambda + // cupdlp_float *dualInfeasBound; // dual bound violation + + cupdlp_float dPrimalObjAverage; + cupdlp_float dDualObjAverage; + cupdlp_float dDualityGapAverage; + cupdlp_float dComplementarityAverage; + cupdlp_float dPrimalFeasAverage; + cupdlp_float dDualFeasAverage; + cupdlp_float dRelObjGapAverage; + cupdlp_float *primalResidualAverage; + cupdlp_float *dualResidualAverage; + + cupdlp_float dPrimalFeasLastRestart; + cupdlp_float dDualFeasLastRestart; + cupdlp_float dDualityGapLastRestart; + + cupdlp_float dPrimalFeasLastCandidate; + cupdlp_float dDualFeasLastCandidate; + cupdlp_float dDualityGapLastCandidate; + + termination_code termCode; + termination_iterate termIterate; +}; + +struct CUPDLP_ITERATES { + /* iterates */ + cupdlp_int nRows; + cupdlp_int nCols; + // todo, CPU VERSION, check + // cupdlp_float *x; + // cupdlp_float *y; + // cupdlp_float *xUpdate; + // cupdlp_float *yUpdate; + // + // cupdlp_int iLastRestartIter; + // cupdlp_float dLastRestartDualityGap; + // cupdlp_float dLastRestartBeta; + // cupdlp_float *xSum; + // cupdlp_float *ySum; + // cupdlp_float *xAverage; + // cupdlp_float *yAverage; + // cupdlp_float *xLastRestart; + // cupdlp_float *yLastRestart; + // + // cupdlp_float *ax; + // cupdlp_float *axUpdate; + // cupdlp_float *axAverage; + // cupdlp_float *aty; + // cupdlp_float *atyUpdate; + // cupdlp_float *atyAverage; + + cupdlp_int iLastRestartIter; + cupdlp_float dLastRestartDualityGap; + cupdlp_float dLastRestartBeta; + cupdlp_float *xSum; + cupdlp_float *ySum; + + cupdlp_float *xLastRestart; + cupdlp_float *yLastRestart; + + CUPDLPvec *x, *xUpdate, *xAverage, *y, *yUpdate, *yAverage, *ax, *axUpdate, + *axAverage, *aty, *atyUpdate, *atyAverage; +}; + +struct CUPDLP_STEPSIZE { + /* stepsize */ + pdhg_linesearch eLineSearchMethod; // 0 = FixedStep + cupdlp_float dPrimalStep; + cupdlp_float dDualStep; + cupdlp_float dSumPrimalStep; + cupdlp_float dSumDualStep; + // Stepsize ratio, + // \beta = dBeta = dDualStep / dPrimalStep, + // in the paper, primal weight is the \omega: + // \omega = \sqrt\beta + cupdlp_float dBeta; + cupdlp_float dTheta; // Used in Malitsky-Pock stepsize + cupdlp_int nStepSizeIter; +}; + +struct CUPDLP_SCALING { + /* scaling */ + cupdlp_int ifScaled; + cupdlp_float *rowScale; + cupdlp_float *colScale; + + /*new scaling*/ + cupdlp_int ifRuizScaling; + cupdlp_int ifL2Scaling; + cupdlp_int ifPcScaling; + cupdlp_int RuizTimes; + cupdlp_float RuizNorm; + cupdlp_float PcAlpha; + + /* original 2 norm */ + cupdlp_float dNormCost; + cupdlp_float dNormRhs; +}; + +struct CUPDLP_TIMERS { + /* timers */ + cupdlp_int nIter; + cupdlp_float dSolvingTime; + cupdlp_float dSolvingBeg; + cupdlp_float dScalingTime; + cupdlp_float dPresolveTime; +#if PDHG_USE_TIMERS + cupdlp_float dAtyTime; + cupdlp_float dAxTime; + cupdlp_float dComputeResidualsTime; + cupdlp_float dUpdateIterateTime; + cupdlp_int nAtyCalls; + cupdlp_int nAxCalls; + cupdlp_int nComputeResidualsCalls; + cupdlp_int nUpdateIterateCalls; +#endif +#ifndef CUPDLP_CPU + // GPU timers + cupdlp_float AllocMem_CopyMatToDeviceTime; + cupdlp_float CopyVecToDeviceTime; + cupdlp_float DeviceMatVecProdTime; + cupdlp_float CopyVecToHostTime; + cupdlp_float FreeDeviceMemTime; + cupdlp_float CudaPrepareTime; +#endif +}; + +struct CUPDLP_WORK { + CUPDLPproblem *problem; + CUPDLPsettings *settings; + CUPDLPresobj *resobj; + CUPDLPiterates *iterates; + CUPDLPstepsize *stepsize; + CUPDLPscaling *scaling; + CUPDLPtimers *timers; + // cupdlp_float *buffer; + CUPDLPvec *buffer; + cupdlp_float *buffer2; + cupdlp_float *buffer3; + + cupdlp_float *rowScale; + cupdlp_float *colScale; +#ifndef CUPDLP_CPU + // CUDAmv *MV; + cusparseHandle_t cusparsehandle; + void *dBuffer; + // cusparseDnVecDescr_t vecbuffer; + cublasHandle_t cublashandle; +#endif +}; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/pdlp/cupdlp/cupdlp_linalg.c b/src/pdlp/cupdlp/cupdlp_linalg.c new file mode 100644 index 0000000000..8d28043fab --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_linalg.c @@ -0,0 +1,803 @@ + +#include "cupdlp_linalg.h" + +/** + * The function `ScatterCol` performs a scatter operation on a specific + * column of a matrix. + * + * @param pdhg A pointer to a structure of type pdhg. + * @param iCol The parameter "iCol" represents the index of the column in the + * matrix that we want to scatter. + * @param multiplier The multiplier is a scalar value that is multiplied with + * each element in the column of the matrix. It is used to scale the values + * before adding them to the target array. + * @param target The "target" parameter is a pointer to a cupdlp_float array + * where the scattered column values will be added. + */ +void ScatterCol(CUPDLPwork *w, cupdlp_int iCol, cupdlp_float multiplier, + cupdlp_float *target) { + CUPDLPcsc *matrix = w->problem->data->csc_matrix; + + for (cupdlp_int p = matrix->colMatBeg[iCol]; p < matrix->colMatBeg[iCol + 1]; + ++p) + target[matrix->colMatIdx[p]] += matrix->colMatElem[p] * multiplier; +} + +void ScatterRow(CUPDLPwork *w, cupdlp_int iRow, cupdlp_float multiplier, + cupdlp_float *target) { + CUPDLPcsr *matrix = w->problem->data->csr_matrix; + + for (cupdlp_int p = matrix->rowMatBeg[iRow]; p < matrix->rowMatBeg[iRow + 1]; + ++p) + target[matrix->rowMatIdx[p]] += matrix->rowMatElem[p] * multiplier; +} + +void AxCPU(CUPDLPwork *w, cupdlp_float *ax, const cupdlp_float *x) { + // #if PDHG_USE_TIMERS + // ++w->timers->nAxCalls; + // cupdlp_float dStartTime = getTimeStamp(); + // #endif + + CUPDLPproblem *lp = w->problem; + + /* no indentity currently + + FILL_ZERO(ax, lp->data->nRows); + + // [A I]*x + for (cupdlp_int iSeq = ncols, iRow = 0; iSeq < lp->nSeq; ++iSeq, ++iRow) + { + if ((pdhg->lower[iSeq] > -INFINITY) && (pdhg->upper[iSeq] < INFINITY)) + { + ax[iRow] = scaling->rowScale ? scaling->rowScale[iRow] * x[iSeq] : + x[iSeq]; + } + else + { + ax[iRow] = 0.0; + } + } + */ + + memset(ax, 0, sizeof(cupdlp_float) * lp->data->nRows); + + for (cupdlp_int iCol = 0; iCol < lp->data->nCols; ++iCol) { + ScatterCol(w, iCol, x[iCol], ax); + } + + // #if PDHG_USE_TIMERS + // w->timers->dAxTime += getTimeStamp() - dStartTime; + // #endif +} + +void ATyCPU(CUPDLPwork *w, cupdlp_float *aty, const cupdlp_float *y) { + // #if PDHG_USE_TIMERS + // ++w->timers->nAtyCalls; + // cupdlp_float dStartTime = getTimeStamp(); + // #endif + + CUPDLPproblem *lp = w->problem; + + /* no indentity currently + FILL_ZERO(aty, lp->nSeq); + + // [A I]'*y + for (cupdlp_int iSeq = ncols, iRow = 0; iSeq < lp->nSeq; ++iSeq, ++iRow) + { + ScatterRow(pdhg, iRow, y[iRow], aty); + + if ((pdhg->lower[iSeq] > -INFINITY) && (pdhg->upper[iSeq] < INFINITY)) + { + aty[iSeq] = (scaling->rowScale ? scaling->rowScale[iRow] * y[iRow] : + y[iRow]); + } + else + { + aty[iSeq] = 0.0; + } + } + */ + + memset(aty, 0, sizeof(cupdlp_float) * lp->data->nCols); + for (cupdlp_int iRow = 0; iRow < lp->data->nRows; ++iRow) { + ScatterRow(w, iRow, y[iRow], aty); + } + + // #if PDHG_USE_TIMERS + // w->timers->dAtyTime += getTimeStamp() - dStartTime; + // #endif +} + +double nrm2(cupdlp_int n, const double *x, cupdlp_int incx) { +#ifdef USE_MY_BLAS + assert(incx == 1); + + double nrm = 0.0; + + for (int i = 0; i < n; ++i) { + nrm += x[i] * x[i]; + } + + return sqrt(nrm); +#else + return dnrm2(n, x, incx); +#endif +} + +double nrminf(cupdlp_int n, const double *x, cupdlp_int incx) { +#ifdef USE_MY_BLAS + assert(incx == 1); + + double nrm = 0.0; + + for (int i = 0; i < n; ++i) { + double tmp = fabs(x[i]); + if (tmp > nrm) nrm = tmp; + } + + return nrm; +#else + return dnrm2(n, x, incx); +#endif +} + +cupdlp_int nrminfindex(cupdlp_int n, const double *x, cupdlp_int incx) { +#ifdef USE_MY_BLAS + assert(incx == 1); + + double nrm = 0.0; + cupdlp_int index = 0; + + for (int i = 0; i < n; ++i) { + double tmp = fabs(x[i]); + if (tmp > nrm) { + nrm = tmp; + index = i; + } + } + + return index; +#else + return dnrminfindex(n, x, incx); +#endif +} + +double twoNorm(double *x, cupdlp_int n) { return nrm2(n, x, 1); } + +double twoNormSquared(double *x, cupdlp_int n) { return pow(twoNorm(x, n), 2); } + +double infNorm(double *x, cupdlp_int n) { return nrminf(n, x, 1); } + +/*------------------------ new added --------------------*/ +double GenNorm(double *x, cupdlp_int n, cupdlp_float p) { + if (p == 2.0) { + return twoNorm(x, n); + } else if (p == INFINITY) { + return infNorm(x, n); + } else { + double nrm = 0.0; + + for (int i = 0; i < n; ++i) { + nrm += pow(fabs(x[i]), p); + } + + return pow(nrm, 1.0 / p); + } +} + +/* x = x .* y*/ +void cupdlp_cdot(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] *= y[i]; + } +} + +/* x = x ./ y*/ +void cupdlp_cdiv(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] /= y[i]; + } +} + +/* xout = weight * x */ +// void cupdlp_scaleVector(cupdlp_float *xout, cupdlp_float *x, cupdlp_float +// weight, const cupdlp_int len) +// { +// for (int i = 0; i < len; i++) +// { +// xout[i] = weight * x[i]; +// } +// } + +/* xout = max(x, lb), lb is vector */ +void cupdlp_projLowerBound(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = x[i] > lb[i] ? x[i] : lb[i]; + } +} + +/* xout = min(x, ub), ub is vector */ +void cupdlp_projUpperBound(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = x[i] < ub[i] ? x[i] : ub[i]; + } +} + +/* xout = max(x, lb), lb is number */ +void cupdlp_projSameLowerBound(cupdlp_float *x, const cupdlp_float lb, + const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = x[i] > lb ? x[i] : lb; + } +} + +/* xout = min(x, ub), ub is number */ +void cupdlp_projSameUpperBound(cupdlp_float *x, const cupdlp_float ub, + const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = x[i] < ub ? x[i] : ub; + } +} + +/* xout = max(x, 0) */ +void cupdlp_projPositive(cupdlp_float *x, const cupdlp_int len) { + cupdlp_projSameLowerBound(x, 0.0, len); +} + +/* xout = min(x, 0) */ +void cupdlp_projNegative(cupdlp_float *x, const cupdlp_int len) { + cupdlp_projSameUpperBound(x, 0.0, len); +} + +/* ||x - y||_2^2 */ +// cupdlp_float cupdlp_diffTwoNormSquared(cupdlp_float *x, cupdlp_float *y, +// const cupdlp_int len) +cupdlp_float diffTwoNormSquared(cupdlp_float *x, cupdlp_float *y, + const cupdlp_int len) { + cupdlp_float res = 0.0; + for (int i = 0; i < len; i++) { + cupdlp_float tmp = x[i] - y[i]; + res += tmp * tmp; + } + return res; +} + +/* ||x - y||_2 */ +// cupdlp_float cupdlp_diffTwoNorm(cupdlp_float *x, cupdlp_float *y, const +// cupdlp_int len) +cupdlp_float diffTwoNorm(cupdlp_float *x, cupdlp_float *y, + const cupdlp_int len) { + // return sqrt(cupdlp_diffTwoNormSquared(x, y, len)); + return sqrt(diffTwoNormSquared(x, y, len)); +} + +/* ||x - y||_inf */ +// cupdlp_float cupdlp_diffInfNorm(cupdlp_float *x, cupdlp_float *y, const +// cupdlp_int len) +cupdlp_float diffInfNorm(cupdlp_float *x, cupdlp_float *y, + const cupdlp_int len) { + cupdlp_float res = 0.0; + for (int i = 0; i < len; i++) { + cupdlp_float tmp = fabs(x[i] - y[i]); + if (tmp > res) res = tmp; + } + return res; +} + +/* (x1 - x2)' (y1 - y2) */ +// cupdlp_float cupdlp_diffDotDiff(cupdlp_float *x1, cupdlp_float *x2, +// cupdlp_float *y1, cupdlp_float *y2, const cupdlp_int len) +cupdlp_float diffDotDiff(cupdlp_float *x1, cupdlp_float *x2, cupdlp_float *y1, + cupdlp_float *y2, const cupdlp_int len) { + cupdlp_float x1y1 = dot(len, x1, 1, y1, 1); + cupdlp_float x2y2 = dot(len, x2, 1, y2, 1); + cupdlp_float x1y2 = dot(len, x1, 1, y2, 1); + cupdlp_float x2y1 = dot(len, x2, 1, y1, 1); + + return x1y1 - x1y2 - x2y1 + x2y2; +} + +/* x = x .* y */ +// void cupdlp_cdot_fb(cupdlp_float *x, const cupdlp_bool *y, const cupdlp_int +// len) +// { +// for (int i = 0; i < len; i++) +// { +// x[i] *= y[i]; +// } +// } + +/*------------------------ new added --------------------*/ + +double dot(cupdlp_int n, const cupdlp_float *x, cupdlp_int incx, const cupdlp_float *y, + cupdlp_int incy) { +#ifdef USE_MY_BLAS + assert(incx == 1 && incy == 1); + + double dres = 0.0; + + for (int i = 0; i < n; ++i) { + dres += x[i] * y[i]; + } + + return dres; +#else + return ddot(n, x, incx, y, incy); +#endif +} + +double Dotprod(const cupdlp_float *x, const cupdlp_float *y, cupdlp_int n) { + return dot(n, x, 1, y, 1); +} + +double Dotprod_Neumaier(const cupdlp_float *x, const cupdlp_float *y, cupdlp_int n) { + return dot(n, x, 1, y, 1); +} + +/* x = x + weight * y */ +void AddToVector(cupdlp_float *x, const cupdlp_float weight, + const cupdlp_float *y, const cupdlp_int n) { +#ifdef USE_MY_BLAS + + for (int i = 0; i < n; ++i) { + x[i] += weight * y[i]; + } + +#else + return ddot(n, x, incx, y, incy); +#endif +} + +/* x = weight * x */ +void ScaleVector(cupdlp_float weight, cupdlp_float *x, cupdlp_int n) { +#ifdef USE_MY_BLAS + + for (int i = 0; i < n; ++i) { + x[i] *= weight; + } + +#else + return ddot(n, x, incx, y, incy); +#endif +} + +void cupdlp_hasLower(cupdlp_float *haslb, const cupdlp_float *lb, + const cupdlp_float bound, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + haslb[i] = lb[i] > bound ? 1.0 : 0.0; + } +} + +void cupdlp_hasUpper(cupdlp_float *hasub, const cupdlp_float *ub, + const cupdlp_float bound, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + hasub[i] = ub[i] < bound ? 1.0 : 0.0; + } +} + +void cupdlp_filter_lower_bound(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_float bound, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = lb[i] > bound ? lb[i] : 0.0; + } +} + +void cupdlp_filter_upper_bound(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_float bound, const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = ub[i] < bound ? ub[i] : 0.0; + } +} + +void cupdlp_init_vector(cupdlp_float *x, const cupdlp_float val, + const cupdlp_int len) { + for (int i = 0; i < len; i++) { + x[i] = val; + } +} + +#ifndef CUPDLP_CPU + +void Ax_single_gpu(CUPDLPwork *w, cusparseDnVecDescr_t vecX, + cusparseDnVecDescr_t vecAx) { + cupdlp_float begin = getTimeStamp(); + cupdlp_float alpha = 1.0; + cupdlp_float beta = 0.0; + + switch (w->problem->data->matrix_format) { + case CSR_CSC: + // cuda_csc_Ax(w->cusparsehandle, w->problem->data->csc_matrix->cuda_csc, + // vecX, vecAx, w->dBuffer, alpha, beta); + + cuda_csr_Ax(w->cusparsehandle, w->problem->data->csr_matrix->cuda_csr, + vecX, vecAx, w->dBuffer, alpha, beta); + break; + case CSC: + cuda_csc_Ax(w->cusparsehandle, w->problem->data->csc_matrix->cuda_csc, + vecX, vecAx, w->dBuffer, alpha, beta); + break; + case CSR: + cuda_csr_Ax(w->cusparsehandle, w->problem->data->csr_matrix->cuda_csr, + vecX, vecAx, w->dBuffer, alpha, beta); + break; + default: + cupdlp_printf("Error: Unknown matrix format in Ax_single_gpu\n"); + exit(1); + } + w->timers->DeviceMatVecProdTime += getTimeStamp() - begin; +} + +void Ax_multi_gpu(CUPDLPdata *d, cupdlp_float *ax, const cupdlp_float *x) { + cupdlp_printf("Error: Ax_multi_gpu not implemented\n"); + exit(1); +} + +void ATy_single_gpu(CUPDLPwork *w, cusparseDnVecDescr_t vecY, + cusparseDnVecDescr_t vecATy) { + cupdlp_float begin = getTimeStamp(); + + cupdlp_float alpha = 1.0; + cupdlp_float beta = 0.0; + + switch (w->problem->data->matrix_format) { + case CSR_CSC: + // cuda_csr_ATy(w->cusparsehandle, w->problem->data->csr_matrix->cuda_csr, + // vecY, vecATy, w->dBuffer, alpha, beta); + cuda_csc_ATy(w->cusparsehandle, w->problem->data->csc_matrix->cuda_csc, + vecY, vecATy, w->dBuffer, alpha, beta); + break; + case CSC: + cuda_csc_ATy(w->cusparsehandle, w->problem->data->csc_matrix->cuda_csc, + vecY, vecATy, w->dBuffer, alpha, beta); + break; + case CSR: + cuda_csr_ATy(w->cusparsehandle, w->problem->data->csr_matrix->cuda_csr, + vecY, vecATy, w->dBuffer, alpha, beta); + break; + default: + printf("Error: Unknown matrix format in Ax_single_gpu\n"); + exit(1); + } + + w->timers->DeviceMatVecProdTime += getTimeStamp() - begin; +} + +void ATy_multi_gpu(CUPDLPdata *d, cupdlp_float *aty, const cupdlp_float *y) { + cupdlp_printf("Error: ATy_multi_gpu not implemented\n"); + exit(1); +} + +#endif + +void Ax(CUPDLPwork *w, CUPDLPvec *ax, const CUPDLPvec *x) { + cupdlp_float begin = getTimeStamp(); + + CUPDLPdata *d = w->problem->data; + switch (d->device) { + case CPU: + AxCPU(w, ax->data, x->data); + break; + case SINGLE_GPU: + +#ifndef CUPDLP_CPU + Ax_single_gpu(w, x->cuda_vec, ax->cuda_vec); +#else + printf("GPU not supported in CPU build\n"); + exit(1); +#endif + break; + case MULTI_GPU: +#ifndef CUPDLP_CPU + Ax_multi_gpu(d, ax, x); +#else + printf("GPU not supported in CPU build\n"); + exit(1); +#endif + break; + default: + printf("Error: Unknown device type in Ax\n"); + exit(1); + } + +#if PDHG_USE_TIMERS + w->timers->dAxTime += getTimeStamp() - begin; + w->timers->nAxCalls++; +#endif +} + +void ATy(CUPDLPwork *w, CUPDLPvec *aty, const CUPDLPvec *y) + +{ + cupdlp_float begin = getTimeStamp(); + + CUPDLPdata *d = w->problem->data; + switch (d->device) { + case CPU: + ATyCPU(w, aty->data, y->data); + break; + case SINGLE_GPU: +#ifndef CUPDLP_CPU + ATy_single_gpu(w, y->cuda_vec, aty->cuda_vec); +#else + printf("GPU not supported in CPU build\n"); + exit(1); +#endif + break; + case MULTI_GPU: +#ifndef CUPDLP_CPU + ATy_multi_gpu(d, aty->data, y->data); +#else + printf("GPU not supported in CPU build\n"); + exit(1); +#endif + break; + default: + printf("Error: Unknown device type in ATy\n"); + exit(1); + } +#if PDHG_USE_TIMERS + w->timers->dAtyTime += getTimeStamp() - begin; + w->timers->nAtyCalls++; +#endif +} + +/*-------------- Apis compatible with both CPU and GPU -------------------*/ +// only implemented the APis need to be used on GPU + +// functions in cublas + +cupdlp_int cupdlp_axpy(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *alpha, const cupdlp_float *x, + cupdlp_float *y) { +#ifndef CUPDLP_CPU +#ifndef SFLOAT + CHECK_CUBLAS(cublasDaxpy(w->cublashandle, n, alpha, x, 1, y, 1)); +#else + CHECK_CUBLAS(cublasSaxpy(w->cublashandle, n, alpha, x, 1, y, 1)); +#endif +#else + // AddToVector(x, *alpha, y, n); + AddToVector(y, *alpha, x, n); +#endif + return 0; +} + +cupdlp_int cupdlp_dot(CUPDLPwork *w, const cupdlp_int n, const cupdlp_float *x, + const cupdlp_float *y, cupdlp_float *res) { +#ifndef CUPDLP_CPU +#ifndef SFLOAT + CHECK_CUBLAS(cublasDdot(w->cublashandle, n, x, 1, y, 1, res)); +#else + CHECK_CUBLAS(cublasSdot(w->cublashandle, n, x, 1, y, 1 res)); +#endif +#else + *res = dot(n, x, 1, y, 1); +#endif + return 0; +} + +cupdlp_int cupdlp_twoNorm(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_float *res) { +#ifndef CUPDLP_CPU +#ifndef SFLOAT + CHECK_CUBLAS(cublasDnrm2(w->cublashandle, n, x, 1, res)); +#else + CHECK_CUBLAS(cublasSnrm2(w->cublashandle, n, x, 1, res)); +#endif +#else + *res = nrm2(n, x, 1); +#endif + return 0; +} + +cupdlp_int cupdlp_infNormIndex(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_int *res) { +#ifndef CUPDLP_CPU +#ifndef SFLOAT + CHECK_CUBLAS(cublasIdamax(w->cublashandle, n, x, 1, res)); +#else + CHECK_CUBLAS(cublasIsamax(w->cublashandle, n, x, 1, res)); +#endif +#else + *res = nrminfindex(n, x, 1); +#endif + return 0; +} + +cupdlp_int cupdlp_scaleVector(CUPDLPwork *w, const cupdlp_float weight, + cupdlp_float *x, const cupdlp_int n) { +#ifndef CUPDLP_CPU +#ifndef SFLOAT + CHECK_CUBLAS(cublasDscal(w->cublashandle, n, &weight, x, 1)); +#else + CHECK_CUBLAS(cublasSscal(w->cublashandle, n, &weight, x, 1)); +#endif +#else + ScaleVector(weight, x, n); +#endif + return 0; +} + +void cupdlp_twoNormSquared(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_float *res) { + cupdlp_dot(w, n, x, x, res); +} + +/* ||x - y||_2^2 */ +void cupdlp_diffTwoNormSquared(CUPDLPwork *w, const cupdlp_float *x, + const cupdlp_float *y, const cupdlp_int len, + cupdlp_float *res) { + CUPDLP_COPY_VEC(w->buffer2, x, cupdlp_float, len); + cupdlp_float alpha = -1.0; + cupdlp_axpy(w, len, &alpha, y, w->buffer2); + cupdlp_twoNormSquared(w, len, w->buffer2, res); +} + +/* ||x - y||_2 */ +void cupdlp_diffTwoNorm(CUPDLPwork *w, const cupdlp_float *x, + const cupdlp_float *y, const cupdlp_int len, + cupdlp_float *res) { + CUPDLP_COPY_VEC(w->buffer2, x, cupdlp_float, len); + cupdlp_float alpha = -1.0; + cupdlp_axpy(w, len, &alpha, y, w->buffer2); + cupdlp_twoNorm(w, len, w->buffer2, res); +} + +/* (x1 - x2)' (y1 - y2) */ +void cupdlp_diffDotDiff(CUPDLPwork *w, const cupdlp_float *x1, + const cupdlp_float *x2, const cupdlp_float *y1, + const cupdlp_float *y2, const cupdlp_int len, + cupdlp_float *res) { + CUPDLP_COPY_VEC(w->buffer2, x1, cupdlp_float, len); + cupdlp_float alpha = -1.0; + cupdlp_axpy(w, len, &alpha, x2, w->buffer2); + CUPDLP_COPY_VEC(w->buffer3, y1, cupdlp_float, len); + cupdlp_axpy(w, len, &alpha, y2, w->buffer3); + // reduce step + cupdlp_dot(w, len, w->buffer2, w->buffer3, res); +} + +// functions not in cublas + +/* element wise dot: x = x .* y*/ +void cupdlp_edot(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_edot_cuda(x, y, len); +#else + cupdlp_cdot(x, y, len); +#endif +} + +/* element wise div: x = x ./ y*/ +void cupdlp_ediv(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_ediv_cuda(x, y, len); +#else + cupdlp_cdiv(x, y, len); +#endif +} + +void cupdlp_projlb(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_projlb_cuda(x, lb, len); +#else + cupdlp_projLowerBound(x, lb, len); +#endif +} + +void cupdlp_projub(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_projub_cuda(x, ub, len); +#else + cupdlp_projUpperBound(x, ub, len); +#endif +} + +void cupdlp_projSamelb(cupdlp_float *x, const cupdlp_float lb, + const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_projSamelb_cuda(x, lb, len); +#else + cupdlp_projSameLowerBound(x, lb, len); +#endif +} + +void cupdlp_projSameub(cupdlp_float *x, const cupdlp_float ub, + const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_projSameub_cuda(x, ub, len); +#else + cupdlp_projSameUpperBound(x, ub, len); +#endif +} + +/* xout = max(x, 0) */ +void cupdlp_projPos(cupdlp_float *x, const cupdlp_int len) { + cupdlp_projSamelb(x, 0.0, len); +} + +/* xout = min(x, 0) */ +void cupdlp_projNeg(cupdlp_float *x, const cupdlp_int len) { + cupdlp_projSameub(x, 0.0, len); +} + +void cupdlp_haslb(cupdlp_float *haslb, const cupdlp_float *lb, + const cupdlp_float bound, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_haslb_cuda(haslb, lb, bound, len); +#else + cupdlp_hasLower(haslb, lb, bound, len); +#endif +} + +void cupdlp_hasub(cupdlp_float *hasub, const cupdlp_float *ub, + const cupdlp_float bound, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_hasub_cuda(hasub, ub, bound, len); +#else + cupdlp_hasUpper(hasub, ub, bound, len); +#endif +} + +void cupdlp_filterlb(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_float bound, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_filterlb_cuda(x, lb, bound, len); +#else + cupdlp_filter_lower_bound(x, lb, bound, len); +#endif +} + +void cupdlp_filterub(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_float bound, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_filterub_cuda(x, ub, bound, len); +#else + cupdlp_filter_upper_bound(x, ub, bound, len); +#endif +} + +void cupdlp_initvec(cupdlp_float *x, const cupdlp_float val, + const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_initvec_cuda(x, val, len); +#else + cupdlp_init_vector(x, val, len); +#endif +} + +void cupdlp_sub(cupdlp_float *xout, const cupdlp_float *x1, + const cupdlp_float *x2, const cupdlp_int len) { +#ifndef CUPDLP_CPU + cupdlp_sub_cuda(xout, x1, x2, len); +#else + CUPDLP_COPY_VEC(xout, x1, cupdlp_float, len); + cupdlp_float alpha = -1.0; + cupdlp_axpy(NULL, len, &alpha, x2, xout); +#endif +} + +void cupdlp_compute_interaction_and_movement(CUPDLPwork *w, + cupdlp_float *dMovement, + cupdlp_float *dInteraction) { + CUPDLPiterates *iterates = w->iterates; + cupdlp_int nCols = w->problem->nCols; + cupdlp_int nRows = w->problem->nRows; + cupdlp_float beta = sqrt(w->stepsize->dBeta); + cupdlp_float dX = 0.0; + cupdlp_float dY = 0.0; + + cupdlp_sub(w->buffer2, iterates->x->data, iterates->xUpdate->data, nCols); + cupdlp_twoNorm(w, nCols, w->buffer2, &dX); + cupdlp_sub(w->buffer3, iterates->y->data, iterates->yUpdate->data, nRows); + cupdlp_twoNorm(w, nRows, w->buffer3, &dY); + + *dMovement = pow(dX, 2.0) * 0.5 * beta + pow(dY, 2.0) / (2.0 * beta); + + cupdlp_sub(w->buffer3, iterates->aty->data, iterates->atyUpdate->data, nCols); + cupdlp_dot(w, nCols, w->buffer2, w->buffer3, dInteraction); +} diff --git a/src/pdlp/cupdlp/cupdlp_linalg.h b/src/pdlp/cupdlp/cupdlp_linalg.h new file mode 100644 index 0000000000..6805f64bec --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_linalg.h @@ -0,0 +1,183 @@ +#ifndef CUPDLP_CUPDLP_LINALG_H +#define CUPDLP_CUPDLP_LINALG_H + +#include "cupdlp_defs.h" +#include "cupdlp_utils.h" +#ifndef CUPDLP_CPU +#include "cuda/cupdlp_cudalinalg.cuh" +#endif + +void ScatterCol(CUPDLPwork *w, cupdlp_int iCol, cupdlp_float multiplier, + cupdlp_float *target); + +void ScatterRow(CUPDLPwork *w, cupdlp_int iRow, cupdlp_float multiplier, + cupdlp_float *target); + +void AxCPU(CUPDLPwork *w, cupdlp_float *ax, const cupdlp_float *x); + +void ATyCPU(CUPDLPwork *w, cupdlp_float *aty, const cupdlp_float *y); + +extern double nrm2(cupdlp_int n, const double *x, cupdlp_int incx); + +extern double nrminf(cupdlp_int n, const double *x, cupdlp_int incx); + +double twoNorm(double *x, cupdlp_int n); + +double twoNormSquared(double *x, cupdlp_int n); + +double infNorm(double *x, cupdlp_int n); + +cupdlp_int infNormIndex(double *x, cupdlp_int n); + +/*------------------------ new added --------------------*/ + +double GenNorm(double *x, cupdlp_int n, cupdlp_float p); + +void cupdlp_cdot(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len); + +void cupdlp_cdiv(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len); + +// void cupdlp_scaleVector(cupdlp_float *xout, cupdlp_float *x, cupdlp_float +// weight, const cupdlp_int len); +void cupdlp_projLowerBound(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_int len); +void cupdlp_projUpperBound(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_int len); +void cupdlp_projSameLowerBound(cupdlp_float *x, const cupdlp_float lb, + const cupdlp_int len); +void cupdlp_projSameUpperBound(cupdlp_float *x, const cupdlp_float ub, + const cupdlp_int len); +void cupdlp_projPositive(cupdlp_float *x, const cupdlp_int len); +void cupdlp_projNegative(cupdlp_float *x, const cupdlp_int len); + +// void cupdlp_projLowerBound(cupdlp_float *xout, cupdlp_float *x, cupdlp_float +// *lb, const cupdlp_int len); void cupdlp_projUpperBound(cupdlp_float *xout, +// cupdlp_float *x, cupdlp_float *ub, const cupdlp_int len); void +// cupdlp_projSameLowerBound(cupdlp_float *xout, cupdlp_float *x, cupdlp_float +// lb, const cupdlp_int len); void cupdlp_projSameUpperBound(cupdlp_float *xout, +// cupdlp_float *x, cupdlp_float ub, const cupdlp_int len); void +// cupdlp_projPositive(cupdlp_float *xout, cupdlp_float *x, const cupdlp_int +// len); void cupdlp_projNegative(cupdlp_float *xout, cupdlp_float *x, const +// cupdlp_int len); cupdlp_float cupdlp_diffTwoNormSquared(cupdlp_float *x, +// cupdlp_float *y, const cupdlp_int len); cupdlp_float +// cupdlp_diffTwoNorm(cupdlp_float *x, cupdlp_float *y, const cupdlp_int len); +// cupdlp_float cupdlp_diffInfNorm(cupdlp_float *x, cupdlp_float *y, const +// cupdlp_int len); cupdlp_float cupdlp_diffDotDiff(cupdlp_float *x1, +// cupdlp_float *x2, cupdlp_float *y1, cupdlp_float *y2, const cupdlp_int len); +// void cupdlp_cdot_fb(cupdlp_float *x, const cupdlp_bool *y, const cupdlp_int +// len); + +/*------------------------ new added --------------------*/ + +extern double dot(cupdlp_int n, const cupdlp_float *x, cupdlp_int incx, + const cupdlp_float *y, cupdlp_int incy); + +extern double Dotprod(const cupdlp_float *x, const cupdlp_float *y, cupdlp_int n); + +// todo, add this +extern double Dotprod_Neumaier(const cupdlp_float *x, const cupdlp_float *y, cupdlp_int n); + +/* x = x + weight * y */ +void AddToVector(cupdlp_float *x, const cupdlp_float weight, + const cupdlp_float *y, const cupdlp_int n); + +void ScaleVector(cupdlp_float weight, cupdlp_float *x, cupdlp_int n); + +// The main matrix-vector multiplication routines +// #ifndef CUPDLP_CPU +// Ax currently only works for CSC matrix multiply dense vector +// void Ax(CUPDLPwork *w, cupdlp_float *ax, const cupdlp_float *x); +// void Ax(CUPDLPwork *w, cupdlp_float *ax, void* vecAx, const cupdlp_float *x, +// void *vecX); +void Ax(CUPDLPwork *w, CUPDLPvec *ax, const CUPDLPvec *x); + +// ATy currently only works for CSR matrix multiply dense vector +// void ATy(CUPDLPwork *w, cupdlp_float *aty, const cupdlp_float *y); +// void ATy(CUPDLPwork *w, cupdlp_float *aty, void *vecATy, const cupdlp_float +// *y, void *vecY); +void ATy(CUPDLPwork *w, CUPDLPvec *aty, const CUPDLPvec *y); + +// #endif + +/*-------------- Apis compatible with both CPU and GPU -------------------*/ +// only implemented the APis need to be used on GPU + +// functions in cublas +cupdlp_int cupdlp_axpy(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *alpha, const cupdlp_float *x, + cupdlp_float *y); + +cupdlp_int cupdlp_dot(CUPDLPwork *w, const cupdlp_int n, const cupdlp_float *x, + const cupdlp_float *y, cupdlp_float *res); + +cupdlp_int cupdlp_twoNorm(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_float *res); + +cupdlp_int cupdlp_infNorm(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_float *res); + +cupdlp_int cupdlp_infNormIndex(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_int *res); + +cupdlp_int cupdlp_scaleVector(CUPDLPwork *w, const cupdlp_float weight, + cupdlp_float *x, const cupdlp_int n); + +void cupdlp_twoNormSquared(CUPDLPwork *w, const cupdlp_int n, + const cupdlp_float *x, cupdlp_float *res); + +void cupdlp_diffTwoNormSquared(CUPDLPwork *w, const cupdlp_float *x, + const cupdlp_float *y, const cupdlp_int len, + cupdlp_float *res); + +void cupdlp_diffTwoNorm(CUPDLPwork *w, const cupdlp_float *x, + const cupdlp_float *y, const cupdlp_int len, + cupdlp_float *res); + +void cupdlp_diffDotDiff(CUPDLPwork *w, const cupdlp_float *x1, + const cupdlp_float *x2, const cupdlp_float *y1, + const cupdlp_float *y2, const cupdlp_int len, + cupdlp_float *res); + +// functions not in cublas +/* element wise dot: x = x .* y*/ +void cupdlp_edot(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len); +/* element wise div: x = x ./ y*/ +void cupdlp_ediv(cupdlp_float *x, const cupdlp_float *y, const cupdlp_int len); + +void cupdlp_projlb(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_int len); + +void cupdlp_projub(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_int len); + +void cupdlp_projSamelb(cupdlp_float *x, const cupdlp_float lb, + const cupdlp_int len); + +void cupdlp_projSameub(cupdlp_float *x, const cupdlp_float ub, + const cupdlp_int len); + +/* xout = max(x, 0) */ +void cupdlp_projPos(cupdlp_float *x, const cupdlp_int len); + +/* xout = min(x, 0) */ +void cupdlp_projNeg(cupdlp_float *x, const cupdlp_int len); + +void cupdlp_haslb(cupdlp_float *haslb, const cupdlp_float *lb, + const cupdlp_float bound, const cupdlp_int len); + +void cupdlp_hasub(cupdlp_float *hasub, const cupdlp_float *ub, + const cupdlp_float bound, const cupdlp_int len); + +void cupdlp_filterlb(cupdlp_float *x, const cupdlp_float *lb, + const cupdlp_float bound, const cupdlp_int len); + +void cupdlp_filterub(cupdlp_float *x, const cupdlp_float *ub, + const cupdlp_float bound, const cupdlp_int len); + +void cupdlp_initvec(cupdlp_float *x, const cupdlp_float val, + const cupdlp_int len); + +void cupdlp_compute_interaction_and_movement(CUPDLPwork *w, + cupdlp_float *dMovement, + cupdlp_float *dIteraction); +#endif // CUPDLP_CUPDLP_LINALG_H diff --git a/src/pdlp/cupdlp/cupdlp_proj.c b/src/pdlp/cupdlp/cupdlp_proj.c new file mode 100644 index 0000000000..97d43d7aec --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_proj.c @@ -0,0 +1,146 @@ +// +// Created by chuwen on 23-11-28. +// + +#include "cupdlp_proj.h" + +#include "cupdlp_defs.h" +#include "cupdlp_linalg.h" +#include "cupdlp_restart.h" +// #include "cupdlp_scaling.h" +#include "cupdlp_solver.h" +#include "cupdlp_step.h" +#include "cupdlp_utils.h" +#include "glbopts.h" + +// primal projection: project x to [lower, upper] +void PDHG_Project_Bounds(CUPDLPwork *work, cupdlp_float *r) { + CUPDLPproblem *problem = work->problem; + + // cupdlp_projUpperBound(r, r, problem->upper, problem->nCols); + // cupdlp_projLowerBound(r, r, problem->lower, problem->nCols); + + cupdlp_projub(r, problem->upper, problem->nCols); + cupdlp_projlb(r, problem->lower, problem->nCols); +} + +void PDHG_Project_Row_Duals(CUPDLPwork *work, cupdlp_float *r) { + CUPDLPproblem *problem = work->problem; + + // cupdlp_projPositive(r + problem->nEqs, r + problem->nEqs, problem->nRows - + // problem->nEqs); + cupdlp_projPos(r + problem->nEqs, problem->nRows - problem->nEqs); +} + +// void PDHG_Restart_Iterate(CUPDLPwork *pdhg) +// { +// CUPDLPproblem *problem = pdhg->problem; +// CUPDLPiterates *iterates = pdhg->iterates; +// CUPDLPstepsize *stepsize = pdhg->stepsize; +// CUPDLPtimers *timers = pdhg->timers; + +// // PDHG_Compute_Average_Iterate(pdhg); +// PDHG_restart_choice restart_choice = PDHG_Check_Restart(pdhg); + +// if (restart_choice == PDHG_NO_RESTART) +// return; + +// PDHG_Compute_Step_Size_Ratio(pdhg); + +// stepsize->dSumPrimalStep = 0.0; +// stepsize->dSumDualStep = 0.0; +// cupdlp_zero(iterates->xSum, cupdlp_float, problem->nCols); +// cupdlp_zero(iterates->ySum, cupdlp_float, problem->nRows); + +// if (restart_choice == PDHG_RESTART_TO_AVERAGE) +// { +// cupdlp_copy(iterates->x, iterates->xAverage, cupdlp_float, +// problem->nCols); cupdlp_copy(iterates->y, iterates->yAverage, +// cupdlp_float, problem->nRows); cupdlp_copy(iterates->ax, +// iterates->axAverage, cupdlp_float, problem->nRows); +// cupdlp_copy(iterates->aty, iterates->atyAverage, cupdlp_float, +// problem->nCols); +// } +// cupdlp_copy(iterates->xLastRestart, iterates->x, cupdlp_float, +// problem->nCols); cupdlp_copy(iterates->yLastRestart, iterates->y, +// cupdlp_float, problem->nRows); + +// iterates->iLastRestartIter = timers->nIter; + +// PDHG_Compute_Residuals(pdhg); +// // cupdlp_printf("Recomputed stepsize ratio: %e, sqrt(ratio)=%e", +// stepsize->dBeta, sqrt(stepsize->dBeta)); +// } + +void PDHG_Restart_Iterate(CUPDLPwork *pdhg) { + switch (pdhg->settings->eRestartMethod) { + case PDHG_WITHOUT_RESTART: + break; + case PDHG_GPU_RESTART: + PDHG_Restart_Iterate_GPU(pdhg); + break; + case PDHG_CPU_RESTART: + // TODO: implement PDHG_Restart_Iterate_CPU(pdhg); + break; + } +} + +void PDHG_Restart_Iterate_GPU(CUPDLPwork *pdhg) { + CUPDLPproblem *problem = pdhg->problem; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPstepsize *stepsize = pdhg->stepsize; + CUPDLPresobj *resobj = pdhg->resobj; + CUPDLPtimers *timers = pdhg->timers; + + // PDHG_Compute_Average_Iterate(pdhg); + PDHG_restart_choice restart_choice = PDHG_Check_Restart_GPU(pdhg); + + if (restart_choice == PDHG_NO_RESTART) return; + + stepsize->dSumPrimalStep = 0.0; + stepsize->dSumDualStep = 0.0; + CUPDLP_ZERO_VEC(iterates->xSum, cupdlp_float, problem->nCols); + CUPDLP_ZERO_VEC(iterates->ySum, cupdlp_float, problem->nRows); + + if (restart_choice == PDHG_RESTART_TO_AVERAGE) { + resobj->dPrimalFeasLastRestart = resobj->dPrimalFeasAverage; + resobj->dDualFeasLastRestart = resobj->dDualFeasAverage; + resobj->dDualityGapLastRestart = resobj->dDualityGapAverage; + + // cupdlp_copy(iterates->x, iterates->xAverage, cupdlp_float, + // problem->nCols); cupdlp_copy(iterates->y, iterates->yAverage, + // cupdlp_float, problem->nRows); cupdlp_copy(iterates->ax, + // iterates->axAverage, cupdlp_float, problem->nRows); + // cupdlp_copy(iterates->aty, iterates->atyAverage, cupdlp_float, + // problem->nCols); + + CUPDLP_COPY_VEC(iterates->x->data, iterates->xAverage->data, cupdlp_float, + problem->nCols); + CUPDLP_COPY_VEC(iterates->y->data, iterates->yAverage->data, cupdlp_float, + problem->nRows); + CUPDLP_COPY_VEC(iterates->ax->data, iterates->axAverage->data, cupdlp_float, + problem->nRows); + CUPDLP_COPY_VEC(iterates->aty->data, iterates->atyAverage->data, + cupdlp_float, problem->nCols); + } else { + resobj->dPrimalFeasLastRestart = resobj->dPrimalFeas; + resobj->dDualFeasLastRestart = resobj->dDualFeas; + resobj->dDualityGapLastRestart = resobj->dDualityGap; + } + + PDHG_Compute_Step_Size_Ratio(pdhg); + + // cupdlp_copy(iterates->xLastRestart, iterates->x, cupdlp_float, + // problem->nCols); cupdlp_copy(iterates->yLastRestart, iterates->y, + // cupdlp_float, problem->nRows); + CUPDLP_COPY_VEC(iterates->xLastRestart, iterates->x->data, cupdlp_float, + problem->nCols); + CUPDLP_COPY_VEC(iterates->yLastRestart, iterates->y->data, cupdlp_float, + problem->nRows); + + iterates->iLastRestartIter = timers->nIter; + + PDHG_Compute_Residuals(pdhg); + // cupdlp_printf("Recomputed stepsize ratio: %e, sqrt(ratio)=%e", + // stepsize->dBeta, sqrt(stepsize->dBeta)); +} diff --git a/src/pdlp/cupdlp/cupdlp_proj.h b/src/pdlp/cupdlp/cupdlp_proj.h new file mode 100644 index 0000000000..67d033e7d8 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_proj.h @@ -0,0 +1,19 @@ +// +// Created by chuwen on 23-11-28. +// + +#ifndef CUPDLP_CUPDLP_PROJ_H +#define CUPDLP_CUPDLP_PROJ_H + +#include "cupdlp_defs.h" +#include "glbopts.h" + +void PDHG_Project_Bounds(CUPDLPwork *work, double *r); + +void PDHG_Project_Row_Duals(CUPDLPwork *work, double *r); + +void PDHG_Restart_Iterate(CUPDLPwork *pdhg); + +void PDHG_Restart_Iterate_GPU(CUPDLPwork *pdhg); + +#endif // CUPDLP_CUPDLP_PROJ_H diff --git a/src/pdlp/cupdlp/cupdlp_restart.c b/src/pdlp/cupdlp/cupdlp_restart.c new file mode 100644 index 0000000000..9aece0b1f4 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_restart.c @@ -0,0 +1,122 @@ +#include "cupdlp_restart.h" + +PDHG_restart_choice PDHG_Check_Restart_GPU(CUPDLPwork *work) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPstepsize *stepsize = work->stepsize; + CUPDLPiterates *iterates = work->iterates; + CUPDLPresobj *resobj = work->resobj; + CUPDLPtimers *timers = work->timers; + + // Is it first time called? + if (timers->nIter == iterates->iLastRestartIter) { + resobj->dPrimalFeasLastRestart = resobj->dPrimalFeas; + resobj->dDualFeasLastRestart = resobj->dDualFeas; + resobj->dDualityGapLastRestart = resobj->dDualityGap; + + resobj->dPrimalFeasLastCandidate = resobj->dPrimalFeas; + resobj->dDualFeasLastCandidate = resobj->dDualFeas; + resobj->dDualityGapLastCandidate = resobj->dDualityGap; + + return PDHG_NO_RESTART; + } + + cupdlp_float muCurrent = PDHG_Restart_Score_GPU( + work->stepsize->dBeta, work->resobj->dPrimalFeas, work->resobj->dDualFeas, + work->resobj->dDualityGap); + cupdlp_float muAverage = PDHG_Restart_Score_GPU( + work->stepsize->dBeta, work->resobj->dPrimalFeasAverage, + work->resobj->dDualFeasAverage, work->resobj->dDualityGapAverage); + + cupdlp_float muCandidate = 0.0; + PDHG_restart_choice restart_choice = PDHG_RESTART_TO_AVERAGE; + if (muCurrent < muAverage) { + restart_choice = PDHG_RESTART_TO_CURRENT; + muCandidate = muCurrent; + } else { + // restart_choice = PDHG_RESTART_TO_AVERAGE; + muCandidate = muAverage; + } + + // Or should we do artificial restart based on iteration count? + // if (2 * (timers->nIter - iterates->iLastRestartIter) >= timers->nIter) { + if ((timers->nIter - iterates->iLastRestartIter) >= 0.36 * timers->nIter) { +#if CUPDLP_DEBUG + cupdlp_printf("Doing artificial restart."); +#endif + } else { + cupdlp_float muLastRestart = PDHG_Restart_Score_GPU( + work->stepsize->dBeta, work->resobj->dPrimalFeasLastRestart, + work->resobj->dDualFeasLastRestart, + work->resobj->dDualityGapLastRestart); + + // Sufficient decay + if (muCandidate < 0.2 * muLastRestart) { +#if CUPDLP_DEBUG + cupdlp_printf("Doing sufficient restart."); +#endif + } else { + cupdlp_float muLastCandidate = PDHG_Restart_Score_GPU( + work->stepsize->dBeta, work->resobj->dPrimalFeasLastCandidate, + work->resobj->dDualFeasLastCandidate, + work->resobj->dDualityGapLastCandidate); + + // Necessary decay + if (muCandidate < 0.8 * muLastRestart && muCandidate > muLastCandidate) { +#if CUPDLP_DEBUG + cupdlp_printf("Doing necessary restart."); +#endif + } else { + restart_choice = PDHG_NO_RESTART; + } + } + } + // record candidate + if (muCurrent < muAverage) { + resobj->dPrimalFeasLastCandidate = resobj->dPrimalFeas; + resobj->dDualFeasLastCandidate = resobj->dDualFeas; + resobj->dDualityGapLastCandidate = resobj->dDualityGap; + } else { + resobj->dPrimalFeasLastCandidate = resobj->dPrimalFeasAverage; + resobj->dDualFeasLastCandidate = resobj->dDualFeasAverage; + resobj->dDualityGapLastCandidate = resobj->dDualityGapAverage; + } + + if (restart_choice != PDHG_NO_RESTART) { + if (muCurrent < muAverage) { + if (work->settings->nLogLevel > 1) + cupdlp_printf("Last restart was iter %d: %s", iterates->iLastRestartIter, + "current\n"); + } else { + if (work->settings->nLogLevel > 1) + cupdlp_printf("Last restart was iter %d: %s", iterates->iLastRestartIter, + "average\n"); + } + } + return restart_choice; +} + +cupdlp_bool PDHG_Check_Restart_Merit_Function(CUPDLPwork *work) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPstepsize *stepsize = work->stepsize; + CUPDLPiterates *iterates = work->iterates; + CUPDLPresobj *resobj = work->resobj; + + return ( + (fabs(resobj->dDualityGap) > 2.0 * fabs(resobj->dDualityGapAverage)) && + (resobj->dPrimalFeas > 2.0 * resobj->dPrimalFeasAverage)); +} + +cupdlp_float PDHG_Restart_Score_GPU(cupdlp_float weightSquared, + cupdlp_float dPrimalFeas, + cupdlp_float dDualFeas, + cupdlp_float dDualityGap) { + cupdlp_float dScoreGPU = 0.0; + + dScoreGPU = + sqrt(weightSquared * dPrimalFeas * dPrimalFeas + + dDualFeas * dDualFeas / weightSquared + dDualityGap * dDualityGap); + + return dScoreGPU; +} diff --git a/src/pdlp/cupdlp/cupdlp_restart.h b/src/pdlp/cupdlp/cupdlp_restart.h new file mode 100644 index 0000000000..1ce8fb1d71 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_restart.h @@ -0,0 +1,31 @@ +// +// Created by chuwen on 23-11-28. +// + +#ifndef CUPDLP_CUPDLP_RESTART_H +#define CUPDLP_CUPDLP_RESTART_H + +#include "cupdlp_defs.h" +#include "cupdlp_linalg.h" +#include "cupdlp_proj.h" +// #include "cupdlp_scaling.h" +#include "cupdlp_step.h" +#include "cupdlp_utils.h" +#include "glbopts.h" + +typedef enum { + PDHG_NO_RESTART = 0, + PDHG_RESTART_TO_CURRENT, + PDHG_RESTART_TO_AVERAGE +} PDHG_restart_choice; + +cupdlp_bool PDHG_Check_Restart_Merit_Function(CUPDLPwork *work); + +PDHG_restart_choice PDHG_Check_Restart_GPU(CUPDLPwork *work); + +cupdlp_float PDHG_Restart_Score_GPU(cupdlp_float weightSquared, + cupdlp_float dPrimalFeas, + cupdlp_float dDualFeas, + cupdlp_float dDualityGap); + +#endif // CUPDLP_CUPDLP_RESTART_H diff --git a/src/pdlp/cupdlp/cupdlp_scaling_cuda.c b/src/pdlp/cupdlp/cupdlp_scaling_cuda.c new file mode 100644 index 0000000000..f5ae4def66 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_scaling_cuda.c @@ -0,0 +1,422 @@ +// +// Created by LJS on 23-11-30. +// Same as the JULIA CPU version +// + +#include "cupdlp_scaling_cuda.h" + +#include "cupdlp_linalg.h" +// #include "cupdlp_scaling.h" +#include "cupdlp_utils.h" + +// This version disable dScalingTarget, which is the target of scaled matrix +// elements cupdlp_retcode scale_problem(CUPDLPwork *w, cupdlp_float +// *col_scaling, cupdlp_float *row_scaling) +cupdlp_retcode scale_problem_cuda(CUPDLPcsc *csc, cupdlp_float *cost, + cupdlp_float *lower, cupdlp_float *upper, + cupdlp_float *rhs, cupdlp_float *col_scaling, + cupdlp_float *row_scaling) { + cupdlp_retcode retcode = RETCODE_OK; + cupdlp_int nRows = csc->nRows; + cupdlp_int nCols = csc->nCols; + + cupdlp_cdiv(cost, col_scaling, nCols); + cupdlp_cdot(lower, col_scaling, nCols); + cupdlp_cdot(upper, col_scaling, nCols); + cupdlp_cdiv(rhs, row_scaling, nRows); + + // row scaling + for (int i = 0; i < csc->colMatBeg[nCols]; i++) { + csc->colMatElem[i] /= row_scaling[csc->colMatIdx[i]]; + } + // col scaling + for (int i = 0; i < nCols; i++) { + for (int j = csc->colMatBeg[i]; j < csc->colMatBeg[i + 1]; j++) { + csc->colMatElem[j] /= col_scaling[i]; + } + } + + // w->scaling->ifScaled = 1; + +exit_cleanup: + return retcode; +} + +cupdlp_retcode cupdlp_ruiz_scaling_cuda(CUPDLPcsc *csc, cupdlp_float *cost, + cupdlp_float *lower, + cupdlp_float *upper, cupdlp_float *rhs, + CUPDLPscaling *scaling) +// cupdlp_retcode cupdlp_ruiz_scaling(CUPDLPwork *work, cupdlp_int max_iter, +// cupdlp_float norm) +{ + cupdlp_retcode retcode = RETCODE_OK; + + cupdlp_int nRows = csc->nRows; + cupdlp_int nCols = csc->nCols; + + cupdlp_float *current_col_scaling = NULL; // for variable + cupdlp_float *current_row_scaling = NULL; // for constraint + CUPDLP_INIT_ZERO_DOUBLE(current_col_scaling, nCols); + CUPDLP_INIT_ZERO_DOUBLE(current_row_scaling, nRows); + + for (cupdlp_int i = 0; i < scaling->RuizTimes; i++) { + cupdlp_zero(current_col_scaling, cupdlp_float, nCols); + cupdlp_zero(current_row_scaling, cupdlp_float, nRows); + + if (csc != NULL) { + for (int j = 0; j < nCols; j++) { + if (csc->colMatBeg[j] == csc->colMatBeg[j + 1]) { + current_col_scaling[j] = 0; + } else { + current_col_scaling[j] = SQRTF(GenNorm( + &csc->colMatElem[csc->colMatBeg[j]], + csc->colMatBeg[j + 1] - csc->colMatBeg[j], scaling->RuizNorm)); + } + } + } + for (int j = 0; j < nCols; j++) { + if (current_col_scaling[j] == 0.0) { + current_col_scaling[j] = 1.0; + } + } + + if (scaling->RuizNorm == INFINITY) { + if (nRows > 0 && csc != NULL) { + // inf norm of rows of csc + for (int j = 0; j < csc->colMatBeg[nCols]; j++) { + if (current_row_scaling[csc->colMatIdx[j]] < + ABS(csc->colMatElem[j])) { + current_row_scaling[csc->colMatIdx[j]] = ABS(csc->colMatElem[j]); + } + } + for (int j = 0; j < nRows; j++) { + if (current_row_scaling[j] == 0.0) { + current_row_scaling[j] = 1.0; + } else { + current_row_scaling[j] = SQRTF(current_row_scaling[j]); + } + } + } + } else { + cupdlp_printf("Currently only support infinity norm for Ruiz scaling\n"); + exit(1); + } + + // apply scaling + // scale_problem(work, current_col_scaling, current_row_scaling); + scale_problem_cuda(csc, cost, lower, upper, rhs, current_col_scaling, + current_row_scaling); + + // update scaling + cupdlp_cdot(scaling->colScale, current_col_scaling, nCols); + cupdlp_cdot(scaling->rowScale, current_row_scaling, nRows); + } +exit_cleanup: + cupdlp_free(current_col_scaling); + cupdlp_free(current_row_scaling); + return retcode; +} + +cupdlp_retcode cupdlp_l2norm_scaling_cuda(CUPDLPcsc *csc, cupdlp_float *cost, + cupdlp_float *lower, + cupdlp_float *upper, + cupdlp_float *rhs, + CUPDLPscaling *scaling) +// cupdlp_retcode cupdlp_l2norm_scaling(CUPDLPwork *work) +{ + cupdlp_retcode retcode = RETCODE_OK; + cupdlp_int nRows = csc->nRows; + cupdlp_int nCols = csc->nCols; + + cupdlp_float *current_col_scaling = NULL; // for variable + cupdlp_float *current_row_scaling = NULL; // for constraint + CUPDLP_INIT_ZERO_DOUBLE(current_col_scaling, nCols); + CUPDLP_INIT_ZERO_DOUBLE(current_row_scaling, nRows); + + if (nRows > 0 && csc != NULL) { + for (int j = 0; j < nCols; j++) { + if (csc->colMatBeg[j] == csc->colMatBeg[j + 1]) { + current_col_scaling[j] = 1.0; + } else { + current_col_scaling[j] = + SQRTF(GenNorm(&csc->colMatElem[csc->colMatBeg[j]], + csc->colMatBeg[j + 1] - csc->colMatBeg[j], 2.0)); + } + } + + for (int i = 0; i < csc->colMatBeg[nCols]; i++) { + current_row_scaling[csc->colMatIdx[i]] += pow(csc->colMatElem[i], 2.0); + } + for (int i = 0; i < nRows; i++) { + current_row_scaling[i] = SQRTF(SQRTF(current_row_scaling[i])); + if (current_row_scaling[i] == 0.0) { + current_row_scaling[i] = 1.0; + } + } + } + // apply scaling + // scale_problem(work, current_col_scaling, current_row_scaling); + scale_problem_cuda(csc, cost, lower, upper, rhs, current_col_scaling, + current_row_scaling); + + // update scaling + cupdlp_cdot(scaling->colScale, current_col_scaling, nCols); + cupdlp_cdot(scaling->rowScale, current_row_scaling, nRows); + +exit_cleanup: + cupdlp_free(current_col_scaling); + cupdlp_free(current_row_scaling); + return retcode; +} + +cupdlp_retcode cupdlp_pc_scaling_cuda(CUPDLPcsc *csc, cupdlp_float *cost, + cupdlp_float *lower, cupdlp_float *upper, + cupdlp_float *rhs, CUPDLPscaling *scaling) +// cupdlp_retcode cupdlp_pc_scaling(CUPDLPwork *work, cupdlp_float alpha) +{ + cupdlp_retcode retcode = RETCODE_OK; + cupdlp_int nRows = csc->nRows; + cupdlp_int nCols = csc->nCols; + cupdlp_float alpha = scaling->PcAlpha; + + cupdlp_float *current_col_scaling = NULL; // for variable + cupdlp_float *current_row_scaling = NULL; // for constraint + CUPDLP_INIT_ZERO_DOUBLE(current_col_scaling, nCols); + CUPDLP_INIT_ZERO_DOUBLE(current_row_scaling, nRows); + + if (alpha > 2.0 || alpha < 0.0) { + cupdlp_printf("alpha should be in [0, 2]\n"); + exit(1); + } + + if (nRows > 0 && csc != NULL) { + for (int i = 0; i < nCols; i++) { + for (int j = csc->colMatBeg[i]; j < csc->colMatBeg[i + 1]; j++) { + current_col_scaling[i] += POWF(ABS(csc->colMatElem[j]), alpha); + } + current_col_scaling[i] = SQRTF(POWF(current_col_scaling[i], 1.0 / alpha)); + if (current_col_scaling[i] == 0.0) { + current_col_scaling[i] = 1.0; + } + } + + for (int i = 0; i < csc->colMatBeg[nCols]; i++) { + current_row_scaling[csc->colMatIdx[i]] += + POWF(ABS(csc->colMatElem[i]), 2.0 - alpha); + } + for (int i = 0; i < nRows; i++) { + current_row_scaling[i] = + SQRTF(POWF(current_row_scaling[i], 1.0 / (2.0 - alpha))); + if (current_row_scaling[i] == 0.0) { + current_row_scaling[i] = 1.0; + } + } + } + + // apply scaling + // scale_problem(work, current_col_scaling, current_row_scaling); + scale_problem_cuda(csc, cost, lower, upper, rhs, current_col_scaling, + current_row_scaling); + + // update scaling + cupdlp_cdot(scaling->colScale, current_col_scaling, nCols); + cupdlp_cdot(scaling->rowScale, current_row_scaling, nRows); + +exit_cleanup: + cupdlp_free(current_col_scaling); + cupdlp_free(current_row_scaling); + return retcode; +} + +cupdlp_retcode H_PDHG_Scale_Data_cuda(cupdlp_int log_level, + CUPDLPcsc *csc, cupdlp_int ifScaling, + CUPDLPscaling *scaling, cupdlp_float *cost, + cupdlp_float *lower, cupdlp_float *upper, + cupdlp_float *rhs) { + cupdlp_retcode retcode = RETCODE_OK; + // scaling->dObjScale = 1.0; + +#if CUPDLP_DEBUG + //------------------- for debug ------------------ + cupdlp_float dMinElem = OUR_DBL_MAX; + cupdlp_float dMaxElem = 0.0; + cupdlp_float dAvgElem = 0.0; + cupdlp_int nRows = csc->nRows; + cupdlp_int nCols = csc->nCols; + + for (cupdlp_int iMatElem = csc->colMatBeg[0]; + iMatElem < csc->colMatBeg[nCols]; iMatElem++) { + cupdlp_float dAbsElem = fabs(csc->colMatElem[iMatElem]); + if (dAbsElem != 0.0) { + dMaxElem = fmax(dMaxElem, dAbsElem); + dMinElem = fmin(dMinElem, dAbsElem); + } + dAvgElem += dAbsElem; + } + dAvgElem /= csc->colMatBeg[nCols]; + + if (log_level) { + cupdlp_printf("Problem before rescaling:\n"); + cupdlp_printf("Absolute value of nonzero constraint matrix elements: largest=%f, " + "smallest=%f, avg=%f\n", + dMaxElem, dMinElem, dAvgElem); + } + // calculate the three statistics of objective vector + dMinElem = OUR_DBL_MAX; + dMaxElem = 0.0; + dAvgElem = 0.0; + for (cupdlp_int iCol = 0; iCol < nCols; iCol++) { + cupdlp_float dAbsElem = fabs(cost[iCol]); + if (dAbsElem != 0.0) { + dMaxElem = fmax(dMaxElem, dAbsElem); + dMinElem = fmin(dMinElem, dAbsElem); + } + dAvgElem += dAbsElem; + } + dAvgElem /= nCols; + if (log_level) + cupdlp_printf("Absolute value of objective vector elements: largest=%f, smallest=%f, " + "avg=%f\n", + dMaxElem, dMinElem, dAvgElem); + // calculate the three statistics of rhs vector + dMinElem = OUR_DBL_MAX; + dMaxElem = 0.0; + dAvgElem = 0.0; + for (cupdlp_int iRow = 0; iRow < nRows; iRow++) { + cupdlp_float dAbsElem = fabs(rhs[iRow]); + if (dAbsElem != 0.0) { + dMaxElem = fmax(dMaxElem, dAbsElem); + dMinElem = fmin(dMinElem, dAbsElem); + } + dAvgElem += dAbsElem; + } + dAvgElem /= nRows; + if (log_level) + cupdlp_printf("Absolute value of rhs vector elements: largest=%f, smallest=%f, " + "avg=%f\n", + dMaxElem, dMinElem, dAvgElem); +//------------------- for debug ------------------ +#endif + + if (ifScaling) { + if (log_level) { + cupdlp_printf("--------------------------------------------------\n"); + cupdlp_printf("running scaling\n"); + } + if (scaling->ifRuizScaling) { + if (log_level) + cupdlp_printf("- use Ruiz scaling\n"); + CUPDLP_CALL( + cupdlp_ruiz_scaling_cuda(csc, cost, lower, upper, rhs, scaling)); + scaling->ifScaled = 1; + } + if (scaling->ifL2Scaling) { + if (log_level) + cupdlp_printf("- use L2 scaling\n"); + CUPDLP_CALL( + cupdlp_l2norm_scaling_cuda(csc, cost, lower, upper, rhs, scaling)); + scaling->ifScaled = 1; + } + if (scaling->ifPcScaling) { + if (log_level) + cupdlp_printf("- use PC scaling\n"); + CUPDLP_CALL( + cupdlp_pc_scaling_cuda(csc, cost, lower, upper, rhs, scaling)); + scaling->ifScaled = 1; + } + + if (log_level) + cupdlp_printf("--------------------------------------------------\n"); + } + + /* make sure the csr matrix is also scaled*/ + // csc2csr(data->csr_matrix, csc); +#if CUPDLP_DEBUG + //------------------- for debug ------------------ + dMinElem = OUR_DBL_MAX; + dMaxElem = 0.0; + dAvgElem = 0.0; + for (cupdlp_int iMatElem = csc->colMatBeg[0]; + iMatElem < csc->colMatBeg[nCols]; iMatElem++) { + cupdlp_float dAbsElem = fabs(csc->colMatElem[iMatElem]); + if (dAbsElem != 0.0) { + dMaxElem = fmax(dMaxElem, dAbsElem); + dMinElem = fmin(dMinElem, dAbsElem); + } + dAvgElem += dAbsElem; + } + dAvgElem /= csc->colMatBeg[nCols]; + + if (log_level) { + cupdlp_printf("Problem after rescaling:\n"); + cupdlp_printf("Absolute value of nonzero constraint matrix elements: largest=%f, " + "smallest=%f, avg=%f\n", + dMaxElem, dMinElem, dAvgElem); + } + // calculate the three statistics of objective vector + dMinElem = OUR_DBL_MAX; + dMaxElem = 0.0; + dAvgElem = 0.0; + for (cupdlp_int iCol = 0; iCol < nCols; iCol++) { + cupdlp_float dAbsElem = fabs(cost[iCol]); + if (dAbsElem != 0.0) { + dMaxElem = fmax(dMaxElem, dAbsElem); + dMinElem = fmin(dMinElem, dAbsElem); + } + dAvgElem += dAbsElem; + } + dAvgElem /= nCols; + if (log_level) + cupdlp_printf("Absolute value of objective vector elements: largest=%f, smallest=%f, " + "avg=%f\n", + dMaxElem, dMinElem, dAvgElem); + // calculate the three statistics of rhs vector + dMinElem = OUR_DBL_MAX; + dMaxElem = 0.0; + dAvgElem = 0.0; + for (cupdlp_int iRow = 0; iRow < nRows; iRow++) { + cupdlp_float dAbsElem = fabs(rhs[iRow]); + if (dAbsElem != 0.0) { + dMaxElem = fmax(dMaxElem, dAbsElem); + dMinElem = fmin(dMinElem, dAbsElem); + } + dAvgElem += dAbsElem; + } + dAvgElem /= nRows; + if (log_level) + cupdlp_printf("Absolute value of rhs vector elements: largest=%f, smallest=%f, " + "avg=%f\n", + dMaxElem, dMinElem, dAvgElem); +//------------------- for debug ------------------ +#endif + +exit_cleanup: + + return retcode; +} + +cupdlp_retcode H_Init_Scaling(cupdlp_int log_level, + CUPDLPscaling *scaling, cupdlp_int ncols, + cupdlp_int nrows, cupdlp_float *cost, + cupdlp_float *rhs) { + cupdlp_retcode retcode = RETCODE_OK; + + scaling->ifRuizScaling = 1; + scaling->ifL2Scaling = 0; + scaling->ifPcScaling = 1; + + // todo, read these paras + scaling->RuizTimes = 10; + scaling->RuizNorm = INFINITY; + scaling->PcAlpha = 1.0; + CUPDLP_INIT_DOUBLE(scaling->colScale, ncols); + CUPDLP_INIT_DOUBLE(scaling->rowScale, nrows); + + for (cupdlp_int iCol = 0; iCol < ncols; iCol++) scaling->colScale[iCol] = 1.0; + for (cupdlp_int iRow = 0; iRow < nrows; iRow++) scaling->rowScale[iRow] = 1.0; + + scaling->dNormCost = twoNorm(cost, ncols); + scaling->dNormRhs = twoNorm(rhs, nrows); +exit_cleanup: + return retcode; +} diff --git a/src/pdlp/cupdlp/cupdlp_scaling_cuda.h b/src/pdlp/cupdlp/cupdlp_scaling_cuda.h new file mode 100644 index 0000000000..0a1352a0aa --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_scaling_cuda.h @@ -0,0 +1,28 @@ +// +// Created by LJS on 23-11-30. +// + +#ifndef CUPDLP_SCALING_CUDA_H +#define CUPDLP_SCALING_CUDA_H + +#include "cupdlp_defs.h" +#include "glbopts.h" +#ifdef __cplusplus +extern "C" { +#endif + +cupdlp_retcode H_PDHG_Scale_Data_cuda(cupdlp_int log_level, + CUPDLPcsc *csc, cupdlp_int ifScaling, + CUPDLPscaling *scaling, cupdlp_float *cost, + cupdlp_float *lower, cupdlp_float *upper, + cupdlp_float *rhs); + +cupdlp_retcode H_Init_Scaling(cupdlp_int log_level, + CUPDLPscaling *scaling, cupdlp_int ncols, + cupdlp_int nrows, cupdlp_float *cost, + cupdlp_float *rhs); + +#ifdef __cplusplus +} +#endif +#endif // CUPDLP_CUPDLP_SCALING_H diff --git a/src/pdlp/cupdlp/cupdlp_solver.c b/src/pdlp/cupdlp/cupdlp_solver.c new file mode 100644 index 0000000000..8ff4d9df97 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_solver.c @@ -0,0 +1,1209 @@ + +#include "cupdlp_solver.h" + +#include "cupdlp_defs.h" +#include "cupdlp_linalg.h" +#include "cupdlp_proj.h" +#include "cupdlp_restart.h" +// #include "cupdlp_scaling.h" +// #include "cupdlp_scaling_new.h" +#include "cupdlp_step.h" +#include "cupdlp_utils.h" +#include "glbopts.h" + +void PDHG_Compute_Primal_Feasibility(CUPDLPwork *work, double *primalResidual, + const double *ax, const double *x, + double *dPrimalFeasibility, + double *dPrimalObj) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPscaling *scaling = work->scaling; + + // primal variable violation + + // todo, add this + // *dPrimalObj = Dotprod_Neumaier(problem->cost, x, lp->nCols); + cupdlp_dot(work, lp->nCols, x, problem->cost, dPrimalObj); + *dPrimalObj = *dPrimalObj * problem->sense_origin + problem->offset; + + // cupdlp_copy(primalResidual, ax, cupdlp_float, lp->nRows); + CUPDLP_COPY_VEC(primalResidual, ax, cupdlp_float, lp->nRows); + + // AddToVector(primalResidual, -1.0, problem->rhs, lp->nRows); + cupdlp_float alpha = -1.0; + cupdlp_axpy(work, lp->nRows, &alpha, problem->rhs, primalResidual); + + // double dPrimalFeas = 0.0; // Redundant + + // todo, check + // cupdlp_projNegative(primalResidual + problem->nEqs, primalResidual + + // problem->nEqs, lp->nRows - problem->nEqs); + // + + cupdlp_projNeg(primalResidual + problem->nEqs, lp->nRows - problem->nEqs); + + if (scaling->ifScaled) { + // cupdlp_edot(primalResidual, scaling->rowScale, lp->nRows); + // cupdlp_edot(primalResidual, scaling->rowScale_gpu, lp->nRows); + + // cupdlp_copy_vec(work->buffer3, scaling->rowScale, cupdlp_float, + // lp->nRows); cupdlp_edot(primalResidual, work->buffer3, lp->nRows); + + cupdlp_edot(primalResidual, work->rowScale, lp->nRows); + } + + if (work->settings->iInfNormAbsLocalTermination) { + cupdlp_int index; + cupdlp_infNormIndex(work, lp->nRows, primalResidual, &index); + *dPrimalFeasibility = fabs(primalResidual[index]); + } else { + cupdlp_twoNorm(work, lp->nRows, primalResidual, dPrimalFeasibility); + } +} + +void PDHG_Compute_Dual_Feasibility(CUPDLPwork *work, double *dualResidual, + const double *aty, const double *x, + const double *y, double *dDualFeasibility, + double *dDualObj, double *dComplementarity, + double *dSlackPos, double *dSlackNeg) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPresobj *resobj = work->resobj; + CUPDLPscaling *scaling = work->scaling; + // todo, compute Neumaier + // *dDualObj = Dotprod_Neumaier(problem->rhs, y, lp->nRows); + cupdlp_dot(work, lp->nRows, y, problem->rhs, dDualObj); + + // *dComplementarity = 0.0; + + // @note: + // original dual residual in pdlp: + // they compute: + // violation + reduced cost + // |max(-y, 0)| + |(I-\Pi)(c-Α'\nu)| + // compute c - A'y + + CUPDLP_COPY_VEC(dualResidual, aty, cupdlp_float, lp->nCols); + cupdlp_float alpha = -1.0; + cupdlp_scaleVector(work, alpha, dualResidual, lp->nCols); + + alpha = 1.0; + cupdlp_axpy(work, lp->nCols, &alpha, problem->cost, dualResidual); + + // julia version + // function compute_reduced_costs_from_primal_gradient_kernel!( + // primal_gradient::CuDeviceVector{Float64}, + // isfinite_variable_lower_bound::CuDeviceVector{Bool}, + // isfinite_variable_upper_bound::CuDeviceVector{Bool}, + // num_variables::Int64, + // reduced_costs::CuDeviceVector{Float64}, + // reduced_costs_violation::CuDeviceVector{Float64}, + // ) + // tx = threadIdx().x + (blockDim().x * (blockIdx().x - 0x1)) + // if tx <= num_variables + // @inbounds begin + // reduced_costs[tx] = max(primal_gradient[tx], 0.0) * + // isfinite_variable_lower_bound[tx] + + // min(primal_gradient[tx], 0.0) * + // isfinite_variable_upper_bound[tx] + // + // reduced_costs_violation[tx] = primal_gradient[tx] - + // reduced_costs[tx] + // end + // end + // return + // end + + CUPDLP_COPY_VEC(dSlackPos, dualResidual, cupdlp_float, lp->nCols); + + cupdlp_projPos(dSlackPos, lp->nCols); + + cupdlp_edot(dSlackPos, problem->hasLower, lp->nCols); + + cupdlp_float temp = 0.0; + // cupdlp_dot(work, lp->nCols, x, dSlackPos, &temp); + // *dComplementarity += temp; + // cupdlp_dot(work, lp->nCols, dSlackPos, resobj->dLowerFiltered, &temp); + // *dComplementarity -= temp; + cupdlp_dot(work, lp->nCols, dSlackPos, resobj->dLowerFiltered, &temp); + *dDualObj += temp; + + CUPDLP_COPY_VEC(dSlackNeg, dualResidual, cupdlp_float, lp->nCols); + + cupdlp_projNeg(dSlackNeg, lp->nCols); + + cupdlp_scaleVector(work, -1.0, dSlackNeg, lp->nCols); + + cupdlp_edot(dSlackNeg, problem->hasUpper, lp->nCols); + + // cupdlp_dot(work, lp->nCols, x, dSlackNeg, &temp); + // *dComplementarity -= temp; + // cupdlp_dot(work, lp->nCols, dSlackNeg, resobj->dUpperFiltered, &temp); + // *dComplementarity += temp; + cupdlp_dot(work, lp->nCols, dSlackNeg, resobj->dUpperFiltered, &temp); + *dDualObj -= temp; + + *dDualObj = *dDualObj * problem->sense_origin + problem->offset; + + alpha = -1.0; + cupdlp_axpy(work, lp->nCols, &alpha, dSlackPos, dualResidual); + alpha = 1.0; + cupdlp_axpy(work, lp->nCols, &alpha, dSlackNeg, dualResidual); + + if (scaling->ifScaled) { + // cupdlp_edot(dualResidual, scaling->colScale, lp->nCols); + // cupdlp_edot(dualResidual, scaling->colScale_gpu, lp->nCols); + + // cupdlp_copy_vec(work->buffer3, scaling->colScale, cupdlp_float, + // lp->nCols); cupdlp_edot(dualResidual, work->buffer3, lp->nCols); + + cupdlp_edot(dualResidual, work->colScale, lp->nCols); + } + + if (work->settings->iInfNormAbsLocalTermination) { + cupdlp_int index; + cupdlp_infNormIndex(work, lp->nCols, dualResidual, &index); + *dDualFeasibility = fabs(dualResidual[index]); + } else { + cupdlp_twoNorm(work, lp->nCols, dualResidual, dDualFeasibility); + } +} + +void PDHG_Compute_Primal_Infeasibility(CUPDLPwork *work, const cupdlp_float *y, + const cupdlp_float *dSlackPos, + const cupdlp_float *dSlackNeg, + const cupdlp_float *aty, + const cupdlp_float dualObj, + cupdlp_float *dPrimalInfeasObj, + cupdlp_float *dPrimalInfeasRes) { + CUPDLPproblem *problem = work->problem; + CUPDLPresobj *resobj = work->resobj; + CUPDLPscaling *scaling = work->scaling; + + cupdlp_float alpha; + + cupdlp_float yNrmSq = 1.0; + cupdlp_float slackPosNrmSq = 1.0; + cupdlp_float slackNegSq = 1.0; + cupdlp_float dScale = 1.0; + + // cupdlp_float dConstrResSq = 0.0; + // y and lambda must be feasible, no need to check bound + // cupdlp_float dBoundLbResSq = 0.0; + // cupdlp_float dBoundUbResSq = 0.0; + + // y, ldb ray + CUPDLP_COPY_VEC(resobj->dualInfeasRay, y, cupdlp_float, problem->data->nRows); + CUPDLP_COPY_VEC(resobj->dualInfeasLbRay, dSlackPos, cupdlp_float, + problem->data->nCols); + CUPDLP_COPY_VEC(resobj->dualInfeasUbRay, dSlackNeg, cupdlp_float, + problem->data->nCols); + cupdlp_twoNormSquared(work, problem->data->nRows, resobj->dualInfeasRay, + &yNrmSq); + cupdlp_twoNormSquared(work, problem->data->nCols, resobj->dualInfeasLbRay, + &slackPosNrmSq); + cupdlp_twoNormSquared(work, problem->data->nCols, resobj->dualInfeasUbRay, + &slackNegSq); + dScale = sqrt(yNrmSq + slackPosNrmSq + slackNegSq); + // dScale /= sqrt(problem->data->nRows + 2 * problem->data->nCols); + if (dScale < 1e-8) { + dScale = 1.0; + } + cupdlp_scaleVector(work, 1 / dScale, resobj->dualInfeasRay, + problem->data->nRows); + cupdlp_scaleVector(work, 1 / dScale, resobj->dualInfeasLbRay, + problem->data->nCols); + cupdlp_scaleVector(work, 1 / dScale, resobj->dualInfeasUbRay, + problem->data->nCols); + + // dual obj + *dPrimalInfeasObj = + (dualObj - problem->offset) / problem->sense_origin / dScale; + + // dual constraints [ATy1 + GTy2 + lambda] + CUPDLP_COPY_VEC(resobj->dualInfeasConstr, aty, cupdlp_float, + problem->data->nCols); + cupdlp_scaleVector(work, 1.0 / dScale, resobj->dualInfeasConstr, + problem->data->nCols); + alpha = 1.0; + cupdlp_axpy(work, problem->data->nCols, &alpha, resobj->dualInfeasLbRay, + resobj->dualInfeasConstr); + alpha = -1.0; + cupdlp_axpy(work, problem->data->nCols, &alpha, resobj->dualInfeasUbRay, + resobj->dualInfeasConstr); + if (scaling->ifScaled) { + cupdlp_edot(resobj->dualInfeasConstr, work->colScale, problem->data->nCols); + } + // cupdlp_twoNormSquared(work, problem->data->nCols, resobj->dualInfeasConstr, + // &dConstrResSq); + cupdlp_twoNorm(work, problem->data->nCols, resobj->dualInfeasConstr, + dPrimalInfeasRes); + + // dual bound + // always satisfied, no need to check +} + +void PDHG_Compute_Dual_Infeasibility(CUPDLPwork *work, const cupdlp_float *x, + const cupdlp_float *ax, + const cupdlp_float primalObj, + cupdlp_float *dDualInfeasObj, + cupdlp_float *dDualInfeasRes) { + CUPDLPproblem *problem = work->problem; + CUPDLPresobj *resobj = work->resobj; + CUPDLPscaling *scaling = work->scaling; + cupdlp_float pScale = 1.0; + cupdlp_float pConstrResSq = 0.0; + cupdlp_float pBoundLbResSq = 0.0; + cupdlp_float pBoundUbResSq = 0.0; + + // x ray + CUPDLP_COPY_VEC(resobj->primalInfeasRay, x, cupdlp_float, + problem->data->nCols); + cupdlp_twoNorm(work, problem->data->nCols, resobj->primalInfeasRay, &pScale); + // pScale /= sqrt(problem->data->nCols); + if (pScale < 1e-8) { + pScale = 1.0; + } + cupdlp_scaleVector(work, 1.0 / pScale, resobj->primalInfeasRay, + problem->data->nCols); + + // primal obj + *dDualInfeasObj = + (primalObj - problem->offset) / problem->sense_origin / pScale; + + // primal constraints [Ax, min(Gx, 0)] + CUPDLP_COPY_VEC(resobj->primalInfeasConstr, ax, cupdlp_float, + problem->data->nRows); + cupdlp_scaleVector(work, 1.0 / pScale, resobj->primalInfeasConstr, + problem->data->nRows); + cupdlp_projNeg(resobj->primalInfeasConstr + problem->nEqs, + problem->data->nRows - problem->nEqs); + if (scaling->ifScaled) { + cupdlp_edot(resobj->primalInfeasConstr, work->rowScale, + problem->data->nRows); + } + cupdlp_twoNormSquared(work, problem->data->nRows, resobj->primalInfeasConstr, + &pConstrResSq); + + // primal bound + // lb + CUPDLP_COPY_VEC(resobj->primalInfeasBound, resobj->primalInfeasRay, + cupdlp_float, problem->data->nCols); + cupdlp_projNeg(resobj->primalInfeasBound, problem->data->nCols); + cupdlp_edot(resobj->primalInfeasBound, problem->hasLower, + problem->data->nCols); + if (scaling->ifScaled) { + cupdlp_ediv(resobj->primalInfeasBound, work->colScale, + problem->data->nCols); + } + cupdlp_twoNormSquared(work, problem->data->nCols, resobj->primalInfeasBound, + &pBoundLbResSq); + // ub + CUPDLP_COPY_VEC(resobj->primalInfeasBound, resobj->primalInfeasRay, + cupdlp_float, problem->data->nCols); + cupdlp_projPos(resobj->primalInfeasBound, problem->data->nCols); + cupdlp_edot(resobj->primalInfeasBound, problem->hasUpper, + problem->data->nCols); + if (scaling->ifScaled) { + cupdlp_ediv(resobj->primalInfeasBound, work->colScale, + problem->data->nCols); + } + cupdlp_twoNormSquared(work, problem->data->nCols, resobj->primalInfeasBound, + &pBoundUbResSq); + + // sum up + *dDualInfeasRes = sqrt(pConstrResSq + pBoundLbResSq + pBoundUbResSq); +} + +// must be called after PDHG_Compute_Residuals(CUPDLPwork *work) +// because it needs resobj->dSlackPos and resobj->dSlackNeg +void PDHG_Compute_Infeas_Residuals(CUPDLPwork *work) { +#if problem_USE_TIMERS + ++problem->nComputeResidualsCalls; + double dStartTime = getTimeStamp(); +#endif + CUPDLPiterates *iterates = work->iterates; + CUPDLPresobj *resobj = work->resobj; + + // current solution + PDHG_Compute_Primal_Infeasibility(work, iterates->y->data, resobj->dSlackPos, + resobj->dSlackNeg, iterates->aty->data, + resobj->dDualObj, &resobj->dPrimalInfeasObj, + &resobj->dPrimalInfeasRes); + PDHG_Compute_Dual_Infeasibility(work, iterates->x->data, iterates->ax->data, + resobj->dPrimalObj, &resobj->dDualInfeasObj, + &resobj->dDualInfeasRes); + + // average solution + PDHG_Compute_Primal_Infeasibility( + work, iterates->yAverage->data, resobj->dSlackPosAverage, + resobj->dSlackNegAverage, iterates->atyAverage->data, + resobj->dDualObjAverage, &resobj->dPrimalInfeasObjAverage, + &resobj->dPrimalInfeasResAverage); + PDHG_Compute_Dual_Infeasibility( + work, iterates->xAverage->data, iterates->axAverage->data, + resobj->dPrimalObjAverage, &resobj->dDualInfeasObjAverage, + &resobj->dDualInfeasResAverage); + +#if problem_USE_TIMERS + problem->dComputeResidualsTime += getTimeStamp() - dStartTime; +#endif +} + +void PDHG_Compute_Residuals(CUPDLPwork *work) { +#if problem_USE_TIMERS + ++problem->nComputeResidualsCalls; + double dStartTime = getTimeStamp(); +#endif + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPresobj *resobj = work->resobj; + CUPDLPiterates *iterates = work->iterates; + CUPDLPscaling *scaling = work->scaling; + CUPDLPsettings *settings = work->settings; + + PDHG_Compute_Primal_Feasibility(work, resobj->primalResidual, + iterates->ax->data, iterates->x->data, + &resobj->dPrimalFeas, &resobj->dPrimalObj); + PDHG_Compute_Dual_Feasibility( + work, resobj->dualResidual, iterates->aty->data, iterates->x->data, + iterates->y->data, &resobj->dDualFeas, &resobj->dDualObj, + &resobj->dComplementarity, resobj->dSlackPos, resobj->dSlackNeg); + + PDHG_Compute_Primal_Feasibility( + work, resobj->primalResidualAverage, iterates->axAverage->data, + iterates->xAverage->data, &resobj->dPrimalFeasAverage, + &resobj->dPrimalObjAverage); + PDHG_Compute_Dual_Feasibility( + work, resobj->dualResidualAverage, iterates->atyAverage->data, + iterates->xAverage->data, iterates->yAverage->data, + &resobj->dDualFeasAverage, &resobj->dDualObjAverage, + &resobj->dComplementarityAverage, resobj->dSlackPosAverage, + resobj->dSlackNegAverage); + + // resobj->dPrimalObj /= (scaling->dObjScale * scaling->dObjScale); + // resobj->dDualObj /= (scaling->dObjScale * scaling->dObjScale); + resobj->dDualityGap = resobj->dPrimalObj - resobj->dDualObj; + resobj->dRelObjGap = + fabs(resobj->dPrimalObj - resobj->dDualObj) / + (1.0 + fabs(resobj->dPrimalObj) + fabs(resobj->dDualObj)); + + // resobj->dPrimalObjAverage /= scaling->dObjScale * scaling->dObjScale; + // resobj->dDualObjAverage /= scaling->dObjScale * scaling->dObjScale; + resobj->dDualityGapAverage = + resobj->dPrimalObjAverage - resobj->dDualObjAverage; + resobj->dRelObjGapAverage = + fabs(resobj->dPrimalObjAverage - resobj->dDualObjAverage) / + (1.0 + fabs(resobj->dPrimalObjAverage) + fabs(resobj->dDualObjAverage)); + +#if problem_USE_TIMERS + problem->dComputeResidualsTime += getTimeStamp() - dStartTime; +#endif +} + +void PDHG_Init_Variables(CUPDLPwork *work) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPstepsize *stepsize = work->stepsize; + CUPDLPiterates *iterates = work->iterates; + + // cupdlp_zero(iterates->x, cupdlp_float, lp->nCols); + CUPDLP_ZERO_VEC(iterates->x->data, cupdlp_float, lp->nCols); + + // XXX: PDLP Does not project x0, so we uncomment for 1-1 comparison + + PDHG_Project_Bounds(work, iterates->x->data); + + // cupdlp_zero(iterates->y, cupdlp_float, lp->nRows); + CUPDLP_ZERO_VEC(iterates->y->data, cupdlp_float, lp->nRows); + + // Ax(work, iterates->ax, iterates->x); + // ATyCPU(work, iterates->aty, iterates->y); + Ax(work, iterates->ax, iterates->x); + ATy(work, iterates->aty, iterates->y); + + // cupdlp_zero(iterates->xSum, cupdlp_float, lp->nCols); + // cupdlp_zero(iterates->ySum, cupdlp_float, lp->nRows); + // cupdlp_zero(iterates->xAverage, cupdlp_float, lp->nCols); + // cupdlp_zero(iterates->yAverage, cupdlp_float, lp->nRows); + CUPDLP_ZERO_VEC(iterates->xSum, cupdlp_float, lp->nCols); + CUPDLP_ZERO_VEC(iterates->ySum, cupdlp_float, lp->nRows); + CUPDLP_ZERO_VEC(iterates->xAverage->data, cupdlp_float, lp->nCols); + CUPDLP_ZERO_VEC(iterates->yAverage->data, cupdlp_float, lp->nRows); + + PDHG_Project_Bounds(work, iterates->xSum); + PDHG_Project_Bounds(work, iterates->xAverage->data); + + stepsize->dSumPrimalStep = 0.0; + stepsize->dSumDualStep = 0.0; + + CUPDLP_ZERO_VEC(iterates->xLastRestart, cupdlp_float, lp->nCols); + CUPDLP_ZERO_VEC(iterates->yLastRestart, cupdlp_float, lp->nRows); +} + +/* TODO: this function seems considering + * l1 <= Ax <= u1 + * l2 <= x <= u2 + * needs rewritten for current formulation + */ +void PDHG_Check_Data(CUPDLPwork *work) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPstepsize *stepsize = work->stepsize; + CUPDLPiterates *iterates = work->iterates; + cupdlp_int nFreeCol = 0; + cupdlp_int nFixedCol = 0; + cupdlp_int nUpperCol = 0; + cupdlp_int nLowerCol = 0; + cupdlp_int nRangedCol = 0; + cupdlp_int nFreeRow = 0; + cupdlp_int nFixedRow = 0; + cupdlp_int nUpperRow = 0; + cupdlp_int nLowerRow = 0; + cupdlp_int nRangedRow = 0; + + for (cupdlp_int iSeq = 0; iSeq < lp->nCols; ++iSeq) { + cupdlp_bool hasLower = problem->lower[iSeq] > -INFINITY; + cupdlp_bool hasUpper = problem->upper[iSeq] < +INFINITY; + + if (!hasLower && !hasUpper) { + ++nFreeCol; + if (work->settings->nLogLevel>0) + cupdlp_printf("Warning: variable %d is free.", iSeq); + } + + if (hasLower && hasUpper) { + if (problem->lower[iSeq] == problem->upper[iSeq]) { + ++nFixedCol; + // cupdlp_printf( "Warning: variable %d is fixed.", iSeq); + } else + ++nRangedCol; + } + + if (hasLower) { + // XXX: uncommented for PDLP comparison + // CUPDLP_ASSERT(iterates->x[iSeq] >= problem->lower[iSeq]); + nLowerCol += !hasUpper; + } + + if (hasUpper) { + // XXX: uncommented for PDLP comparison + // CUPDLP_ASSERT(iterates->x[iSeq] <= problem->upper[iSeq]); + nUpperCol += !hasLower; + } + } + + for (cupdlp_int iSeq = lp->nCols; iSeq < lp->nCols; ++iSeq) { + cupdlp_bool hasLower = problem->lower[iSeq] > -INFINITY; + cupdlp_bool hasUpper = problem->upper[iSeq] < +INFINITY; + + if (!hasLower && !hasUpper) { + ++nFreeRow; + if (work->settings->nLogLevel>0) + cupdlp_printf("Warning: row %d is free.", iSeq - lp->nCols); + } + + if (hasLower && hasUpper) { + if (problem->lower[iSeq] == problem->upper[iSeq]) + ++nFixedRow; + else + ++nRangedRow; + } + + if (hasLower) { + // CUPDLP_ASSERT(iterates->x[iSeq] >= problem->lower[iSeq]); + nLowerRow += !hasUpper; + } + + if (hasUpper) { + // CUPDLP_ASSERT(iterates->x[iSeq] <= problem->upper[iSeq]); + nUpperRow += !hasLower; + } + } + + for (cupdlp_int iRow = 0; iRow < lp->nRows; ++iRow) { + CUPDLP_ASSERT(iterates->y->data[iRow] < +INFINITY); + CUPDLP_ASSERT(iterates->y->data[iRow] > -INFINITY); + } + + for (cupdlp_int iRow = 0; iRow < lp->nRows; ++iRow) { + if (problem->data->csr_matrix->rowMatBeg[iRow + 1] - + problem->data->csr_matrix->rowMatBeg[iRow] == + 1) { + if (work->settings->nLogLevel>0) + cupdlp_printf("Warning: row %d is a singleton row.", iRow); + } + } + + CUPDLP_ASSERT(nRangedRow == 0); + if (work->settings->nLogLevel>0) { + cupdlp_printf("nFreeCol : %d\n", nFreeCol); + cupdlp_printf("nFixedCol : %d\n", nFixedCol); + cupdlp_printf("nRangedCol: %d\n", nRangedCol); + cupdlp_printf("nLowerCol : %d\n", nLowerCol); + cupdlp_printf("nUpperCol : %d\n", nUpperCol); + cupdlp_printf("nFreeRow : %d\n", nFreeRow); + cupdlp_printf("nFixedRow : %d\n", nFixedRow); + cupdlp_printf("nRangedRow: %d\n", nRangedRow); + cupdlp_printf("nLowerRow : %d\n", nLowerRow); + cupdlp_printf("nUpperRow : %d\n", nUpperRow); + } + + // We need to test problems ranged row-bounds more carefully. + CUPDLP_ASSERT(nRangedRow == 0); +} + +termination_code PDHG_Check_Primal_Infeasibility( + CUPDLPwork *pdhg, cupdlp_float dPrimalInfeasObj, + cupdlp_float dPrimalInfeasRes) { + CUPDLPresobj *resobj = pdhg->resobj; + + termination_code primalCode = FEASIBLE; + + if (dPrimalInfeasObj > 0.0) { + if (dPrimalInfeasRes < resobj->dFeasTol * dPrimalInfeasObj) + primalCode = INFEASIBLE; + } + + return primalCode; +} + +termination_code PDHG_Check_Dual_Infeasibility(CUPDLPwork *pdhg, + cupdlp_float dDualInfeasObj, + cupdlp_float dDualInfeasRes) { + CUPDLPresobj *resobj = pdhg->resobj; + + termination_code dualCode = FEASIBLE; + + if (dDualInfeasObj < 0.0) { + if (dDualInfeasRes < -resobj->dFeasTol * dDualInfeasObj) + dualCode = INFEASIBLE; + } + + return dualCode; +} + +termination_code PDHG_Check_Infeasibility(CUPDLPwork *pdhg, int bool_print) { + CUPDLPresobj *resobj = pdhg->resobj; + termination_code t_code = FEASIBLE; + + // current solution + + // primal infeasibility + if (PDHG_Check_Primal_Infeasibility(pdhg, resobj->dPrimalInfeasObj, + resobj->dPrimalInfeasRes) == INFEASIBLE) { + resobj->primalCode = INFEASIBLE; + resobj->termInfeasIterate = LAST_ITERATE; + t_code = INFEASIBLE_OR_UNBOUNDED; + } + + // dual infeasibility + if (PDHG_Check_Dual_Infeasibility(pdhg, resobj->dDualInfeasObj, + resobj->dDualInfeasRes) == INFEASIBLE) { + resobj->dualCode = INFEASIBLE; + resobj->termInfeasIterate = LAST_ITERATE; + t_code = INFEASIBLE_OR_UNBOUNDED; + } + + // average solution + // primal infeasibility + if (PDHG_Check_Primal_Infeasibility(pdhg, resobj->dPrimalInfeasObjAverage, + resobj->dPrimalInfeasResAverage) == + INFEASIBLE) { + resobj->primalCode = INFEASIBLE; + resobj->termInfeasIterate = AVERAGE_ITERATE; + t_code = INFEASIBLE_OR_UNBOUNDED; + } + + // dual infeasibility + if (PDHG_Check_Dual_Infeasibility(pdhg, resobj->dDualInfeasObjAverage, + resobj->dDualInfeasResAverage) == + INFEASIBLE) { + resobj->dualCode = INFEASIBLE; + resobj->termInfeasIterate = AVERAGE_ITERATE; + t_code = INFEASIBLE_OR_UNBOUNDED; + } + + if (bool_print) { + printf("Last iter:\n"); + printf(" Primal obj = %+.4e, res = %+.4e\n", resobj->dPrimalInfeasObj, + resobj->dPrimalInfeasRes); + printf(" Dual obj = %+.4e, res = %+.4e\n", resobj->dDualInfeasObj, + resobj->dDualInfeasRes); + printf("Average iter:\n"); + printf(" Primal obj = %+.4e, res = %+.4e\n", + resobj->dPrimalInfeasObjAverage, resobj->dPrimalInfeasResAverage); + printf(" Dual obj = %+.4e, res = %+.4e\n", resobj->dDualInfeasObjAverage, + resobj->dDualInfeasResAverage); + } + + return t_code; +} + +cupdlp_bool PDHG_Check_Termination(CUPDLPwork *pdhg, int bool_print) { + CUPDLPproblem *problem = pdhg->problem; + CUPDLPsettings *settings = pdhg->settings; + CUPDLPresobj *resobj = pdhg->resobj; + CUPDLPscaling *scaling = pdhg->scaling; +#if PDHG_DISPLAY_TERMINATION_CHECK + // todo, check, is it correct + if (bool_print) { + cupdlp_printf( + "Termination check: %e|%e %e|%e %e|%e\n", resobj->dPrimalFeas, + settings->dPrimalTol * (1.0 + scaling->dNormRhs), resobj->dDualFeas, + settings->dDualTol * (1.0 + scaling->dNormCost), resobj->dRelObjGap, + settings->dGapTol); + } + +#endif + int bool_pass = 0; + if (pdhg->settings->iInfNormAbsLocalTermination) { + bool_pass = + (resobj->dPrimalFeas < settings->dPrimalTol) && + (resobj->dDualFeas < settings->dDualTol); + } else { + bool_pass = + (resobj->dPrimalFeas < settings->dPrimalTol * (1.0 + scaling->dNormRhs)) && + (resobj->dDualFeas < settings->dDualTol * (1.0 + scaling->dNormCost)); + } + bool_pass = bool_pass && (resobj->dRelObjGap < settings->dGapTol); + return bool_pass; +} + +cupdlp_bool PDHG_Check_Termination_Average(CUPDLPwork *pdhg, int bool_print) { + CUPDLPproblem *problem = pdhg->problem; + CUPDLPsettings *settings = pdhg->settings; + CUPDLPresobj *resobj = pdhg->resobj; + CUPDLPscaling *scaling = pdhg->scaling; +#if PDHG_DISPLAY_TERMINATION_CHECK + if (bool_print) { + cupdlp_printf("Termination check: %e|%e %e|%e %e|%e\n", + resobj->dPrimalFeasAverage, + settings->dPrimalTol * (1.0 + scaling->dNormRhs), + resobj->dDualFeasAverage, + settings->dDualTol * (1.0 + scaling->dNormCost), + resobj->dRelObjGapAverage, settings->dGapTol); + } +#endif + int bool_pass = ((resobj->dPrimalFeasAverage < + settings->dPrimalTol * (1.0 + scaling->dNormRhs)) && + (resobj->dDualFeasAverage < + settings->dDualTol * (1.0 + scaling->dNormCost)) && + (resobj->dRelObjGapAverage < settings->dGapTol)); + return bool_pass; +} + +void PDHG_Print_Header(CUPDLPwork *pdhg) { + // cupdlp_printf("%9s %15s %15s %8s %8s %10s %8s %7s\n", "Iter", + // "Primal.Obj", "Dual.Obj", "Gap", "Compl", "Primal.Inf", + // "Dual.Inf", "Time"); + cupdlp_printf("%9s %15s %15s %8s %10s %8s %7s\n", "Iter", "Primal.Obj", + "Dual.Obj", "Gap", "Primal.Inf", "Dual.Inf", "Time"); +} + +void PDHG_Print_Iter(CUPDLPwork *pdhg) { + /* Format time as xxx.yy for < 1000s and as integer afterwards. */ + CUPDLPresobj *resobj = pdhg->resobj; + CUPDLPtimers *timers = pdhg->timers; + char timeString[8]; + if (timers->dSolvingTime < 100.0) + cupdlp_snprintf(timeString, 8, "%6.2fs", timers->dSolvingTime); + else + cupdlp_snprintf(timeString, 8, "%6ds", (cupdlp_int)timers->dSolvingTime); + + // cupdlp_printf("%9d %+15.8e %+15.8e %+8.2e %8.2e %10.2e %8.2e %7s + // [L]\n", + // timers->nIter, resobj->dPrimalObj, resobj->dDualObj, + // resobj->dDualityGap, resobj->dComplementarity, + // resobj->dPrimalFeas, resobj->dDualFeas, timeString); + + cupdlp_printf("%9d %+15.8e %+15.8e %+8.2e %10.2e %8.2e %7s [L]\n", + timers->nIter, resobj->dPrimalObj, resobj->dDualObj, + resobj->dDualityGap, resobj->dPrimalFeas, resobj->dDualFeas, + timeString); +} + +void PDHG_Print_Iter_Average(CUPDLPwork *pdhg) { + /* Format time as xxx.yy for < 1000s and as integer afterwards. */ + CUPDLPresobj *resobj = pdhg->resobj; + CUPDLPtimers *timers = pdhg->timers; + char timeString[8]; + if (timers->dSolvingTime < 100.0) + cupdlp_snprintf(timeString, 8, "%6.2fs", timers->dSolvingTime); + else + cupdlp_snprintf(timeString, 8, "%6ds", (cupdlp_int)timers->dSolvingTime); + + // cupdlp_printf("%9d %+15.8e %+15.8e %+8.2e %8.2e %10.2e %8.2e %7s + // [A]\n", + // timers->nIter, resobj->dPrimalObjAverage, + // resobj->dDualObjAverage, resobj->dDualityGapAverage, + // resobj->dComplementarityAverage, resobj->dPrimalFeasAverage, + // resobj->dDualFeasAverage, timeString); + cupdlp_printf("%9d %+15.8e %+15.8e %+8.2e %10.2e %8.2e %7s [A]\n", + timers->nIter, resobj->dPrimalObjAverage, + resobj->dDualObjAverage, resobj->dDualityGapAverage, + resobj->dPrimalFeasAverage, resobj->dDualFeasAverage, + timeString); +} + +void PDHG_Compute_SolvingTime(CUPDLPwork *pdhg) { + CUPDLPtimers *timers = pdhg->timers; + timers->dSolvingTime = getTimeStamp() - timers->dSolvingBeg; +} + +cupdlp_retcode PDHG_Solve(CUPDLPwork *pdhg) { + cupdlp_retcode retcode = RETCODE_OK; + + CUPDLPproblem *problem = pdhg->problem; + CUPDLPstepsize *stepsize = pdhg->stepsize; + CUPDLPsettings *settings = pdhg->settings; + CUPDLPresobj *resobj = pdhg->resobj; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPtimers *timers = pdhg->timers; + CUPDLPscaling *scaling = pdhg->scaling; + + timers->dSolvingBeg = getTimeStamp(); + + PDHG_Init_Data(pdhg); + + CUPDLP_CALL(PDHG_Init_Step_Sizes(pdhg)); + + PDHG_Init_Variables(pdhg); + + // todo: translate check_data into cuda or do it on cpu + // PDHG_Check_Data(pdhg); + + // PDHG_Print_Header(pdhg); + + // Repeat the iteration logging header periodically if logging style + // is minimal (pdhg->settings->nLogLevel=1), initialising + // iter_log_since_header so that an initial header is printed + const int iter_log_between_header = 50; + int iter_log_since_header = iter_log_between_header; + for (timers->nIter = 0; timers->nIter < settings->nIterLim; ++timers->nIter) { + PDHG_Compute_SolvingTime(pdhg); +#if CUPDLP_DUMP_ITERATES_STATS & CUPDLP_DEBUG + PDHG_Dump_Stats(pdhg); +#endif + int bool_checking = (timers->nIter < 10) || + (timers->nIter == (settings->nIterLim - 1)) || + (timers->dSolvingTime > settings->dTimeLim); + int bool_print = 0; +#if CUPDLP_DEBUG + bool_checking = (bool_checking || !(timers->nIter % CUPDLP_DEBUG_INTERVAL)); + bool_print = bool_checking; +#else + bool_checking = + (bool_checking || !(timers->nIter % CUPDLP_RELEASE_INTERVAL)); + bool_print = + (bool_checking && !(timers->nIter % (CUPDLP_RELEASE_INTERVAL * + settings->nLogInterval))) || + (timers->nIter == (settings->nIterLim - 1)) || + (timers->dSolvingTime > settings->dTimeLim); + // Ensure that bool_print is false if the logging level has been + // set to 0 via the HiGHS option + bool_print = pdhg->settings->nLogLevel>0 && bool_print; +#endif + // Full printing is false only if the logging level has been set + // to 0 or 1 via the HiGHS option + int full_print = pdhg->settings->nLogLevel >= 2; + if (bool_checking) { + PDHG_Compute_Average_Iterate(pdhg); + PDHG_Compute_Residuals(pdhg); + PDHG_Compute_Infeas_Residuals(pdhg); + + if (bool_print) { + // With reduced printing, the header is only needed for the + // first iteration since only average iteration printing is + // carried out + if (full_print || + iter_log_since_header == iter_log_between_header) { + PDHG_Print_Header(pdhg); + iter_log_since_header = 0; + } + if (full_print) PDHG_Print_Iter(pdhg); + PDHG_Print_Iter_Average(pdhg); + iter_log_since_header++; + } + + // Termination check printing is only done when printing is full + int termination_print = bool_print && full_print; + if (PDHG_Check_Termination(pdhg, termination_print)) { + // cupdlp_printf("Optimal current solution.\n"); + resobj->termIterate = LAST_ITERATE; + resobj->termCode = OPTIMAL; + break; + } + + // Don't allow "average" termination if + // iInfNormAbsLocalTermination is set + if (!pdhg->settings->iInfNormAbsLocalTermination && + PDHG_Check_Termination_Average(pdhg, termination_print)) { + // cupdlp_printf("Optimal average solution.\n"); + + CUPDLP_COPY_VEC(iterates->x->data, iterates->xAverage->data, + cupdlp_float, problem->nCols); + CUPDLP_COPY_VEC(iterates->y->data, iterates->yAverage->data, + cupdlp_float, problem->nRows); + CUPDLP_COPY_VEC(iterates->ax->data, iterates->axAverage->data, + cupdlp_float, problem->nRows); + CUPDLP_COPY_VEC(iterates->aty->data, iterates->atyAverage->data, + cupdlp_float, problem->nCols); + CUPDLP_COPY_VEC(resobj->dSlackPos, resobj->dSlackPosAverage, + cupdlp_float, problem->nCols); + CUPDLP_COPY_VEC(resobj->dSlackNeg, resobj->dSlackNegAverage, + cupdlp_float, problem->nCols); + + resobj->termIterate = AVERAGE_ITERATE; + resobj->termCode = OPTIMAL; + break; + } + + if (PDHG_Check_Infeasibility(pdhg, 0) == INFEASIBLE_OR_UNBOUNDED) { + // cupdlp_printf("Infeasible or unbounded.\n"); + // if (resobj->primalCode == INFEASIBLE && resobj->dualCode == FEASIBLE) + // { + // resobj->dualCode = UNBOUNDED; + // } else if (resobj->primalCode == FEASIBLE && + // resobj->dualCode == INFEASIBLE) { + // resobj->primalCode = UNBOUNDED; + // } + // resobj->termCode = resobj->primalCode; + resobj->termCode = INFEASIBLE_OR_UNBOUNDED; + break; + } + + if (timers->dSolvingTime > settings->dTimeLim) { + // cupdlp_printf("Time limit reached.\n"); + resobj->termCode = TIMELIMIT_OR_ITERLIMIT; + break; + } + + if (timers->nIter >= (settings->nIterLim - 1)) { + // cupdlp_printf("Iteration limit reached.\n"); + resobj->termCode = TIMELIMIT_OR_ITERLIMIT; + break; + } + + PDHG_Restart_Iterate(pdhg); + } + + // CUPDLP_CALL(PDHG_Update_Iterate(pdhg)); + if (PDHG_Update_Iterate(pdhg) == RETCODE_FAILED) { + // cupdlp_printf("Time limit reached.\n"); + resobj->termCode = TIMELIMIT_OR_ITERLIMIT; + break; + } + } + + // print at last + if (pdhg->settings->nLogLevel>0) { + int full_print = pdhg->settings->nLogLevel >= 2; + if (full_print) { + PDHG_Print_Header(pdhg); + PDHG_Print_Iter(pdhg); + } + PDHG_Print_Iter_Average(pdhg); + } + + if (pdhg->settings->nLogLevel>0) { + cupdlp_printf("\n"); + cupdlp_printf("%-27s ", "Solving information:"); + + switch (resobj->termCode) { + case OPTIMAL: + if (resobj->termIterate == LAST_ITERATE) { + cupdlp_printf("Optimal current solution.\n"); + } else if (resobj->termIterate == AVERAGE_ITERATE) { + cupdlp_printf("Optimal average solution.\n"); + } + break; + case TIMELIMIT_OR_ITERLIMIT: + if (timers->dSolvingTime > settings->dTimeLim) { + cupdlp_printf("Time limit reached.\n"); + } else if (timers->nIter >= (settings->nIterLim - 1)) { + cupdlp_printf("Iteration limit reached.\n"); + } + break; + case INFEASIBLE_OR_UNBOUNDED: + if (resobj->primalCode == INFEASIBLE && resobj->dualCode == FEASIBLE) { + cupdlp_printf("Infeasible or unbounded: primal infeasible."); + } else if (resobj->primalCode == FEASIBLE && + resobj->dualCode == INFEASIBLE) { + cupdlp_printf("Infeasible or unbounded: dual infeasible."); + } else { + cupdlp_printf( + "Infeasible or unbounded: both primal and dual infeasible."); + } + + if (resobj->termInfeasIterate == LAST_ITERATE) { + cupdlp_printf(" [L]\n"); + } else if (resobj->termInfeasIterate == AVERAGE_ITERATE) { + cupdlp_printf(" [A]\n"); + } + break; + default: + cupdlp_printf("Unexpected.\n"); + break; + } + + if (resobj->termCode == OPTIMAL && resobj->termIterate == AVERAGE_ITERATE) { + cupdlp_printf("%27s %+15.8e\n", + "Primal objective:", resobj->dPrimalObjAverage); + cupdlp_printf("%27s %+15.8e\n", "Dual objective:", resobj->dDualObjAverage); + cupdlp_printf("%27s %8.2e / %8.2e\n", + "Primal infeas (abs/rel):", resobj->dPrimalFeasAverage, + resobj->dPrimalFeasAverage / (1.0 + scaling->dNormRhs)); + cupdlp_printf("%27s %8.2e / %8.2e\n", + "Dual infeas (abs/rel):", resobj->dDualFeasAverage, + resobj->dDualFeasAverage / (1.0 + scaling->dNormCost)); + cupdlp_printf("%27s %8.2e / %8.2e\n", + "Duality gap (abs/rel):", fabs(resobj->dDualityGapAverage), + resobj->dRelObjGapAverage); + } else { + cupdlp_printf("%27s %+15.8e\n", "Primal objective:", resobj->dPrimalObj); + cupdlp_printf("%27s %+15.8e\n", "Dual objective:", resobj->dDualObj); + cupdlp_printf("%27s %8.2e / %8.2e\n", + "Primal infeas (abs/rel):", resobj->dPrimalFeas, + resobj->dPrimalFeas / (1.0 + scaling->dNormRhs)); + cupdlp_printf("%27s %8.2e / %8.2e\n", + "Dual infeas (abs/rel):", resobj->dDualFeas, + resobj->dDualFeas / (1.0 + scaling->dNormCost)); + cupdlp_printf("%27s %8.2e / %8.2e\n", + "Duality gap (abs/rel):", fabs(resobj->dDualityGap), + resobj->dRelObjGap); + } + cupdlp_printf("%27s %d\n", "Number of iterations:", timers->nIter); + cupdlp_printf("\n"); + } + +#if PDHG_USE_TIMERS + if (pdhg->settings->nLogLevel>1) { + cupdlp_printf("Timing information:\n"); + // cupdlp_printf("%21s %e in %d iterations\n", "Total solver time", + // timers->dSolvingTime, timers->nIter); + cupdlp_printf( + "%21s %e in %d iterations\n", "Total solver time", + timers->dSolvingTime + timers->dScalingTime + timers->dPresolveTime, + timers->nIter); + cupdlp_printf("%21s %e in %d iterations\n", "Solve time", + timers->dSolvingTime, timers->nIter); + cupdlp_printf("%21s %e \n", "Iters per sec", + timers->nIter / timers->dSolvingTime); + cupdlp_printf("%21s %e\n", "Scaling time", timers->dScalingTime); + cupdlp_printf("%21s %e\n", "Presolve time", timers->dPresolveTime); + cupdlp_printf("%21s %e in %d calls\n", "Ax", timers->dAxTime, + timers->nAxCalls); + cupdlp_printf("%21s %e in %d calls\n", "Aty", timers->dAtyTime, + timers->nAtyCalls); + cupdlp_printf("%21s %e in %d calls\n", "ComputeResiduals", + timers->dComputeResidualsTime, timers->nComputeResidualsCalls); + cupdlp_printf("%21s %e in %d calls\n", "UpdateIterates", + timers->dUpdateIterateTime, timers->nUpdateIterateCalls); + } +#endif + +#ifndef CUPDLP_CPU + if (pdhg->settings->nLogLevel>0) { + cupdlp_printf("\n"); + cupdlp_printf("GPU Timing information:\n"); + cupdlp_printf("%21s %e\n", "CudaPrepare", timers->CudaPrepareTime); + cupdlp_printf("%21s %e\n", "Alloc&CopyMatToDevice", + timers->AllocMem_CopyMatToDeviceTime); + cupdlp_printf("%21s %e\n", "CopyVecToDevice", timers->CopyVecToDeviceTime); + cupdlp_printf("%21s %e\n", "DeviceMatVecProd", timers->DeviceMatVecProdTime); + cupdlp_printf("%21s %e\n", "CopyVecToHost", timers->CopyVecToHostTime); + } +#endif + +exit_cleanup: + return retcode; +} + +cupdlp_retcode PDHG_PostSolve(CUPDLPwork *pdhg, cupdlp_int nCols_origin, + cupdlp_int *constraint_new_idx, + cupdlp_int *constraint_type, + cupdlp_float *col_value, cupdlp_float *col_dual, + cupdlp_float *row_value, cupdlp_float *row_dual, + cupdlp_int *value_valid, cupdlp_int *dual_valid) { + cupdlp_retcode retcode = RETCODE_OK; + + CUPDLPproblem *problem = pdhg->problem; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPscaling *scaling = pdhg->scaling; + CUPDLPresobj *resobj = pdhg->resobj; + cupdlp_float sense = problem->sense_origin; + + // flag + cupdlp_int col_value_flag = 0; + cupdlp_int col_dual_flag = 0; + cupdlp_int row_value_flag = 0; + cupdlp_int row_dual_flag = 0; + + // allocate buffer + cupdlp_float *col_buffer = NULL; + cupdlp_float *row_buffer = NULL; + cupdlp_float *col_buffer2 = NULL; + // no need for row_buffer2 + // cupdlp_float *row_buffer2 = NULL; + CUPDLP_INIT_DOUBLE(col_buffer, problem->nCols); + CUPDLP_INIT_DOUBLE(row_buffer, problem->nRows); + CUPDLP_INIT_DOUBLE(col_buffer2, problem->nCols); + // CUPDLP_INIT_DOUBLE(row_buffer2, problem->nRows); + + // unscale + if (scaling->ifScaled) { + cupdlp_ediv(iterates->x->data, pdhg->colScale, problem->nCols); + cupdlp_ediv(iterates->y->data, pdhg->rowScale, problem->nRows); + cupdlp_edot(resobj->dSlackPos, pdhg->colScale, problem->nCols); + cupdlp_edot(resobj->dSlackNeg, pdhg->colScale, problem->nCols); + cupdlp_edot(iterates->ax->data, pdhg->rowScale, problem->nRows); + cupdlp_edot(iterates->aty->data, pdhg->colScale, problem->nCols); + } + + // col value: extract x from (x, z) + if (col_value) { + CUPDLP_COPY_VEC(col_value, iterates->x->data, cupdlp_float, nCols_origin); + + col_value_flag = 1; + } + + // row value + if (row_value) { + if (constraint_new_idx) { + CUPDLP_COPY_VEC(row_buffer, iterates->ax->data, cupdlp_float, + problem->nRows); + + // un-permute row value + for (int i = 0; i < problem->nRows; i++) { + row_value[i] = row_buffer[constraint_new_idx[i]]; + } + } else { + CUPDLP_COPY_VEC(row_value, iterates->ax->data, cupdlp_float, + problem->nRows); + } + + if (constraint_type) { + CUPDLP_COPY_VEC(col_buffer, iterates->x->data, cupdlp_float, + problem->nCols); + + // EQ = 0, LEQ = 1, GEQ = 2, BOUND = 3 + for (int i = 0, j = 0; i < problem->nRows; i++) { + if (constraint_type[i] == 1) { // LEQ: multiply -1 + row_value[i] = -row_value[i]; + } else if (constraint_type[i] == 3) { // BOUND: get Ax from Ax - z + row_value[i] = row_value[i] + col_buffer[nCols_origin + j]; + j++; + } + } + } + + row_value_flag = 1; + } + + // col duals of l <= x <= u + if (col_dual) { + CUPDLP_COPY_VEC(col_buffer, resobj->dSlackPos, cupdlp_float, nCols_origin); + CUPDLP_COPY_VEC(col_buffer2, resobj->dSlackNeg, cupdlp_float, nCols_origin); + + for (int i = 0; i < nCols_origin; i++) { + col_dual[i] = col_buffer[i] - col_buffer2[i]; + } + + ScaleVector(sense, col_dual, nCols_origin); + + col_dual_flag = 1; + } + + // row dual: recover y + if (row_dual) { + if (constraint_new_idx) { + CUPDLP_COPY_VEC(row_buffer, iterates->y->data, cupdlp_float, + problem->nRows); + // un-permute row dual + for (int i = 0; i < problem->nRows; i++) { + row_dual[i] = row_buffer[constraint_new_idx[i]]; + } + } else { + CUPDLP_COPY_VEC(row_dual, iterates->y->data, cupdlp_float, + problem->nRows); + } + + ScaleVector(sense, row_dual, problem->nRows); + + if (constraint_type) { + // EQ = 0, LEQ = 1, GEQ = 2, BOUND = 3 + for (int i = 0; i < problem->nRows; i++) { + if (constraint_type[i] == 1) { // LEQ: multiply -1 + row_dual[i] = -row_dual[i]; + } + } + } + + row_dual_flag = 1; + } + + // valid + if (value_valid) { + *value_valid = col_value_flag && row_value_flag; + } + + if (dual_valid) { + *dual_valid = col_dual_flag && row_dual_flag; + } + +exit_cleanup: + // free buffer + CUPDLP_FREE(col_buffer); + CUPDLP_FREE(row_buffer); + CUPDLP_FREE(col_buffer2); + // CUPDLP_FREE(row_buffer2); + + return retcode; +} + +cupdlp_retcode LP_SolvePDHG( + CUPDLPwork *pdhg, cupdlp_bool *ifChangeIntParam, cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, cupdlp_float *floatParam, char *fp, + cupdlp_int nCols_origin, cupdlp_float *col_value, cupdlp_float *col_dual, + cupdlp_float *row_value, cupdlp_float *row_dual, cupdlp_int *value_valid, + cupdlp_int *dual_valid, cupdlp_bool ifSaveSol, char *fp_sol, + cupdlp_int *constraint_new_idx, cupdlp_int *constraint_type, + cupdlp_int *model_status, cupdlp_int* num_iter) { + cupdlp_retcode retcode = RETCODE_OK; + + // Set the parameters first - which is silent + CUPDLP_CALL(PDHG_SetUserParam(pdhg, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + + // Call PDHG_PrintHugeCUPDHG() if logging level (set in + // PDHG_SetUserParam) is verbose + if (pdhg->settings->nLogLevel > 1) + PDHG_PrintHugeCUPDHG(); + + CUPDLP_CALL(PDHG_Solve(pdhg)); + + *model_status = (cupdlp_int)pdhg->resobj->termCode; + *num_iter = (cupdlp_int)pdhg->timers->nIter; + + CUPDLP_CALL(PDHG_PostSolve(pdhg, nCols_origin, constraint_new_idx, + constraint_type, col_value, col_dual, row_value, + row_dual, value_valid, dual_valid)); + + if (fp) + writeJson(fp, pdhg); + + if (ifSaveSol && fp_sol) { + if (strcmp(fp, fp_sol) != 0) { + writeSol(fp_sol, nCols_origin, pdhg->problem->nRows, col_value, col_dual, + row_value, row_dual); + } else { + if (pdhg->settings->nLogLevel>0) + cupdlp_printf("Warning: fp and fp_sol are the same, stop saving solution.\n"); + } + } + +exit_cleanup: + PDHG_Destroy(&pdhg); + return retcode; +} diff --git a/src/pdlp/cupdlp/cupdlp_solver.h b/src/pdlp/cupdlp/cupdlp_solver.h new file mode 100644 index 0000000000..f672acc381 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_solver.h @@ -0,0 +1,98 @@ +// +// Created by chuwen on 23-11-27. +// + +#ifndef CUPDLP_CUPDLP_SOLVER_H +#define CUPDLP_CUPDLP_SOLVER_H + +#include "cupdlp_defs.h" +#include "glbopts.h" + +#ifdef __cplusplus +extern "C" { +#endif +#define CUPDLP_CHECK_TIMEOUT(pdhg) \ + { \ + PDHG_Compute_SolvingTime(pdhg); \ + if (pdhg->timers->dSolvingTime > pdhg->settings->dTimeLim) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } + +void PDHG_Compute_Primal_Feasibility(CUPDLPwork *work, double *primalResidual, + const double *ax, const double *x, + double *dPrimalFeasibility, + double *dPrimalObj); + +void PDHG_Compute_Dual_Feasibility(CUPDLPwork *work, double *dualResidual, + const double *aty, const double *x, + const double *y, double *dDualFeasibility, + double *dDualObj, double *dComplementarity, + double *dSlackPos, double *dSlackNeg); + +void PDHG_Compute_Residuals(CUPDLPwork *work); + +void PDHG_Compute_Primal_Infeasibility(CUPDLPwork *work, const cupdlp_float *y, + const cupdlp_float *dSlackPos, + const cupdlp_float *dSlackNeg, + const cupdlp_float *aty, + const cupdlp_float dualObj, + cupdlp_float *dPrimalInfeasObj, + cupdlp_float *dPrimalInfeasRes); + +void PDHG_Compute_Dual_Infeasibility(CUPDLPwork *work, const cupdlp_float *x, + const cupdlp_float *ax, + const cupdlp_float primalObj, + cupdlp_float *dDualInfeasObj, + cupdlp_float *dDualInfeasRes); + +void PDHG_Compute_Infeas_Residuals(CUPDLPwork *work); + +void PDHG_Init_Variables(CUPDLPwork *work); + +void PDHG_Check_Data(CUPDLPwork *work); + +cupdlp_bool PDHG_Check_Termination(CUPDLPwork *pdhg, int bool_print); + +cupdlp_bool PDHG_Check_Termination_Average(CUPDLPwork *pdhg, int bool_print); + +termination_code PDHG_Check_Infeasibility(CUPDLPwork *pdhg, int bool_print); + +termination_code PDHG_Check_Primal_Infeasibility(CUPDLPwork *pdhg, + cupdlp_float dPrimalInfeasObj, + cupdlp_float dPrimalInfeasRes); +termination_code PDHG_Check_Dual_Infeasibility(CUPDLPwork *pdhg, + cupdlp_float dDualInfeasObj, + cupdlp_float dDualInfeasRes); + +void PDHG_Print_Header(CUPDLPwork *pdhg); + +void PDHG_Print_Iter(CUPDLPwork *pdhg); + +void PDHG_Print_Iter_Average(CUPDLPwork *pdhg); + +void PDHG_Compute_SolvingTime(CUPDLPwork *pdhg); + +cupdlp_retcode PDHG_Solve(CUPDLPwork *pdhg); + +cupdlp_retcode PDHG_PostSolve(CUPDLPwork *pdhg, cupdlp_int nCols_origin, + cupdlp_int *constraint_new_idx, + cupdlp_int *constraint_type, + cupdlp_float *col_value, cupdlp_float *col_dual, + cupdlp_float *row_value, cupdlp_float *row_dual, + cupdlp_int *value_valid, cupdlp_int *dual_valid); + +cupdlp_retcode LP_SolvePDHG( + CUPDLPwork *pdhg, cupdlp_bool *ifChangeIntParam, cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, cupdlp_float *floatParam, char *fp, + cupdlp_int nCols_origin, cupdlp_float *col_value, cupdlp_float *col_dual, + cupdlp_float *row_value, cupdlp_float *row_dual, cupdlp_int *value_valid, + cupdlp_int *dual_valid, cupdlp_bool ifSaveSol, char *fp_sol, + cupdlp_int *constraint_new_idx, cupdlp_int *constraint_type, + cupdlp_int *model_status, cupdlp_int* num_iter); + +#ifdef __cplusplus +} +#endif +#endif // CUPDLP_CUPDLP_SOLVER_H diff --git a/src/pdlp/cupdlp/cupdlp_step.c b/src/pdlp/cupdlp/cupdlp_step.c new file mode 100644 index 0000000000..2ad4d7cb4d --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_step.c @@ -0,0 +1,439 @@ +// +// Created by chuwen on 23-11-28. +// + +#include "cupdlp_step.h" + +#include "cupdlp_defs.h" +#include "cupdlp_linalg.h" +#include "cupdlp_proj.h" +// #include "cupdlp_scaling.h" +#include "cupdlp_solver.h" +#include "cupdlp_utils.h" +#include "glbopts.h" + +// xUpdate = x^k - dPrimalStep * (c - A'y^k) +void PDHG_primalGradientStep(CUPDLPwork *work, cupdlp_float dPrimalStepSize) { + CUPDLPiterates *iterates = work->iterates; + CUPDLPproblem *problem = work->problem; + +#if !defined(CUPDLP_CPU) & USE_KERNELS + cupdlp_pgrad_cuda(iterates->xUpdate->data, iterates->x->data, problem->cost, + iterates->aty->data, dPrimalStepSize, problem->nCols); +#else + + // cupdlp_copy(iterates->xUpdate, iterates->x, cupdlp_float, problem->nCols); + CUPDLP_COPY_VEC(iterates->xUpdate->data, iterates->x->data, cupdlp_float, + problem->nCols); + + // AddToVector(iterates->xUpdate, -dPrimalStepSize, problem->cost, + // problem->nCols); AddToVector(iterates->xUpdate, dPrimalStepSize, + // iterates->aty, problem->nCols); + + cupdlp_float alpha = -dPrimalStepSize; + cupdlp_axpy(work, problem->nCols, &alpha, problem->cost, + iterates->xUpdate->data); + alpha = dPrimalStepSize; + cupdlp_axpy(work, problem->nCols, &alpha, iterates->aty->data, + iterates->xUpdate->data); +#endif +} + +// yUpdate = y^k + dDualStep * (b - A * (2x^{k+1} - x^{k}) +void PDHG_dualGradientStep(CUPDLPwork *work, cupdlp_float dDualStepSize) { + CUPDLPiterates *iterates = work->iterates; + CUPDLPproblem *problem = work->problem; + +#if !defined(CUPDLP_CPU) & USE_KERNELS + cupdlp_dgrad_cuda(iterates->yUpdate->data, iterates->y->data, problem->rhs, + iterates->ax->data, iterates->axUpdate->data, dDualStepSize, + problem->nRows); +#else + + // cupdlp_copy(iterates->yUpdate, iterates->y, cupdlp_float, problem->nRows); + CUPDLP_COPY_VEC(iterates->yUpdate->data, iterates->y->data, cupdlp_float, + problem->nRows); + + // AddToVector(iterates->yUpdate, dDualStepSize, problem->rhs, + // problem->nRows); AddToVector(iterates->yUpdate, -2.0 * dDualStepSize, + // iterates->axUpdate, problem->nRows); AddToVector(iterates->yUpdate, + // dDualStepSize, iterates->ax, problem->nRows); + + cupdlp_float alpha = dDualStepSize; + cupdlp_axpy(work, problem->nRows, &alpha, problem->rhs, + iterates->yUpdate->data); + alpha = -2.0 * dDualStepSize; + cupdlp_axpy(work, problem->nRows, &alpha, iterates->axUpdate->data, + iterates->yUpdate->data); + alpha = dDualStepSize; + cupdlp_axpy(work, problem->nRows, &alpha, iterates->ax->data, + iterates->yUpdate->data); +#endif +} + +cupdlp_retcode PDHG_Power_Method(CUPDLPwork *work, cupdlp_float *lambda) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPiterates *iterates = work->iterates; + + if (work->settings->nLogLevel>0) + cupdlp_printf("Power Method:\n"); + + cupdlp_float *q = work->buffer->data; + + cupdlp_initvec(q, 1.0, lp->nRows); + + double res = 0.0; + for (cupdlp_int iter = 0; iter < 20; ++iter) { + // z = A*A'*q + ATy(work, iterates->aty, work->buffer); + Ax(work, iterates->ax, iterates->aty); + + // q = z / norm(z) + CUPDLP_COPY_VEC(q, iterates->ax->data, cupdlp_float, lp->nRows); + cupdlp_float qNorm = 0.0; + cupdlp_twoNorm(work, lp->nRows, q, &qNorm); + cupdlp_scaleVector(work, 1.0 / qNorm, q, lp->nRows); + + ATy(work, iterates->aty, work->buffer); + + cupdlp_twoNormSquared(work, lp->nCols, iterates->aty->data, lambda); + + cupdlp_float alpha = -(*lambda); + cupdlp_axpy(work, lp->nRows, &alpha, q, iterates->ax->data); + + cupdlp_twoNormSquared(work, lp->nCols, iterates->ax->data, &res); + + if (work->settings->nLogLevel>0) + cupdlp_printf("% d %e %.3f\n", iter, *lambda, res); + } + +exit_cleanup: + return retcode; +} + +void PDHG_Compute_Step_Size_Ratio(CUPDLPwork *pdhg) { + CUPDLPproblem *problem = pdhg->problem; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPstepsize *stepsize = pdhg->stepsize; + cupdlp_float dMeanStepSize = + sqrt(stepsize->dPrimalStep * stepsize->dDualStep); + + // cupdlp_float dDiffPrimal = cupdlp_diffTwoNorm(iterates->x, + // iterates->xLastRestart, problem->nCols); cupdlp_float dDiffDual = + // cupdlp_diffTwoNorm(iterates->y, iterates->yLastRestart, problem->nRows); + + cupdlp_float dDiffPrimal = 0.0; + cupdlp_diffTwoNorm(pdhg, iterates->x->data, iterates->xLastRestart, + problem->nCols, &dDiffPrimal); + cupdlp_float dDiffDual = 0.0; + cupdlp_diffTwoNorm(pdhg, iterates->y->data, iterates->yLastRestart, + problem->nRows, &dDiffDual); + + if (fmin(dDiffPrimal, dDiffDual) > 1e-10) { + cupdlp_float dBetaUpdate = dDiffDual / dDiffPrimal; + cupdlp_float dLogBetaUpdate = + 0.5 * log(dBetaUpdate) + 0.5 * log(sqrt(stepsize->dBeta)); + stepsize->dBeta = exp(dLogBetaUpdate) * exp(dLogBetaUpdate); + } + + stepsize->dPrimalStep = dMeanStepSize / sqrt(stepsize->dBeta); + stepsize->dDualStep = stepsize->dPrimalStep * stepsize->dBeta; + stepsize->dTheta = 1.0; +} + +void PDHG_Update_Iterate_Constant_Step_Size(CUPDLPwork *pdhg) { + // CUPDLP_ASSERT(0); + CUPDLPproblem *problem = pdhg->problem; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPstepsize *stepsize = pdhg->stepsize; + + // Ax(pdhg, iterates->ax, iterates->x); + // ATyCPU(pdhg, iterates->aty, iterates->y); + Ax(pdhg, iterates->ax, iterates->x); + ATy(pdhg, iterates->aty, iterates->y); + + // x^{k+1} = proj_{X}(x^k - dPrimalStep * (c - A'y^k)) + PDHG_primalGradientStep(pdhg, stepsize->dPrimalStep); + + PDHG_Project_Bounds(pdhg, iterates->xUpdate->data); + // Ax(pdhg, iterates->axUpdate, iterates->xUpdate); + Ax(pdhg, iterates->axUpdate, iterates->xUpdate); + + // y^{k+1} = y^k + dDualStep * (b - A * (2x^{k+1} - x^{k}) + PDHG_dualGradientStep(pdhg, stepsize->dDualStep); + + PDHG_Project_Row_Duals(pdhg, iterates->yUpdate->data); + // ATyCPU(pdhg, iterates->atyUpdate, iterates->yUpdate); + ATy(pdhg, iterates->atyUpdate, iterates->yUpdate); +} + +void PDHG_Update_Iterate_Malitsky_Pock(CUPDLPwork *pdhg) { + cupdlp_printf("Malitsky-Pock is not implemented\n"); + cupdlp_printf(" - use %d and %d instead", PDHG_FIXED_LINESEARCH, + PDHG_ADAPTIVE_LINESEARCH); + exit(-1); +} + +cupdlp_retcode PDHG_Update_Iterate_Adaptive_Step_Size(CUPDLPwork *pdhg) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLPproblem *problem = pdhg->problem; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPstepsize *stepsize = pdhg->stepsize; + + cupdlp_float dStepSizeUpdate = + sqrt(stepsize->dPrimalStep * stepsize->dDualStep); + + cupdlp_bool isDone = false; + // number of steps this round + int stepIterThis = 0; + while (!isDone) { + ++stepsize->nStepSizeIter; + ++stepIterThis; + + cupdlp_float dPrimalStepUpdate = dStepSizeUpdate / sqrt(stepsize->dBeta); + cupdlp_float dDualStepUpdate = dStepSizeUpdate * sqrt(stepsize->dBeta); + + // x^{k+1} = proj_{X}(x^k - dPrimalStep * (cupdlp - A'y^k)) + PDHG_primalGradientStep(pdhg, dPrimalStepUpdate); + + PDHG_Project_Bounds(pdhg, iterates->xUpdate->data); + Ax(pdhg, iterates->axUpdate, iterates->xUpdate); + + // y^{k+1} = proj_{Y}(y^k + dDualStep * (b - A * (2 * x^{k+1} - x^{k}))) + PDHG_dualGradientStep(pdhg, dDualStepUpdate); + + PDHG_Project_Row_Duals(pdhg, iterates->yUpdate->data); + ATy(pdhg, iterates->atyUpdate, iterates->yUpdate); + + cupdlp_float dMovement = 0.0; + cupdlp_float dInteraction = 0.0; + +#if !defined(CUPDLP_CPU) & USE_KERNELS + cupdlp_compute_interaction_and_movement(pdhg, &dMovement, &dInteraction); +#else + cupdlp_float dX = 0.0; + cupdlp_diffTwoNormSquared(pdhg, iterates->x->data, iterates->xUpdate->data, + problem->nCols, &dX); + dX *= 0.5 * sqrt(stepsize->dBeta); + + cupdlp_float dY = 0.0; + cupdlp_diffTwoNormSquared(pdhg, iterates->y->data, iterates->yUpdate->data, + problem->nRows, &dY); + dY /= 2.0 * sqrt(stepsize->dBeta); + dMovement = dX + dY; + + // \Deltax' (A\Deltay) + cupdlp_diffDotDiff(pdhg, iterates->x->data, iterates->xUpdate->data, + iterates->aty->data, iterates->atyUpdate->data, + problem->nCols, &dInteraction); +#endif + +#if CUPDLP_DUMP_LINESEARCH_STATS & CUPDLP_DEBUG + cupdlp_float dInteractiony = 0.0; + // \Deltay' (A\Deltax) + cupdlp_diffDotDiff(pdhg, iterates->y->data, iterates->yUpdate->data, + iterates->ax->data, iterates->axUpdate->data, + problem->nRows, &dInteractiony); +#endif + + cupdlp_float dStepSizeLimit; + if (dInteraction != 0.0) { + dStepSizeLimit = dMovement / fabs(dInteraction); + } else { + dStepSizeLimit = INFINITY; + } + if (dStepSizeUpdate <= dStepSizeLimit) { + isDone = true; + // break; + } else { + CUPDLP_CHECK_TIMEOUT(pdhg); + } + + cupdlp_float dFirstTerm = (1.0 - pow(stepsize->nStepSizeIter + 1.0, + -PDHG_STEPSIZE_REDUCTION_EXP)) * + dStepSizeLimit; + cupdlp_float dSecondTerm = + (1.0 + pow(stepsize->nStepSizeIter + 1.0, -PDHG_STEPSIZE_GROWTH_EXP)) * + dStepSizeUpdate; + dStepSizeUpdate = fmin(dFirstTerm, dSecondTerm); +#if CUPDLP_DUMP_LINESEARCH_STATS & CUPDLP_DEBUG + cupdlp_printf(" -- stepsize iteration %d: %f %f\n", stepIterThis, + dStepSizeUpdate, dStepSizeLimit); + + cupdlp_printf(" -- PrimalStep DualStep: %f %f\n", stepsize->dPrimalStep, + stepsize->dDualStep); + cupdlp_printf(" -- FirstTerm SecondTerm: %f %f\n", dFirstTerm, dSecondTerm); + cupdlp_printf(" -- nStepSizeIter: %d\n", stepsize->nStepSizeIter); + cupdlp_printf(" -- RED_EXP GRO_EXP: %f %f\n", PDHG_STEPSIZE_REDUCTION_EXP, + PDHG_STEPSIZE_GROWTH_EXP); + + cupdlp_printf(" -- iteraction(x) interaction(y): %f %f\n", dInteraction, + dInteractiony); + cupdlp_printf(" -- movement (scaled norm) : %f\n", dMovement); + cupdlp_printf(" -- movement (scaled norm) : %f\n", dMovement); + if (stepIterThis > 200) break; // avoid unlimited runs due to bugs. +#endif + } + + stepsize->dPrimalStep = dStepSizeUpdate / sqrt(stepsize->dBeta); + stepsize->dDualStep = dStepSizeUpdate * sqrt(stepsize->dBeta); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode PDHG_Init_Step_Sizes(CUPDLPwork *pdhg) { + cupdlp_retcode retcode = RETCODE_OK; + + CUPDLPproblem *problem = pdhg->problem; + CUPDLPiterates *iterates = pdhg->iterates; + CUPDLPstepsize *stepsize = pdhg->stepsize; + + if (stepsize->eLineSearchMethod == PDHG_FIXED_LINESEARCH) { + CUPDLP_CALL(PDHG_Power_Method(pdhg, &stepsize->dPrimalStep)); + // PDLP Intial primal weight = norm(cost) / norm(rhs) = sqrt(beta) + // cupdlp_float a = twoNormSquared(problem->cost, problem->nCols); + // cupdlp_float b = twoNormSquared(problem->rhs, problem->nRows); + cupdlp_float a = 0.0; + cupdlp_float b = 0.0; + cupdlp_twoNormSquared(pdhg, problem->nCols, problem->cost, &a); + cupdlp_twoNormSquared(pdhg, problem->nRows, problem->rhs, &b); + + if (fmin(a, b) > 1e-6) { + stepsize->dBeta = a / b; + } else { + stepsize->dBeta = 1.0; + } + + stepsize->dPrimalStep = 0.8 / sqrt(stepsize->dPrimalStep); + stepsize->dDualStep = stepsize->dPrimalStep; + stepsize->dPrimalStep /= sqrt(stepsize->dBeta); + stepsize->dDualStep *= sqrt(stepsize->dBeta); + } else { + stepsize->dTheta = 1.0; + + // PDLP Intial primal weight = norm(cost) / norm(rhs) = sqrt(beta) + // cupdlp_float a = twoNormSquared(problem->cost, problem->nCols); + // cupdlp_float b = twoNormSquared(problem->rhs, problem->nRows); + cupdlp_float a = 0.0; + cupdlp_float b = 0.0; + cupdlp_twoNormSquared(pdhg, problem->nCols, problem->cost, &a); + cupdlp_twoNormSquared(pdhg, problem->nRows, problem->rhs, &b); + + if (fmin(a, b) > 1e-6) { + stepsize->dBeta = a / b; + } else { + stepsize->dBeta = 1.0; + } + // infNorm can be avoid by previously calculated infNorm of csc matrix + stepsize->dPrimalStep = + // (1.0 / infNorm(problem->data->csc_matrix->colMatElem, + // problem->data->csc_matrix->nMatElem)) / + (1.0 / problem->data->csc_matrix->MatElemNormInf) / + sqrt(stepsize->dBeta); + stepsize->dDualStep = stepsize->dPrimalStep * stepsize->dBeta; + iterates->dLastRestartBeta = stepsize->dBeta; + } + + iterates->iLastRestartIter = 0; + stepsize->dSumPrimalStep = 0; + stepsize->dSumDualStep = 0; + +exit_cleanup: + return retcode; +} + +void PDHG_Compute_Average_Iterate(CUPDLPwork *work) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPstepsize *stepsize = work->stepsize; + CUPDLPiterates *iterates = work->iterates; + + cupdlp_float dPrimalScale = + stepsize->dSumPrimalStep > 0.0 ? 1.0 / stepsize->dSumPrimalStep : 1.0; + cupdlp_float dDualScale = + stepsize->dSumDualStep > 0.0 ? 1.0 / stepsize->dSumDualStep : 1.0; + + // cupdlp_scaleVector(iterates->xAverage, iterates->xSum, dPrimalScale, + // lp->nCols); cupdlp_scaleVector(iterates->yAverage, iterates->ySum, + // dDualScale, lp->nRows); + + CUPDLP_COPY_VEC(iterates->xAverage->data, iterates->xSum, cupdlp_float, + lp->nCols); + CUPDLP_COPY_VEC(iterates->yAverage->data, iterates->ySum, cupdlp_float, + lp->nRows); + cupdlp_scaleVector(work, dPrimalScale, iterates->xAverage->data, lp->nCols); + cupdlp_scaleVector(work, dDualScale, iterates->yAverage->data, lp->nRows); + + // Ax(work, iterates->axAverage, iterates->xAverage); + // ATyCPU(work, iterates->atyAverage, iterates->yAverage); + Ax(work, iterates->axAverage, iterates->xAverage); + ATy(work, iterates->atyAverage, iterates->yAverage); +} + +void PDHG_Update_Average(CUPDLPwork *work) { + CUPDLPproblem *problem = work->problem; + CUPDLPdata *lp = problem->data; + CUPDLPstepsize *stepsize = work->stepsize; + CUPDLPiterates *iterates = work->iterates; + + // PDLP weighs average iterates in this way + cupdlp_float dMeanStepSize = + sqrt(stepsize->dPrimalStep * stepsize->dDualStep); + // AddToVector(iterates->xSum, dMeanStepSize, iterates->xUpdate, + // lp->nCols); AddToVector(iterates->ySum, dMeanStepSize, + // iterates->yUpdate, lp->nRows); + cupdlp_axpy(work, lp->nCols, &dMeanStepSize, iterates->xUpdate->data, + iterates->xSum); + cupdlp_axpy(work, lp->nRows, &dMeanStepSize, iterates->yUpdate->data, + iterates->ySum); + + stepsize->dSumPrimalStep += dMeanStepSize; + stepsize->dSumDualStep += dMeanStepSize; +} + +cupdlp_retcode PDHG_Update_Iterate(CUPDLPwork *pdhg) { + cupdlp_retcode retcode = RETCODE_OK; + +#if PDHG_USE_TIMERS + CUPDLPtimers *timers = pdhg->timers; + ++timers->nUpdateIterateCalls; + cupdlp_float dStartTime = getTimeStamp(); +#endif + + CUPDLPproblem *problem = pdhg->problem; + CUPDLPstepsize *stepsize = pdhg->stepsize; + CUPDLPiterates *iterates = pdhg->iterates; + + switch (stepsize->eLineSearchMethod) { + case PDHG_FIXED_LINESEARCH: + PDHG_Update_Iterate_Constant_Step_Size(pdhg); + break; + case PDHG_MALITSKY_POCK_LINESEARCH: + PDHG_Update_Iterate_Malitsky_Pock(pdhg); + break; + case PDHG_ADAPTIVE_LINESEARCH: + CUPDLP_CALL(PDHG_Update_Iterate_Adaptive_Step_Size(pdhg)); + break; + } + + PDHG_Update_Average(pdhg); + + CUPDLP_COPY_VEC(iterates->x->data, iterates->xUpdate->data, cupdlp_float, + problem->nCols); + CUPDLP_COPY_VEC(iterates->y->data, iterates->yUpdate->data, cupdlp_float, + problem->nRows); + CUPDLP_COPY_VEC(iterates->ax->data, iterates->axUpdate->data, cupdlp_float, + problem->nRows); + CUPDLP_COPY_VEC(iterates->aty->data, iterates->atyUpdate->data, cupdlp_float, + problem->nCols); + +#if PDHG_USE_TIMERS + timers->dUpdateIterateTime += getTimeStamp() - dStartTime; +#endif + +exit_cleanup: + return RETCODE_OK; +} diff --git a/src/pdlp/cupdlp/cupdlp_step.h b/src/pdlp/cupdlp/cupdlp_step.h new file mode 100644 index 0000000000..04481c4774 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_step.h @@ -0,0 +1,33 @@ +// +// Created by chuwen on 23-11-28. +// + +#ifndef CUPDLP_CUPDLP_STEP_H +#define CUPDLP_CUPDLP_STEP_H + +#include "cupdlp_defs.h" +// #include "cupdlp_scaling.h" +#include "glbopts.h" + +cupdlp_retcode PDHG_Power_Method(CUPDLPwork *work, double *lambda); + +void PDHG_Compute_Step_Size_Ratio(CUPDLPwork *pdhg); + +void PDHG_Update_Iterate_Constant_Step_Size(CUPDLPwork *pdhg); + +void PDHG_Update_Iterate_Malitsky_Pock(CUPDLPwork *pdhg); + +cupdlp_retcode PDHG_Update_Iterate_Adaptive_Step_Size(CUPDLPwork *pdhg); + +cupdlp_retcode PDHG_Init_Step_Sizes(CUPDLPwork *pdhg); + +void PDHG_Compute_Average_Iterate(CUPDLPwork *work); + +void PDHG_Update_Average(CUPDLPwork *work); + +cupdlp_retcode PDHG_Update_Iterate(CUPDLPwork *pdhg); + +void PDHG_primalGradientStep(CUPDLPwork *work, cupdlp_float dPrimalStepSize); +void PDHG_dualGradientStep(CUPDLPwork *work, cupdlp_float dDualStepSize); + +#endif // CUPDLP_CUPDLP_STEP_H diff --git a/src/pdlp/cupdlp/cupdlp_utils.c b/src/pdlp/cupdlp/cupdlp_utils.c new file mode 100644 index 0000000000..19d15ef4cb --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_utils.c @@ -0,0 +1,1726 @@ +// +// Created by chuwen on 23-11-26. +// + +#include "cupdlp_utils.h" + +#include +#include + +#include + +#include "cupdlp_cs.h" +#include "cupdlp_linalg.h" +#include "glbopts.h" + +#ifndef CUPDLP_CPU + +#include "cuda/cupdlp_cudalinalg.cuh" + +#endif + +void dense_clear(CUPDLPdense *dense) { + if (dense) { + if (dense->data) { + cupdlp_free(dense->data); + } + cupdlp_free(dense); + } +} + +cupdlp_int csc_clear(CUPDLPcsc *csc) { + if (csc) { +#ifndef CUPDLP_CPU + if (csc->cuda_csc != NULL) { + CHECK_CUSPARSE(cusparseDestroySpMat(csc->cuda_csc)) + } +#endif + if (csc->colMatBeg) { + CUPDLP_FREE_VEC(csc->colMatBeg); + } + if (csc->colMatIdx) { + CUPDLP_FREE_VEC(csc->colMatIdx); + } + if (csc->colMatElem) { + CUPDLP_FREE_VEC(csc->colMatElem); + } + CUPDLP_FREE_VEC(csc); + } + return 0; +} + +cupdlp_int csr_clear(CUPDLPcsr *csr) { + if (csr) { +#ifndef CUPDLP_CPU + if (csr->cuda_csr != NULL) { + CHECK_CUSPARSE(cusparseDestroySpMat(csr->cuda_csr)) + } +#endif + if (csr->rowMatBeg) { + CUPDLP_FREE_VEC(csr->rowMatBeg); + } + if (csr->rowMatIdx) { + CUPDLP_FREE_VEC(csr->rowMatIdx); + } + if (csr->rowMatElem) { + CUPDLP_FREE_VEC(csr->rowMatElem); + } + CUPDLP_FREE_VEC(csr); + } + return 0; +} + +void data_clear(CUPDLPdata *data) { + if (data) { + switch (data->matrix_format) { + case DENSE: + dense_clear(data->dense_matrix); + break; + case CSR: + csr_clear(data->csr_matrix); + break; + case CSC: + csc_clear(data->csc_matrix); + break; + case CSR_CSC: + csr_clear(data->csr_matrix); + csc_clear(data->csc_matrix); + break; + } + cupdlp_free(data); + } +} + +void problem_clear(CUPDLPproblem *problem) { + if (problem) { + if (problem->data) { + data_clear(problem->data); + } + // if (problem->colMatBeg) { + // cupdlp_free(problem->colMatBeg); + // } + // if (problem->colMatIdx) { + // cupdlp_free(problem->colMatIdx); + // } + // if (problem->colMatElem) { + // cupdlp_free(problem->colMatElem); + // } + // if (problem->rowMatBeg) { + // cupdlp_free(problem->rowMatBeg); + // } + // if (problem->rowMatIdx) { + // cupdlp_free(problem->rowMatIdx); + // } + // if (problem->rowMatElem) { + // cupdlp_free(problem->rowMatElem); + // } + if (problem->lower) { + CUPDLP_FREE_VEC(problem->lower); + } + if (problem->upper) { + CUPDLP_FREE_VEC(problem->upper); + } + if (problem->cost) { + CUPDLP_FREE_VEC(problem->cost); + } + if (problem->rhs) { + CUPDLP_FREE_VEC(problem->rhs); + } + if (problem->hasLower) { + CUPDLP_FREE_VEC(problem->hasLower); + } + if (problem->hasUpper) { + CUPDLP_FREE_VEC(problem->hasUpper); + } + CUPDLP_FREE_VEC(problem); + } +} + +void settings_clear(CUPDLPsettings *settings) { + if (settings) { + cupdlp_free(settings); + } +} + +cupdlp_int vec_clear(CUPDLPvec *vec) { + if (vec) { + if (vec->data) { + CUPDLP_FREE_VEC(vec->data); + } +#ifndef CUPDLP_CPU + CHECK_CUSPARSE(cusparseDestroyDnVec(vec->cuda_vec)) +#endif + cupdlp_free(vec); + } + + return 0; +} + +void iterates_clear(CUPDLPiterates *iterates) { + if (iterates) { + if (iterates->x) { + // CUPDLP_FREE_VEC(iterates->x); + vec_clear(iterates->x); + } + if (iterates->y) { + // CUPDLP_FREE_VEC(iterates->y); + vec_clear(iterates->y); + } + if (iterates->xUpdate) { + // CUPDLP_FREE_VEC(iterates->xUpdate); + vec_clear(iterates->xUpdate); + } + if (iterates->yUpdate) { + // CUPDLP_FREE_VEC(iterates->yUpdate); + vec_clear(iterates->yUpdate); + } + if (iterates->xSum) { + CUPDLP_FREE_VEC(iterates->xSum); + } + if (iterates->ySum) { + CUPDLP_FREE_VEC(iterates->ySum); + } + if (iterates->xAverage) { + // CUPDLP_FREE_VEC(iterates->xAverage); + vec_clear(iterates->xAverage); + } + if (iterates->yAverage) { + // CUPDLP_FREE_VEC(iterates->yAverage); + vec_clear(iterates->yAverage); + } + if (iterates->xLastRestart) { + CUPDLP_FREE_VEC(iterates->xLastRestart); + } + if (iterates->yLastRestart) { + CUPDLP_FREE_VEC(iterates->yLastRestart); + } + if (iterates->ax) { + // CUPDLP_FREE_VEC(iterates->ax); + vec_clear(iterates->ax); + } + if (iterates->axUpdate) { + // CUPDLP_FREE_VEC(iterates->axUpdate); + vec_clear(iterates->axUpdate); + } + if (iterates->axAverage) { + // CUPDLP_FREE_VEC(iterates->axAverage); + vec_clear(iterates->axAverage); + } + if (iterates->aty) { + // CUPDLP_FREE_VEC(iterates->aty); + vec_clear(iterates->aty); + } + if (iterates->atyUpdate) { + // CUPDLP_FREE_VEC(iterates->atyUpdate); + vec_clear(iterates->atyUpdate); + } + if (iterates->atyAverage) { + // CUPDLP_FREE_VEC(iterates->atyAverage); + vec_clear(iterates->atyAverage); + } + CUPDLP_FREE_VEC(iterates); + } +} + +void resobj_clear(CUPDLPresobj *resobj) { + if (resobj) { + if (resobj->primalResidual) { + CUPDLP_FREE_VEC(resobj->primalResidual); + } + if (resobj->dualResidual) { + CUPDLP_FREE_VEC(resobj->dualResidual); + } + if (resobj->primalResidualAverage) { + CUPDLP_FREE_VEC(resobj->primalResidualAverage); + } + if (resobj->dualResidualAverage) { + CUPDLP_FREE_VEC(resobj->dualResidualAverage); + } + if (resobj->dSlackPos) { + CUPDLP_FREE_VEC(resobj->dSlackPos); + } + if (resobj->dSlackNeg) { + CUPDLP_FREE_VEC(resobj->dSlackNeg); + } + if (resobj->dSlackPosAverage) { + CUPDLP_FREE_VEC(resobj->dSlackPosAverage); + } + if (resobj->dSlackNegAverage) { + CUPDLP_FREE_VEC(resobj->dSlackNegAverage); + } + if (resobj->dLowerFiltered) { + CUPDLP_FREE_VEC(resobj->dLowerFiltered); + } + if (resobj->dUpperFiltered) { + CUPDLP_FREE_VEC(resobj->dUpperFiltered); + } + if (resobj->primalInfeasRay) { + CUPDLP_FREE_VEC(resobj->primalInfeasRay); + } + if (resobj->primalInfeasConstr) { + CUPDLP_FREE_VEC(resobj->primalInfeasConstr); + } + if (resobj->primalInfeasBound) { + CUPDLP_FREE_VEC(resobj->primalInfeasBound); + } + if (resobj->dualInfeasRay) { + CUPDLP_FREE_VEC(resobj->dualInfeasRay); + } + if (resobj->dualInfeasLbRay) { + CUPDLP_FREE_VEC(resobj->dualInfeasLbRay); + } + if (resobj->dualInfeasUbRay) { + CUPDLP_FREE_VEC(resobj->dualInfeasUbRay); + } + if (resobj->dualInfeasConstr) { + CUPDLP_FREE_VEC(resobj->dualInfeasConstr); + } + // if (resobj->dualInfeasBound) { + // CUPDLP_FREE_VEC(resobj->dualInfeasBound); + // } + CUPDLP_FREE_VEC(resobj); + } +} + +void stepsize_clear(CUPDLPstepsize *stepsize) { + if (stepsize) { + cupdlp_free(stepsize); + } +} + +void timers_clear(CUPDLPtimers *timers) { +#ifndef CUPDLP_CPU + cupdlp_printf("%20s %e\n", "Free Device memory", timers->FreeDeviceMemTime); +#endif + + if (timers) { + cupdlp_free(timers); + } +} + +void scaling_clear(CUPDLPscaling *scaling) { + if (scaling) { + if (scaling->colScale) { + // cupdlp_free(scaling->colScale); + CUPDLP_FREE_VEC(scaling->colScale); // now on gpu + } + if (scaling->rowScale) { + // cupdlp_free(scaling->rowScale); + CUPDLP_FREE_VEC(scaling->rowScale); // now on gpu + } + cupdlp_free(scaling); + } +} + +cupdlp_int PDHG_Clear(CUPDLPwork *w) { + CUPDLPproblem *problem = w->problem; + CUPDLPsettings *settings = w->settings; + CUPDLPiterates *iterates = w->iterates; + CUPDLPresobj *resobj = w->resobj; + CUPDLPstepsize *stepsize = w->stepsize; + CUPDLPtimers *timers = w->timers; + CUPDLPscaling *scaling = w->scaling; + + if (w) { + cupdlp_float begin = getTimeStamp(); +#ifndef CUPDLP_CPU + + // CUDAmv *MV = w->MV; + // if (MV) + // { + // cupdlp_float begin = getTimeStamp(); + // cuda_free_mv(MV); + // timers->FreeDeviceMemTime += getTimeStamp() - begin; + // } + CHECK_CUBLAS(cublasDestroy(w->cublashandle)) + CHECK_CUSPARSE(cusparseDestroy(w->cusparsehandle)) + CHECK_CUDA(cudaFree(w->dBuffer)) + if (w->buffer2) CUPDLP_FREE_VEC(w->buffer2); + if (w->buffer3) CUPDLP_FREE_VEC(w->buffer3); +#endif + if (w->colScale) CUPDLP_FREE_VEC(w->colScale); + if (w->rowScale) CUPDLP_FREE_VEC(w->rowScale); + + if (w->buffer) { + // CUPDLP_FREE_VEC(w->buffer); + vec_clear(w->buffer); + } + + if (problem) { + // problem_clear(problem); + problem = cupdlp_NULL; + } + + if (iterates) { + iterates_clear(iterates); + } + + if (resobj) { + resobj_clear(resobj); + } + +#ifndef CUPDLP_CPU + timers->FreeDeviceMemTime += getTimeStamp() - begin; +#endif + + if (settings) { + settings_clear(settings); + } + if (stepsize) { + stepsize_clear(stepsize); + } + if (timers) { + timers_clear(timers); + } + if (scaling) { + // scaling_clear(scaling); + scaling = cupdlp_NULL; + } + cupdlp_free(w); + } + + return 0; +} + +void PDHG_PrintPDHGParam(CUPDLPwork *w) { + if (w->settings->nLogLevel < 2) return; + CUPDLPsettings *settings = w->settings; + CUPDLPstepsize *stepsize = w->stepsize; + CUPDLPresobj *resobj = w->resobj; + CUPDLPiterates *iterates = w->iterates; + CUPDLPscaling *scaling = w->scaling; + CUPDLPtimers *timers = w->timers; + + cupdlp_printf("\n"); + + cupdlp_printf("\n"); + cupdlp_printf("--------------------------------------------------\n"); + cupdlp_printf("CUPDHG Parameters:\n"); + cupdlp_printf("--------------------------------------------------\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" nIterLim: %d\n", settings->nIterLim); + cupdlp_printf(" dTimeLim (sec): %.2f\n", settings->dTimeLim); + cupdlp_printf(" ifScaling: %d\n", settings->ifScaling); + // cupdlp_printf(" iScalingMethod: %d\n", settings->iScalingMethod);) + cupdlp_printf(" ifRuizScaling: %d\n", scaling->ifRuizScaling); + cupdlp_printf(" ifL2Scaling: %d\n", scaling->ifL2Scaling); + cupdlp_printf(" ifPcScaling: %d\n", scaling->ifPcScaling); + cupdlp_printf(" eLineSearchMethod: %d\n", stepsize->eLineSearchMethod); + // cupdlp_printf(" dScalingLimit: %.4e\n", settings->dScalingLimit); + cupdlp_printf(" dPrimalTol: %.4e\n", settings->dPrimalTol); + cupdlp_printf(" dDualTol: %.4e\n", settings->dDualTol); + cupdlp_printf(" dGapTol: %.4e\n", settings->dGapTol); + cupdlp_printf(" dFeasTol: %.4e\n", resobj->dFeasTol); + cupdlp_printf(" eRestartMethod: %d\n", settings->eRestartMethod); + cupdlp_printf(" nLogLevel: %d\n", settings->nLogLevel); + cupdlp_printf(" nLogInterval: %d\n", settings->nLogInterval); + cupdlp_printf(" iInfNormAbsLocalTermination: %d\n", settings->iInfNormAbsLocalTermination); + cupdlp_printf("\n"); + cupdlp_printf("--------------------------------------------------\n"); + cupdlp_printf("\n"); +} + +void PDHG_PrintHugeCUPDHG() { + cupdlp_printf("\n"); + cupdlp_printf(" ____ _ _ ____ ____ _ ____\n"); + cupdlp_printf(" / ___| | | | _ \\| _ \\| | | _ \\\n"); + cupdlp_printf("| | | | | | |_) | | | | | | |_) |\n"); + cupdlp_printf("| |___| |_| | __/| |_| | |___| __/\n"); + cupdlp_printf(" \\____|\\___/|_| |____/|_____|_|\n"); + cupdlp_printf("\n"); +} + +void PDHG_PrintUserParamHelper() { + PDHG_PrintHugeCUPDHG(); + + cupdlp_printf("CUPDHG User Parameters:\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -h: print this helper\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -nIterLim: maximum iteration number\n"); + cupdlp_printf(" type: int\n"); + cupdlp_printf(" default: INT_MAX\n"); + cupdlp_printf(" range: >= 0\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -ifScaling: whether to use scaling\n"); + cupdlp_printf(" type: bool\n"); + cupdlp_printf(" default: true\n"); + cupdlp_printf(" range: true or false\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -eLineSearchMethod: which line search method to use\n"); + cupdlp_printf( + " 0-Fixed, 1-Malitsky (not support), " + "2-Adaptive\n"); + cupdlp_printf(" type: int\n"); + cupdlp_printf(" default: 2\n"); + cupdlp_printf(" range: 0 to 2\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -dPrimalTol: primal tolerance\n"); + cupdlp_printf(" type: double\n"); + cupdlp_printf(" default: 1e-4\n"); + cupdlp_printf(" range: >= 0\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -dDualTol: dual tolerance\n"); + cupdlp_printf(" type: double\n"); + cupdlp_printf(" default: 1e-4\n"); + cupdlp_printf(" range: >= 0\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -dGapTol: gap tolerance\n"); + cupdlp_printf(" type: double\n"); + cupdlp_printf(" default: 1e-4\n"); + cupdlp_printf(" range: >= 0\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -dFeasTol: feasibility tolerance\n"); + cupdlp_printf(" type: double\n"); + cupdlp_printf(" default: 1e-8\n"); + cupdlp_printf(" range: >= 0\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -dTimeLim: time limit (in seconds)\n"); + cupdlp_printf(" type: double\n"); + cupdlp_printf(" default: 3600\n"); + cupdlp_printf(" range: >= 0\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -eRestartMethod: which restart method to use\n"); + cupdlp_printf(" 0-None, 1-KKTversion\n"); + cupdlp_printf(" type: int\n"); + cupdlp_printf(" default: 1\n"); + cupdlp_printf(" range: 0 to 1\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -ifRuizScaling: whether to use Ruiz scaling\n"); + cupdlp_printf(" type: bool\n"); + cupdlp_printf(" default: true\n"); + cupdlp_printf(" range: true or false\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -ifL2Scaling: whether to use L2 scaling\n"); + cupdlp_printf(" type: bool\n"); + cupdlp_printf(" default: false\n"); + cupdlp_printf(" range: true or false\n"); + cupdlp_printf("\n"); + + cupdlp_printf(" -ifPcScaling: whether to use Pock-Chambolle scaling\n"); + cupdlp_printf(" type: bool\n"); + cupdlp_printf(" default: true\n"); + cupdlp_printf(" range: true or false\n"); + cupdlp_printf("\n"); + + // cupdlp_printf( + // " -ifPre: whether to use HiGHS presolver (and thus postsolver)\n"); + // cupdlp_printf(" type: bool\n"); + // cupdlp_printf(" default: true\n"); + // cupdlp_printf(" range: true or false\n"); + // cupdlp_printf("\n"); +} + +cupdlp_retcode getUserParam(int argc, char **argv, + cupdlp_bool *ifChangeIntParam, cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + + // set ifChangeIntParam and ifChangeFloatParam to false + for (cupdlp_int i = 0; i < N_INT_USER_PARAM; ++i) { + ifChangeIntParam[i] = false; + } + + for (cupdlp_int i = 0; i < N_FLOAT_USER_PARAM; ++i) { + ifChangeFloatParam[i] = false; + } + + // parse command line arguments + for (cupdlp_int i = 0; i < argc - 1; ++i) { + if (strcmp(argv[i], "-h") == 0) { + PDHG_PrintUserParamHelper(); + + retcode = RETCODE_FAILED; + goto exit_cleanup; + } + + if (strcmp(argv[i], "-nIterLim") == 0) { + ifChangeIntParam[N_ITER_LIM] = true; + intParam[N_ITER_LIM] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-ifScaling") == 0) { + ifChangeIntParam[IF_SCALING] = true; + intParam[IF_SCALING] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-iScalingMethod") == 0) { + ifChangeIntParam[I_SCALING_METHOD] = true; + intParam[I_SCALING_METHOD] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-eLineSearchMethod") == 0) { + ifChangeIntParam[E_LINE_SEARCH_METHOD] = true; + intParam[E_LINE_SEARCH_METHOD] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-dScalingLimit") == 0) { + ifChangeFloatParam[D_SCALING_LIMIT] = true; + floatParam[D_SCALING_LIMIT] = atof(argv[i + 1]); + } else if (strcmp(argv[i], "-dPrimalTol") == 0) { + ifChangeFloatParam[D_PRIMAL_TOL] = true; + floatParam[D_PRIMAL_TOL] = atof(argv[i + 1]); + } else if (strcmp(argv[i], "-dDualTol") == 0) { + ifChangeFloatParam[D_DUAL_TOL] = true; + floatParam[D_DUAL_TOL] = atof(argv[i + 1]); + } else if (strcmp(argv[i], "-dGapTol") == 0) { + ifChangeFloatParam[D_GAP_TOL] = true; + floatParam[D_GAP_TOL] = atof(argv[i + 1]); + } else if (strcmp(argv[i], "-dFeasTol") == 0) { + ifChangeFloatParam[D_FEAS_TOL] = true; + floatParam[D_FEAS_TOL] = atof(argv[i + 1]); + } else if (strcmp(argv[i], "-dTimeLim") == 0) { + ifChangeFloatParam[D_TIME_LIM] = true; + floatParam[D_TIME_LIM] = atof(argv[i + 1]); + } else if (strcmp(argv[i], "-eRestartMethod") == 0) { + ifChangeIntParam[E_RESTART_METHOD] = true; + intParam[E_RESTART_METHOD] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-ifRuizScaling") == 0) { + ifChangeIntParam[IF_RUIZ_SCALING] = true; + intParam[IF_RUIZ_SCALING] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-ifL2Scaling") == 0) { + ifChangeIntParam[IF_L2_SCALING] = true; + intParam[IF_L2_SCALING] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-ifPcScaling") == 0) { + ifChangeIntParam[IF_PC_SCALING] = true; + intParam[IF_PC_SCALING] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-nLogLevel") == 0) { + ifChangeIntParam[N_LOG_LEVEL] = true; + intParam[N_LOG_LEVEL] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-nLogInt") == 0) { + ifChangeIntParam[N_LOG_INTERVAL] = true; + intParam[N_LOG_INTERVAL] = atoi(argv[i + 1]); + } else if (strcmp(argv[i], "-ifPre") == 0) { + ifChangeIntParam[IF_PRESOLVE] = true; + intParam[IF_PRESOLVE] = atoi(argv[i + 1]); + } + } + + if (argc>0) { + if (strcmp(argv[argc - 1], "-h") == 0) { + PDHG_PrintUserParamHelper(); + + retcode = RETCODE_FAILED; + goto exit_cleanup; + } + } + +exit_cleanup: + return retcode; +} + +cupdlp_retcode settings_SetUserParam(CUPDLPsettings *settings, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + + if (ifChangeIntParam[N_ITER_LIM]) { + settings->nIterLim = intParam[N_ITER_LIM]; + } + + if (ifChangeIntParam[N_LOG_LEVEL]) { + settings->nLogLevel = intParam[N_LOG_LEVEL]; + } + + if (ifChangeIntParam[N_LOG_INTERVAL]) { + settings->nLogInterval = intParam[N_LOG_INTERVAL]; + } + + if (ifChangeIntParam[IF_SCALING]) { + settings->ifScaling = intParam[IF_SCALING]; + } + + if (ifChangeIntParam[I_SCALING_METHOD]) { + settings->iScalingMethod = intParam[I_SCALING_METHOD]; + } + + if (ifChangeFloatParam[D_SCALING_LIMIT]) { + settings->dScalingLimit = floatParam[D_SCALING_LIMIT]; + } + + if (ifChangeFloatParam[D_PRIMAL_TOL]) { + settings->dPrimalTol = floatParam[D_PRIMAL_TOL]; + } + + if (ifChangeFloatParam[D_DUAL_TOL]) { + settings->dDualTol = floatParam[D_DUAL_TOL]; + } + + if (ifChangeFloatParam[D_GAP_TOL]) { + settings->dGapTol = floatParam[D_GAP_TOL]; + } + + if (ifChangeFloatParam[D_TIME_LIM]) { + settings->dTimeLim = floatParam[D_TIME_LIM]; + } + + if (ifChangeIntParam[E_RESTART_METHOD]) { + settings->eRestartMethod = intParam[E_RESTART_METHOD]; + } + + if (ifChangeIntParam[I_INF_NORM_ABS_LOCAL_TERMINATION]) { + settings->iInfNormAbsLocalTermination = intParam[I_INF_NORM_ABS_LOCAL_TERMINATION]; + } + +exit_cleanup: + return retcode; +} + +cupdlp_retcode resobj_SetUserParam(CUPDLPresobj *resobj, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + + if (ifChangeFloatParam[D_FEAS_TOL]) { + resobj->dFeasTol = floatParam[D_FEAS_TOL]; + } + +exit_cleanup: + return retcode; +} + +cupdlp_retcode iterates_SetUserParam(CUPDLPiterates *iterates, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + +exit_cleanup: + return retcode; +} + +cupdlp_retcode stepsize_SetUserParam(CUPDLPstepsize *stepsize, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + + if (ifChangeIntParam[E_LINE_SEARCH_METHOD]) { + stepsize->eLineSearchMethod = intParam[E_LINE_SEARCH_METHOD]; + } + +exit_cleanup: + return retcode; +} + +cupdlp_retcode scaling_SetUserParam(CUPDLPscaling *scaling, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + + if (ifChangeIntParam[IF_RUIZ_SCALING]) { + scaling->ifRuizScaling = intParam[IF_RUIZ_SCALING]; + } + + if (ifChangeIntParam[IF_L2_SCALING]) { + scaling->ifL2Scaling = intParam[IF_L2_SCALING]; + } + + if (ifChangeIntParam[IF_PC_SCALING]) { + scaling->ifPcScaling = intParam[IF_PC_SCALING]; + } + +exit_cleanup: + return retcode; +} + +cupdlp_retcode timers_SetUserParam(CUPDLPtimers *timers, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + +exit_cleanup: + return retcode; +} + +cupdlp_retcode PDHG_SetUserParam(CUPDLPwork *w, cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam) { + cupdlp_retcode retcode = RETCODE_OK; + + CUPDLPsettings *settings = w->settings; + CUPDLPstepsize *stepsize = w->stepsize; + CUPDLPresobj *resobj = w->resobj; + CUPDLPiterates *iterates = w->iterates; + CUPDLPscaling *scaling = w->scaling; + CUPDLPtimers *timers = w->timers; + + CUPDLP_CALL(settings_SetUserParam(settings, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + CUPDLP_CALL(stepsize_SetUserParam(stepsize, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + CUPDLP_CALL(resobj_SetUserParam(resobj, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + CUPDLP_CALL(iterates_SetUserParam(iterates, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + CUPDLP_CALL(scaling_SetUserParam(scaling, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + CUPDLP_CALL(timers_SetUserParam(timers, ifChangeIntParam, intParam, + ifChangeFloatParam, floatParam)); + + PDHG_PrintPDHGParam(w); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode settings_Alloc(CUPDLPsettings *settings) { + cupdlp_retcode retcode = RETCODE_OK; + // settings->nIterLim = INFINITY; + settings->nIterLim = INT_MAX; // INFINITY cause bug on MacOS + settings->nLogLevel = 2; // Ensures that, by default, cuPDLP-C printing is unchanged + settings->nLogInterval = 100; + // settings->dTimeLim = INFINITY; + settings->dTimeLim = 3600; + settings->ifScaling = true; + settings->iScalingMethod = 3; // no use + settings->dScalingLimit = 5; // no use + settings->eRestartMethod = PDHG_GPU_RESTART; + settings->iInfNormAbsLocalTermination = 0; + + // termination criteria + settings->dPrimalTol = 1e-4; + settings->dDualTol = 1e-4; + settings->dGapTol = 1e-4; + + return retcode; +} + +cupdlp_retcode resobj_Alloc(CUPDLPresobj *resobj, CUPDLPproblem *problem, + cupdlp_int ncols, cupdlp_int nrows) { + cupdlp_retcode retcode = RETCODE_OK; + + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->primalResidual, nrows); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dualResidual, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->primalResidualAverage, nrows); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dualResidualAverage, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dSlackPos, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dSlackNeg, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dSlackPosAverage, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dSlackNegAverage, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dLowerFiltered, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dUpperFiltered, ncols); + + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->primalInfeasRay, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->primalInfeasConstr, nrows); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->primalInfeasBound, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dualInfeasRay, nrows); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dualInfeasLbRay, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dualInfeasUbRay, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(resobj->dualInfeasConstr, ncols); + // CUPDLP_INIT_DOUBLE_ZERO_VEC(resobj->dualInfeasBound, nrows); + + // need to translate to cuda type + // for (int i = 0; i < ncols; i++) + // { + // resobj->dLowerFiltered[i] = problem->lower[i] > -INFINITY ? + // problem->lower[i] : 0.0; resobj->dUpperFiltered[i] = problem->upper[i] + // < +INFINITY ? problem->upper[i] : 0.0; + // } + + cupdlp_filterlb(resobj->dLowerFiltered, problem->lower, -INFINITY, ncols); + cupdlp_filterub(resobj->dUpperFiltered, problem->upper, +INFINITY, ncols); + + // initialization + resobj->dFeasTol = 1e-8; + resobj->dPrimalObj = 0.0; + resobj->dDualObj = 0.0; + resobj->dDualityGap = 0.0; + resobj->dComplementarity = 0.0; + resobj->dPrimalFeas = 0.0; + resobj->dDualFeas = 0.0; + resobj->dRelObjGap = 0.0; + resobj->dPrimalObjAverage = 0.0; + resobj->dDualObjAverage = 0.0; + resobj->dDualityGapAverage = 0.0; + resobj->dComplementarityAverage = 0.0; + resobj->dPrimalFeasAverage = 0.0; + resobj->dDualFeasAverage = 0.0; + resobj->dRelObjGapAverage = 0.0; + resobj->dPrimalFeasLastRestart = 0.0; + resobj->dDualFeasLastRestart = 0.0; + resobj->dDualityGapLastRestart = 0.0; + resobj->dPrimalFeasLastCandidate = 0.0; + resobj->dDualFeasLastCandidate = 0.0; + resobj->dDualityGapLastCandidate = 0.0; + + resobj->primalCode = FEASIBLE; + resobj->dualCode = FEASIBLE; + resobj->termInfeasIterate = LAST_ITERATE; + resobj->dPrimalInfeasObj = 0.0; + resobj->dDualInfeasObj = 0.0; + resobj->dPrimalInfeasRes = 1.0; + resobj->dDualInfeasRes = 1.0; + resobj->dPrimalInfeasObjAverage = 0.0; + resobj->dDualInfeasObjAverage = 0.0; + resobj->dPrimalInfeasResAverage = 1.0; + resobj->dDualInfeasResAverage = 1.0; + + resobj->termCode = TIMELIMIT_OR_ITERLIMIT; + resobj->termIterate = LAST_ITERATE; + + // todo, pass work + // cupdlp_twoNorm(problem->cost, ncols, &resobj->dNormCost); + // twoNorm(problem->rhs, nrows); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode iterates_Alloc(CUPDLPiterates *iterates, cupdlp_int ncols, + cupdlp_int nrows) { + cupdlp_retcode retcode = RETCODE_OK; + + iterates->nCols = ncols; + iterates->nRows = nrows; + + CUPDLP_INIT_ZERO_DOUBLE_VEC(iterates->xSum, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(iterates->ySum, nrows); + CUPDLP_INIT_ZERO_DOUBLE_VEC(iterates->xLastRestart, ncols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(iterates->yLastRestart, nrows); + + CUPDLP_INIT_CUPDLP_VEC(iterates->x, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->xUpdate, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->xAverage, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->y, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->yUpdate, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->yAverage, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->ax, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->axUpdate, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->axAverage, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->aty, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->atyUpdate, 1); + CUPDLP_INIT_CUPDLP_VEC(iterates->atyAverage, 1); + + CUPDLP_CALL(vec_Alloc(iterates->x, ncols)); + CUPDLP_CALL(vec_Alloc(iterates->xUpdate, ncols)); + CUPDLP_CALL(vec_Alloc(iterates->xAverage, ncols)); + CUPDLP_CALL(vec_Alloc(iterates->y, nrows)); + CUPDLP_CALL(vec_Alloc(iterates->yUpdate, nrows)); + CUPDLP_CALL(vec_Alloc(iterates->yAverage, nrows)); + CUPDLP_CALL(vec_Alloc(iterates->ax, nrows)); + CUPDLP_CALL(vec_Alloc(iterates->axUpdate, nrows)); + CUPDLP_CALL(vec_Alloc(iterates->axAverage, nrows)); + CUPDLP_CALL(vec_Alloc(iterates->aty, ncols)); + CUPDLP_CALL(vec_Alloc(iterates->atyUpdate, ncols)); + CUPDLP_CALL(vec_Alloc(iterates->atyAverage, ncols)); + + // initialization + iterates->iLastRestartIter = 0; + iterates->dLastRestartDualityGap = 0.0; + iterates->dLastRestartBeta = 0.0; + +exit_cleanup: + return retcode; +} + +cupdlp_retcode stepsize_Alloc(CUPDLPstepsize *stepsize) { + cupdlp_retcode retcode = RETCODE_OK; + + stepsize->eLineSearchMethod = PDHG_ADAPTIVE_LINESEARCH; + + // initialization + stepsize->nStepSizeIter = 0; + stepsize->dPrimalStep = 0.0; + stepsize->dDualStep = 0.0; + stepsize->dSumPrimalStep = 0.0; + stepsize->dSumDualStep = 0.0; + stepsize->dBeta = 0.0; + stepsize->dTheta = 0.0; + +exit_cleanup: + return retcode; +} + +cupdlp_retcode scaling_Alloc(CUPDLPscaling *scaling, CUPDLPproblem *problem, + cupdlp_int ncols, cupdlp_int nrows) { + cupdlp_retcode retcode = RETCODE_OK; + scaling->ifScaled = 0; + + CUPDLP_INIT_DOUBLE(scaling->colScale, ncols); + CUPDLP_INIT_DOUBLE(scaling->rowScale, nrows); + + scaling->ifRuizScaling = 1; + scaling->ifL2Scaling = 0; + scaling->ifPcScaling = 1; + + scaling->dNormCost = twoNorm(problem->cost, problem->nCols); + scaling->dNormRhs = twoNorm(problem->rhs, problem->nRows); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode timers_Alloc(CUPDLPtimers *timers) { + cupdlp_retcode retcode = RETCODE_OK; + + timers->nIter = 0; + timers->dSolvingTime = 0.0; + timers->dSolvingBeg = 0.0; + timers->dScalingTime = 0.0; + timers->dPresolveTime = 0.0; + +#if PDHG_USE_TIMERS + timers->dAtyTime = 0.0; + timers->dAxTime = 0.0; + timers->dComputeResidualsTime = 0.0; + timers->dUpdateIterateTime = 0.0; + timers->nAtyCalls = 0; + timers->nAxCalls = 0; + timers->nComputeResidualsCalls = 0; + timers->nUpdateIterateCalls = 0; +#endif +#ifndef CUPDLP_CPU + // GPU timers + timers->AllocMem_CopyMatToDeviceTime = 0.0; + timers->CopyVecToDeviceTime = 0.0; + timers->DeviceMatVecProdTime = 0.0; + timers->CopyVecToHostTime = 0.0; + timers->FreeDeviceMemTime = 0.0; + timers->CudaPrepareTime = 0.0; +#endif + +exit_cleanup: + return retcode; +} + +cupdlp_retcode vec_Alloc(CUPDLPvec *vec, cupdlp_int n) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLP_INIT_ZERO_DOUBLE_VEC(vec->data, n); + vec->len = n; +#ifndef CUPDLP_CPU + CHECK_CUSPARSE( + cusparseCreateDnVec(&vec->cuda_vec, n, vec->data, CudaComputeType)); +#endif + +exit_cleanup: + return retcode; +} + +cupdlp_retcode PDHG_Alloc(CUPDLPwork *w) { + cupdlp_retcode retcode = RETCODE_OK; + + CUPDLP_INIT_SETTINGS(w->settings, 1); + CUPDLP_INIT_RESOBJ(w->resobj, 1); + CUPDLP_INIT_ITERATES(w->iterates, 1); + CUPDLP_INIT_STEPSIZE(w->stepsize, 1); + + CUPDLP_INIT_TIMERS(w->timers, 1); + CUPDLP_CALL(timers_Alloc(w->timers)); + + cupdlp_float begin = getTimeStamp(); + // buffer + CUPDLP_INIT_CUPDLP_VEC(w->buffer, 1); + CUPDLP_CALL(vec_Alloc(w->buffer, w->problem->data->nRows)); + CUPDLP_INIT_ZERO_DOUBLE_VEC(w->buffer2, + MAX(w->problem->data->nCols, w->problem->data->nRows)); + CUPDLP_INIT_ZERO_DOUBLE_VEC(w->buffer3, + MAX(w->problem->data->nCols, w->problem->data->nRows)); + + // for scaling + CUPDLP_INIT_ZERO_DOUBLE_VEC(w->colScale, w->problem->data->nCols); + CUPDLP_INIT_ZERO_DOUBLE_VEC(w->rowScale, w->problem->data->nRows); + + CUPDLP_CALL(settings_Alloc(w->settings)); + CUPDLP_CALL(resobj_Alloc(w->resobj, w->problem, w->problem->data->nCols, + w->problem->data->nRows)); + CUPDLP_CALL(iterates_Alloc(w->iterates, w->problem->data->nCols, + w->problem->data->nRows)); + CUPDLP_CALL(stepsize_Alloc(w->stepsize)); + +#ifndef CUPDLP_CPU + // CHECK_CUSPARSE(cusparseCreate(&w->cusparsehandle)); + // CHECK_CUBLAS(cublasCreate(&w->cublashandle)); + cuda_alloc_MVbuffer( + // w->problem->data->matrix_format, + w->cusparsehandle, w->problem->data->csc_matrix->cuda_csc, + w->iterates->x->cuda_vec, w->iterates->ax->cuda_vec, + w->problem->data->csr_matrix->cuda_csr, w->iterates->y->cuda_vec, + w->iterates->aty->cuda_vec, &w->dBuffer); + w->timers->AllocMem_CopyMatToDeviceTime += getTimeStamp() - begin; +#endif + +exit_cleanup: + return retcode; +} + +cupdlp_retcode PDHG_Create(CUPDLPwork **ww, CUPDLPproblem *lp, + CUPDLPscaling *scaling) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLP_INIT_ZERO_CUPDLP_WORK(*ww, 1); + + CUPDLPwork *w = *ww; + w->problem = lp; + w->scaling = scaling; + +exit_cleanup: + return retcode; +} + +void PDHG_Destroy(CUPDLPwork **w) { + if (w && *w) { + PDHG_Clear(*w); +#ifndef CUPDLP_CPU + cudaDeviceReset(); +#endif + } +} + +void PDHG_Init_Data(CUPDLPwork *work) {} + +double my_clock(void) { +#ifdef CUPDLP_TIMER + // struct timeval t; + // clock_gettime(&t, NULL); + // gettimeofday(&t, NULL); + double timeee = 0; + +#ifndef WIN32 + timeee = time(NULL); +#else + timeee = clock(); +#endif + + return timeee; + // return (1e-06 * t.tv_usec + t.tv_sec); +#else + return 0; +#endif +} + +double getTimeStamp(void) { return my_clock(); } + +void dense_copy(CUPDLPdense *dst, CUPDLPdense *src) { + dst->nRows = src->nRows; + dst->nCols = src->nCols; + cupdlp_copy(dst->data, src->data, cupdlp_float, src->nRows * src->nCols); + + return; +} + +void csr_copy(CUPDLPcsr *dst, CUPDLPcsr *src) { + dst->nRows = src->nRows; + dst->nCols = src->nCols; + dst->nMatElem = src->nMatElem; + cupdlp_copy(dst->rowMatBeg, src->rowMatBeg, cupdlp_int, src->nRows + 1); + cupdlp_copy(dst->rowMatIdx, src->rowMatIdx, cupdlp_int, src->nMatElem); + cupdlp_copy(dst->rowMatElem, src->rowMatElem, cupdlp_float, src->nMatElem); + + return; +} + +cupdlp_int csc_copy(CUPDLPcsc *dst, CUPDLPcsc *src) { + dst->nRows = src->nRows; + dst->nCols = src->nCols; + dst->nMatElem = src->nMatElem; + CUPDLP_COPY_VEC(dst->colMatBeg, src->colMatBeg, cupdlp_int, src->nCols + 1); + CUPDLP_COPY_VEC(dst->colMatIdx, src->colMatIdx, cupdlp_int, src->nMatElem); + CUPDLP_COPY_VEC(dst->colMatElem, src->colMatElem, cupdlp_float, + src->nMatElem); + +#ifndef CUPDLP_CPU + // Pointer to GPU csc matrix + CHECK_CUSPARSE(cusparseCreateCsc( + &dst->cuda_csc, src->nRows, src->nCols, src->nMatElem, dst->colMatBeg, + dst->colMatIdx, dst->colMatElem, CUSPARSE_INDEX_32I, CUSPARSE_INDEX_32I, + CUSPARSE_INDEX_BASE_ZERO, CudaComputeType)); +#endif + + return 0; +} + +void csr2csc(CUPDLPcsc *csc, CUPDLPcsr *csr) { + cupdlp_dcs *cs_csr = + cupdlp_dcs_spalloc(csr->nCols, csc->nRows, csc->nMatElem, 1, 0); + cupdlp_copy(cs_csr->p, csr->rowMatBeg, cupdlp_int, csr->nRows + 1); + cupdlp_copy(cs_csr->i, csr->rowMatIdx, cupdlp_int, csr->nMatElem); + cupdlp_copy(cs_csr->x, csr->rowMatElem, cupdlp_float, csr->nMatElem); + + cupdlp_dcs *cs_csc = cupdlp_dcs_transpose(cs_csr, 1); + csc->nCols = cs_csc->m; + csc->nRows = cs_csc->n; + csc->nMatElem = cs_csc->nzmax; + cupdlp_copy(csc->colMatBeg, cs_csc->p, cupdlp_int, cs_csc->n + 1); + cupdlp_copy(csc->colMatIdx, cs_csc->i, cupdlp_int, cs_csc->nzmax); + cupdlp_copy(csc->colMatElem, cs_csc->x, cupdlp_float, cs_csc->nzmax); + + // cupdlp_dcs_free(cs_csc); + // cupdlp_dcs_free(cs_csr); + cupdlp_dcs_spfree(cs_csc); + cupdlp_dcs_spfree(cs_csr); + + return; +} + +cupdlp_int csc2csr(CUPDLPcsr *csr, CUPDLPcsc *csc) { + // The transpose may need to be done on the GPU + // Currently, it is done on the CPU + + cupdlp_dcs *cs_csc = + cupdlp_dcs_spalloc(csc->nRows, csc->nCols, csc->nMatElem, 1, 0); + cupdlp_copy(cs_csc->p, csc->colMatBeg, cupdlp_int, csc->nCols + 1); + cupdlp_copy(cs_csc->i, csc->colMatIdx, cupdlp_int, csc->nMatElem); + cupdlp_copy(cs_csc->x, csc->colMatElem, cupdlp_float, csc->nMatElem); + + cupdlp_dcs *cs_csr = cupdlp_dcs_transpose(cs_csc, 1); + csr->nCols = cs_csr->m; + csr->nRows = cs_csr->n; + csr->nMatElem = cs_csr->nzmax; + CUPDLP_COPY_VEC(csr->rowMatBeg, cs_csr->p, cupdlp_int, cs_csr->n + 1); + CUPDLP_COPY_VEC(csr->rowMatIdx, cs_csr->i, cupdlp_int, cs_csr->nzmax); + CUPDLP_COPY_VEC(csr->rowMatElem, cs_csr->x, cupdlp_float, cs_csr->nzmax); + + // cupdlp_dcs_free(cs_csc); + // cupdlp_dcs_free(cs_csr); + cupdlp_dcs_spfree(cs_csc); + cupdlp_dcs_spfree(cs_csr); + +#ifndef CUPDLP_CPU + // Pointer to GPU csc matrix + CHECK_CUSPARSE(cusparseCreateCsr( + &csr->cuda_csr, csr->nRows, csr->nCols, csr->nMatElem, csr->rowMatBeg, + csr->rowMatIdx, csr->rowMatElem, CUSPARSE_INDEX_32I, CUSPARSE_INDEX_32I, + CUSPARSE_INDEX_BASE_ZERO, CudaComputeType)); +#endif + + return 0; +} + +void dense2csr(CUPDLPcsr *csr, CUPDLPdense *dense) { + csr->nRows = dense->nRows; + csr->nCols = dense->nCols; + + cupdlp_int nnz = 0; + cupdlp_int iCol = 0; + cupdlp_int iRow = 0; + csr->rowMatBeg[0] = 0; + for (iRow = 0; iRow < csr->nRows; ++iRow) { + for (iCol = 0; iCol < csr->nCols; ++iCol) { + if (dense->data[iCol * csr->nRows + iRow] != 0) { + csr->rowMatIdx[nnz] = iCol; + csr->rowMatElem[nnz] = dense->data[iCol * csr->nRows + iRow]; + ++nnz; + } + } + csr->rowMatBeg[iRow + 1] = nnz; + } + csr->nMatElem = nnz; + + return; +} + +void dense2csc(CUPDLPcsc *csc, CUPDLPdense *dense) { + csc->nRows = dense->nRows; + csc->nCols = dense->nCols; + + cupdlp_int nnz = 0; + cupdlp_int iCol = 0; + cupdlp_int iRow = 0; + csc->colMatBeg[0] = 0; + for (iCol = 0; iCol < csc->nCols; ++iCol) { + for (iRow = 0; iRow < csc->nRows; ++iRow) { + if (dense->data[iCol * csc->nRows + iRow] != 0) { + csc->colMatIdx[nnz] = iRow; + csc->colMatElem[nnz] = dense->data[iCol * csc->nRows + iRow]; + ++nnz; + } + } + csc->colMatBeg[iCol + 1] = nnz; + } + + csc->nMatElem = nnz; + + return; +} + +void csr2dense(CUPDLPdense *dense, CUPDLPcsr *csr) { + dense->nRows = csr->nRows; + dense->nCols = csr->nCols; + + cupdlp_int iRow = 0; + cupdlp_int iCol = 0; + cupdlp_int iMatElem = 0; + for (iRow = 0; iRow < dense->nRows; ++iRow) + for (iCol = 0; iCol < dense->nCols; ++iCol) { + if (iCol == csr->rowMatIdx[iMatElem]) { + dense->data[iRow * dense->nCols + iCol] = csr->rowMatElem[iMatElem]; + ++iMatElem; + } else { + dense->data[iRow * dense->nCols + iCol] = 0; + } + } + + return; +} + +void csc2dense(CUPDLPdense *dense, CUPDLPcsc *csc) { + dense->nRows = csc->nRows; + dense->nCols = csc->nCols; + + cupdlp_int iRow = 0; + cupdlp_int iCol = 0; + cupdlp_int iMatElem = 0; + for (iCol = 0; iCol < dense->nCols; ++iCol) + for (iRow = 0; iRow < dense->nRows; ++iRow) { + if (iRow == csc->colMatIdx[iMatElem]) { + dense->data[iRow * dense->nCols + iCol] = csc->colMatElem[iMatElem]; + ++iMatElem; + } else { + dense->data[iRow * dense->nCols + iCol] = 0; + } + } + + return; +} + +cupdlp_retcode dense_create(CUPDLPdense **dense) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLP_INIT_DENSE_MATRIX(*dense, 1); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode csr_create(CUPDLPcsr **csr) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLP_INIT_CSR_MATRIX(*csr, 1); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode csc_create(CUPDLPcsc **csc) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLP_INIT_CSC_MATRIX(*csc, 1); + +exit_cleanup: + return retcode; +} + +cupdlp_retcode dense_alloc_matrix(CUPDLPdense *dense, cupdlp_int nRows, + cupdlp_int nCols, void *src, + CUPDLP_MATRIX_FORMAT src_matrix_format) { + cupdlp_retcode retcode = RETCODE_OK; + CUPDLP_INIT_ZERO_DOUBLE_VEC(dense->data, nRows * nCols); + + switch (src_matrix_format) { + case DENSE: + dense_copy(dense, (CUPDLPdense *)src); + break; + case CSR: + csr2dense(dense, (CUPDLPcsr *)src); + break; + case CSC: + csc2dense(dense, (CUPDLPcsc *)src); + break; + default: + break; + } +exit_cleanup: + return retcode; +} + +cupdlp_retcode csr_alloc_matrix(CUPDLPcsr *csr, cupdlp_int nRows, + cupdlp_int nCols, void *src, + CUPDLP_MATRIX_FORMAT src_matrix_format) { + cupdlp_retcode retcode = RETCODE_OK; + cupdlp_int nnz = 0; + switch (src_matrix_format) { + case DENSE: + nnz = nRows * nCols; + break; + case CSR: + nnz = ((CUPDLPcsr *)src)->nMatElem; + break; + case CSC: + nnz = ((CUPDLPcsc *)src)->nMatElem; + break; + default: + break; + } + // todo make sure this is right + CUPDLP_INIT_ZERO_INT_VEC(csr->rowMatBeg, nRows + 1); + CUPDLP_INIT_ZERO_INT_VEC(csr->rowMatIdx, nnz); + CUPDLP_INIT_ZERO_DOUBLE_VEC(csr->rowMatElem, nnz); + + switch (src_matrix_format) { + case DENSE: + dense2csr(csr, (CUPDLPdense *)src); + break; + case CSR: + csr_copy(csr, (CUPDLPcsr *)src); + break; + case CSC: + csc2csr(csr, (CUPDLPcsc *)src); + break; + default: + break; + } +exit_cleanup: + return retcode; +} + +cupdlp_retcode csc_alloc_matrix(CUPDLPcsc *csc, cupdlp_int nRows, + cupdlp_int nCols, void *src, + CUPDLP_MATRIX_FORMAT src_matrix_format) { + cupdlp_retcode retcode = RETCODE_OK; + cupdlp_int nnz = 0; + switch (src_matrix_format) { + case DENSE: + nnz = nRows * nCols; + break; + case CSR: + nnz = ((CUPDLPcsr *)src)->nMatElem; + break; + case CSC: + nnz = ((CUPDLPcsc *)src)->nMatElem; + break; + default: + break; + } + CUPDLP_INIT_ZERO_INT_VEC(csc->colMatBeg, nCols + 1); + CUPDLP_INIT_ZERO_INT_VEC(csc->colMatIdx, nnz); + CUPDLP_INIT_ZERO_DOUBLE_VEC(csc->colMatElem, nnz); + + switch (src_matrix_format) { + case DENSE: + dense2csc(csc, (CUPDLPdense *)src); + break; + case CSR: + csr2csc(csc, (CUPDLPcsr *)src); + break; + case CSC: + csc_copy(csc, (CUPDLPcsc *)src); + break; + default: + break; + } +exit_cleanup: + return retcode; +} + +cupdlp_retcode dense_alloc(CUPDLPdense *dense, cupdlp_int nRows, + cupdlp_int nCols, cupdlp_float *val) { + cupdlp_retcode retcode = RETCODE_OK; + dense->nRows = nRows; + dense->nCols = nCols; + dense->data = cupdlp_NULL; + CUPDLP_INIT_ZERO_DOUBLE_VEC(dense->data, nRows * nCols); + + CUPDLP_COPY_VEC(dense->data, val, cupdlp_float, nRows * nCols); +exit_cleanup: + return retcode; +} + +cupdlp_retcode csr_alloc(CUPDLPcsr *csr, cupdlp_int nRows, cupdlp_int nCols, + cupdlp_int nnz, cupdlp_int *row_ptr, + cupdlp_int *col_ind, cupdlp_float *val) { + cupdlp_retcode retcode = RETCODE_OK; + csr->nRows = nRows; + csr->nCols = nCols; + csr->nMatElem = nnz; + csr->rowMatBeg = cupdlp_NULL; + csr->rowMatIdx = cupdlp_NULL; + csr->rowMatElem = cupdlp_NULL; + + CUPDLP_INIT_ZERO_INT_VEC(csr->rowMatBeg, nRows + 1); + CUPDLP_INIT_ZERO_INT_VEC(csr->rowMatIdx, nnz); + CUPDLP_INIT_ZERO_DOUBLE_VEC(csr->rowMatElem, nnz); + + CUPDLP_COPY_VEC(csr->rowMatBeg, row_ptr, cupdlp_int, nRows + 1); + CUPDLP_COPY_VEC(csr->rowMatIdx, col_ind, cupdlp_int, nnz); + CUPDLP_COPY_VEC(csr->rowMatElem, val, cupdlp_float, nnz); +exit_cleanup: + return retcode; +} + +cupdlp_retcode csc_alloc(CUPDLPcsc *csc, cupdlp_int nRows, cupdlp_int nCols, + cupdlp_int nnz, cupdlp_int *col_ptr, + cupdlp_int *row_ind, cupdlp_float *val) { + cupdlp_retcode retcode = RETCODE_OK; + csc->nRows = nRows; + csc->nCols = nCols; + csc->nMatElem = nnz; + csc->colMatBeg = cupdlp_NULL; + csc->colMatIdx = cupdlp_NULL; + csc->colMatElem = cupdlp_NULL; + CUPDLP_INIT_ZERO_INT_VEC(csc->colMatBeg, nCols + 1); + CUPDLP_INIT_ZERO_INT_VEC(csc->colMatIdx, nnz); + CUPDLP_INIT_ZERO_DOUBLE_VEC(csc->colMatElem, nnz); + + CUPDLP_COPY_VEC(csc->colMatBeg, col_ptr, cupdlp_int, nCols + 1); + CUPDLP_COPY_VEC(csc->colMatIdx, row_ind, cupdlp_int, nnz); + CUPDLP_COPY_VEC(csc->colMatElem, val, cupdlp_float, nnz); +exit_cleanup: + return retcode; +} + +void vecPrint(const char *s, const cupdlp_float *a, cupdlp_int n) { + cupdlp_printf("%s: ", s); + for (cupdlp_int i = 0; i < n; ++i) { + cupdlp_printf("%.3f ", a[i]); + } + cupdlp_printf("\n"); +} + +void vecIntPrint(const char *s, const cupdlp_int *a, cupdlp_int n) { + cupdlp_printf("%s: ", s); + for (cupdlp_int i = 0; i < n; ++i) { + cupdlp_printf("%d ", a[i]); + } + cupdlp_printf("\n"); +} + +void PDHG_Dump_Stats(CUPDLPwork *w) { + cupdlp_int nCols = w->iterates->nCols; + cupdlp_int nRows = w->iterates->nRows; + CUPDLPiterates *iterates = w->iterates; + CUPDLPstepsize *stepsize = w->stepsize; + + cupdlp_printf("------------------------------------------------\n"); + cupdlp_printf("Iteration % 3d\n", w->timers->nIter); +#if CUPDLP_DUMP_ITERATES + vecPrint("x", iterates->x->data, nCols); + vecPrint("y", iterates->y->data, nRows); + vecPrint("xSum", iterates->xSum, nCols); + vecPrint("ySum", iterates->ySum, nRows); + vecPrint("Ax ", iterates->ax->data, nRows); + vecPrint("A'y", iterates->aty->data, nCols); + vecPrint("xLastRestart", iterates->xLastRestart, nCols); + vecPrint("yLastRestart", iterates->yLastRestart, nRows); +#endif + cupdlp_printf( + "PrimalStep: %e, SumPrimalStep: %e, DualStep: %e, SumDualStep: %e\n", + stepsize->dPrimalStep, stepsize->dSumPrimalStep, stepsize->dDualStep, + stepsize->dSumDualStep); + cupdlp_printf("Stepsize: %e, Primal weight: %e Ratio: %e\n", + sqrt(stepsize->dPrimalStep * stepsize->dDualStep), + sqrt(stepsize->dBeta), stepsize->dTheta); +} + +void csrPrintDense(const char *s, CUPDLPcsr *csr) { + cupdlp_printf("------------------------------------------------\n"); + cupdlp_printf("%s:\n", s); + cupdlp_int deltaCol = 0; + for (cupdlp_int iRow = 0; iRow < csr->nRows; ++iRow) { + for (cupdlp_int iElem = csr->rowMatBeg[iRow]; + iElem < csr->rowMatBeg[iRow + 1]; ++iElem) { + if (iElem == csr->rowMatBeg[iRow]) + deltaCol = csr->rowMatIdx[iElem]; + else + deltaCol = csr->rowMatIdx[iElem] - csr->rowMatIdx[iElem - 1] - 1; + for (cupdlp_int i = 0; i < deltaCol; ++i) { + cupdlp_printf(" "); + } + cupdlp_printf("%6.3f ", csr->rowMatElem[iElem]); + } + cupdlp_printf("\n"); + } + cupdlp_printf("------------------------------------------------\n"); +} + +void cscPrintDense(const char *s, CUPDLPcsc *csc) { + cupdlp_printf("------------------------------------------------\n"); + cupdlp_printf("%s (Trans):\n", s); + cupdlp_int deltaRow = 0; + for (cupdlp_int iCol = 0; iCol < csc->nCols; ++iCol) { + for (cupdlp_int iElem = csc->colMatBeg[iCol]; + iElem < csc->colMatBeg[iCol + 1]; ++iElem) { + if (iElem == csc->colMatBeg[iCol]) + deltaRow = csc->colMatIdx[iElem]; + else + deltaRow = csc->colMatIdx[iElem] - csc->colMatIdx[iElem - 1] - 1; + for (cupdlp_int i = 0; i < deltaRow; ++i) { + cupdlp_printf(" "); + } + cupdlp_printf("%6.3f ", csc->colMatElem[iElem]); + } + cupdlp_printf("\n"); + } + cupdlp_printf("------------------------------------------------\n"); +} + +#ifndef CUPDLP_OUTPUT_NAMES +const char *termCodeNames[] = {"OPTIMAL", + "INFEASIBLE", + "UNBOUNDED", + "INFEASIBLE_OR_UNBOUNDED", + "TIMELIMIT_OR_ITERLIMIT", + "FEASIBLE"}; +const char *termIterateNames[] = { + "LAST_ITERATE", + "AVERAGE_ITERATE", +}; +#endif + +void writeJson(const char *fout, CUPDLPwork *work) { + FILE *fptr; + + cupdlp_printf("--------------------------------\n"); + cupdlp_printf("--- saving to %s\n", fout); + cupdlp_printf("--------------------------------\n"); + // Open a file in writing mode + fptr = fopen(fout, "w"); + + fprintf(fptr, "{"); + + // solver + fprintf(fptr, "\"solver\":\"%s\",", "cuPDLP-C"); + + // timers + fprintf(fptr, "\"nIter\":%d,", work->timers->nIter); + fprintf(fptr, "\"nAtyCalls\":%d,", work->timers->nAtyCalls); + fprintf(fptr, "\"nAxCalls\":%d,", work->timers->nAxCalls); + fprintf(fptr, "\"dSolvingBeg\":%f,", work->timers->dSolvingBeg); + fprintf(fptr, "\"dSolvingTime\":%f,", work->timers->dSolvingTime); + fprintf(fptr, "\"dPresolveTime\":%f,", work->timers->dPresolveTime); + fprintf(fptr, "\"dScalingTime\":%f,", work->timers->dScalingTime); +#ifndef CUPDLP_CPU + fprintf(fptr, "\"AllocMem_CopyMatToDeviceTime\":%f,", + work->timers->AllocMem_CopyMatToDeviceTime); + fprintf(fptr, "\"CopyVecToDeviceTime\":%f,", + work->timers->CopyVecToDeviceTime); + fprintf(fptr, "\"CopyVecToHostTime\":%f,", work->timers->CopyVecToHostTime); + fprintf(fptr, "\"DeviceMatVecProdTime\":%f,", + work->timers->DeviceMatVecProdTime); +#endif + // residuals + fprintf(fptr, "\"dPrimalObj\":%.14f,", work->resobj->dPrimalObj); + fprintf(fptr, "\"dDualObj\":%.14f,", work->resobj->dDualObj); + fprintf(fptr, "\"dPrimalFeas\":%.14f,", work->resobj->dPrimalFeas); + fprintf(fptr, "\"dDualFeas\":%.14f,", work->resobj->dDualFeas); + fprintf(fptr, "\"dPrimalObjAverage\":%.14f,", + work->resobj->dPrimalObjAverage); + fprintf(fptr, "\"dDualObjAverage\":%.14f,", work->resobj->dDualObjAverage); + fprintf(fptr, "\"dPrimalFeasAverage\":%.14f,", + work->resobj->dPrimalFeasAverage); + fprintf(fptr, "\"dDualFeasAverage\":%.14f,", work->resobj->dDualFeasAverage); + fprintf(fptr, "\"dDualityGap\":%.14f,", work->resobj->dDualityGap); + fprintf(fptr, "\"dDualityGapAverage\":%.14f,", + work->resobj->dDualityGapAverage); + // fprintf(fptr, "\"dComplementarity\":%.14f,", + // work->resobj->dComplementarity); fprintf(fptr, + // "\"dComplementarityAverage\":%.14f,", + // work->resobj->dComplementarityAverage); + + // todo should this be added to postsolve? + // todo, fix dNormCost and this + if (work->resobj->termIterate == AVERAGE_ITERATE) { + fprintf(fptr, "\"dRelPrimalFeas\":%.14f,", + work->resobj->dPrimalFeasAverage / (1.0 + work->scaling->dNormRhs)); + fprintf(fptr, "\"dRelDualFeas\":%.14f,", + work->resobj->dDualFeasAverage / (1.0 + work->scaling->dNormCost)); + fprintf(fptr, "\"dRelDualityGap\":%.14f,", work->resobj->dRelObjGapAverage); + } else { + fprintf(fptr, "\"dRelPrimalFeas\":%.14f,", + work->resobj->dPrimalFeas / (1.0 + work->scaling->dNormRhs)); + fprintf(fptr, "\"dRelDualFeas\":%.14f,", + work->resobj->dDualFeas / (1.0 + work->scaling->dNormCost)); + fprintf(fptr, "\"dRelDualityGap\":%.14f,", work->resobj->dRelObjGap); + } + fprintf(fptr, "\"terminationCode\":\"%s\",", + termCodeNames[work->resobj->termCode]); + fprintf(fptr, "\"terminationIterate\":\"%s\",", + termIterateNames[work->resobj->termIterate]); + fprintf(fptr, "\"primalCode\":\"%s\",", + termCodeNames[work->resobj->primalCode]); + fprintf(fptr, "\"dualCode\":\"%s\",", termCodeNames[work->resobj->dualCode]); + fprintf(fptr, "\"terminationInfeasIterate\":\"%s\"", + termIterateNames[work->resobj->termInfeasIterate]); + + fprintf(fptr, "}"); + // Close the file + fclose(fptr); +} + +void writeSol(const char *fout, cupdlp_int nCols, cupdlp_int nRows, + cupdlp_float *col_value, cupdlp_float *col_dual, + cupdlp_float *row_value, cupdlp_float *row_dual) { + FILE *fptr; + + cupdlp_printf("--------------------------------\n"); + cupdlp_printf("--- saving sol to %s\n", fout); + cupdlp_printf("--------------------------------\n"); + // Open a file in writing mode + fptr = fopen(fout, "w"); + fprintf(fptr, "{"); + + // nCols + fprintf(fptr, "\n"); + + fprintf(fptr, "\"nCols\": %d", nCols); + + // nRows + fprintf(fptr, ",\n"); + + fprintf(fptr, "\"nRows\": %d", nRows); + + // col value + fprintf(fptr, ",\n"); + + fprintf(fptr, "\"col_value\": ["); + if (col_value && nCols) { + for (int i = 0; i < nCols - 1; ++i) { + fprintf(fptr, "%.14f,", col_value[i]); + } + fprintf(fptr, "%.14f", col_value[nCols - 1]); + } + fprintf(fptr, "]"); + + // col dual + fprintf(fptr, ",\n"); + fprintf(fptr, "\"col_dual\": ["); + if (col_dual && nCols) { + for (int i = 0; i < nCols - 1; ++i) { + fprintf(fptr, "%.14f,", col_dual[i]); + } + fprintf(fptr, "%.14f", col_dual[nCols - 1]); + } + fprintf(fptr, "]"); + + // row value + fprintf(fptr, ",\n"); + fprintf(fptr, "\"row_value\": ["); + if (row_value && nRows) { + for (int i = 0; i < nRows - 1; ++i) { + fprintf(fptr, "%.14f,", row_value[i]); + } + fprintf(fptr, "%.14f", row_value[nRows - 1]); + } + fprintf(fptr, "]"); + + // row dual + fprintf(fptr, ",\n"); + fprintf(fptr, "\"row_dual\": ["); + if (row_dual && nRows) { + for (int i = 0; i < nRows - 1; ++i) { + fprintf(fptr, "%.14f,", row_dual[i]); + } + fprintf(fptr, "%.14f", row_dual[nRows - 1]); + } + fprintf(fptr, "]"); + + // end writing + fprintf(fptr, "\n"); + fprintf(fptr, "}"); + + // Close the file + fclose(fptr); +} diff --git a/src/pdlp/cupdlp/cupdlp_utils.h b/src/pdlp/cupdlp/cupdlp_utils.h new file mode 100644 index 0000000000..49c9510a06 --- /dev/null +++ b/src/pdlp/cupdlp/cupdlp_utils.h @@ -0,0 +1,193 @@ +// +// Created by chuwen on 23-11-26. +// + +#ifndef CUPDLP_CUPDLP_UTILS_H +#define CUPDLP_CUPDLP_UTILS_H + +#include +#ifdef CUPDLP_TIMER +#include +#endif +#include "cupdlp_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void data_clear(CUPDLPdata *data); + +void problem_clear(CUPDLPproblem *problem); + +cupdlp_int vec_clear(CUPDLPvec *vec); + +void settings_clear(CUPDLPsettings *settings); + +void iterates_clear(CUPDLPiterates *iterates); + +void resobj_clear(CUPDLPresobj *resobj); + +void stepsize_clear(CUPDLPstepsize *stepsize); + +void timers_clear(CUPDLPtimers *timers); + +void scaling_clear(CUPDLPscaling *scaling); + +cupdlp_int PDHG_Clear(CUPDLPwork *w); + +void PDHG_PrintPDHGParam(CUPDLPwork *w); + +void PDHG_PrintHugeCUPDHG(); + +void PDHG_PrintUserParamHelper(); + +cupdlp_retcode getUserParam(int argc, char **argv, + cupdlp_bool *ifChangeIntParam, cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode settings_SetUserParam(CUPDLPsettings *settings, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode resobj_SetUserParam(CUPDLPresobj *resobj, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode iterates_SetUserParam(CUPDLPiterates *iterates, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode stepsize_SetUserParam(CUPDLPstepsize *stepsize, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode scaling_SetUserParam(CUPDLPscaling *scaling, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode timers_SetUserParam(CUPDLPtimers *timers, + cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode PDHG_SetUserParam(CUPDLPwork *w, cupdlp_bool *ifChangeIntParam, + cupdlp_int *intParam, + cupdlp_bool *ifChangeFloatParam, + cupdlp_float *floatParam); + +cupdlp_retcode vec_Alloc(CUPDLPvec *vec, cupdlp_int n); + +cupdlp_retcode settings_Alloc(CUPDLPsettings *settings); + +cupdlp_retcode resobj_Alloc(CUPDLPresobj *resobj, CUPDLPproblem *problem, + cupdlp_int ncols, cupdlp_int nrows); + +cupdlp_retcode iterates_Alloc(CUPDLPiterates *iterates, cupdlp_int ncols, + cupdlp_int nrows); + +cupdlp_retcode stepsize_Alloc(CUPDLPstepsize *stepsize); + +cupdlp_retcode scaling_Alloc(CUPDLPscaling *scaling, CUPDLPproblem *problem, + cupdlp_int ncols, cupdlp_int nrows); + +cupdlp_retcode timers_Alloc(CUPDLPtimers *timers); + +void PDHG_Init_Data(CUPDLPwork *w); + +cupdlp_retcode PDHG_Alloc(CUPDLPwork *w); + +cupdlp_retcode PDHG_Create(CUPDLPwork **ww, CUPDLPproblem *lp, + CUPDLPscaling *scaling); + +void PDHG_Destroy(CUPDLPwork **w); + +void vecPrint(const char *s, const cupdlp_float *a, cupdlp_int n); + +void vecIntPrint(const char *s, const cupdlp_int *a, cupdlp_int n); + +void PDHG_Dump_Stats(CUPDLPwork *w); + +/* TODO: Add compatibility for Windows platform */ +double my_clock(void); + +extern double getTimeStamp(void); + +cupdlp_retcode dense_create(CUPDLPdense **dense); + +cupdlp_retcode csr_create(CUPDLPcsr **csr); + +cupdlp_retcode csc_create(CUPDLPcsc **csc); + +cupdlp_retcode dense_alloc(CUPDLPdense *dense, cupdlp_int nRows, + cupdlp_int nCols, cupdlp_float *val); + +cupdlp_retcode csr_alloc(CUPDLPcsr *csr, cupdlp_int nRows, cupdlp_int nCols, + cupdlp_int nnz, cupdlp_int *col_ptr, + cupdlp_int *row_ind, cupdlp_float *val); + +cupdlp_retcode csc_alloc(CUPDLPcsc *csc, cupdlp_int nRows, cupdlp_int nCols, + cupdlp_int nnz, cupdlp_int *row_ptr, + cupdlp_int *col_ind, cupdlp_float *val); + +cupdlp_retcode dense_alloc_matrix(CUPDLPdense *dense, cupdlp_int nRows, + cupdlp_int nCols, void *src, + CUPDLP_MATRIX_FORMAT src_matrix_format); + +cupdlp_retcode csr_alloc_matrix(CUPDLPcsr *csr, cupdlp_int nRows, + cupdlp_int nCols, void *src, + CUPDLP_MATRIX_FORMAT src_matrix_format); + +cupdlp_retcode csc_alloc_matrix(CUPDLPcsc *csc, cupdlp_int nRows, + cupdlp_int nCols, void *src, + CUPDLP_MATRIX_FORMAT src_matrix_format); + +void dense2csr(CUPDLPcsr *csr, CUPDLPdense *dense); + +void dense2csc(CUPDLPcsc *csc, CUPDLPdense *dense); + +void csr2csc(CUPDLPcsc *csc, CUPDLPcsr *csr); + +cupdlp_int csc2csr(CUPDLPcsr *csr, CUPDLPcsc *csc); + +void csr2dense(CUPDLPdense *dense, CUPDLPcsr *csr); + +void csc2dense(CUPDLPdense *dense, CUPDLPcsc *csc); + +cupdlp_int csc_clear(CUPDLPcsc *csc); + +cupdlp_int csr_clear(CUPDLPcsr *csr); + +void dense_clear(CUPDLPdense *dense); + +void dense_copy(CUPDLPdense *dst, CUPDLPdense *src); + +void csr_copy(CUPDLPcsr *dst, CUPDLPcsr *src); + +cupdlp_int csc_copy(CUPDLPcsc *dst, CUPDLPcsc *src); + +void csrPrintDense(const char *s, CUPDLPcsr *csr); + +void cscPrintDense(const char *s, CUPDLPcsc *csc); + +void writeJson(const char *fout, CUPDLPwork *work); + +void writeSol(const char *fout, cupdlp_int nCols, cupdlp_int nRows, + cupdlp_float *col_value, cupdlp_float *col_dual, + cupdlp_float *row_value, cupdlp_float *row_dual); + +#ifdef __cplusplus +} +#endif +#endif // CUPDLP_CUPDLP_UTILS_H diff --git a/src/pdlp/cupdlp/glbopts.h b/src/pdlp/cupdlp/glbopts.h new file mode 100644 index 0000000000..756d34be19 --- /dev/null +++ b/src/pdlp/cupdlp/glbopts.h @@ -0,0 +1,379 @@ +#ifndef GLB_H_GUARD +#define GLB_H_GUARD + +/* #ifndef CUPDLP_CPU */ +/* #include cublas */ +/* #include cudaMalloc, cudaMemcpy, etc. */ +/* #include cusparseSpMV */ +/* #endif */ + +#ifdef __cplusplus + +extern "C" { +#endif + +#include + +// return code +#define RETCODE_OK (0) +#define RETCODE_FAILED (1) +#define BOOL (1) +// #define DLONG + +#ifndef cupdlp +#define cupdlp(x) cupdlp_##x +#endif + +/* cupdlp VERSION NUMBER ---------------------------------------------- */ +#define cupdlp_VERSION \ + ("1.0.0") /* string literals automatically null-terminated */ + +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#define cupdlp_printf mexPrintf +#define _cupdlp_free mxFree +#define _cupdlp_malloc mxMalloc +#define _cupdlp_calloc mxCalloc +#define _cupdlp_realloc mxRealloc +#elif defined PYTHON +#include +#include +#define cupdlp_printf(...) \ + { \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + PySys_WriteStdout(__VA_ARGS__); \ + PyGILState_Release(gilstate); \ + } +#define _cupdlp_free free +#define _cupdlp_malloc malloc +#define _cupdlp_calloc calloc +#define _cupdlp_realloc realloc +#else + +#include +#include + +#define cupdlp_printf printf +#define cupdlp_snprintf snprintf +#define _cupdlp_free free +#define _cupdlp_malloc malloc +#define _cupdlp_calloc calloc +#define _cupdlp_realloc realloc +#endif + +/* for cuda */ +#ifndef CUPDLP_CPU + +/* #define CUPDLP_FREE_VEC(x) \ */ +/* { \ */ +/* cudaFree(x); \ */ +/* x = cupdlp_NULL; \ */ +/* } */ + +/* #define CUPDLP_COPY_VEC(dst, src, type, size) \ */ +/* cudaMemcpy(dst, src, sizeof(type) * (size), cudaMemcpyDefault) */ + +/* #define CUPDLP_INIT_VEC(var, size) \ */ +/* { \ */ +/* cusparseStatus_t status = \ */ +/* cudaMalloc((void **)&var, (size) * sizeof(typeof(*var))); \ */ +/* if (status != CUSPARSE_STATUS_SUCCESS) { \ */ +/* printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ */ +/* cusparseGetErrorString(status), status); \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ +/* #define CUPDLP_INIT_ZERO_VEC(var, size) \ */ +/* { \ */ +/* cusparseStatus_t status = \ */ +/* cudaMalloc((void **)&var, (size) * sizeof(typeof(*var))); \ */ +/* if (status != CUSPARSE_STATUS_SUCCESS) { \ */ +/* printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ */ +/* cusparseGetErrorString(status), status); \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* status = cudaMemset(var, 0, (size) * sizeof(typeof(*var))); \ */ +/* if (status != CUSPARSE_STATUS_SUCCESS) { \ */ +/* printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ */ +/* cusparseGetErrorString(status), status); \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ +/* #define CUPDLP_ZERO_VEC(var, type, size) \ */ +/* cudaMemset(var, 0, sizeof(type) * (size)) */ + +#else +#define CUPDLP_COPY_VEC(dst, src, type, size) \ + memcpy(dst, src, sizeof(type) * (size)) + + /* CUPDLP_INIT_VEC is not used */ + +/* #define CUPDLP_INIT_VEC(var, size) \ */ + /* { \ */ + /* (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ */ + /* if ((var) == cupdlp_NULL) { \ */ + /* retcode = RETCODE_FAILED; \ */ + /* goto exit_cleanup; \ */ + /* } \ */ + /* } */ +/* #define CUPDLP_INIT_ZERO_VEC(var, size) \ */ + /* { \ */ + /* (var) = (typeof(var))calloc(size, sizeof(typeof(*var))); \ */ + /* if ((var) == cupdlp_NULL) { \ */ + /* retcode = RETCODE_FAILED; \ */ + /* goto exit_cleanup; \ */ + /* } \ */ + /* } */ +#define CUPDLP_INIT_ZERO_DOUBLE_VEC(var, size) \ + { \ + (var) = (double*)calloc(size, sizeof(double)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_ZERO_INT_VEC(var, size) \ + { \ + (var) = (int*)calloc(size, sizeof(int)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_FREE_VEC(x) \ + { \ + _cupdlp_free(x); \ + x = cupdlp_NULL; \ + } +#define CUPDLP_ZERO_VEC(var, type, size) memset(var, 0, sizeof(type) * (size)) + +#endif + +#define cupdlp_free(x) \ + { \ + _cupdlp_free(x); \ + x = cupdlp_NULL; \ + } +#define cupdlp_malloc(x) _cupdlp_malloc(x) +#define cupdlp_calloc(x, y) _cupdlp_calloc(x, y) +#define cupdlp_realloc(x, y) _cupdlp_realloc(x, y) +#define cupdlp_zero(var, type, size) memset(var, 0, sizeof(type) * (size)) +#define cupdlp_copy(dst, src, type, size) \ + memcpy(dst, src, sizeof(type) * (size)) +/* #define CUPDLP_INIT(var, size) \ */ +/* { \ */ +/* (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ */ +/* if ((var) == cupdlp_NULL) { \ */ +/* retcode = RETCODE_FAILED; \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ +#define CUPDLP_INIT_DOUBLE(var, size) \ + { \ + (var) = (double*)malloc((size) * sizeof(double)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_CUPDLP_VEC(var, size) \ + { \ + (var) = (CUPDLPvec*)malloc((size) * sizeof(CUPDLPvec)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_DENSE_MATRIX(var, size) \ + { \ + (var) = (CUPDLPdense*)malloc((size) * sizeof(CUPDLPdense)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_CSR_MATRIX(var, size) \ + { \ + (var) = (CUPDLPcsr*)malloc((size) * sizeof(CUPDLPcsr)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_CSC_MATRIX(var, size) \ + { \ + (var) = (CUPDLPcsc*)malloc((size) * sizeof(CUPDLPcsc)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_SETTINGS(var, size) \ + { \ + (var) = (CUPDLPsettings*)malloc((size) * sizeof(CUPDLPsettings)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_RESOBJ(var, size) \ + { \ + (var) = (CUPDLPresobj*)malloc((size) * sizeof(CUPDLPresobj)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_ITERATES(var, size) \ + { \ + (var) = (CUPDLPiterates*)malloc((size) * sizeof(CUPDLPiterates)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_STEPSIZE(var, size) \ + { \ + (var) = (CUPDLPstepsize*)malloc((size) * sizeof(CUPDLPstepsize)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_TIMERS(var, size) \ + { \ + (var) = (CUPDLPtimers*)malloc((size) * sizeof(CUPDLPtimers)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +/* #define CUPDLP_INIT_ZERO(var, size) \ */ +/* { \ */ +/* (var) = (typeof(var))calloc(size, sizeof(typeof(*var))); \ */ +/* if ((var) == cupdlp_NULL) { \ */ +/* retcode = RETCODE_FAILED; \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ +#define CUPDLP_INIT_ZERO_DOUBLE(var, size) \ + { \ + (var) = (double*)calloc(size, sizeof(double)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_INIT_ZERO_CUPDLP_WORK(var, size) \ + { \ + (var) = (CUPDLPwork*)calloc(size, sizeof(CUPDLPwork)); \ + if ((var) == cupdlp_NULL) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } +#define CUPDLP_FREE(var) cupdlp_free(var) +#define CUPDLP_CALL(func) \ + { \ + if ((func) != RETCODE_OK) { \ + retcode = RETCODE_FAILED; \ + goto exit_cleanup; \ + } \ + } + +#ifndef SFLOAT +#ifdef DLONG +typedef long long cupdlp_int; +#else +typedef int cupdlp_int; +#endif +typedef double cupdlp_float; +#ifndef NAN +#define NAN ((cupdlp_float)0x7ff8000000000000) +#endif +#ifndef INFINITY +#define INFINITY NAN +#endif +#else +typedef float cupdlp_float; +#ifndef NAN +#define NAN ((float)0x7fc00000) +#endif +#ifndef INFINITY +#define INFINITY NAN +#endif +#endif + +#ifdef BOOL + +#include + +typedef bool cupdlp_bool; +#endif + +#define cupdlp_NULL 0 + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef ABS +#define ABS(x) (((x) < 0) ? -(x) : (x)) +#endif + +#ifndef POWF +#ifdef SFLOAT +#define POWF powf +#else +#define POWF pow +#endif +#endif + +#ifndef SQRTF +#ifdef SFLOAT +#define SQRTF sqrtf +#else +#define SQRTF sqrt +#endif +#endif + +#if EXTRA_VERBOSE > 1 +#if (defined _WIN32 || defined _WIN64 || defined _WINDLL) +#define __func__ __FUNCTION__ +#endif +#define DEBUG_FUNC \ + cupdlp_printf("IN function: %s, time: %4f ms, file: %s, line: %i\n", \ + __func__, cupdlp(tocq)(&global_timer), __FILE__, __LINE__); +#define RETURN +cupdlp_printf("EXIT function: %s, time: %4f ms, file: %s, line: %i\n", __func__, + cupdlp(tocq)(&global_timer), __FILE__, __LINE__); +return +#else +#define DEBUG_FUNC +#define RETURN return +#endif + +#define EPS_TOL (1E-18) +#define EPS (1E-8) // for condition number in subnp +#define SAFEDIV_POS(X, Y) ((Y) < EPS_TOL ? ((X) / EPS_TOL) : (X) / (Y)) + +#define CONVERGED_INTERVAL (1) +#define INDETERMINATE_TOL (1e-9) + +#define OUR_DBL_MAX 1E+20 + +#ifndef CUPDLP_ASSERT_H + +#include + +#define CUPDLP_ASSERT assert +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 08aa0fef18..f2d549e865 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -15,6 +15,7 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "Highs.h" #include "io/HighsIO.h" #include "lp_data/HConst.h" @@ -25,13 +26,13 @@ #include "mip/HighsImplications.h" #include "mip/HighsMipSolverData.h" #include "mip/HighsObjectiveFunction.h" -#include "pdqsort/pdqsort.h" #include "presolve/HighsPostsolveStack.h" #include "test/DevKkt.h" #include "util/HFactor.h" #include "util/HighsCDouble.h" #include "util/HighsIntegers.h" #include "util/HighsLinearSumBounds.h" +#include "util/HighsMemoryAllocation.h" #include "util/HighsSplay.h" #include "util/HighsUtils.h" @@ -68,23 +69,27 @@ void HPresolve::debugPrintRow(HighsPostsolveStack& postsolve_stack, } #endif -void HPresolve::setInput(HighsLp& model_, const HighsOptions& options_, - HighsTimer* timer) { +bool HPresolve::okSetInput(HighsLp& model_, const HighsOptions& options_, + const HighsInt presolve_reduction_limit, + HighsTimer* timer) { model = &model_; options = &options_; this->timer = timer; - colLowerSource.resize(model->num_col_, -1); - colUpperSource.resize(model->num_col_, -1); - implColLower.resize(model->num_col_, -kHighsInf); - implColUpper.resize(model->num_col_, kHighsInf); - - rowDualLower.resize(model->num_row_, -kHighsInf); - rowDualUpper.resize(model->num_row_, kHighsInf); - implRowDualLower.resize(model->num_row_, -kHighsInf); - implRowDualUpper.resize(model->num_row_, kHighsInf); - rowDualUpperSource.resize(model->num_row_, -1); - rowDualLowerSource.resize(model->num_row_, -1); + if (!okResize(colLowerSource, model->num_col_, HighsInt{-1})) return false; + if (!okResize(colUpperSource, model->num_col_, HighsInt{-1})) return false; + if (!okResize(implColLower, model->num_col_, -kHighsInf)) return false; + if (!okResize(implColUpper, model->num_col_, kHighsInf)) return false; + if (!okResize(colImplSourceByRow, model->num_row_)) return false; + if (!okResize(implRowDualSourceByCol, model->num_col_)) return false; + if (!okResize(rowDualLower, model->num_row_, -kHighsInf)) return false; + if (!okResize(rowDualUpper, model->num_row_, kHighsInf)) return false; + if (!okResize(implRowDualLower, model->num_row_, -kHighsInf)) return false; + if (!okResize(implRowDualUpper, model->num_row_, kHighsInf)) return false; + if (!okResize(rowDualUpperSource, model->num_row_, HighsInt{-1})) + return false; + if (!okResize(rowDualLowerSource, model->num_row_, HighsInt{-1})) + return false; for (HighsInt i = 0; i != model->num_row_; ++i) { if (model->row_lower_[i] == -kHighsInf) rowDualUpper[i] = 0; @@ -97,36 +102,54 @@ void HPresolve::setInput(HighsLp& model_, const HighsOptions& options_, } else primal_feastol = options->mip_feasibility_tolerance; - if (model_.a_matrix_.isRowwise()) - fromCSR(model->a_matrix_.value_, model->a_matrix_.index_, - model->a_matrix_.start_); - else - fromCSC(model->a_matrix_.value_, model->a_matrix_.index_, - model->a_matrix_.start_); + if (model_.a_matrix_.isRowwise()) { + // Does this even happen? + assert(model_.a_matrix_.isColwise()); + if (!okFromCSR(model->a_matrix_.value_, model->a_matrix_.index_, + model->a_matrix_.start_)) + return false; + } else { + if (!okFromCSC(model->a_matrix_.value_, model->a_matrix_.index_, + model->a_matrix_.start_)) + return false; + } // initialize everything as changed, but do not add all indices // since the first thing presolve will do is a scan for easy reductions // of each row and column and set the flag of processed columns to false // from then on they are added to the vector whenever there are changes - changedRowFlag.resize(model->num_row_, true); - rowDeleted.resize(model->num_row_, false); - changedRowIndices.reserve(model->num_row_); - changedColFlag.resize(model->num_col_, true); - colDeleted.resize(model->num_col_, false); - changedColIndices.reserve(model->num_col_); + if (!okResize(changedRowFlag, model->num_row_, uint8_t{1})) return false; + if (!okResize(rowDeleted, model->num_row_)) return false; + if (!okReserve(changedRowIndices, model->num_row_)) return false; + if (!okResize(changedColFlag, model->num_col_, uint8_t{1})) return false; + if (!okResize(colDeleted, model->num_col_)) return false; + if (!okReserve(changedColIndices, model->num_col_)) return false; numDeletedCols = 0; numDeletedRows = 0; - 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)); + // initialize substitution opportunities + for (HighsInt row = 0; row != model->num_row_; ++row) { + if (!isDualImpliedFree(row)) continue; + for (const HighsSliceNonzero& nonzero : getRowVector(row)) { + if (isImpliedFree(nonzero.index())) + substitutionOpportunities.emplace_back(row, nonzero.index()); + } + } + // Take value passed in as reduction limit, allowing different + // values to be used for initial presolve, and after restart + reductionLimit = + presolve_reduction_limit < 0 ? kHighsSize_tInf : presolve_reduction_limit; + if (options->presolve != kHighsOffString && + reductionLimit < kHighsSize_tInf) { + highsLogDev(options->log_options, HighsLogType::kInfo, + "HPresolve::okSetInput reductionLimit = %d\n", + int(reductionLimit)); + } + return true; } // for MIP presolve -void HPresolve::setInput(HighsMipSolver& mipsolver) { +bool HPresolve::okSetInput(HighsMipSolver& mipsolver, + const HighsInt presolve_reduction_limit) { this->mipsolver = &mipsolver; probingContingent = 1000; @@ -144,8 +167,8 @@ void HPresolve::setInput(HighsMipSolver& mipsolver) { mipsolver.mipdata_->domain.col_upper_; } - setInput(mipsolver.mipdata_->presolvedModel, *mipsolver.options_mip_, - &mipsolver.timer_); + return okSetInput(mipsolver.mipdata_->presolvedModel, *mipsolver.options_mip_, + presolve_reduction_limit, &mipsolver.timer_); } bool HPresolve::rowCoefficientsIntegral(HighsInt row, double scale) const { @@ -169,10 +192,7 @@ bool HPresolve::isUpperImplied(HighsInt col) const { } bool HPresolve::isImpliedFree(HighsInt col) const { - return (model->col_lower_[col] == -kHighsInf || - implColLower[col] >= model->col_lower_[col] - primal_feastol) && - (model->col_upper_[col] == kHighsInf || - implColUpper[col] <= model->col_upper_[col] + primal_feastol); + return isLowerImplied(col) && isUpperImplied(col); } bool HPresolve::isDualImpliedFree(HighsInt row) const { @@ -183,6 +203,25 @@ bool HPresolve::isDualImpliedFree(HighsInt row) const { implRowDualLower[row] >= -options->dual_feasibility_tolerance); } +void HPresolve::dualImpliedFreeGetRhsAndRowType( + HighsInt row, double& rhs, HighsPostsolveStack::RowType& rowType, + bool relaxRowDualBounds) { + assert(isDualImpliedFree(row)); + if (model->row_lower_[row] == model->row_upper_[row]) { + rowType = HighsPostsolveStack::RowType::kEq; + rhs = model->row_upper_[row]; + } else if (model->row_upper_[row] != kHighsInf && + implRowDualUpper[row] <= options->dual_feasibility_tolerance) { + rowType = HighsPostsolveStack::RowType::kLeq; + rhs = model->row_upper_[row]; + if (relaxRowDualBounds) changeRowDualUpper(row, kHighsInf); + } else { + rowType = HighsPostsolveStack::RowType::kGeq; + rhs = model->row_lower_[row]; + if (relaxRowDualBounds) changeRowDualLower(row, -kHighsInf); + } +} + bool HPresolve::isImpliedIntegral(HighsInt col) { bool runDualDetection = true; @@ -439,7 +478,7 @@ double HPresolve::getMaxAbsRowVal(HighsInt row) const { void HPresolve::updateRowDualImpliedBounds(HighsInt row, HighsInt col, double val) { - // propagate implied row dual bound bound + // propagate implied row dual bound // if the column has an infinite lower bound the reduced cost cannot be // positive, i.e. the column corresponds to a <= constraint in the dual with // right hand side -cost which becomes a >= constraint with side +cost. @@ -643,6 +682,46 @@ void HPresolve::updateColImpliedBounds(HighsInt row, HighsInt col, double val) { } } +void HPresolve::recomputeColImpliedBounds(HighsInt row) { + // recompute implied column bounds affected by a modification in a row + // (removed / added non-zeros, etc.) + if (colImplSourceByRow[row].empty()) return; + std::set affectedCols(colImplSourceByRow[row]); + for (auto it = affectedCols.cbegin(); it != affectedCols.cend(); it++) { + // set implied bounds to infinite values if they were deduced from the given + // row + if (colLowerSource[*it] == row) changeImplColLower(*it, -kHighsInf, -1); + if (colUpperSource[*it] == row) changeImplColUpper(*it, kHighsInf, -1); + + // iterate over column and recompute the implied bounds + for (const HighsSliceNonzero& nonz : getColumnVector(*it)) { + updateColImpliedBounds(nonz.index(), *it, nonz.value()); + } + } +} + +void HPresolve::recomputeRowDualImpliedBounds(HighsInt col) { + // recompute implied row dual bounds affected by a modification in a column + // (removed / added non-zeros, etc.) + if (implRowDualSourceByCol[col].empty()) return; + std::set affectedRows(implRowDualSourceByCol[col]); + for (auto it = affectedRows.cbegin(); it != affectedRows.cend(); it++) { + // set implied bounds to infinite values if they were deduced from the given + // column + if (rowDualLowerSource[*it] == col) + changeImplRowDualLower(*it, -kHighsInf, -1); + if (rowDualUpperSource[*it] == col) + changeImplRowDualUpper(*it, kHighsInf, -1); + + // iterate over row and recompute the implied bounds + for (const HighsSliceNonzero& nonz : getRowVector(*it)) { + // integer columns cannot be used to tighten bounds on dual multipliers + if (model->integrality_[nonz.index()] != HighsVarType::kInteger) + updateRowDualImpliedBounds(*it, nonz.index(), nonz.value()); + } + } +} + HighsInt HPresolve::findNonzero(HighsInt row, HighsInt col) { if (rowroot[row] == -1) return -1; @@ -679,6 +758,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { implColUpper[newColIndex[i]] = implColUpper[i]; colLowerSource[newColIndex[i]] = colLowerSource[i]; colUpperSource[newColIndex[i]] = colUpperSource[i]; + implRowDualSourceByCol[newColIndex[i]] = implRowDualSourceByCol[i]; colhead[newColIndex[i]] = colhead[i]; colsize[newColIndex[i]] = colsize[i]; if (have_col_names) @@ -696,6 +776,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { implColUpper.resize(model->num_col_); colLowerSource.resize(model->num_col_); colUpperSource.resize(model->num_col_); + implRowDualSourceByCol.resize(model->num_col_); colhead.resize(model->num_col_); colsize.resize(model->num_col_); if (have_col_names) model->col_names_.resize(model->num_col_); @@ -722,6 +803,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { implRowDualUpper[newRowIndex[i]] = implRowDualUpper[i]; rowDualLowerSource[newRowIndex[i]] = rowDualLowerSource[i]; rowDualUpperSource[newRowIndex[i]] = rowDualUpperSource[i]; + colImplSourceByRow[newRowIndex[i]] = colImplSourceByRow[i]; rowroot[newRowIndex[i]] = rowroot[i]; rowsize[newRowIndex[i]] = rowsize[i]; rowsizeInteger[newRowIndex[i]] = rowsizeInteger[i]; @@ -746,6 +828,25 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { rowDualUpperSource[i] = newColIndex[rowDualUpperSource[i]]; } + for (HighsInt i = 0; i != model->num_col_; ++i) { + std::set newSet; + std::for_each(implRowDualSourceByCol[i].cbegin(), + implRowDualSourceByCol[i].cend(), [&](const HighsInt& row) { + if (newRowIndex[row] != -1) + newSet.emplace(newRowIndex[row]); + }); + implRowDualSourceByCol[i] = std::move(newSet); + } + + for (HighsInt i = 0; i != model->num_row_; ++i) { + std::set newSet; + std::for_each(colImplSourceByRow[i].cbegin(), colImplSourceByRow[i].cend(), + [&](const HighsInt& col) { + if (newColIndex[col] != -1) + newSet.emplace(newColIndex[col]); + }); + colImplSourceByRow[i] = std::move(newSet); + } rowDeleted.assign(model->num_row_, false); model->row_lower_.resize(model->num_row_); model->row_upper_.resize(model->num_row_); @@ -755,6 +856,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { implRowDualUpper.resize(model->num_row_); rowDualLowerSource.resize(model->num_row_); rowDualUpperSource.resize(model->num_row_); + colImplSourceByRow.resize(model->num_row_); rowroot.resize(model->num_row_); rowsize.resize(model->num_row_); rowsizeInteger.resize(model->num_row_); @@ -798,6 +900,8 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { changedRowIndices.end()); for (auto& rowColPair : substitutionOpportunities) { + // skip deleted elements + if (rowColPair.first == -1) continue; rowColPair.first = newRowIndex[rowColPair.first]; rowColPair.second = newColIndex[rowColPair.second]; } @@ -925,7 +1029,7 @@ HPresolve::Result HPresolve::dominatedColumns( if (aj > ak + options->small_matrix_value) return false; } - // check row only occuring in the column vector of k + // check row only occurring in the column vector of k for (const HighsSliceNonzero& nonz : getColumnVector(k)) { HighsInt row = nonz.index(); double ak = scalk * nonz.value(); @@ -1245,13 +1349,13 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { toCSC(model->a_matrix_.value_, model->a_matrix_.index_, model->a_matrix_.start_); - fromCSC(model->a_matrix_.value_, model->a_matrix_.index_, - model->a_matrix_.start_); + okFromCSC(model->a_matrix_.value_, model->a_matrix_.index_, + model->a_matrix_.start_); mipsolver->mipdata_->cliquetable.setMaxEntries(numNonzeros()); // first tighten all bounds if they have an implied bound that is tighter - // thatn their column bound before probing this is not done for continuous + // than their column bound before probing this is not done for continuous // columns since it may allow stronger dual presolve and more aggregations double hugeBound = primal_feastol / kHighsTiny; for (HighsInt i = 0; i != model->num_col_; ++i) { @@ -1349,81 +1453,78 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { std::max(mipsolver->submip ? HighsInt{0} : HighsInt{100000}, 10 * numNonzeros()); HighsInt numFail = 0; - for (const std::tuple& binvar : - binaries) { + for (const auto& binvar : binaries) { HighsInt i = std::get<3>(binvar); - if (cliquetable.getSubstitution(i) != nullptr) continue; - - if (domain.isBinary(i)) { - // when a large percentage of columns have been deleted, stop this round - // of probing - // if (numDel > std::max(model->num_col_ * 0.2, 1000.)) break; - if (numDel > - std::max(1000., (model->num_row_ + model->num_col_) * 0.05)) { - probingEarlyAbort = true; - break; - } + if (cliquetable.getSubstitution(i) != nullptr || !domain.isBinary(i)) + continue; - // break in case of too many new implications to not spent ages in - // probing - if (cliquetable.isFull() || - cliquetable.numCliques() - numCliquesStart > - std::max(HighsInt{1000000}, 2 * numNonzeros()) || - implications.getNumImplications() - numImplicsStart > - std::max(HighsInt{1000000}, 2 * numNonzeros())) - break; + // when a large percentage of columns have been deleted, stop this round + // of probing + // if (numDel > std::max(model->num_col_ * 0.2, 1000.)) break; + probingEarlyAbort = + numDel > + std::max(HighsInt{1000}, (model->num_row_ + model->num_col_) / 20); + if (probingEarlyAbort) break; + + // break in case of too many new implications to not spent ages in + // probing + if (cliquetable.isFull() || + cliquetable.numCliques() - numCliquesStart > + std::max(HighsInt{1000000}, 2 * numNonzeros()) || + implications.getNumImplications() - numImplicsStart > + std::max(HighsInt{1000000}, 2 * numNonzeros())) + break; - // if (numProbed % 10 == 0) - // printf( - // "numprobed=%d numDel=%d newcliques=%d " - // "numNeighbourhoodQueries=%ld " - // "splayContingent=%ld\n", - // numProbed, numDel, cliquetable.numCliques() - numCliquesStart, - // cliquetable.numNeighbourhoodQueries, splayContingent); - if (cliquetable.numNeighbourhoodQueries > splayContingent) break; - - if (probingContingent - numProbed < 0) break; - - HighsInt numBoundChgs = 0; - HighsInt numNewCliques = -cliquetable.numCliques(); - if (!implications.runProbing(i, numBoundChgs)) continue; - probingContingent += numBoundChgs; - numNewCliques += cliquetable.numCliques(); - numNewCliques = std::max(numNewCliques, HighsInt{0}); - while (domain.getChangedCols().size() != numChangedCols) { - if (domain.isFixed(domain.getChangedCols()[numChangedCols++])) - ++probingNumDelCol; - } - HighsInt newNumDel = probingNumDelCol - numDelStart + - implications.substitutions.size() + - cliquetable.getSubstitutions().size(); - - if (newNumDel > numDel) { - probingContingent += numDel; - if (!mipsolver->submip) { - splayContingent += 100 * (newNumDel + numDelStart); - splayContingent += 1000 * numNewCliques; - } - numDel = newNumDel; - numFail = 0; - } else if (mipsolver->submip || numNewCliques == 0) { - splayContingent -= 100 * numFail; - ++numFail; - } else { + // if (numProbed % 10 == 0) + // printf( + // "numprobed=%d numDel=%d newcliques=%d " + // "numNeighbourhoodQueries=%ld " + // "splayContingent=%ld\n", + // numProbed, numDel, cliquetable.numCliques() - numCliquesStart, + // cliquetable.numNeighbourhoodQueries, splayContingent); + if (cliquetable.numNeighbourhoodQueries > splayContingent) break; + + if (probingContingent - numProbed < 0) break; + + HighsInt numBoundChgs = 0; + HighsInt numNewCliques = -cliquetable.numCliques(); + if (!implications.runProbing(i, numBoundChgs)) continue; + probingContingent += numBoundChgs; + numNewCliques += cliquetable.numCliques(); + numNewCliques = std::max(numNewCliques, HighsInt{0}); + while (domain.getChangedCols().size() != numChangedCols) { + if (domain.isFixed(domain.getChangedCols()[numChangedCols++])) + ++probingNumDelCol; + } + HighsInt newNumDel = probingNumDelCol - numDelStart + + implications.substitutions.size() + + cliquetable.getSubstitutions().size(); + + if (newNumDel > numDel) { + probingContingent += numDel; + if (!mipsolver->submip) { + splayContingent += 100 * (newNumDel + numDelStart); splayContingent += 1000 * numNewCliques; - numFail = 0; } + numDel = newNumDel; + numFail = 0; + } else if (mipsolver->submip || numNewCliques == 0) { + splayContingent -= 100 * numFail; + ++numFail; + } else { + splayContingent += 1000 * numNewCliques; + numFail = 0; + } - ++numProbed; - numProbes[i] += 1; + ++numProbed; + numProbes[i] += 1; - // printf("nprobed: %" HIGHSINT_FORMAT ", numCliques: %" HIGHSINT_FORMAT - // "\n", nprobed, - // cliquetable.numCliques()); - if (domain.infeasible()) { - return Result::kPrimalInfeasible; - } + // printf("nprobed: %" HIGHSINT_FORMAT ", numCliques: %" HIGHSINT_FORMAT + // "\n", nprobed, + // cliquetable.numCliques()); + if (domain.infeasible()) { + return Result::kPrimalInfeasible; } } @@ -1439,13 +1540,11 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { if (!rowDeleted[delrow]) removeRow(delrow); cliquetable.getDeletedRows().clear(); - // add nonzeros from clique lifting before removign fixed variables, since + // add nonzeros from clique lifting before removing fixed variables, since // this might lead to stronger constraint sides - std::vector>& - extensionvars = cliquetable.getCliqueExtensions(); + auto& extensionvars = cliquetable.getCliqueExtensions(); HighsInt addednnz = extensionvars.size(); - for (std::pair cliqueextension : - extensionvars) { + for (const auto& cliqueextension : extensionvars) { if (rowDeleted[cliqueextension.first]) { --addednnz; continue; @@ -1577,6 +1676,12 @@ void HPresolve::markRowDeleted(HighsInt row) { changedRowFlag[row] = true; rowDeleted[row] = true; ++numDeletedRows; + + // remove row from column-wise implied bound storage + if (rowDualLowerSource[row] != -1) + implRowDualSourceByCol[rowDualLowerSource[row]].erase(row); + if (rowDualUpperSource[row] != -1) + implRowDualSourceByCol[rowDualUpperSource[row]].erase(row); } void HPresolve::markColDeleted(HighsInt col) { @@ -1585,6 +1690,11 @@ void HPresolve::markColDeleted(HighsInt col) { changedColFlag[col] = true; colDeleted[col] = true; ++numDeletedCols; + // remove column from row-wise implied bound storage + if (colLowerSource[col] != -1) + colImplSourceByRow[colLowerSource[col]].erase(col); + if (colUpperSource[col] != -1) + colImplSourceByRow[colUpperSource[col]].erase(col); } void HPresolve::changeColUpper(HighsInt col, double newUpper) { @@ -1665,14 +1775,17 @@ void HPresolve::changeImplColUpper(HighsInt col, double newUpper, oldImplUpper > model->col_upper_[col] + primal_feastol && newUpper <= model->col_upper_[col] + primal_feastol; - // remember the source of this lower bound, so that we can correctly identify + // remember the source of this upper bound, so that we can correctly identify // weak domination + if (colUpperSource[col] != -1 && colLowerSource[col] != colUpperSource[col]) + colImplSourceByRow[colUpperSource[col]].erase(col); + if (originRow != -1) colImplSourceByRow[originRow].emplace(col); + colUpperSource[col] = originRow; implColUpper[col] = newUpper; - // if the old and the new implied bound not better than the lower bound - // nothing - // needs to be updated + // if the old and the new implied bound are not better than the upper bound, + // nothing needs to be updated if (!newImpliedFree && std::min(oldImplUpper, newUpper) >= model->col_upper_[col]) return; @@ -1694,8 +1807,7 @@ void HPresolve::changeImplColLower(HighsInt col, double newLower, if (oldImplLower <= model->col_lower_[col] + primal_feastol && newLower > model->col_lower_[col] + primal_feastol) { // the dual constraint can additionally be considered a <= constraint and - // was free, or a - // >= constraint before + // was free, or a >= constraint before markChangedCol(col); } bool newImpliedFree = @@ -1705,10 +1817,14 @@ void HPresolve::changeImplColLower(HighsInt col, double newLower, // remember the source of this lower bound, so that we can correctly identify // weak domination + if (colLowerSource[col] != -1 && colLowerSource[col] != colUpperSource[col]) + colImplSourceByRow[colLowerSource[col]].erase(col); + if (originRow != -1) colImplSourceByRow[originRow].emplace(col); + colLowerSource[col] = originRow; implColLower[col] = newLower; - // if the old and the new implied bound not better than the lower bound + // if the old and the new implied bound are not better than the lower bound, // nothing needs to be updated if (!newImpliedFree && std::max(oldImplLower, newLower) <= model->col_lower_[col]) @@ -1738,8 +1854,13 @@ void HPresolve::changeImplRowDualUpper(HighsInt row, double newUpper, oldImplUpper > rowDualUpper[row] + options->dual_feasibility_tolerance && newUpper <= rowDualUpper[row] + options->dual_feasibility_tolerance; - // remember the source of this lower bound, so that we can correctly identify - // weakdomination + // remember the source of this upper bound, so that we can correctly identify + // weak domination + if (rowDualUpperSource[row] != -1 && + rowDualLowerSource[row] != rowDualUpperSource[row]) + implRowDualSourceByCol[rowDualUpperSource[row]].erase(row); + if (originCol != -1) implRowDualSourceByCol[originCol].emplace(row); + rowDualUpperSource[row] = originCol; implRowDualUpper[row] = newUpper; @@ -1772,7 +1893,12 @@ void HPresolve::changeImplRowDualLower(HighsInt row, double newLower, newLower >= rowDualLower[row] - options->dual_feasibility_tolerance; // remember the source of this lower bound, so that we can correctly identify - // a weakly domination + // weak domination + if (rowDualLowerSource[row] != -1 && + rowDualLowerSource[row] != rowDualUpperSource[row]) + implRowDualSourceByCol[rowDualLowerSource[row]].erase(row); + if (originCol != -1) implRowDualSourceByCol[originCol].emplace(row); + rowDualLowerSource[row] = originCol; implRowDualLower[row] = newLower; @@ -1848,12 +1974,12 @@ HPresolve::Result HPresolve::applyConflictGraphSubstitutions( ++probingNumDelCol; - postsolve_stack.doubletonEquation(-1, substitution.substcol, - substitution.staycol, 1.0, - -substitution.scale, substitution.offset, - model->col_lower_[substitution.substcol], - model->col_upper_[substitution.substcol], - 0.0, false, false, HighsEmptySlice()); + postsolve_stack.doubletonEquation( + -1, substitution.substcol, substitution.staycol, 1.0, + -substitution.scale, substitution.offset, + model->col_lower_[substitution.substcol], + model->col_upper_[substitution.substcol], 0.0, false, false, + HighsPostsolveStack::RowType::kEq, HighsEmptySlice()); markColDeleted(substitution.substcol); substitute(substitution.substcol, substitution.staycol, substitution.offset, substitution.scale); @@ -1881,7 +2007,8 @@ HPresolve::Result HPresolve::applyConflictGraphSubstitutions( postsolve_stack.doubletonEquation( -1, subst.substcol, subst.replace.col, 1.0, -scale, offset, model->col_lower_[subst.substcol], model->col_upper_[subst.substcol], - 0.0, false, false, HighsEmptySlice()); + 0.0, false, false, HighsPostsolveStack::RowType::kEq, + HighsEmptySlice()); markColDeleted(subst.substcol); substitute(subst.substcol, subst.replace.col, offset, scale); HPRESOLVE_CHECKED_CALL(checkLimits(postsolve_stack)); @@ -1906,20 +2033,20 @@ HighsTripletPositionSlice HPresolve::getStoredRow() const { rowpositions.data(), rowpositions.size()); } -void HPresolve::fromCSC(const std::vector& Aval, - const std::vector& Aindex, - const std::vector& Astart) { +bool HPresolve::okFromCSC(const std::vector& Aval, + const std::vector& Aindex, + const std::vector& Astart) { Avalue.clear(); Acol.clear(); Arow.clear(); freeslots.clear(); - colhead.assign(model->num_col_, -1); - rowroot.assign(model->num_row_, -1); - colsize.assign(model->num_col_, 0); - rowsize.assign(model->num_row_, 0); - rowsizeInteger.assign(model->num_row_, 0); - rowsizeImplInt.assign(model->num_row_, 0); + if (!okAssign(colhead, model->num_col_, HighsInt{-1})) return false; + if (!okAssign(rowroot, model->num_row_, HighsInt{-1})) return false; + if (!okAssign(colsize, model->num_col_)) return false; + if (!okAssign(rowsize, model->num_row_)) return false; + if (!okAssign(rowsizeInteger, model->num_row_)) return false; + if (!okAssign(rowsizeImplInt, model->num_row_)) return false; impliedRowBounds.setNumSums(0); impliedDualRowBounds.setNumSums(0); @@ -1938,8 +2065,8 @@ void HPresolve::fromCSC(const std::vector& Aval, HighsInt nnz = Aval.size(); Avalue = Aval; - Acol.reserve(nnz); - Arow.reserve(nnz); + if (!okReserve(Acol, nnz)) return false; + if (!okReserve(Arow, nnz)) return false; for (HighsInt i = 0; i != ncol; ++i) { HighsInt collen = Astart[i + 1] - Astart[i]; @@ -1948,36 +2075,42 @@ void HPresolve::fromCSC(const std::vector& Aval, Aindex.begin() + Astart[i + 1]); } - Anext.resize(nnz); - Aprev.resize(nnz); - ARleft.resize(nnz); - ARright.resize(nnz); + if (!okResize(Anext, nnz)) return false; + if (!okResize(Aprev, nnz)) return false; + if (!okResize(ARleft, nnz)) return false; + if (!okResize(ARright, nnz)) return false; for (HighsInt pos = 0; pos != nnz; ++pos) link(pos); if (equations.empty()) { - eqiters.assign(model->num_row_, equations.end()); + try { + eqiters.assign(model->num_row_, equations.end()); + } catch (const std::bad_alloc& e) { + printf("HPresolve::okFromCSC eqiters.assign fails with %s\n", e.what()); + return false; + } for (HighsInt i = 0; i != model->num_row_; ++i) { // register equation if (model->row_lower_[i] == model->row_upper_[i]) eqiters[i] = equations.emplace(rowsize[i], i).first; } } + return true; } -void HPresolve::fromCSR(const std::vector& ARval, - const std::vector& ARindex, - const std::vector& ARstart) { +bool HPresolve::okFromCSR(const std::vector& ARval, + const std::vector& ARindex, + const std::vector& ARstart) { Avalue.clear(); Acol.clear(); Arow.clear(); freeslots.clear(); - colhead.assign(model->num_col_, -1); - rowroot.assign(model->num_row_, -1); - colsize.assign(model->num_col_, 0); - rowsize.assign(model->num_row_, 0); - rowsizeInteger.assign(model->num_row_, 0); - rowsizeImplInt.assign(model->num_row_, 0); + if (!okAssign(colhead, model->num_col_, HighsInt{-1})) return false; + if (!okAssign(rowroot, model->num_row_, HighsInt{-1})) return false; + if (!okAssign(colsize, model->num_col_)) return false; + if (!okAssign(rowsize, model->num_row_)) return false; + if (!okAssign(rowsizeInteger, model->num_row_)) return false; + if (!okAssign(rowsizeImplInt, model->num_row_)) return false; impliedRowBounds.setNumSums(0); impliedDualRowBounds.setNumSums(0); @@ -1996,8 +2129,8 @@ void HPresolve::fromCSR(const std::vector& ARval, HighsInt nnz = ARval.size(); Avalue = ARval; - Acol.reserve(nnz); - Arow.reserve(nnz); + if (!okReserve(Acol, nnz)) return false; + if (!okReserve(Arow, nnz)) return false; // entries.reserve(nnz); for (HighsInt i = 0; i != nrow; ++i) { @@ -2007,20 +2140,26 @@ void HPresolve::fromCSR(const std::vector& ARval, ARindex.begin() + ARstart[i + 1]); } - Anext.resize(nnz); - Aprev.resize(nnz); - ARleft.resize(nnz); - ARright.resize(nnz); + if (!okResize(Anext, nnz)) return false; + if (!okResize(Aprev, nnz)) return false; + if (!okResize(ARleft, nnz)) return false; + if (!okResize(ARright, nnz)) return false; for (HighsInt pos = 0; pos != nnz; ++pos) link(pos); if (equations.empty()) { - eqiters.assign(nrow, equations.end()); + try { + eqiters.assign(nrow, equations.end()); + } catch (const std::bad_alloc& e) { + printf("HPresolve::okFromCSR eqiters.assign fails with %s\n", e.what()); + return false; + } for (HighsInt i = 0; i != nrow; ++i) { // register equation if (model->row_lower_[i] == model->row_upper_[i]) eqiters[i] = equations.emplace(rowsize[i], i).first; } } + return true; } HighsInt HPresolve::countFillin(HighsInt row) { @@ -2092,6 +2231,17 @@ bool HPresolve::checkFillin(HighsHashTable& fillinCache, return true; } +void HPresolve::reinsertEquation(HighsInt row) { + // check if this is an equation row and it now has a different size + if (model->row_lower_[row] == model->row_upper_[row] && + eqiters[row] != equations.end() && eqiters[row]->first != rowsize[row]) { + // if that is the case reinsert it into the equation set that is ordered + // by sparsity + equations.erase(eqiters[row]); + eqiters[row] = equations.emplace(rowsize[row], row).first; + } +} + void HPresolve::transformColumn(HighsPostsolveStack& postsolve_stack, HighsInt col, double scale, double constant) { if (mipsolver != nullptr) @@ -2172,8 +2322,6 @@ void HPresolve::scaleRow(HighsInt row, double scale, bool integral) { } void HPresolve::scaleStoredRow(HighsInt row, double scale, bool integral) { - HighsInt rowlen = rowpositions.size(); - model->row_upper_[row] *= scale; model->row_lower_[row] *= scale; implRowDualLower[row] /= scale; @@ -2184,17 +2332,13 @@ void HPresolve::scaleStoredRow(HighsInt row, double scale, bool integral) { model->row_upper_[row] = std::round(model->row_upper_[row]); if (model->row_lower_[row] != kHighsInf) model->row_lower_[row] = std::round(model->row_lower_[row]); - for (HighsInt j = 0; j < rowlen; ++j) { - Avalue[rowpositions[j]] *= scale; - if (std::abs(Avalue[rowpositions[j]]) <= options->small_matrix_value) - unlink(rowpositions[j]); - } - } else - for (HighsInt j = 0; j < rowlen; ++j) { - Avalue[rowpositions[j]] *= scale; - if (std::abs(Avalue[rowpositions[j]]) <= options->small_matrix_value) - unlink(rowpositions[j]); - } + } + + for (size_t j = 0; j < rowpositions.size(); ++j) { + Avalue[rowpositions[j]] *= scale; + if (std::abs(Avalue[rowpositions[j]]) <= options->small_matrix_value) + unlink(rowpositions[j]); + } impliedRowBounds.sumScaled(row, scale); if (scale < 0) { @@ -2259,15 +2403,11 @@ void HPresolve::substitute(HighsInt row, HighsInt col, double rhs) { addToMatrix(colrow, Acol[rowiter], scale * Avalue[rowiter]); } + // recompute implied column bounds affected by the substitution + recomputeColImpliedBounds(colrow); + // check if this is an equation row and it now has a different size - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); // printf("after substitution: "); // debugPrintRow(colrow); } @@ -2295,6 +2435,12 @@ void HPresolve::substitute(HighsInt row, HighsInt col, double rhs) { model->col_cost_[col] = 0.0; } + // recompute implied row dual bounds affected by substitution + for (HighsInt rowiter : rowpositions) { + if (Acol[rowiter] == col) continue; + recomputeRowDualImpliedBounds(Acol[rowiter]); + } + // finally remove the entries of the row that was used for substitution for (HighsInt rowiter : rowpositions) unlink(rowiter); } @@ -2358,7 +2504,8 @@ void HPresolve::toCSR(std::vector& ARval, } HPresolve::Result HPresolve::doubletonEq(HighsPostsolveStack& postsolve_stack, - HighsInt row) { + HighsInt row, + HighsPostsolveStack::RowType rowType) { assert(analysis_.allow_rule_[kPresolveRuleDoubletonEquation]); const bool logging_on = analysis_.logging_on_; if (logging_on) @@ -2372,107 +2519,88 @@ HPresolve::Result HPresolve::doubletonEq(HighsPostsolveStack& postsolve_stack, HighsInt nzPos1 = rowroot[row]; HighsInt nzPos2 = ARright[nzPos1] != -1 ? ARright[nzPos1] : ARleft[nzPos1]; - HighsInt substcol; - HighsInt staycol; - double substcoef; - double staycoef; - double rhs = model->row_upper_[row]; - if (model->integrality_[Acol[nzPos1]] == HighsVarType::kInteger) { - if (model->integrality_[Acol[nzPos2]] == HighsVarType::kInteger) { - // both columns integer. For substitution choose smaller absolute - // coefficient value, or sparser column if values are equal - if (std::abs(Avalue[nzPos1]) < - std::abs(Avalue[nzPos2]) - options->small_matrix_value) { - substcol = Acol[nzPos1]; - staycol = Acol[nzPos2]; - - substcoef = Avalue[nzPos1]; - staycoef = Avalue[nzPos2]; - } else if (std::abs(Avalue[nzPos2]) < - std::abs(Avalue[nzPos1]) - options->small_matrix_value) { - substcol = Acol[nzPos2]; - staycol = Acol[nzPos1]; - - substcoef = Avalue[nzPos2]; - staycoef = Avalue[nzPos1]; - } else if (colsize[Acol[nzPos1]] < colsize[Acol[nzPos2]]) { - substcol = Acol[nzPos1]; - staycol = Acol[nzPos2]; - - substcoef = Avalue[nzPos1]; - staycoef = Avalue[nzPos2]; + auto colAtPos1Better = [&]() { + if (model->integrality_[Acol[nzPos1]] == HighsVarType::kInteger) { + if (model->integrality_[Acol[nzPos2]] == HighsVarType::kInteger) { + // both columns integer. For substitution choose smaller absolute + // coefficient value, or sparser column if values are equal + if (std::fabs(Avalue[nzPos1]) < + std::fabs(Avalue[nzPos2]) - options->small_matrix_value) { + return true; + } else if (std::fabs(Avalue[nzPos2]) < + std::fabs(Avalue[nzPos1]) - options->small_matrix_value) { + return false; + } else if (colsize[Acol[nzPos1]] < colsize[Acol[nzPos2]]) { + return true; + } else { + return false; + } } else { - substcol = Acol[nzPos2]; - staycol = Acol[nzPos1]; - - substcoef = Avalue[nzPos2]; - staycoef = Avalue[nzPos1]; + // one col is integral, substitute the continuous one + return false; } - - // check integrality conditions - double roundCoef = std::round(staycoef / substcoef) * substcoef; - if (std::abs(roundCoef - staycoef) > options->small_matrix_value) - return Result::kOk; - staycoef = roundCoef; - double roundRhs = std::round(rhs / substcoef) * substcoef; - if (std::abs(rhs - roundRhs) > primal_feastol) - return Result::kPrimalInfeasible; - rhs = roundRhs; } else { - // one col is integral, substitute the continuous one - substcol = Acol[nzPos2]; - staycol = Acol[nzPos1]; - - substcoef = Avalue[nzPos2]; - staycoef = Avalue[nzPos1]; - } - } else { - if (model->integrality_[Acol[nzPos2]] == HighsVarType::kInteger) { - // one col is integral, substitute the continuous one - substcol = Acol[nzPos1]; - staycol = Acol[nzPos2]; - - substcoef = Avalue[nzPos1]; - staycoef = Avalue[nzPos2]; - } else { - // both columns continuous the one with a larger absolute coefficient - // value if the difference is more than factor 2, and otherwise the one - // with fewer nonzeros if those are equal - bool colAtPos1Better; - HighsInt col1Size = colsize[Acol[nzPos1]]; - if (col1Size == 1) - colAtPos1Better = true; - else { - HighsInt col2Size = colsize[Acol[nzPos2]]; - if (col2Size == 1) - colAtPos1Better = false; + if (model->integrality_[Acol[nzPos2]] == HighsVarType::kInteger) { + // one col is integral, substitute the continuous one + return true; + } else { + // both columns continuous the one with a larger absolute coefficient + // value if the difference is more than factor 2, and otherwise the one + // with fewer nonzeros if those are equal + HighsInt col1Size = colsize[Acol[nzPos1]]; + if (col1Size == 1) + return true; else { - double abs1Val = std::fabs(Avalue[nzPos1]); - double abs2Val = std::fabs(Avalue[nzPos2]); - if (col1Size != col2Size && - std::max(abs1Val, abs2Val) <= 2.0 * std::min(abs1Val, abs2Val)) - colAtPos1Better = col1Size < col2Size; - else if (abs1Val > abs2Val) - colAtPos1Better = true; - else - colAtPos1Better = false; + HighsInt col2Size = colsize[Acol[nzPos2]]; + if (col2Size == 1) + return false; + else { + double abs1Val = std::fabs(Avalue[nzPos1]); + double abs2Val = std::fabs(Avalue[nzPos2]); + if (col1Size != col2Size && + std::max(abs1Val, abs2Val) <= 2.0 * std::min(abs1Val, abs2Val)) + return (col1Size < col2Size); + else if (abs1Val > abs2Val) + return true; + else + return false; + } } } + } + }; - if (colAtPos1Better) { - substcol = Acol[nzPos1]; - staycol = Acol[nzPos2]; + HighsInt substcol; + HighsInt staycol; + double substcoef; + double staycoef; - substcoef = Avalue[nzPos1]; - staycoef = Avalue[nzPos2]; - } else { - substcol = Acol[nzPos2]; - staycol = Acol[nzPos1]; + if (colAtPos1Better()) { + substcol = Acol[nzPos1]; + staycol = Acol[nzPos2]; - substcoef = Avalue[nzPos2]; - staycoef = Avalue[nzPos1]; - } - } + substcoef = Avalue[nzPos1]; + staycoef = Avalue[nzPos2]; + } else { + substcol = Acol[nzPos2]; + staycol = Acol[nzPos1]; + + substcoef = Avalue[nzPos2]; + staycoef = Avalue[nzPos1]; + } + + double rhs = model->row_upper_[row]; + if (model->integrality_[substcol] == HighsVarType::kInteger && + model->integrality_[staycol] == HighsVarType::kInteger) { + // check integrality conditions + double roundCoef = std::round(staycoef / substcoef) * substcoef; + if (std::fabs(roundCoef - staycoef) > options->small_matrix_value) + return Result::kOk; + staycoef = roundCoef; + double roundRhs = std::round(rhs / substcoef) * substcoef; + if (std::fabs(rhs - roundRhs) > primal_feastol) + return Result::kPrimalInfeasible; + rhs = roundRhs; } double oldStayLower = model->col_lower_[staycol]; @@ -2512,22 +2640,16 @@ HPresolve::Result HPresolve::doubletonEq(HighsPostsolveStack& postsolve_stack, } // possibly tighten bounds of the column that stays - bool lowerTightened = false; - bool upperTightened = false; - if (stayImplLower > oldStayLower + primal_feastol) { - lowerTightened = true; - changeColLower(staycol, stayImplLower); - } + bool lowerTightened = stayImplLower > oldStayLower + primal_feastol; + if (lowerTightened) changeColLower(staycol, stayImplLower); - if (stayImplUpper < oldStayUpper - primal_feastol) { - upperTightened = true; - changeColUpper(staycol, stayImplUpper); - } + bool upperTightened = stayImplUpper < oldStayUpper - primal_feastol; + if (upperTightened) changeColUpper(staycol, stayImplUpper); - postsolve_stack.doubletonEquation(row, substcol, staycol, substcoef, staycoef, - rhs, substLower, substUpper, - model->col_cost_[substcol], lowerTightened, - upperTightened, getColumnVector(substcol)); + postsolve_stack.doubletonEquation( + row, substcol, staycol, substcoef, staycoef, rhs, substLower, substUpper, + model->col_cost_[substcol], lowerTightened, upperTightened, rowType, + getColumnVector(substcol)); // finally modify matrix markColDeleted(substcol); @@ -2749,9 +2871,10 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, // forcing column of size %" HIGHSINT_FORMAT "\n", // colsize[col]); if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn(col, getColumnVector(col), - model->col_cost_[col], - model->col_lower_[col], true); + postsolve_stack.forcingColumn( + col, getColumnVector(col), model->col_cost_[col], + model->col_lower_[col], true, + model->integrality_[col] == HighsVarType::kInteger); markColDeleted(col); HighsInt coliter = colhead[col]; while (coliter != -1) { @@ -2782,9 +2905,10 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, // printf("removing forcing column of size %" HIGHSINT_FORMAT "\n", // colsize[col]); if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn(col, getColumnVector(col), - model->col_cost_[col], - model->col_upper_[col], false); + postsolve_stack.forcingColumn( + col, getColumnVector(col), model->col_cost_[col], + model->col_upper_[col], false, + model->integrality_[col] == HighsVarType::kInteger); markColDeleted(col); HighsInt coliter = colhead[col]; while (coliter != -1) { @@ -2834,19 +2958,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, // for substitution storeRow(row); - HighsPostsolveStack::RowType rowType = HighsPostsolveStack::RowType::kEq; + HighsPostsolveStack::RowType rowType; double rhs; - if (model->row_lower_[row] == model->row_upper_[row]) { - rhs = model->row_upper_[row]; - rowType = HighsPostsolveStack::RowType::kEq; - } else if ((model->row_upper_[row] != kHighsInf && - implRowDualUpper[row] <= options->dual_feasibility_tolerance)) { - rhs = model->row_upper_[row]; - rowType = HighsPostsolveStack::RowType::kLeq; - } else { - rhs = model->row_lower_[row]; - rowType = HighsPostsolveStack::RowType::kGeq; - } + dualImpliedFreeGetRhsAndRowType(row, rhs, rowType); postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], rowType, getStoredRow(), @@ -2911,65 +3025,68 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, return checkLimits(postsolve_stack); } + auto checkRedundantBounds = [&](HighsInt col, HighsInt row) { + // check if column singleton has redundant bounds + assert(model->col_cost_[col] != 0.0); + if (colsize[col] != 1) return; + if (model->col_cost_[col] > 0) { + assert(model->col_lower_[col] == -kHighsInf || + (model->col_lower_[col] <= implColLower[col] + primal_feastol && + colLowerSource[col] == row)); + if (model->col_lower_[col] > implColLower[col] - primal_feastol) + changeColLower(col, -kHighsInf); + } else { + assert(model->col_upper_[col] == kHighsInf || + (model->col_upper_[col] >= implColUpper[col] - primal_feastol && + colUpperSource[col] == row)); + if (model->col_upper_[col] < implColUpper[col] + primal_feastol) + changeColUpper(col, kHighsInf); + } + }; + + // Store original bounds + double origRowUpper = model->row_upper_[row]; + double origRowLower = model->row_lower_[row]; + if (model->row_lower_[row] != model->row_upper_[row]) { if (implRowDualLower[row] > options->dual_feasibility_tolerance) { + // Convert to equality constraint (note that currently postsolve will not + // know about this conversion) model->row_upper_[row] = model->row_lower_[row]; - if (mipsolver == nullptr) { - HighsInt col = rowDualLowerSource[row]; - assert(model->col_cost_[col] != 0.0); - if (colsize[col] == 1) { - double colCoef = Avalue[colhead[col]]; - if (model->col_cost_[col] > 0) { - assert( - model->col_lower_[col] == -kHighsInf || - (model->col_lower_[col] <= implColLower[col] + primal_feastol && - colLowerSource[col] == row)); - if (model->col_lower_[col] > implColLower[col] - primal_feastol) - changeColLower(col, -kHighsInf); - } else { - assert( - model->col_upper_[col] == kHighsInf || - (model->col_upper_[col] >= implColUpper[col] - primal_feastol && - colUpperSource[col] == row)); - if (model->col_upper_[col] < implColUpper[col] + primal_feastol) - changeColUpper(col, kHighsInf); - } - } - } - } - - if (implRowDualUpper[row] < -options->dual_feasibility_tolerance) { + // Since row upper bound is now finite, lower bound on row dual is + // -kHighsInf + changeRowDualLower(row, -kHighsInf); + if (mipsolver == nullptr) + checkRedundantBounds(rowDualLowerSource[row], row); + } else if (implRowDualUpper[row] < -options->dual_feasibility_tolerance) { + // Convert to equality constraint (note that currently postsolve will not + // know about this conversion) model->row_lower_[row] = model->row_upper_[row]; - if (mipsolver == nullptr) { - HighsInt col = rowDualUpperSource[row]; - assert(model->col_cost_[col] != 0.0); - if (colsize[col] == 1) { - if (model->col_cost_[col] > 0) { - assert( - model->col_lower_[col] == -kHighsInf || - (model->col_lower_[col] <= implColLower[col] + primal_feastol && - colLowerSource[col] == row)); - if (model->col_lower_[col] > implColLower[col] - primal_feastol) - changeColLower(col, -kHighsInf); - } else { - assert( - model->col_upper_[col] == kHighsInf || - (model->col_upper_[col] >= implColUpper[col] - primal_feastol && - colUpperSource[col] == row)); - if (model->col_upper_[col] < implColUpper[col] + primal_feastol) - changeColUpper(col, kHighsInf); - } - } - } + // Since row lower bound is now finite, upper bound on row dual is + // kHighsInf + changeRowDualUpper(row, kHighsInf); + if (mipsolver == nullptr) + checkRedundantBounds(rowDualUpperSource[row], row); } } + // Get row bounds double rowUpper = model->row_upper_[row]; double rowLower = model->row_lower_[row]; - if (rowsize[row] == 2 && rowLower == rowUpper) { - if (analysis_.allow_rule_[kPresolveRuleDoubletonEquation]) - return doubletonEq(postsolve_stack, row); + // Handle doubleton equations + if (rowsize[row] == 2 && rowLower == rowUpper && + analysis_.allow_rule_[kPresolveRuleDoubletonEquation]) { + HighsPostsolveStack::RowType rowType; + if (origRowLower == origRowUpper) { + rowType = HighsPostsolveStack::RowType::kEq; + } else if (origRowUpper != kHighsInf) { + rowType = HighsPostsolveStack::RowType::kLeq; + } else { + assert(origRowLower != -kHighsInf); + rowType = HighsPostsolveStack::RowType::kGeq; + } + return doubletonEq(postsolve_stack, row, rowType); } // todo: do additional single row presolve for mip here. It may assume a @@ -3052,7 +3169,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, -1, nonz.index(), binCol, 1.0, -scale, offset, model->col_lower_[nonz.index()], model->col_upper_[nonz.index()], 0.0, false, false, - HighsEmptySlice()); + HighsPostsolveStack::RowType::kEq, HighsEmptySlice()); substitute(nonz.index(), binCol, offset, scale); } else { // This case yields the following implications: @@ -3070,7 +3187,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, -1, nonz.index(), binCol, 1.0, -scale, offset, model->col_lower_[nonz.index()], model->col_upper_[nonz.index()], 0.0, false, false, - HighsEmptySlice()); + HighsPostsolveStack::RowType::kEq, HighsEmptySlice()); substitute(nonz.index(), binCol, offset, scale); } } @@ -3175,7 +3292,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, if (x1Cand != -1) { HighsInt x1Pos = rowpositions[x1Cand]; HighsInt x1 = Acol[x1Pos]; - double rhs2 = rhs / d; + double rhs2 = rhs / static_cast(d); if (std::abs(std::round(rhs2) - rhs2) <= mipsolver->mipdata_->epsilon) { // the right hand side is integral, so we can substitute @@ -3188,7 +3305,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, } else { // we can substitute x1 = d * z + b, with b = a1^-1 rhs (mod d) - // first compute the modular multiplciative inverse of a1^-1 + // first compute the modular multiplicative inverse of a1^-1 // (mod d) of a1 int64_t a1 = std::round(intScale * Avalue[x1Pos]); a1 = HighsIntegers::mod(a1, d); @@ -3205,9 +3322,11 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, // z is fixed after rounding its new bounds. If that is the case // we directly fix x1 instead of first substituting with d * z + // b. - double zLower = - std::ceil((model->col_lower_[x1] - b) / d - primal_feastol); - double zUpper = std::floor((model->col_upper_[x1] - b) / d + + double zLower = std::ceil((model->col_lower_[x1] - b) / + static_cast(d) - + primal_feastol); + double zUpper = std::floor((model->col_upper_[x1] - b) / + static_cast(d) + primal_feastol); if (zLower == zUpper) { @@ -3245,7 +3364,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, } } } else { - // inequality or ranged row, first store row posititions + // inequality or ranged row, first store row positions storeRow(row); if (rowsize[row] == rowsizeInteger[row] + rowsizeImplInt[row]) { @@ -3490,7 +3609,6 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, if (model->row_lower_[row] == -kHighsInf && impliedRowUpper != kHighsInf) { - HighsInt numTightened = 0; double maxCoefValue = impliedRowUpper - model->row_upper_[row]; HighsCDouble rhs = model->row_upper_[row]; for (const HighsSliceNonzero& nonz : getStoredRow()) { @@ -3498,17 +3616,15 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, continue; if (nonz.value() > maxCoefValue + primal_feastol) { - // <= contraint, we decrease the coefficient value and the right + // <= constraint, we decrease the coefficient value and the right // hand side double delta = maxCoefValue - nonz.value(); addToMatrix(row, nonz.index(), delta); rhs += delta * model->col_upper_[nonz.index()]; - ++numTightened; } else if (nonz.value() < -maxCoefValue - primal_feastol) { double delta = -maxCoefValue - nonz.value(); addToMatrix(row, nonz.index(), delta); rhs += delta * model->col_lower_[nonz.index()]; - ++numTightened; } } @@ -3517,7 +3633,6 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, if (model->row_upper_[row] == kHighsInf && impliedRowLower != -kHighsInf) { - HighsInt numTightened = 0; double maxCoefValue = model->row_lower_[row] - impliedRowLower; HighsCDouble rhs = model->row_lower_[row]; for (const HighsSliceNonzero& nonz : getStoredRow()) { @@ -3528,12 +3643,10 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, double delta = maxCoefValue - nonz.value(); addToMatrix(row, nonz.index(), delta); rhs += delta * model->col_lower_[nonz.index()]; - ++numTightened; } else if (nonz.value() < -maxCoefValue - primal_feastol) { double delta = -maxCoefValue - nonz.value(); addToMatrix(row, nonz.index(), delta); rhs += delta * model->col_upper_[nonz.index()]; - ++numTightened; } } @@ -3712,9 +3825,21 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, bool hasRowUpper = model->row_upper_[row] != kHighsInf || implRowDualUpper[row] < -options->dual_feasibility_tolerance; + // #1711: This looks very dodgy: surely model->row_lower_[row] != + // -kHighsInf: dates from before 25/02/21 + // + // bool hasRowLower = + // model->row_lower_[row] != kHighsInf || + // implRowDualLower[row] > options->dual_feasibility_tolerance; + // + // Using this corrected line will reduce the number of calls to + // updateColImpliedBounds, as model->row_lower_[row] != kHighsInf is + // never false bool hasRowLower = - model->row_lower_[row] != kHighsInf || + model->row_lower_[row] != -kHighsInf || implRowDualLower[row] > options->dual_feasibility_tolerance; + // #1711: Unsurprisingly, the assert is triggered very frequently + // assert(true_hasRowLower == hasRowLower); if ((hasRowUpper && impliedRowBounds.getNumInfSumLowerOrig(row) <= 1) || (hasRowLower && impliedRowBounds.getNumInfSumUpperOrig(row) <= 1)) { @@ -3812,9 +3937,10 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, return checkLimits(postsolve_stack); } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn(col, getColumnVector(col), - model->col_cost_[col], - model->col_lower_[col], true); + postsolve_stack.forcingColumn( + col, getColumnVector(col), model->col_cost_[col], + model->col_lower_[col], true, + model->integrality_[col] == HighsVarType::kInteger); markColDeleted(col); HighsInt coliter = colhead[col]; while (coliter != -1) { @@ -3836,9 +3962,10 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0) { - postsolve_stack.forcingColumn(col, getColumnVector(col), - model->col_cost_[col], - model->col_upper_[col], false); + postsolve_stack.forcingColumn( + col, getColumnVector(col), model->col_cost_[col], + model->col_upper_[col], false, + model->integrality_[col] == HighsVarType::kInteger); markColDeleted(col); HighsInt coliter = colhead[col]; while (coliter != -1) { @@ -3922,7 +4049,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, model->col_upper_[col] != kHighsInf) && model->col_upper_[col] - model->col_lower_[col] > 0.5) { // substitute with the bound that is smaller in magnitude and only - // suibstitute if bound is not large for an integer + // substitute if bound is not large for an integer if (std::abs(model->col_upper_[col]) > std::abs(model->col_lower_[col])) { if (std::abs(model->col_lower_[col]) < 1000.5) transformColumn(postsolve_stack, col, 1.0, model->col_lower_[col]); @@ -4012,7 +4139,7 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { // - fast presolve loop // - parallel rows and columns // - if (changes found) fast presolve loop - // - aggregator // add limit that catches many subsitutions but stops when + // - aggregator // add limit that catches many substitutions but stops when // many failures, do not run exhaustively as now // - if (changes found) start main loop from beginning // - primal and dual matrix sparsification @@ -4046,13 +4173,32 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { HighsInt numCol = model->num_col_ - numDeletedCols; HighsInt numRow = model->num_row_ - numDeletedRows; HighsInt numNonz = Avalue.size() - freeslots.size(); +#ifndef NDEBUG + std::string time_str = + " " + std::to_string(this->timer->read(run_clock)) + "s"; +#else + std::string time_str = + " " + std::to_string(int(this->timer->read(run_clock))) + "s"; +#endif highsLogUser(options->log_options, HighsLogType::kInfo, "%" HIGHSINT_FORMAT " rows, %" HIGHSINT_FORMAT - " cols, %" HIGHSINT_FORMAT " nonzeros\n", - numRow, numCol, numNonz); + " cols, %" HIGHSINT_FORMAT " nonzeros %s\n", + numRow, numCol, numNonz, time_str.c_str()); } }; + // Need to check for time-out in checkLimits. However, when + // presolve is called from the MIP solver timer->solve_clock is + // running, and when presolve is called before the LP solver, the + // HighsClock is running. So have to set run_clock accordingly. + assert(this->timer); + if (this->timer->runningRunHighsClock()) { + run_clock = timer->run_highs_clock; + } else { + assert(this->timer->running(timer->solve_clock)); + run_clock = timer->solve_clock; + } + HPRESOLVE_CHECKED_CALL(initialRowAndColPresolve(postsolve_stack)); HighsInt numParallelRowColCalls = 0; @@ -4067,6 +4213,9 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { bool domcolAfterProbingCalled = false; bool dependentEquationsCalled = mipsolver != nullptr; HighsInt lastPrintSize = kHighsIInf; + + // Start of main presolve loop + // while (true) { HighsInt currSize = model->num_col_ - numDeletedCols + model->num_row_ - numDeletedRows; @@ -4101,7 +4250,12 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { highsLogDev(options->log_options, HighsLogType::kInfo, "Sparsify removed %.1f%% of nonzeros\n", nzReduction); - fastPresolveLoop(postsolve_stack); + // #1710 exposes that this should not be + // + // fastPresolveLoop(postsolve_stack); + // + // but + HPRESOLVE_CHECKED_CALL(fastPresolveLoop(postsolve_stack)); } trySparsify = false; } @@ -4114,8 +4268,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { toCSC(model->a_matrix_.value_, model->a_matrix_.index_, model->a_matrix_.start_); - fromCSC(model->a_matrix_.value_, model->a_matrix_.index_, - model->a_matrix_.start_); + okFromCSC(model->a_matrix_.value_, model->a_matrix_.index_, + model->a_matrix_.start_); } storeCurrentProblemSize(); HPRESOLVE_CHECKED_CALL(detectParallelRowsAndCols(postsolve_stack)); @@ -4162,8 +4316,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { toCSC(model->a_matrix_.value_, model->a_matrix_.index_, model->a_matrix_.start_); - fromCSC(model->a_matrix_.value_, model->a_matrix_.index_, - model->a_matrix_.start_); + okFromCSC(model->a_matrix_.value_, model->a_matrix_.index_, + model->a_matrix_.start_); } storeCurrentProblemSize(); if (analysis_.allow_rule_[kPresolveRuleDependentEquations]) { @@ -4206,7 +4360,6 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { } HPresolve::Result HPresolve::checkLimits(HighsPostsolveStack& postsolve_stack) { - // todo: check timelimit size_t numreductions = postsolve_stack.numReductions(); bool debug_report = false; @@ -4250,11 +4403,13 @@ HPresolve::Result HPresolve::checkLimits(HighsPostsolveStack& postsolve_stack) { postsolve_stack.debug_prev_numreductions = numreductions; } - if (timer != nullptr && (numreductions & 1023u) == 0) { - if (timer->readRunHighsClock() >= options->time_limit) + if ((numreductions & 1023u) == 0) { + assert(timer); + assert(run_clock >= 0); + if (timer->read(run_clock) >= options->time_limit) { return Result::kStopped; + } } - return numreductions >= reductionLimit ? Result::kStopped : Result::kOk; } @@ -4282,24 +4437,33 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) { postsolve_stack.debug_prev_col_upper = 0; postsolve_stack.debug_prev_row_lower = 0; postsolve_stack.debug_prev_row_upper = 0; + // Presolve should only be called with a model that has a non-empty + // constraint matrix unless it has no rows + assert(model->a_matrix_.numNz() || model->num_row_ == 0); + auto reportReductions = [&]() { + if (options->presolve != kHighsOffString && + reductionLimit < kHighsSize_tInf) { + highsLogUser(options->log_options, HighsLogType::kInfo, + "Presolve performed %" PRId64 " of %" PRId64 + " permitted reductions\n", + postsolve_stack.numReductions(), reductionLimit); + } + }; switch (presolve(postsolve_stack)) { case Result::kStopped: case Result::kOk: break; case Result::kPrimalInfeasible: presolve_status_ = HighsPresolveStatus::kInfeasible; + reportReductions(); return HighsModelStatus::kInfeasible; case Result::kDualInfeasible: presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + reportReductions(); return HighsModelStatus::kUnboundedOrInfeasible; } + reportReductions(); - 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) { @@ -4437,7 +4601,7 @@ HPresolve::Result HPresolve::removeDependentEquations( matrix.index_.push_back(nonz.index()); } - // add entry for artifical rhs column + // add entry for artificial rhs column if (model->row_lower_[eq] != 0.0) { matrix.value_.push_back(model->row_lower_[eq]); matrix.index_.push_back(model->num_col_); @@ -4549,7 +4713,7 @@ HPresolve::Result HPresolve::removeDependentFreeCols( // matrix.index_.push_back(nonz.index()); // } // - // // add entry for artifical cost row + // // add entry for artificial cost row // if (model->col_cost_[col] != 0.0) { // matrix.value_.push_back(model->col_cost_[col]); // matrix.index_.push_back(model->num_row_); @@ -4599,8 +4763,6 @@ HPresolve::Result HPresolve::removeDependentFreeCols( HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { assert(analysis_.allow_rule_[kPresolveRuleAggregator]); - HighsInt numsubst = 0; - HighsInt numsubstint = 0; const bool logging_on = analysis_.logging_on_; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleAggregator); substitutionOpportunities.erase( @@ -4667,23 +4829,8 @@ HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { if (rowsize[row] == 2 || colsize[col] == 2) { double rhs; HighsPostsolveStack::RowType rowType; - if (model->row_lower_[row] == model->row_upper_[row]) { - rowType = HighsPostsolveStack::RowType::kEq; - rhs = model->row_upper_[row]; - } else if ((model->row_upper_[row] != kHighsInf && - implRowDualUpper[row] <= - options->dual_feasibility_tolerance)) { - rowType = HighsPostsolveStack::RowType::kLeq; - rhs = model->row_upper_[row]; - changeRowDualUpper(row, kHighsInf); - } else { - rowType = HighsPostsolveStack::RowType::kGeq; - rhs = model->row_lower_[row]; - changeRowDualLower(row, -kHighsInf); - } + dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, true); - ++numsubst; - if (model->integrality_[col] == HighsVarType::kInteger) ++numsubstint; storeRow(row); postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], @@ -4728,23 +4875,9 @@ HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { } nfail = 0; - ++numsubst; - if (model->integrality_[col] == HighsVarType::kInteger) ++numsubstint; double rhs; HighsPostsolveStack::RowType rowType; - if (model->row_lower_[row] == model->row_upper_[row]) { - rowType = HighsPostsolveStack::RowType::kEq; - rhs = model->row_upper_[row]; - } else if ((model->row_upper_[row] != kHighsInf && - implRowDualUpper[row] <= options->dual_feasibility_tolerance)) { - rowType = HighsPostsolveStack::RowType::kLeq; - rhs = model->row_upper_[row]; - changeRowDualUpper(row, kHighsInf); - } else { - rowType = HighsPostsolveStack::RowType::kGeq; - rhs = model->row_lower_[row]; - changeRowDualLower(row, -kHighsInf); - } + dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, true); postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], rowType, getStoredRow(), @@ -4792,14 +4925,7 @@ void HPresolve::substitute(HighsInt substcol, HighsInt staycol, double offset, // debugPrintRow(colrow); // check if this is an equation row and it now has a different size - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); } // substitute column in the objective function @@ -4846,14 +4972,7 @@ void HPresolve::fixColToLower(HighsPostsolveStack& postsolve_stack, unlink(colpos); - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); } model->offset_ += model->col_cost_[col] * fixval; @@ -4893,14 +5012,7 @@ void HPresolve::fixColToUpper(HighsPostsolveStack& postsolve_stack, unlink(colpos); - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); } model->offset_ += model->col_cost_[col] * fixval; @@ -4929,14 +5041,7 @@ void HPresolve::fixColToZero(HighsPostsolveStack& postsolve_stack, unlink(colpos); - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); } model->col_cost_[col] = 0; @@ -4980,14 +5085,7 @@ void HPresolve::removeFixedCol(HighsInt col) { unlink(colpos); - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); } model->offset_ += model->col_cost_[col] * fixval; @@ -5095,8 +5193,7 @@ HighsInt HPresolve::strengthenInequalities() { // do not run on very dense rows as this could get expensive if (rowsize[row] > - std::max(HighsInt{1000}, - HighsInt(0.05 * (model->num_col_ - numDeletedCols)))) + std::max(HighsInt{1000}, (model->num_col_ - numDeletedCols) / 20)) continue; // printf("strengthening knapsack of %" HIGHSINT_FORMAT " vars\n", @@ -5346,7 +5443,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( colHashes.assign(colsize.begin(), colsize.end()); // Step 1: Determine scales for rows and columns and remove column singletons - // from the intial row hashes which are initialized with the row sizes + // from the initial row hashes which are initialized with the row sizes for (HighsInt i = 0; i != nnz; ++i) { if (Avalue[i] == 0.0) continue; assert(!colDeleted[Acol[i]]); @@ -5360,7 +5457,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( double absRowMax = std::abs(rowMax[Arow[i]].first); // among the largest values which are equal in tolerance - // we use the nonzero with the smalles row/column index for the column/row + // we use the nonzero with the smallest row/column index for the column/row // scale so that we ensure that duplicate rows/columns are scaled to have // the same sign if (absVal >= absRowMax - options->small_matrix_value) { @@ -5405,9 +5502,6 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( // computed hash values. Whenever a bucket already contains a row/column, // check if we can apply a (nearly) parallel row reduction or a // parallel/dominated column reduction. - HighsInt numRowBuckets = 0; - HighsInt numColBuckets = 0; - std::unordered_multimap buckets; const bool debug_report = false; @@ -5423,7 +5517,6 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt delCol = -1; HighsInt parallelColCandidate = -2; - if (it == buckets.end()) ++numColBuckets; while (it != buckets.end() && it->first == colHashes[i]) { parallelColCandidate = it->second; last = it++; @@ -5466,7 +5559,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( auto colUpperInf = [&]() { if (!checkColImplBounds) return false; if (mipsolver == nullptr) { - // for LP we check strict reduncancy of the bounds as otherwise dual + // for LP we check strict redundancy of the bounds as otherwise dual // postsolve might fail when the bound is used in the optimal solution return colScale > 0 ? model->col_upper_[col] == kHighsInf || implColUpper[col] < @@ -5477,12 +5570,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } else { // for MIP we do not need dual postsolve so the reduction is valid if // the bound is weakly redundant - return colScale > 0 ? model->col_upper_[col] == kHighsInf || - implColUpper[col] <= - model->col_upper_[col] + primal_feastol - : model->col_lower_[col] == -kHighsInf || - implColLower[col] >= - model->col_lower_[col] - primal_feastol; + return colScale > 0 ? isUpperImplied(col) : isLowerImplied(col); } }; @@ -5496,12 +5584,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( implColUpper[col] < model->col_upper_[col] - primal_feastol; } else { - return colScale > 0 ? model->col_lower_[col] == -kHighsInf || - implColLower[col] >= - model->col_lower_[col] - primal_feastol - : model->col_upper_[col] == kHighsInf || - implColUpper[col] <= - model->col_upper_[col] + primal_feastol; + return colScale > 0 ? isLowerImplied(col) : isUpperImplied(col); } }; @@ -5512,9 +5595,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( implColUpper[duplicateCol] < model->col_upper_[duplicateCol] - primal_feastol; } else { - return model->col_upper_[duplicateCol] == kHighsInf || - implColUpper[duplicateCol] <= - model->col_upper_[duplicateCol] + primal_feastol; + return isUpperImplied(duplicateCol); } }; @@ -5525,9 +5606,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( implColLower[duplicateCol] > model->col_lower_[duplicateCol] + primal_feastol; } else { - return model->col_lower_[duplicateCol] == -kHighsInf || - implColLower[duplicateCol] >= - model->col_lower_[duplicateCol] - primal_feastol; + return isLowerImplied(duplicateCol); } }; @@ -5825,7 +5904,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt colpos = coliter; HighsInt colrow = Arow[coliter]; - // if an an integer column was merged into a continuous one make + // if an integer column was merged into a continuous one make // sure to update the integral rowsize if (rowsizeIntReduction) { assert(rowsizeIntReduction == 1); @@ -5835,15 +5914,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( unlink(colpos); - if (model->row_lower_[colrow] == model->row_upper_[colrow] && - eqiters[colrow] != equations.end() && - eqiters[colrow]->first != rowsize[colrow]) { - // if that is the case reinsert it into the equation set that is - // ordered by sparsity - equations.erase(eqiters[colrow]); - eqiters[colrow] = - equations.emplace(rowsize[colrow], colrow).first; - } + reinsertEquation(colrow); } // set cost to zero model->col_cost_[duplicateCol] = 0; @@ -5892,7 +5963,6 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( if (rowsize[i] <= 1 || (rowsize[i] == 2 && model->row_lower_[i] == model->row_upper_[i])) { HPRESOLVE_CHECKED_CALL(rowPresolve(postsolve_stack, i)); - ++numRowBuckets; continue; } auto it = buckets.find(rowHashes[i]); @@ -5907,10 +5977,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( continue; #endif HighsInt delRow = -1; - if (it == buckets.end()) - ++numRowBuckets; - else - storeRow(i); + if (it != buckets.end()) storeRow(i); while (it != buckets.end() && it->first == rowHashes[i]) { HighsInt parallelRowCand = it->second; last = it++; @@ -6212,7 +6279,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { postsolve_stack.initializeIndexMaps(lp.num_row_, lp.num_col_); { HPresolve presolve; - presolve.setInput(model, options); + presolve.okSetInput(model, options, options.presolve_reduction_limit); // presolve.setReductionLimit(1622017); if (presolve.run(postsolve_stack) != HighsModelStatus::kNotset) return; Highs highs; @@ -6229,7 +6296,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { basis = reducedbasis; postsolve_stack.undo(options, sol, basis); refineBasis(lp, sol, basis); - calculateRowValues(model, sol); + calculateRowValuesQuad(model, sol); #if 0 Highs highs; highs.passModel(model); @@ -6276,14 +6343,14 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { { HPresolve presolve; - presolve.setInput(model, options); + presolve.okSetInput(model, options, options.presolve_reduction_limit); presolve.computeIntermediateMatrix(flagRow, flagCol, reductionLim); } #if 1 model = lp; model.integrality_.assign(lp.num_col_, HighsVarType::kContinuous); HPresolve presolve; - presolve.setInput(model, options); + presolve.okSetInput(model, options, options.presolve_reduction_limit); HighsPostsolveStack tmp; tmp.initializeIndexMaps(model.num_row_, model.num_col_); presolve.setReductionLimit(reductionLim); @@ -6312,7 +6379,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { temp_basis.row_status[i] = basis.row_status[tmp.getOrigRowIndex(i)]; } temp_sol.row_value.resize(model.num_row_); - calculateRowValues(model, sol); + calculateRowValuesQuad(model, sol); temp_basis.valid = true; refineBasis(model, temp_sol, temp_basis); Highs highs; @@ -6340,7 +6407,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { postsolve_stack.undoUntil(options, flagRow, flagCol, sol, basis, reductionLim); - calculateRowValues(model, sol); + calculateRowValuesQuad(model, sol); kktinfo = dev_kkt_check::initInfo(); checkResult = dev_kkt_check::checkKkt(state, kktinfo); checkResult = checkResult && kktinfo.pass_bfs; @@ -6589,14 +6656,7 @@ HPresolve::Result HPresolve::sparsify(HighsPostsolveStack& postsolve_stack) { for (HighsInt pos : rowpositions) addToMatrix(row, Acol[pos], scale * Avalue[pos]); - if (model->row_lower_[row] == model->row_upper_[row] && - eqiters[row] != equations.end() && - eqiters[row]->first != rowsize[row]) { - // if that is the case reinsert it into the equation set that is ordered - // by sparsity - equations.erase(eqiters[row]); - eqiters[row] = equations.emplace(rowsize[row], row).first; - } + reinsertEquation(row); } HPRESOLVE_CHECKED_CALL(checkLimits(postsolve_stack)); diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index cd5d36bc98..fe21860ff0 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -43,6 +43,7 @@ class HPresolve { HighsTimer* timer; HighsMipSolver* mipsolver = nullptr; double primal_feastol; + HighsInt run_clock = -1; // triplet storage std::vector Avalue; @@ -83,6 +84,8 @@ class HPresolve { std::vector implRowDualUpper; std::vector rowDualLowerSource; std::vector rowDualUpperSource; + std::vector> colImplSourceByRow; + std::vector> implRowDualSourceByCol; // implied bounds on values of primal and dual rows computed from the bounds // of primal and dual variables @@ -155,6 +158,10 @@ class HPresolve { void updateColImpliedBounds(HighsInt row, HighsInt col, double val); + void recomputeColImpliedBounds(HighsInt row); + + void recomputeRowDualImpliedBounds(HighsInt col); + void updateRowDualImpliedBounds(HighsInt row, HighsInt col, double val); bool rowCoefficientsIntegral(HighsInt row, double scale) const; @@ -163,6 +170,10 @@ class HPresolve { bool isDualImpliedFree(HighsInt row) const; + void dualImpliedFreeGetRhsAndRowType(HighsInt row, double& rhs, + HighsPostsolveStack::RowType& rowType, + bool relaxRowDualBounds = false); + bool isImpliedIntegral(HighsInt col); bool isImpliedInteger(HighsInt col); @@ -176,19 +187,21 @@ class HPresolve { bool checkFillin(HighsHashTable& fillinCache, HighsInt row, HighsInt col); + void reinsertEquation(HighsInt row); + #ifndef NDEBUG void debugPrintRow(HighsPostsolveStack& postsolve_stack, HighsInt row); #endif HighsInt findNonzero(HighsInt row, HighsInt col); - void fromCSC(const std::vector& Aval, - const std::vector& Aindex, - const std::vector& Astart); + bool okFromCSC(const std::vector& Aval, + const std::vector& Aindex, + const std::vector& Astart); - void fromCSR(const std::vector& ARval, - const std::vector& ARindex, - const std::vector& ARstart); + bool okFromCSR(const std::vector& ARval, + const std::vector& ARindex, + const std::vector& ARstart); void toCSC(std::vector& Aval, std::vector& Aindex, std::vector& Astart); @@ -259,11 +272,13 @@ class HPresolve { public: // for LP presolve - void setInput(HighsLp& model_, const HighsOptions& options_, - HighsTimer* timer = nullptr); + bool okSetInput(HighsLp& model_, const HighsOptions& options_, + const HighsInt presolve_reduction_limit, + HighsTimer* timer = nullptr); // for MIP presolve - void setInput(HighsMipSolver& mipsolver); + bool okSetInput(HighsMipSolver& mipsolver, + const HighsInt presolve_reduction_limit); void setReductionLimit(size_t reductionLimit) { this->reductionLimit = reductionLimit; @@ -279,7 +294,8 @@ class HPresolve { Result dominatedColumns(HighsPostsolveStack& postsolve_stack); - Result doubletonEq(HighsPostsolveStack& postsolve_stack, HighsInt row); + Result doubletonEq(HighsPostsolveStack& postsolve_stack, HighsInt row, + HighsPostsolveStack::RowType rowType); Result singletonRow(HighsPostsolveStack& postsolve_stack, HighsInt row); @@ -291,9 +307,6 @@ class HPresolve { Result colPresolve(HighsPostsolveStack& postsolve_stack, HighsInt col); - Result solveOneRowComponent(HighsPostsolveStack& postsolve_stack, - HighsInt row); - Result initialRowAndColPresolve(HighsPostsolveStack& postsolve_stack); HighsModelStatus run(HighsPostsolveStack& postsolve_stack); diff --git a/src/presolve/HPresolveAnalysis.cpp b/src/presolve/HPresolveAnalysis.cpp index 16f780a082..8812e41400 100644 --- a/src/presolve/HPresolveAnalysis.cpp +++ b/src/presolve/HPresolveAnalysis.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -143,7 +143,7 @@ void HPresolveAnalysis::stopPresolveRuleLog(const HighsInt rule_type) { presolve_log_.rule[rule_type].col_removed += num_removed_col; presolve_log_.rule[rule_type].row_removed += num_removed_row; - // Set the rule type to be illegal to idicate that stop has been + // Set the rule type to be illegal to indicate that stop has been // called, and update the record of num_deleted_rows/cols log_rule_type_ = kPresolveRuleIllegal; num_deleted_rows0_ = *numDeletedRows; diff --git a/src/presolve/HPresolveAnalysis.h b/src/presolve/HPresolveAnalysis.h index f370bf7034..9857ce1249 100644 --- a/src/presolve/HPresolveAnalysis.h +++ b/src/presolve/HPresolveAnalysis.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 0196514612..bd73f128e4 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -72,12 +72,25 @@ void HighsPostsolveStack::LinearTransform::transformToPresolvedSpace( primalSol[col] /= scale; } +static HighsBasisStatus computeRowStatus(double dual, + HighsPostsolveStack::RowType rowType) { + if (rowType == HighsPostsolveStack::RowType::kEq) + return dual < 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower; + else if (rowType == HighsPostsolveStack::RowType::kGeq) + return HighsBasisStatus::kLower; + else + return HighsBasisStatus::kUpper; +} + void HighsPostsolveStack::FreeColSubstitution::undo( const HighsOptions& options, const std::vector& rowValues, const std::vector& colValues, HighsSolution& solution, HighsBasis& basis) { - double colCoef = 0; + // a (removed) cut may have been used in this reduction. + bool isModelRow = static_cast(row) < solution.row_value.size(); + // compute primal values + double colCoef = 0; HighsCDouble rowValue = 0; for (const auto& rowVal : rowValues) { if (rowVal.index == col) @@ -88,34 +101,53 @@ 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]); + if (isModelRow) + solution.row_value[row] = + double(rowValue + colCoef * solution.col_value[col]); solution.col_value[col] = double((rhs - rowValue) / colCoef); // if no dual values requested, return here if (!solution.dual_valid) return; // compute the row dual value such that reduced cost of basic column is 0 - solution.row_dual[row] = 0; - HighsCDouble dualval = colCost; - for (const auto& colVal : colValues) - dualval -= colVal.value * solution.row_dual[colVal.index]; + if (isModelRow) { + solution.row_dual[row] = 0; + HighsCDouble dualval = colCost; + for (const auto& colVal : colValues) { + if (static_cast(colVal.index) < solution.row_dual.size()) + dualval -= colVal.value * solution.row_dual[colVal.index]; + } + solution.row_dual[row] = double(dualval / colCoef); + } solution.col_dual[col] = 0; - solution.row_dual[row] = double(dualval / colCoef); // set basis status if necessary if (!basis.valid) return; basis.col_status[col] = HighsBasisStatus::kBasic; - if (rowType == RowType::kEq) - basis.row_status[row] = solution.row_dual[row] < 0 - ? HighsBasisStatus::kUpper - : HighsBasisStatus::kLower; - else if (rowType == RowType::kGeq) - basis.row_status[row] = HighsBasisStatus::kLower; + if (isModelRow) + basis.row_status[row] = computeRowStatus(solution.row_dual[row], rowType); +} + +static HighsBasisStatus computeStatus(double dual, HighsBasisStatus& status, + double dual_feasibility_tolerance) { + if (dual > dual_feasibility_tolerance) + status = HighsBasisStatus::kLower; + else if (dual < -dual_feasibility_tolerance) + status = HighsBasisStatus::kUpper; + + return status; +} + +static HighsBasisStatus computeStatus(double dual, + double dual_feasibility_tolerance) { + if (dual > dual_feasibility_tolerance) + return HighsBasisStatus::kLower; + else if (dual < -dual_feasibility_tolerance) + return HighsBasisStatus::kUpper; else - basis.row_status[row] = HighsBasisStatus::kUpper; + return HighsBasisStatus::kBasic; } void HighsPostsolveStack::DoubletonEquation::undo( @@ -129,39 +161,34 @@ void HighsPostsolveStack::DoubletonEquation::undo( // can only do primal postsolve, stop here if (row == -1 || !solution.dual_valid) return; - HighsBasisStatus colStatus; - - if (basis.valid) { - if (solution.col_dual[col] > options.dual_feasibility_tolerance) - basis.col_status[col] = HighsBasisStatus::kLower; - else if (solution.col_dual[col] < -options.dual_feasibility_tolerance) - basis.col_status[col] = HighsBasisStatus::kUpper; + const HighsBasisStatus colStatus = + !basis.valid + ? computeStatus(solution.col_dual[col], + options.dual_feasibility_tolerance) + : computeStatus(solution.col_dual[col], basis.col_status[col], + options.dual_feasibility_tolerance); - colStatus = basis.col_status[col]; - } else { - if (solution.col_dual[col] > options.dual_feasibility_tolerance) - colStatus = HighsBasisStatus::kLower; - else if (solution.col_dual[col] < -options.dual_feasibility_tolerance) - colStatus = HighsBasisStatus::kUpper; - else - colStatus = HighsBasisStatus::kBasic; - } + // a (removed) cut may have been used in this reduction. + bool isModelRow = static_cast(row) < solution.row_value.size(); // compute the current dual values of the row and the substituted column // before deciding on which column becomes basic - // for each entry in a row i of the substituted column we added the doubleton - // equation row with scale -a_i/substCoef. Therefore the dual multiplier of - // this row i implicitly increases the dual multiplier of this doubleton - // equation row with that scale. + // for each entry in a row i of the substituted column we added the + // doubleton equation row with scale -a_i/substCoef. Therefore the dual + // multiplier of this row i implicitly increases the dual multiplier of this + // doubleton equation row with that scale. HighsCDouble rowDual = 0.0; - solution.row_dual[row] = 0; - for (const auto& colVal : colValues) - rowDual -= colVal.value * solution.row_dual[colVal.index]; - rowDual /= coefSubst; - solution.row_dual[row] = double(rowDual); - - // the equation was also added to the objective, so the current values need to - // be changed + if (isModelRow) { + solution.row_dual[row] = 0; + for (const auto& colVal : colValues) { + if (static_cast(colVal.index) < solution.row_dual.size()) + rowDual -= colVal.value * solution.row_dual[colVal.index]; + } + rowDual /= coefSubst; + solution.row_dual[row] = double(rowDual); + } + // the equation was also added to the objective, so the current values need + // to be changed solution.col_dual[colSubst] = substCost; solution.col_dual[col] += substCost * coef / coefSubst; @@ -171,7 +198,7 @@ void HighsPostsolveStack::DoubletonEquation::undo( // so alter the dual multiplier of the row to make the dual multiplier of // column zero double rowDualDelta = solution.col_dual[col] / coef; - solution.row_dual[row] = double(rowDual + rowDualDelta); + if (isModelRow) solution.row_dual[row] = double(rowDual + rowDualDelta); solution.col_dual[col] = 0.0; solution.col_dual[colSubst] = double( HighsCDouble(solution.col_dual[colSubst]) - rowDualDelta * coefSubst); @@ -187,10 +214,10 @@ void HighsPostsolveStack::DoubletonEquation::undo( basis.col_status[col] = HighsBasisStatus::kBasic; } } else { - // otherwise make the reduced cost of the subsituted column zero and make + // otherwise make the reduced cost of the substituted column zero and make // that column basic double rowDualDelta = solution.col_dual[colSubst] / coefSubst; - solution.row_dual[row] = double(rowDual + rowDualDelta); + if (isModelRow) solution.row_dual[row] = double(rowDual + rowDualDelta); solution.col_dual[colSubst] = 0.0; solution.col_dual[col] = double(HighsCDouble(solution.col_dual[col]) - rowDualDelta * coef); @@ -199,15 +226,18 @@ void HighsPostsolveStack::DoubletonEquation::undo( if (!basis.valid) return; - if (solution.row_dual[row] < 0) - basis.row_status[row] = HighsBasisStatus::kLower; - else - basis.row_status[row] = HighsBasisStatus::kUpper; + if (isModelRow) + basis.row_status[row] = computeRowStatus(solution.row_dual[row], rowType); } void HighsPostsolveStack::EqualityRowAddition::undo( const HighsOptions& options, const std::vector& eqRowValues, HighsSolution& solution, HighsBasis& basis) const { + // (removed) cuts may have been used in this reduction. + if (static_cast(row) >= solution.row_value.size() || + static_cast(addedEqRow) >= solution.row_value.size()) + return; + // nothing more to do if the row is zero in the dual solution or there is // no dual solution if (!solution.dual_valid || solution.row_dual[row] == 0.0) return; @@ -225,18 +255,22 @@ void HighsPostsolveStack::EqualityRowAdditions::undo( const HighsOptions& options, const std::vector& eqRowValues, const std::vector& targetRows, HighsSolution& solution, HighsBasis& basis) const { + // a (removed) cut may have been used in this reduction. + if (static_cast(addedEqRow) >= solution.row_value.size()) return; + // nothing more to do if the row is zero in the dual solution or there is // no dual solution if (!solution.dual_valid) return; // the dual multiplier of the rows where the eq row was added implicitly - // increases the dual multiplier of the equation with the scale that was used - // for adding the equation + // increases the dual multiplier of the equation with the scale that was + // used for adding the equation HighsCDouble eqRowDual = solution.row_dual[addedEqRow]; - for (const auto& targetRow : targetRows) - eqRowDual += - HighsCDouble(targetRow.value) * solution.row_dual[targetRow.index]; - + for (const auto& targetRow : targetRows) { + if (static_cast(targetRow.index) < solution.row_dual.size()) + eqRowDual += + HighsCDouble(targetRow.value) * solution.row_dual[targetRow.index]; + } solution.row_dual[addedEqRow] = double(eqRowDual); assert(!basis.valid); @@ -251,32 +285,36 @@ void HighsPostsolveStack::ForcingColumn::undo( HighsInt debug_num_use_row_value = 0; const bool debug_report = false; - if (atInfiniteUpper) { - // choose largest value as then all rows are feasible + + auto computeColVal = [&](HighsInt direction) { + // choose solution value that makes all rows 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; - colValFromNonbasicRow = colValFromRow; - nonbasicRowStatus = colVal.value > 0 ? HighsBasisStatus::kLower - : HighsBasisStatus::kUpper; + if (static_cast(colVal.index) < solution.row_value.size()) { + double colValFromRow = solution.row_value[colVal.index] / colVal.value; + if (direction * colValFromRow > direction * colValFromNonbasicRow) { + nonbasicRow = colVal.index; + colValFromNonbasicRow = colValFromRow; + nonbasicRowStatus = direction * colVal.value > 0 + ? HighsBasisStatus::kLower + : HighsBasisStatus::kUpper; + } } } + // round solution value if column is integer-constrained + if (nonbasicRow != -1 && colIntegral) + colValFromNonbasicRow = + direction * std::ceil(direction * colValFromNonbasicRow - + options.mip_feasibility_tolerance); + }; + + if (atInfiniteUpper) { + // choose largest value as then all rows are feasible + computeColVal(HighsInt{1}); } 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; - colValFromNonbasicRow = colValFromRow; - nonbasicRowStatus = colVal.value > 0 ? HighsBasisStatus::kUpper - : HighsBasisStatus::kLower; - } - } + computeColVal(HighsInt{-1}); } if (debug_num_use_row_value && debug_report) { printf( @@ -305,8 +343,11 @@ void HighsPostsolveStack::ForcingColumn::undo( void HighsPostsolveStack::ForcingColumnRemovedRow::undo( const HighsOptions& options, const std::vector& rowValues, HighsSolution& solution, HighsBasis& basis) const { - // we use the row value as storage for the scaled value implied on the column - // dual + // a (removed) cut may have been used in this reduction. + if (static_cast(row) >= solution.row_value.size()) return; + + // we use the row value as storage for the scaled value implied on the + // column dual HighsCDouble val = rhs; for (const auto& rowVal : rowValues) val -= rowVal.value * solution.col_value[rowVal.index]; @@ -321,65 +362,61 @@ void HighsPostsolveStack::ForcingColumnRemovedRow::undo( void HighsPostsolveStack::SingletonRow::undo(const HighsOptions& options, HighsSolution& solution, HighsBasis& basis) const { + // a (removed) cut may have been used in this reduction. + bool isModelRow = static_cast(row) < solution.row_value.size(); + // nothing to do if the rows dual value is zero in the dual solution or // there is no dual solution if (!solution.dual_valid) return; - HighsBasisStatus colStatus; - - if (basis.valid) { - if (solution.col_dual[col] > options.dual_feasibility_tolerance) - basis.col_status[col] = HighsBasisStatus::kLower; - else if (solution.col_dual[col] < -options.dual_feasibility_tolerance) - basis.col_status[col] = HighsBasisStatus::kUpper; - - colStatus = basis.col_status[col]; - } else { - if (solution.col_dual[col] > options.dual_feasibility_tolerance) - colStatus = HighsBasisStatus::kLower; - else if (solution.col_dual[col] < -options.dual_feasibility_tolerance) - colStatus = HighsBasisStatus::kUpper; - else - colStatus = HighsBasisStatus::kBasic; - } + const HighsBasisStatus colStatus = + !basis.valid + ? computeStatus(solution.col_dual[col], + options.dual_feasibility_tolerance) + : computeStatus(solution.col_dual[col], basis.col_status[col], + options.dual_feasibility_tolerance); if ((!colLowerTightened || colStatus != HighsBasisStatus::kLower) && (!colUpperTightened || colStatus != HighsBasisStatus::kUpper)) { // the tightened bound is not used in the basic solution // hence we simply make the row basic and give it a dual multiplier of 0 - if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic; - solution.row_dual[row] = 0; + if (isModelRow) { + if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic; + solution.row_dual[row] = 0; + } return; } // choose the row dual value such that the columns reduced cost becomes // zero - solution.row_dual[row] = solution.col_dual[col] / coef; + if (isModelRow) solution.row_dual[row] = solution.col_dual[col] / coef; solution.col_dual[col] = 0; if (!basis.valid) return; - switch (colStatus) { - case HighsBasisStatus::kLower: - assert(colLowerTightened); - if (coef > 0) - // tightened lower bound comes from row lower bound - basis.row_status[row] = HighsBasisStatus::kLower; - else - // tightened lower bound comes from row upper bound - basis.row_status[row] = HighsBasisStatus::kUpper; - - break; - case HighsBasisStatus::kUpper: - if (coef > 0) - // tightened upper bound comes from row lower bound - basis.row_status[row] = HighsBasisStatus::kUpper; - else - // tightened lower bound comes from row upper bound - basis.row_status[row] = HighsBasisStatus::kLower; - break; - default: - assert(false); + if (isModelRow) { + switch (colStatus) { + case HighsBasisStatus::kLower: + assert(colLowerTightened); + if (coef > 0) + // tightened lower bound comes from row lower bound + basis.row_status[row] = HighsBasisStatus::kLower; + else + // tightened lower bound comes from row upper bound + basis.row_status[row] = HighsBasisStatus::kUpper; + + break; + case HighsBasisStatus::kUpper: + if (coef > 0) + // tightened upper bound comes from row lower bound + basis.row_status[row] = HighsBasisStatus::kUpper; + else + // tightened lower bound comes from row upper bound + basis.row_status[row] = HighsBasisStatus::kLower; + break; + default: + assert(false); + } } // column becomes basic @@ -400,8 +437,8 @@ void HighsPostsolveStack::FixedCol::undo(const HighsOptions& options, HighsCDouble reducedCost = colCost; for (const auto& colVal : colValues) { - assert((HighsInt)solution.row_dual.size() > colVal.index); - reducedCost -= colVal.value * solution.row_dual[colVal.index]; + if (static_cast(colVal.index) < solution.row_dual.size()) + reducedCost -= colVal.value * solution.row_dual[colVal.index]; } solution.col_dual[col] = double(reducedCost); @@ -419,6 +456,9 @@ void HighsPostsolveStack::FixedCol::undo(const HighsOptions& options, void HighsPostsolveStack::RedundantRow::undo(const HighsOptions& options, HighsSolution& solution, HighsBasis& basis) const { + // a (removed) cut may have been used in this reduction. + if (static_cast(row) >= solution.row_value.size()) return; + // set row dual to zero if dual solution requested if (!solution.dual_valid) return; @@ -435,34 +475,21 @@ void HighsPostsolveStack::ForcingRow::undo( // compute the row dual multiplier and determine the new basic column HighsInt basicCol = -1; double dualDelta = 0; - if (rowType == RowType::kLeq) { - for (const auto& rowVal : rowValues) { - double colDual = - solution.col_dual[rowVal.index] - rowVal.value * dualDelta; - if (colDual * rowVal.value < 0) { - // column is dual infeasible, decrease the row dual such that its - // reduced cost become zero and remember this column as the new basic - // column for this row - dualDelta = solution.col_dual[rowVal.index] / rowVal.value; - basicCol = rowVal.index; - } - } - } else { - for (const auto& rowVal : rowValues) { - double colDual = - solution.col_dual[rowVal.index] - rowVal.value * dualDelta; - if (colDual * rowVal.value > 0) { - // column is dual infeasible, decrease the row dual such that its - // reduced cost become zero and remember this column as the new basic - // column for this row - dualDelta = solution.col_dual[rowVal.index] / rowVal.value; - basicCol = rowVal.index; - } + HighsInt direction = rowType == RowType::kLeq ? 1 : -1; + for (const auto& rowVal : rowValues) { + double colDual = solution.col_dual[rowVal.index] - rowVal.value * dualDelta; + if (direction * colDual * rowVal.value < 0) { + // column is dual infeasible, decrease the row dual such that its + // reduced cost become zero and remember this column as the new basic + // column for this row + dualDelta = solution.col_dual[rowVal.index] / rowVal.value; + basicCol = rowVal.index; } } if (basicCol != -1) { - solution.row_dual[row] = solution.row_dual[row] + dualDelta; + bool isModelRow = static_cast(row) < solution.row_dual.size(); + if (isModelRow) solution.row_dual[row] = solution.row_dual[row] + dualDelta; for (const auto& rowVal : rowValues) { solution.col_dual[rowVal.index] = double(solution.col_dual[rowVal.index] - @@ -471,9 +498,10 @@ void HighsPostsolveStack::ForcingRow::undo( solution.col_dual[basicCol] = 0; if (basis.valid) { - basis.row_status[row] = - (rowType == RowType::kGeq ? HighsBasisStatus::kLower - : HighsBasisStatus::kUpper); + if (isModelRow) + basis.row_status[row] = + (rowType == RowType::kGeq ? HighsBasisStatus::kLower + : HighsBasisStatus::kUpper); basis.col_status[basicCol] = HighsBasisStatus::kBasic; } @@ -483,32 +511,50 @@ void HighsPostsolveStack::ForcingRow::undo( void HighsPostsolveStack::DuplicateRow::undo(const HighsOptions& options, HighsSolution& solution, HighsBasis& basis) const { + // (removed) cuts may have been used in this reduction. + if (static_cast(row) >= solution.row_value.size()) return; + bool duplicateIsModelRow = + static_cast(duplicateRow) < solution.row_value.size(); + if (!solution.dual_valid) return; if (!rowUpperTightened && !rowLowerTightened) { // simple case of row2 being redundant, in which case it just gets a // dual multiplier of 0 and is made basic - solution.row_dual[duplicateRow] = 0.0; - if (basis.valid) basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; + if (duplicateIsModelRow) { + solution.row_dual[duplicateRow] = 0.0; + if (basis.valid) + basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; + } return; } - HighsBasisStatus rowStatus; + const HighsBasisStatus rowStatus = + !basis.valid + ? computeStatus(solution.row_dual[row], + options.dual_feasibility_tolerance) + : computeStatus(solution.row_dual[row], basis.row_status[row], + options.dual_feasibility_tolerance); - if (basis.valid) { - if (solution.row_dual[row] < -options.dual_feasibility_tolerance) - basis.row_status[row] = HighsBasisStatus::kUpper; - else if (solution.row_dual[row] > options.dual_feasibility_tolerance) - basis.row_status[row] = HighsBasisStatus::kLower; - - rowStatus = basis.row_status[row]; - } else { - if (solution.row_dual[row] < -options.dual_feasibility_tolerance) - rowStatus = HighsBasisStatus::kUpper; - else if (solution.row_dual[row] > options.dual_feasibility_tolerance) - rowStatus = HighsBasisStatus::kLower; - else - rowStatus = HighsBasisStatus::kBasic; - } + auto computeRowDualAndStatus = [&](bool tighened) { + if (tighened) { + if (duplicateIsModelRow) { + solution.row_dual[duplicateRow] = + solution.row_dual[row] / duplicateRowScale; + if (basis.valid) { + if (duplicateRowScale > 0) + basis.row_status[duplicateRow] = HighsBasisStatus::kUpper; + else + basis.row_status[duplicateRow] = HighsBasisStatus::kLower; + } + } + solution.row_dual[row] = 0.0; + if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic; + } else if (duplicateIsModelRow) { + solution.row_dual[duplicateRow] = 0.0; + if (basis.valid) + basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; + } + }; // at least one bound of the row was tightened by using the bound of the // scaled parallel row, hence we might need to make the parallel row @@ -517,48 +563,20 @@ void HighsPostsolveStack::DuplicateRow::undo(const HighsOptions& options, switch (rowStatus) { case HighsBasisStatus::kBasic: // if row is basic the parallel row is also basic - solution.row_dual[duplicateRow] = 0.0; - if (basis.valid) - basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; + if (duplicateIsModelRow) { + solution.row_dual[duplicateRow] = 0.0; + if (basis.valid) + basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; + } break; case HighsBasisStatus::kUpper: // if row sits on its upper bound, and the row upper bound was // tightened using the parallel row we make the row basic and // transfer its dual value to the parallel row with the proper scale - if (rowUpperTightened) { - solution.row_dual[duplicateRow] = - solution.row_dual[row] / duplicateRowScale; - solution.row_dual[row] = 0.0; - if (basis.valid) { - basis.row_status[row] = HighsBasisStatus::kBasic; - if (duplicateRowScale > 0) - basis.row_status[duplicateRow] = HighsBasisStatus::kUpper; - else - basis.row_status[duplicateRow] = HighsBasisStatus::kLower; - } - } else { - solution.row_dual[duplicateRow] = 0.0; - if (basis.valid) - basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; - } + computeRowDualAndStatus(rowUpperTightened); break; case HighsBasisStatus::kLower: - if (rowLowerTightened) { - solution.row_dual[duplicateRow] = - solution.row_dual[row] / duplicateRowScale; - solution.row_dual[row] = 0.0; - if (basis.valid) { - basis.row_status[row] = HighsBasisStatus::kBasic; - if (duplicateRowScale > 0) - basis.row_status[duplicateRow] = HighsBasisStatus::kUpper; - else - basis.row_status[duplicateRow] = HighsBasisStatus::kLower; - } - } else { - solution.row_dual[duplicateRow] = 0.0; - if (basis.valid) - basis.row_status[duplicateRow] = HighsBasisStatus::kBasic; - } + computeRowDualAndStatus(rowLowerTightened); break; default: assert(false); @@ -828,7 +846,8 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, basis.col_status[col] = HighsBasisStatus::kNonbasic; if (debug_report) printf( - "When demerging, neither col nor duplicateCol can be nonbasic\n"); + "When demerging, neither col nor duplicateCol can be " + "nonbasic\n"); if (kAllowDeveloperAssert) assert(666 == 999); } } @@ -856,7 +875,7 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( // 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 + // 1+a]. For this to be a continuous sequence of integers, we must // have a <= 2. // // * If x is integer and y is continuous: @@ -886,10 +905,12 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( 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_lo = x_int ? std::ceil(colLower - tolerance) : colLower; + const double x_up = x_int ? std::floor(colUpper + tolerance) : colUpper; + const double y_lo = + y_int ? std::ceil(duplicateColLower - tolerance) : duplicateColLower; + const double y_up = + y_int ? std::floor(duplicateColUpper + tolerance) : duplicateColUpper; const double x_len = x_up - x_lo; const double y_len = y_up - y_lo; std::string newline = "\n"; @@ -962,7 +983,8 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( if (abs_scale > scale_limit) { if (debug_report) printf( - "%sDuplicateColumn::checkMerge: scale = %g, but |scale| must be " + "%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); @@ -995,9 +1017,8 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( }; 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; + return v >= l - primal_feasibility_tolerance && + v <= u + primal_feasibility_tolerance; }; const double merge_value = col_value[col]; const double value_max = 1000; @@ -1007,10 +1028,16 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( 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; + const double x_lo = + x_int ? std::ceil(colLower - mip_feasibility_tolerance) : colLower; + const double x_up = + x_int ? std::floor(colUpper + mip_feasibility_tolerance) : colUpper; + const double y_lo = + y_int ? std::ceil(duplicateColLower - mip_feasibility_tolerance) + : duplicateColLower; + const double y_up = + y_int ? std::floor(duplicateColUpper + mip_feasibility_tolerance) + : duplicateColUpper; if (kAllowDeveloperAssert) assert(scale); double x_v = merge_value; double y_v; @@ -1284,14 +1311,16 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( if (!check) { if (debug_report) printf( - "DuplicateColumn::undo error: std::fabs(x_v) < kHighsInf is false\n"); + "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"); + "DuplicateColumn::undo error: std::fabs(y_v) < kHighsInf is " + "false\n"); if (allow_assert) assert(check); } check = residual <= residual_tolerance; diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 2aec12c5a8..2200977b5b 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -16,6 +16,7 @@ #ifndef PRESOLVE_HIGHS_POSTSOLVE_STACK_H_ #define PRESOLVE_HIGHS_POSTSOLVE_STACK_H_ +#include #include #include #include @@ -104,6 +105,7 @@ class HighsPostsolveStack { HighsInt col; bool lowerTightened; bool upperTightened; + RowType rowType; void undo(const HighsOptions& options, const std::vector& colValues, HighsSolution& solution, @@ -173,6 +175,7 @@ class HighsPostsolveStack { double colBound; HighsInt col; bool atInfiniteUpper; + bool colIntegral; void undo(const HighsOptions& options, const std::vector& colValues, HighsSolution& solution, @@ -234,7 +237,7 @@ class HighsPostsolveStack { }; HighsDataStack reductionValues; - std::vector> reductions; + std::vector> reductions; std::vector origColIndex; std::vector origRowIndex; std::vector linearlyTransformable; @@ -245,11 +248,15 @@ class HighsPostsolveStack { HighsInt origNumRow = -1; void reductionAdded(ReductionType type) { - HighsInt position = reductionValues.getCurrentDataSize(); + size_t position = reductionValues.getCurrentDataSize(); reductions.emplace_back(type, position); } public: + const HighsInt* getOrigRowsIndex() const { return origRowIndex.data(); } + + const HighsInt* getOrigColsIndex() const { return origColIndex.data(); } + HighsInt getOrigRowIndex(HighsInt row) const { assert(row < (HighsInt)origRowIndex.size()); return origRowIndex[row]; @@ -261,19 +268,19 @@ class HighsPostsolveStack { } void appendCutsToModel(HighsInt numCuts) { - HighsInt currNumRow = origRowIndex.size(); - HighsInt newNumRow = currNumRow + numCuts; + size_t currNumRow = origRowIndex.size(); + size_t newNumRow = currNumRow + numCuts; origRowIndex.resize(newNumRow); - for (HighsInt i = currNumRow; i != newNumRow; ++i) + for (size_t i = currNumRow; i != newNumRow; ++i) origRowIndex[i] = origNumRow++; } void removeCutsFromModel(HighsInt numCuts) { origNumRow -= numCuts; - HighsInt origRowIndexSize = origRowIndex.size(); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) { - if (origRowIndex[i] < origNumRow) break; + size_t origRowIndexSize = origRowIndex.size(); + for (size_t i = origRowIndex.size(); i > 0; --i) { + if (origRowIndex[i - 1] < origNumRow) break; --origRowIndexSize; } @@ -321,6 +328,7 @@ class HighsPostsolveStack { double coefSubst, double coef, double rhs, double substLower, double substUpper, double substCost, bool lowerTightened, bool upperTightened, + RowType rowType, const HighsMatrixSlice& colVec) { colValues.clear(); for (const HighsSliceNonzero& colVal : colVec) @@ -329,7 +337,7 @@ class HighsPostsolveStack { reductionValues.push(DoubletonEquation{ coef, coefSubst, rhs, substLower, substUpper, substCost, row == -1 ? -1 : origRowIndex[row], origColIndex[colSubst], - origColIndex[col], lowerTightened, upperTightened}); + origColIndex[col], lowerTightened, upperTightened, rowType}); reductionValues.push(colValues); reductionAdded(ReductionType::kDoubletonEquation); } @@ -445,13 +453,14 @@ class HighsPostsolveStack { template void forcingColumn(HighsInt col, const HighsMatrixSlice& colVec, - double cost, double boundVal, bool atInfiniteUpper) { + double cost, double boundVal, bool atInfiniteUpper, + bool colIntegral) { colValues.clear(); for (const HighsSliceNonzero& colVal : colVec) colValues.emplace_back(origRowIndex[colVal.index()], colVal.value()); - reductionValues.push( - ForcingColumn{cost, boundVal, origColIndex[col], atInfiniteUpper}); + reductionValues.push(ForcingColumn{cost, boundVal, origColIndex[col], + atInfiniteUpper, colIntegral}); reductionValues.push(colValues); reductionAdded(ReductionType::kForcingColumn); } @@ -511,7 +520,7 @@ class HighsPostsolveStack { const std::vector& origPrimalSolution) { std::vector reducedSolution = origPrimalSolution; - for (const std::pair& primalColTransformation : + for (const std::pair& primalColTransformation : reductions) { switch (primalColTransformation.first) { case ReductionType::kDuplicateColumn: { @@ -533,8 +542,8 @@ class HighsPostsolveStack { } } - HighsInt reducedNumCol = origColIndex.size(); - for (HighsInt i = 0; i < reducedNumCol; ++i) + size_t reducedNumCol = origColIndex.size(); + for (size_t i = 0; i < reducedNumCol; ++i) reducedSolution[i] = reducedSolution[origColIndex[i]]; reducedSolution.resize(reducedNumCol); @@ -542,7 +551,36 @@ class HighsPostsolveStack { } bool isColLinearlyTransformable(HighsInt col) const { - return linearlyTransformable[col]; + return (linearlyTransformable[col] != 0); + } + + template + void undoIterateBackwards(std::vector& values, + const std::vector& index, + HighsInt origSize) { + values.resize(origSize); +#ifdef DEBUG_EXTRA + // Fill vector with NaN for debugging purposes + std::vector valuesNew; + valuesNew.resize(origSize, std::numeric_limits::signaling_NaN()); + for (size_t i = index.size(); i > 0; --i) { + assert(static_cast(index[i - 1]) >= i - 1); + valuesNew[index[i - 1]] = values[i - 1]; + } + std::copy(valuesNew.cbegin(), valuesNew.cend(), values.begin()); +#else + for (size_t i = index.size(); i > 0; --i) { + assert(static_cast(index[i - 1]) >= i - 1); + values[index[i - 1]] = values[i - 1]; + } +#endif + } + + /// check if vector contains NaN or Inf + bool containsNanOrInf(const std::vector& v) const { + return std::find_if(v.cbegin(), v.cend(), [](const double& d) { + return (std::isnan(d) || std::isinf(d)); + }) != v.cend(); } /// undo presolve steps for primal dual solution and basis @@ -557,49 +595,33 @@ class HighsPostsolveStack { // expand solution to original index space assert(origNumCol > 0); - solution.col_value.resize(origNumCol); - for (HighsInt i = origColIndex.size() - 1; i >= 0; --i) { - assert(origColIndex[i] >= i); - solution.col_value[origColIndex[i]] = solution.col_value[i]; - } + undoIterateBackwards(solution.col_value, origColIndex, origNumCol); assert(origNumRow >= 0); - solution.row_value.resize(origNumRow); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) { - assert(origRowIndex[i] >= i); - solution.row_value[origRowIndex[i]] = solution.row_value[i]; - } + undoIterateBackwards(solution.row_value, origRowIndex, origNumRow); if (perform_dual_postsolve) { // if dual solution is given, expand dual solution and basis to original // index space - solution.col_dual.resize(origNumCol); - for (HighsInt i = origColIndex.size() - 1; i >= 0; --i) - solution.col_dual[origColIndex[i]] = solution.col_dual[i]; + undoIterateBackwards(solution.col_dual, origColIndex, origNumCol); - solution.row_dual.resize(origNumRow); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) - solution.row_dual[origRowIndex[i]] = solution.row_dual[i]; + undoIterateBackwards(solution.row_dual, origRowIndex, origNumRow); } if (perform_basis_postsolve) { // if basis is given, expand basis status values to original index space - basis.col_status.resize(origNumCol); - for (HighsInt i = origColIndex.size() - 1; i >= 0; --i) - basis.col_status[origColIndex[i]] = basis.col_status[i]; + undoIterateBackwards(basis.col_status, origColIndex, origNumCol); - basis.row_status.resize(origNumRow); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) - basis.row_status[origRowIndex[i]] = basis.row_status[i]; + undoIterateBackwards(basis.row_status, origRowIndex, origNumRow); } // now undo the changes - for (HighsInt i = reductions.size() - 1; i >= 0; --i) { + for (size_t i = reductions.size(); 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), + int(i - 1), int(reductions[i - 1].first), int(report_col), solution.col_value[report_col]); - switch (reductions[i].first) { + switch (reductions[i - 1].first) { case ReductionType::kLinearTransform: { LinearTransform reduction; reductionValues.pop(reduction); @@ -689,13 +711,23 @@ class HighsPostsolveStack { break; } default: - printf("Reduction case %d not handled\n", int(reductions[i].first)); + printf("Reduction case %d not handled\n", + int(reductions[i - 1].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]); + +#ifdef DEBUG_EXTRA + // solution should not contain NaN or Inf + assert(!containsNanOrInf(solution.col_value)); + // row values are not determined by postsolve + // assert(!containsNanOrInf(solution.row_value)); + assert(!containsNanOrInf(solution.col_dual)); + assert(!containsNanOrInf(solution.row_dual)); +#endif } /// undo presolve steps for primal solution @@ -727,7 +759,7 @@ class HighsPostsolveStack { void undoUntil(const HighsOptions& options, const std::vector& flagRow, const std::vector& flagCol, HighsSolution& solution, - HighsBasis& basis, HighsInt numReductions) { + HighsBasis& basis, size_t numReductions) { reductionValues.resetPosition(); // Do these returns ever happen? How is it known that undo has not @@ -746,44 +778,28 @@ class HighsPostsolveStack { bool perform_basis_postsolve = basis.valid; // expand solution to original index space - solution.col_value.resize(origNumCol); - for (HighsInt i = origColIndex.size() - 1; i >= 0; --i) { - assert(origColIndex[i] >= i); - solution.col_value[origColIndex[i]] = solution.col_value[i]; - } + undoIterateBackwards(solution.col_value, origColIndex, origNumCol); - solution.row_value.resize(origNumRow); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) { - assert(origRowIndex[i] >= i); - solution.row_value[origRowIndex[i]] = solution.row_value[i]; - } + undoIterateBackwards(solution.row_value, origRowIndex, origNumRow); if (perform_dual_postsolve) { // if dual solution is given, expand dual solution and basis to original // index space - solution.col_dual.resize(origNumCol); - for (HighsInt i = origColIndex.size() - 1; i >= 0; --i) - solution.col_dual[origColIndex[i]] = solution.col_dual[i]; + undoIterateBackwards(solution.col_dual, origColIndex, origNumCol); - solution.row_dual.resize(origNumRow); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) - solution.row_dual[origRowIndex[i]] = solution.row_dual[i]; + undoIterateBackwards(solution.row_dual, origRowIndex, origNumRow); } if (perform_basis_postsolve) { // if basis is given, expand basis status values to original index space - basis.col_status.resize(origNumCol); - for (HighsInt i = origColIndex.size() - 1; i >= 0; --i) - basis.col_status[origColIndex[i]] = basis.col_status[i]; + undoIterateBackwards(basis.col_status, origColIndex, origNumCol); - basis.row_status.resize(origNumRow); - for (HighsInt i = origRowIndex.size() - 1; i >= 0; --i) - basis.row_status[origRowIndex[i]] = basis.row_status[i]; + undoIterateBackwards(basis.row_status, origRowIndex, origNumRow); } // now undo the changes - for (HighsInt i = reductions.size() - 1; i >= numReductions; --i) { - switch (reductions[i].first) { + for (size_t i = reductions.size(); i > numReductions; --i) { + switch (reductions[i - 1].first) { case ReductionType::kLinearTransform: { LinearTransform reduction; reductionValues.pop(reduction); @@ -873,6 +889,14 @@ class HighsPostsolveStack { } } } +#ifdef DEBUG_EXTRA + // solution should not contain NaN or Inf + assert(!containsNanOrInf(solution.col_value)); + // row values are not determined by postsolve + // assert(!containsNanOrInf(solution.row_value)); + assert(!containsNanOrInf(solution.col_dual)); + assert(!containsNanOrInf(solution.row_dual)); +#endif } size_t numReductions() const { return reductions.size(); } diff --git a/src/presolve/HighsSymmetry.cpp b/src/presolve/HighsSymmetry.cpp index 33f60faf1e..a34d1d7902 100644 --- a/src/presolve/HighsSymmetry.cpp +++ b/src/presolve/HighsSymmetry.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -18,10 +18,10 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "mip/HighsCliqueTable.h" #include "mip/HighsDomain.h" #include "parallel/HighsParallel.h" -#include "pdqsort/pdqsort.h" #include "util/HighsDisjointSets.h" void HighsSymmetryDetection::removeFixPoints() { @@ -42,7 +42,6 @@ void HighsSymmetryDetection::removeFixPoints() { [&](HighsInt vertex) { if (cellSize(vertexToCell[vertex]) == 1) { --unitCellIndex; - HighsInt oldCellStart = vertexToCell[vertex]; vertexToCell[vertex] = unitCellIndex; return true; } @@ -74,7 +73,7 @@ void HighsSymmetryDetection::removeFixPoints() { // if the cell number is different to the current cell number this is the // start of a new cell if (cellNumber != vertexToCell[vertex]) { - // remember the number of this cell to indetify its end + // remember the number of this cell to identify its end cellNumber = vertexToCell[vertex]; // set the link of the cell start to point to its end currentPartitionLinks[cellStart] = i; @@ -179,7 +178,6 @@ std::shared_ptr HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom) { const auto& domchgStack = localdom.getDomainChangeStack(); const auto& branchingPos = localdom.getBranchingPositions(); - const auto& prevBounds = localdom.getPreviousBounds(); StabilizerOrbits stabilizerOrbits; stabilizerOrbits.stabilizedCols.reserve(permutationColumns.size()); @@ -275,7 +273,6 @@ HighsInt StabilizerOrbits::orbitalFixing(HighsDomain& domain) const { if (fixcol != -1) { HighsInt oldNumFixed = numFixed; - double fixVal = domain.col_lower_[fixcol]; auto oldSize = domain.getDomainChangeStack().size(); if (domain.col_lower_[fixcol] == 1.0) { for (HighsInt j = orbitStarts[i]; j < orbitStarts[i + 1]; ++j) { @@ -542,7 +539,7 @@ HighsInt HighsOrbitopeMatrix::orbitalFixingForPackingOrbitope( ++numFixed; if (domain.infeasible()) { // this can happen due to deductions from earlier fixings - // otherwise it would have been caughgt by the infeasibility + // otherwise it would have been caught by the infeasibility // check within the next loop that goes over i // printf("packing orbitope propagation found infeasibility\n"); return numFixed; @@ -575,7 +572,7 @@ HighsInt HighsOrbitopeMatrix::orbitalFixingForPackingOrbitope( ++numFixed; if (domain.infeasible()) { // this can happen due to deductions from earlier fixings - // otherwise it would have been caughgt by the infeasibility + // otherwise it would have been caught by the infeasibility // check within the next loop that goes over i // printf("packing orbitope propagation found infeasibility\n"); return numFixed; @@ -815,7 +812,6 @@ bool HighsSymmetryDetection::updateCellMembership(HighsInt i, HighsInt cell, HighsInt vertex = currentPartition[i]; if (vertexToCell[vertex] != cell) { // set new cell id - HighsInt oldCellStart = vertexToCell[vertex]; vertexToCell[vertex] = cell; if (i != cell) currentPartitionLinks[i] = cell; @@ -1289,7 +1285,7 @@ void HighsSymmetryDetection::loadModelAsGraph(const HighsLp& model, // if the cell number is different to the current cell number this is the // start of a new cell if (cellNumber != vertexToCell[vertex]) { - // remember the number of this cell to indetify its end + // remember the number of this cell to identify its end cellNumber = vertexToCell[vertex]; // set the link of the cell start to point to its end currentPartitionLinks[cellStart] = i; @@ -1503,7 +1499,7 @@ HighsSymmetryDetection::computeComponentData( componentData.componentStarts.size()); componentData.permComponentStarts.push_back(numUsedPerms); - HighsInt numComponents = componentData.componentStarts.size(); + // HighsInt numComponents = componentData.componentStarts.size(); // printf("found %d components\n", numComponents); componentData.componentStarts.push_back(numActiveCols); @@ -1578,7 +1574,6 @@ bool HighsSymmetryDetection::isFullOrbitope(const ComponentData& componentData, orbitopeMatrix.numRows = orbitopeNumRows; orbitopeMatrix.rowLength = orbitopeOrbitSize; assert(componentSize == orbitopeMatrix.numRows * orbitopeMatrix.rowLength); - HighsInt orbitopeIndex = symmetries.orbitopes.size(); const HighsInt* perm = symmetries.permutations.data() + p0 * numActiveCols; HighsHashTable colSet; @@ -1811,7 +1806,7 @@ void HighsSymmetryDetection::run(HighsSymmetries& symmetries) { // than the best leaves and it would have been already pruned if // it's certificate value was larger unless it is equal to the first // leave nodes certificate value which is caught by the first case - // of the if confition. Hence, having a lexicographically smaller + // of the if condition. Hence, having a lexicographically smaller // certificate value than the best leave is the only way to get // here. assert(bestLeaveCertificate[bestLeavePrefixLen] > diff --git a/src/presolve/HighsSymmetry.h b/src/presolve/HighsSymmetry.h index 40f6354a55..53e08aaf27 100644 --- a/src/presolve/HighsSymmetry.h +++ b/src/presolve/HighsSymmetry.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -25,7 +25,7 @@ #include "util/HighsHash.h" #include "util/HighsInt.h" -/// class that is responsible for assiging distinct colors for each distinct +/// class that is responsible for assigning distinct colors for each distinct /// double value class HighsMatrixColoring { using u32 = std::uint32_t; @@ -44,7 +44,6 @@ class HighsMatrixColoring { // iterator points to smallest element in map which fulfills key >= value - // tolerance auto it = colorMap.lower_bound(value - tolerance); - u32 color; // check if there is no such element, or if this element has a key value + // tolerance in which case we create a new color and store it with the key // value @@ -80,10 +79,12 @@ struct HighsOrbitopeMatrix { std::vector rowIsSetPacking; std::vector matrix; - HighsInt& entry(HighsInt i, HighsInt j) { return matrix[i + j * numRows]; } + HighsInt& entry(HighsInt i, HighsInt j) { + return matrix[i + static_cast(j) * numRows]; + } const HighsInt& entry(HighsInt i, HighsInt j) const { - return matrix[i + j * numRows]; + return matrix[i + static_cast(j) * numRows]; } HighsInt& operator()(HighsInt i, HighsInt j) { return entry(i, j); } diff --git a/src/presolve/ICrash.cpp b/src/presolve/ICrash.cpp index 934bcb11a7..07e8f77007 100644 --- a/src/presolve/ICrash.cpp +++ b/src/presolve/ICrash.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -158,7 +158,7 @@ void update(Quadratic& idata) { idata.lp_objective = vectorProduct(idata.lp.col_cost_, idata.xk.col_value); // residual & residual_norm_2 - calculateRowValues(idata.lp, idata.xk); + calculateRowValuesQuad(idata.lp, idata.xk); updateResidual(idata.options.breakpoints, idata.lp, idata.xk, idata.residual); idata.residual_norm_2 = getNorm2(idata.residual); @@ -232,7 +232,7 @@ void updateParameters(Quadratic& idata, const ICrashOptions& options, if (iteration % 3 == 0) { idata.mu = 0.1 * idata.mu; } else { - calculateRowValues(idata.lp, idata.xk); + calculateRowValuesQuad(idata.lp, idata.xk); std::vector residual(idata.lp.num_row_, 0); updateResidualFast(idata.lp, idata.xk, residual); for (int row = 0; row < idata.lp.num_row_; row++) @@ -289,7 +289,7 @@ static void solveSubproblemICA(Quadratic& idata, const ICrashOptions& options) { static void solveSubproblemQP(Quadratic& idata, const ICrashOptions& options) { bool minor_iteration_details = false; - calculateRowValues(idata.lp, idata.xk); + calculateRowValuesQuad(idata.lp, idata.xk); std::vector residual(idata.lp.num_row_, 0); updateResidualFast(idata.lp, idata.xk, residual); double objective = 0; diff --git a/src/presolve/ICrash.h b/src/presolve/ICrash.h index e797237368..dfe3d40bc7 100644 --- a/src/presolve/ICrash.h +++ b/src/presolve/ICrash.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -85,7 +85,13 @@ struct Quadratic { double mu; std::vector lambda; - Quadratic(HighsLp lp_, ICrashOptions options_) : lp(lp_), options(options_) {} + Quadratic(HighsLp lp_, ICrashOptions options_) + : lp(lp_), + options(options_), + lp_objective(0.0), + quadratic_objective(0.0), + residual_norm_2(0.0), + mu(0.0) {} }; // Functions: Call. diff --git a/src/presolve/ICrashUtil.cpp b/src/presolve/ICrashUtil.cpp index 8d0852215f..22df2d27cd 100644 --- a/src/presolve/ICrashUtil.cpp +++ b/src/presolve/ICrashUtil.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -83,15 +83,9 @@ void printMinorIterationDetails(const double iteration, const double col, bool initialize(const HighsLp& lp, HighsSolution& solution, std::vector& lambda) { - if (!isSolutionRightSize(lp, solution)) { - // clear and resize solution. - solution.col_value.clear(); - solution.col_dual.clear(); - solution.row_value.clear(); - solution.row_dual.clear(); - - solution.col_value.resize(lp.num_col_); - } + // Clear and resize primal column solution. + solution.clear(); + solution.col_value.resize(lp.num_col_); for (int col = 0; col < lp.num_col_; col++) { if (lp.col_lower_[col] <= 0 && lp.col_upper_[col] >= 0) diff --git a/src/presolve/ICrashUtil.h b/src/presolve/ICrashUtil.h index 67c52b5d51..1d53d8a69d 100644 --- a/src/presolve/ICrashUtil.h +++ b/src/presolve/ICrashUtil.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 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 bbc22b1b7d..6a5573404a 100644 --- a/src/presolve/ICrashX.cpp +++ b/src/presolve/ICrashX.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -41,12 +41,15 @@ HighsStatus callCrossover(const HighsOptions& options, const HighsLp& lp, // Modify parameters.debug according to log_dev_level parameters.debug = 0; if (options.log_dev_level == kHighsLogDevLevelDetailed) { - parameters.debug = 0; + // Default options.log_dev_level setting is kHighsLogDevLevelNone, yielding + // default setting debug = 0 } else if (options.log_dev_level == kHighsLogDevLevelInfo) { parameters.debug = 2; } else if (options.log_dev_level == kHighsLogDevLevelVerbose) { parameters.debug = 4; } + parameters.highs_logging = true; + parameters.log_options = &options.log_options; ipx::LpSolver lps; lps.SetParameters(parameters); diff --git a/src/presolve/ICrashX.h b/src/presolve/ICrashX.h index cc28dfc2fa..351a40edb3 100644 --- a/src/presolve/ICrashX.h +++ b/src/presolve/ICrashX.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/presolve/PresolveComponent.cpp b/src/presolve/PresolveComponent.cpp index 234996eb12..7162162619 100644 --- a/src/presolve/PresolveComponent.cpp +++ b/src/presolve/PresolveComponent.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -33,9 +33,13 @@ void PresolveComponent::negateReducedLpColDuals() { HighsPresolveStatus PresolveComponent::run() { presolve::HPresolve presolve; - presolve.setInput(data_.reduced_lp_, *options_, timer); + if (!presolve.okSetInput(data_.reduced_lp_, *options_, + options_->presolve_reduction_limit, timer)) { + presolve_status_ = HighsPresolveStatus::kOutOfMemory; + return presolve_status_; + } - HighsModelStatus status = presolve.run(data_.postSolveStack); + presolve.run(data_.postSolveStack); data_.presolve_log_ = presolve.getPresolveLog(); presolve_status_ = presolve.getPresolveStatus(); return presolve_status_; diff --git a/src/presolve/PresolveComponent.h b/src/presolve/PresolveComponent.h index 8c81de81b0..8ec5bcba53 100644 --- a/src/presolve/PresolveComponent.h +++ b/src/presolve/PresolveComponent.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/qpsolver/a_asm.cpp b/src/qpsolver/a_asm.cpp index cd48273cde..821058619e 100644 --- a/src/qpsolver/a_asm.cpp +++ b/src/qpsolver/a_asm.cpp @@ -1,9 +1,9 @@ #include "qpsolver/a_asm.hpp" #include "qpsolver/quass.hpp" +#include "util/HighsCDouble.h" QpAsmStatus solveqp_actual(Instance& instance, Settings& settings, QpHotstartInformation& startinfo, Statistics& stats, QpModelStatus& status, QpSolution& solution, HighsTimer& qp_timer) { - Runtime rt(instance); - rt.statistics = stats; + Runtime rt(instance, stats); rt.settings = settings; Quass quass(rt); @@ -20,8 +20,107 @@ QpAsmStatus solveqp_actual(Instance& instance, Settings& settings, QpHotstartInf solution.rowactivity = rt.rowactivity; solution.dualcon = rt.dualcon; - return QpAsmStatus::OK; + return QpAsmStatus::kOk; } +std::string qpBasisStatusToString(const BasisStatus qp_basis_status) { + switch (qp_basis_status) { + case BasisStatus::kInactive: + return "Inactive"; + case BasisStatus::kActiveAtLower: + return "Active at lower bound"; + case BasisStatus::kActiveAtUpper: + return "Active at upper bound"; + case BasisStatus::kInactiveInBasis: + return "Inactive in basis"; + default: + return "Unidentified QP basis status"; + } +} + +std::string qpModelStatusToString(const QpModelStatus qp_model_status) { + switch (qp_model_status) { + case QpModelStatus::kNotset: + return "Not set"; + case QpModelStatus::kUndetermined: + return "Undertermined"; + case QpModelStatus::kOptimal: + return "Optimal"; + case QpModelStatus::kUnbounded: + return "Unbounded"; + case QpModelStatus::kInfeasible: + return "Infeasible"; + case QpModelStatus::kIterationLimit: + return "Iteration limit"; + case QpModelStatus::kTimeLimit: + return "Time ;limit"; + case QpModelStatus::kLargeNullspace: + return "Large nullspace"; + case QpModelStatus::kError: + return "Error"; + default: + return "Unidentified QP model status"; + } +} + +void assessQpPrimalFeasibility(const Instance& instance, const double primal_feasibility_tolerance, + const std::vector& var_value, const std::vector& con_value, + HighsInt& num_var_infeasibilities, double& max_var_infeasibility, double& sum_var_infeasibilities, + HighsInt& num_con_infeasibilities, double& max_con_infeasibility, double& sum_con_infeasibilities, + double& max_con_residual, double& sum_con_residuals) { + num_var_infeasibilities = 0; + max_var_infeasibility = 0; + sum_var_infeasibilities = 0; + num_con_infeasibilities = 0; + max_con_infeasibility = 0; + sum_con_infeasibilities = 0; + max_con_residual = 0; + sum_con_residuals = 0; + // Valid solution, but is it feasible? + std::vector con_value_quad; + con_value_quad.assign(instance.num_con, HighsCDouble{0.0}); + for (HighsInt iVar = 0; iVar < instance.num_var; iVar++) { + double lower = instance.var_lo[iVar]; + double upper = instance.var_up[iVar]; + double primal = var_value[iVar]; + double var_infeasibility = 0; + if (primal < lower - primal_feasibility_tolerance) { + var_infeasibility = lower - primal; + } else if (primal > upper + primal_feasibility_tolerance) { + var_infeasibility = primal - upper; + } + if (var_infeasibility > 0) { + if (var_infeasibility > primal_feasibility_tolerance) + num_var_infeasibilities++; + max_var_infeasibility = + std::max(var_infeasibility, max_var_infeasibility); + sum_var_infeasibilities += var_infeasibility; + } + for (HighsInt iEl = instance.A.mat.start[iVar]; iEl < instance.A.mat.start[iVar+1]; iEl++) { + con_value_quad[instance.A.mat.index[iEl]] += primal * instance.A.mat.value[iEl]; + } + } + for (HighsInt iCon = 0; iCon < instance.num_con; iCon++) { + double lower = instance.con_lo[iCon]; + double upper = instance.con_up[iCon]; + double primal = con_value[iCon]; + double con_infeasibility = 0; + if (primal < lower - primal_feasibility_tolerance) { + con_infeasibility = lower - primal; + } else if (primal > upper + primal_feasibility_tolerance) { + con_infeasibility = primal - upper; + } + if (con_infeasibility > 0) { + if (con_infeasibility > primal_feasibility_tolerance) + num_con_infeasibilities++; + max_con_infeasibility = + std::max(con_infeasibility, max_con_infeasibility); + sum_con_infeasibilities += con_infeasibility; + } + double con_residual = std::fabs(primal-double(con_value_quad[iCon])); + max_con_residual = std::max(con_residual, max_con_residual); + sum_con_residuals += con_residual; + } +} diff --git a/src/qpsolver/a_asm.hpp b/src/qpsolver/a_asm.hpp index 827aa28025..5d80757bc7 100644 --- a/src/qpsolver/a_asm.hpp +++ b/src/qpsolver/a_asm.hpp @@ -8,22 +8,24 @@ #include "util/HighsTimer.h" enum class QpAsmStatus { - OK, - NEGATIVEEIGENVALUEINREDUCEDHESSIAN, - BASISRANKDEFICIENT + kOk, + kWarning, + kError + // NEGATIVEEIGENVALUEINREDUCEDHESSIAN, + // BASISRANKDEFICIENT }; struct QpSolution { - Vector primal; - Vector rowactivity; - Vector dualvar; - Vector dualcon; + QpVector primal; + QpVector rowactivity; + QpVector dualvar; + QpVector dualcon; std::vector status_var; std::vector status_con; - QpSolution(Instance& instance) : primal(Vector(instance.num_var)), - rowactivity(Vector(instance.num_con)), + QpSolution(Instance& instance) : primal(QpVector(instance.num_var)), + rowactivity(QpVector(instance.num_con)), dualvar(instance.num_var), dualcon(instance.num_con), status_var(instance.num_var), @@ -35,11 +37,11 @@ struct QpHotstartInformation { std::vector active; std::vector inactive; std::vector status; - Vector primal; - Vector rowact; + QpVector primal; + QpVector rowact; QpHotstartInformation(HighsInt num_var, HighsInt num_row) - : primal(Vector(num_var)), rowact(Vector(num_row)) {} + : primal(QpVector(num_var)), rowact(QpVector(num_row)) {} }; // the purpose of this is the pure algorithmic solution of a QP instance with given hotstart information. @@ -50,7 +52,16 @@ struct QpHotstartInformation { // 4) start from a qp solution that was attained from a perturbed instance and cleanup // 5) start from a qp solution and cleanup after recomputing basis and reduced hessian factorization +std::string qpBasisStatusToString(const BasisStatus qp_basis_status); +std::string qpModelStatusToString(const QpModelStatus qp_model_status); +void assessQpPrimalFeasibility(const Instance& instance, const double primal_feasibility_tolerance, + const std::vector& var_value, const std::vector& con_value, + HighsInt& num_var_infeasibilities, double& max_var_infeasibility, double& sum_var_infeasibilities, + HighsInt& num_con_infeasibilities, double& max_con_infeasibility, double& sum_con_infeasibilities, + double& max_con_residual, double& sum_con_residuals); QpAsmStatus solveqp_actual(Instance& instance, Settings& settings, QpHotstartInformation& startinfo, Statistics& stats, QpModelStatus& status, QpSolution& solution, HighsTimer& qp_timer); + + #endif diff --git a/src/qpsolver/a_quass.cpp b/src/qpsolver/a_quass.cpp index d0fb639a41..b62d22ebc9 100644 --- a/src/qpsolver/a_quass.cpp +++ b/src/qpsolver/a_quass.cpp @@ -2,10 +2,124 @@ #include "qpsolver/a_asm.hpp" #include "qpsolver/feasibility_highs.hpp" +#include "qpsolver/feasibility_bounded.hpp" +static QpAsmStatus quass2highs(Instance& instance, + Settings& settings, + Statistics& stats, + QpModelStatus& qp_model_status, + QpSolution& qp_solution, + HighsModelStatus& highs_model_status, + HighsBasis& highs_basis, + HighsSolution& highs_solution) { + settings.qp_model_status_log.fire(qp_model_status); + QpAsmStatus qp_asm_return_status = QpAsmStatus::kError; + switch (qp_model_status) { + case QpModelStatus::kOptimal: + highs_model_status = HighsModelStatus::kOptimal; + qp_asm_return_status = QpAsmStatus::kOk; + break; + case QpModelStatus::kUnbounded: + highs_model_status = HighsModelStatus::kUnbounded; + qp_asm_return_status = QpAsmStatus::kOk; + break; + case QpModelStatus::kInfeasible: + highs_model_status = HighsModelStatus::kInfeasible; + qp_asm_return_status = QpAsmStatus::kOk; + break; + case QpModelStatus::kIterationLimit: + highs_model_status = HighsModelStatus::kIterationLimit; + qp_asm_return_status = QpAsmStatus::kWarning; + break; + case QpModelStatus::kTimeLimit: + highs_model_status = HighsModelStatus::kTimeLimit; + qp_asm_return_status = QpAsmStatus::kWarning; + break; + case QpModelStatus::kUndetermined: + highs_model_status = HighsModelStatus::kSolveError; + qp_asm_return_status = QpAsmStatus::kError; + return QpAsmStatus::kError; + case QpModelStatus::kLargeNullspace: + highs_model_status = HighsModelStatus::kSolveError; + return QpAsmStatus::kError; + case QpModelStatus::kError: + highs_model_status = HighsModelStatus::kSolveError; + return QpAsmStatus::kError; + case QpModelStatus::kNotset: + highs_model_status = HighsModelStatus::kNotset; + return QpAsmStatus::kError; + default: + highs_model_status = HighsModelStatus::kNotset; + return QpAsmStatus::kError; + } + + assert(qp_asm_return_status != QpAsmStatus::kError); + // extract variable values + highs_solution.col_value.resize(instance.num_var); + highs_solution.col_dual.resize(instance.num_var); + for (HighsInt iCol = 0; iCol < instance.num_var; iCol++) { + highs_solution.col_value[iCol] = qp_solution.primal.value[iCol]; + highs_solution.col_dual[iCol] = instance.sense * qp_solution.dualvar.value[iCol]; + } + // extract constraint activity + highs_solution.row_value.resize(instance.num_con); + highs_solution.row_dual.resize(instance.num_con); + // Negate the vector and Hessian + for (HighsInt iRow = 0; iRow < instance.num_con; iRow++) { + highs_solution.row_value[iRow] = qp_solution.rowactivity.value[iRow]; + highs_solution.row_dual[iRow] = instance.sense * qp_solution.dualcon.value[iRow]; + } + highs_solution.value_valid = true; + highs_solution.dual_valid = true; + // extract basis status + highs_basis.col_status.resize(instance.num_var); + highs_basis.row_status.resize(instance.num_con); -QpAsmStatus solveqp(Instance& instance, Settings& settings, Statistics& stats, QpModelStatus& modelstatus, QpSolution& solution, HighsTimer& qp_timer) { + const bool debug_report = false;// instance.num_var + instance.num_con < 100; + for (HighsInt i = 0; i < instance.num_var; i++) { + if (debug_report) printf("Column %2d: status %s\n", int(i), qpBasisStatusToString(qp_solution.status_var[i]).c_str()); + if (qp_solution.status_var[i] == BasisStatus::kActiveAtLower) { + highs_basis.col_status[i] = HighsBasisStatus::kLower; + } else if (qp_solution.status_var[i] == BasisStatus::kActiveAtUpper) { + highs_basis.col_status[i] = HighsBasisStatus::kUpper; + } else if (qp_solution.status_var[i] == BasisStatus::kInactiveInBasis) { + highs_basis.col_status[i] = HighsBasisStatus::kNonbasic; + } else { + assert(qp_solution.status_var[i] == BasisStatus::kInactive); + highs_basis.col_status[i] = HighsBasisStatus::kBasic; + } + } + + for (HighsInt i = 0; i < instance.num_con; i++) { + if (debug_report) printf("Row %2d: status %s\n", int(i), qpBasisStatusToString(qp_solution.status_con[i]).c_str()); + if (qp_solution.status_con[i] == BasisStatus::kActiveAtLower) { + highs_basis.row_status[i] = HighsBasisStatus::kLower; + } else if (qp_solution.status_con[i] == BasisStatus::kActiveAtUpper) { + highs_basis.row_status[i] = HighsBasisStatus::kUpper; + } else if (qp_solution.status_con[i] == BasisStatus::kInactiveInBasis) { + highs_basis.row_status[i] = HighsBasisStatus::kNonbasic; + } else { + assert(qp_solution.status_con[i] == BasisStatus::kInactive); + highs_basis.row_status[i] = HighsBasisStatus::kBasic; + } + } + highs_basis.valid = true; + highs_basis.alien = false; + return qp_asm_return_status; +} + +QpAsmStatus solveqp(Instance& instance, + Settings& settings, + Statistics& stats, + HighsModelStatus& highs_model_status, + HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsTimer& qp_timer) { + + QpModelStatus qp_model_status = QpModelStatus::kUndetermined; + + QpSolution qp_solution(instance); // presolve @@ -13,15 +127,37 @@ QpAsmStatus solveqp(Instance& instance, Settings& settings, Statistics& stats, Q // perturb instance, store perturbance information + // regularize + for (HighsInt i=0; i active, std::vector status, std::vector inactive) - : runtime(rt), + : Ztprod_res(rt.instance.num_var), + buffer_Zprod(rt.instance.num_var), + 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 active, } void Basis::build() { + // report(); + updatessinceinvert = 0; - baseindex.resize(activeconstraintidx.size() + nonactiveconstraintsidx.size()); + baseindex.resize(active_constraint_index.size() + non_active_constraint_index.size()); constraintindexinbasisfactor.clear(); basisfactor = HFactor(); constraintindexinbasisfactor.assign(Atran.num_row + Atran.num_col, -1); - assert((HighsInt)(nonactiveconstraintsidx.size() + activeconstraintidx.size()) == + assert((HighsInt)(non_active_constraint_index.size() + active_constraint_index.size()) == Atran.num_row); HighsInt counter = 0; - for (HighsInt i : nonactiveconstraintsidx) { + for (HighsInt i : non_active_constraint_index) { baseindex[counter++] = i; } - for (HighsInt i : activeconstraintidx) { + for (HighsInt i : active_constraint_index) { baseindex[counter++] = i; } @@ -67,56 +71,171 @@ void Basis::build() { basisfactor.build(); for (size_t i = 0; - i < activeconstraintidx.size() + nonactiveconstraintsidx.size(); i++) { + i < active_constraint_index.size() + non_active_constraint_index.size(); i++) { constraintindexinbasisfactor[baseindex[i]] = i; } } void Basis::rebuild() { + // report(); + updatessinceinvert = 0; constraintindexinbasisfactor.clear(); constraintindexinbasisfactor.assign(Atran.num_row + Atran.num_col, -1); - assert((HighsInt)(nonactiveconstraintsidx.size() + activeconstraintidx.size()) == + assert((HighsInt)(non_active_constraint_index.size() + active_constraint_index.size()) == Atran.num_row); basisfactor.build(); for (size_t i = 0; - i < activeconstraintidx.size() + nonactiveconstraintsidx.size(); i++) { + i < active_constraint_index.size() + non_active_constraint_index.size(); i++) { constraintindexinbasisfactor[baseindex[i]] = i; } + reinversion_hint = false; } void Basis::report() { - printf("basis: "); - for (HighsInt a_ : activeconstraintidx) { - printf("%" HIGHSINT_FORMAT " ", a_); + // + // Basis of dimension qp_num_var, analogous to primal simplex + // nonbasic variables, partitioned into + // + // * Indices of active variables/constraints, so index values in {0, + // * ..., qp_num_con-1}. These are listed in active_constraint_index + // * and, for each the value of basisstatus will be ActiveAtLower or + // * ActiveAtUpper + // + // * Indices of inactive variables, so index values in {qp_num_con, + // * ..., qp_num_con+qp_num_var-1} (or possibly also from {0, ..., + // * qp_num_con-1}?) used to complete the basis. The number of + // * inactive variables defines the dimension of the null space. + // + // Remaining qp_num_con indices may be degenerate, otherwise they + // are off their bounds. They are analogous to primal simplex basic + // variables, in that their values are solved for. + // + // Hence the correspondence between the QP basis and a HiGHS + // (simplex) basis is as follows + // + // For variables or constraints + // + // BasisStatus::kInactive: HighsBasisStatus::kBasic + // + // BasisStatus::kActiveAtLower: HighsBasisStatus::kLower + // + // BasisStatus::kActiveAtUpper: HighsBasisStatus::kUpper + // + // BasisStatus::kInactiveInBasis: HighsBasisStatus::kNonbasic + // + // + const HighsInt qp_num_var = Atran.num_row; + const HighsInt qp_num_con = Atran.num_col; + const HighsInt num_active_in_basis = active_constraint_index.size(); + const HighsInt num_inactive_in_basis = non_active_constraint_index.size(); + + HighsInt num_var_inactive = 0; + HighsInt num_var_active_at_lower = 0; + HighsInt num_var_active_at_upper = 0; + HighsInt num_var_inactive_in_basis = 0; + HighsInt num_con_inactive = 0; + HighsInt num_con_active_at_lower = 0; + HighsInt num_con_active_at_upper = 0; + HighsInt num_con_inactive_in_basis = 0; + + for (HighsInt i = 0; i < qp_num_var; i++) { + switch (basisstatus[qp_num_con + i]) { + case BasisStatus::kInactive: + num_var_inactive++; + continue; + case BasisStatus::kActiveAtLower: + num_var_active_at_lower++; + continue; + case BasisStatus::kActiveAtUpper: + num_var_active_at_upper++; + continue; + case BasisStatus::kInactiveInBasis: + num_var_inactive_in_basis++; + continue; + default: + assert(111==123); + } + } + + for (HighsInt i = 0; i < qp_num_con; i++) { + switch (basisstatus[i]) { + case BasisStatus::kInactive: + num_con_inactive++; + continue; + case BasisStatus::kActiveAtLower: + num_con_active_at_lower++; + continue; + case BasisStatus::kActiveAtUpper: + num_con_active_at_upper++; + continue; + case BasisStatus::kInactiveInBasis: + num_con_inactive_in_basis++; + continue; + default: + assert(111==123); + } } - printf(" - "); - for (HighsInt n_ : nonactiveconstraintsidx) { - printf("%" HIGHSINT_FORMAT " ", n_); + + const bool print_basis = num_inactive_in_basis + num_active_in_basis < 100; + if (print_basis) { + printf("basis: "); + for (HighsInt a_ : active_constraint_index) { + if (a_ < qp_num_con) { + printf("c%-3d ", int(a_)); + } else { + printf("v%-3d ", int(a_-qp_num_con)); + } + } + printf(" - "); + for (HighsInt n_ : non_active_constraint_index) { + if (n_ < qp_num_con) { + printf("c%-3d ", int(n_)); + } else { + printf("v%-3d ", int(n_-qp_num_con)); + } + } + printf("\n"); } - printf("\n"); + + printf("Basis::report: QP(%6d [inact %6d; act %6d], %6d)", + int(qp_num_var), int(num_inactive_in_basis), int(num_active_in_basis), + int(qp_num_con)); + printf(" (inact / lo / up / basis) for var (%6d / %6d / %6d / %6d) and con (%6d / %6d / %6d / %6d)\n", + int(num_var_inactive), + int(num_var_active_at_lower), + int(num_var_active_at_upper), + int(num_var_inactive_in_basis), + int(num_con_inactive), + int(num_con_active_at_lower), + int(num_con_active_at_upper), + int(num_con_inactive_in_basis)); + assert(qp_num_var == num_inactive_in_basis + num_active_in_basis); + assert(qp_num_con == num_var_inactive + num_con_inactive); + assert(num_inactive_in_basis == num_var_inactive_in_basis + num_con_inactive_in_basis); + assert(num_active_in_basis == num_var_active_at_lower + num_var_active_at_upper + num_con_active_at_lower + num_con_active_at_upper); } // move that constraint into V section basis (will correspond to Nullspace // from now on) void Basis::deactivate(HighsInt conid) { // printf("deact %" HIGHSINT_FORMAT "\n", conid); - assert(contains(activeconstraintidx, conid)); - basisstatus[conid] = BasisStatus::InactiveInBasis; - remove(activeconstraintidx, conid); - nonactiveconstraintsidx.push_back(conid); + assert(contains(active_constraint_index, conid)); + basisstatus[conid] = BasisStatus::kInactiveInBasis; + remove(active_constraint_index, conid); + non_active_constraint_index.push_back(conid); } QpSolverStatus Basis::activate(const Settings& settings, HighsInt conid, BasisStatus newstatus, HighsInt nonactivetoremove, Pricing* pricing) { // printf("activ %" HIGHSINT_FORMAT "\n", conid); - if (!contains(activeconstraintidx, (HighsInt)conid)) { - basisstatus[nonactivetoremove] = BasisStatus::Inactive; + if (!contains(active_constraint_index, (HighsInt)conid)) { + basisstatus[nonactivetoremove] = BasisStatus::kInactive; basisstatus[conid] = newstatus; - activeconstraintidx.push_back(conid); + active_constraint_index.push_back(conid); } else { printf("Degeneracy? constraint %" HIGHSINT_FORMAT " already in basis\n", conid); @@ -128,7 +247,7 @@ QpSolverStatus Basis::activate(const Settings& settings, HighsInt conid, BasisSt HighsInt rowtoremove = constraintindexinbasisfactor[nonactivetoremove]; baseindex[rowtoremove] = conid; - remove(nonactiveconstraintsidx, nonactivetoremove); + remove(non_active_constraint_index, nonactivetoremove); updatebasis(settings, conid, nonactivetoremove, pricing); if (updatessinceinvert != 0) { @@ -144,7 +263,8 @@ void Basis::updatebasis(const Settings& settings, HighsInt newactivecon, HighsIn return; } - HighsInt hint = 99999; + const HighsInt kHintNotChanged = 99999; + HighsInt hint = kHintNotChanged; HighsInt droppedcon_rowindex = constraintindexinbasisfactor[droppedcon]; if (buffered_p != droppedcon) { @@ -163,15 +283,15 @@ void Basis::updatebasis(const Settings& settings, HighsInt newactivecon, HighsIn basisfactor.update(&col_aq, &row_ep, &row_out, &hint); updatessinceinvert++; - if (updatessinceinvert >= settings.reinvertfrequency || hint != 99999) { - rebuild(); + if (updatessinceinvert >= settings.reinvertfrequency || hint != kHintNotChanged) { + reinversion_hint = true; } // since basis changed, buffered values are no longer valid buffered_p = -1; buffered_q = -1; } -Vector& Basis::btran(const Vector& rhs, Vector& target, bool buffer, +QpVector& Basis::btran(const QpVector& rhs, QpVector& target, bool buffer, HighsInt p) { HVector rhs_hvec = vec2hvec(rhs); basisfactor.btranCall(rhs_hvec, 1.0); @@ -188,7 +308,7 @@ Vector& Basis::btran(const Vector& rhs, Vector& target, bool buffer, return hvec2vec(rhs_hvec, target); } -Vector Basis::btran(const Vector& rhs, bool buffer, HighsInt p) { +QpVector Basis::btran(const QpVector& rhs, bool buffer, HighsInt p) { HVector rhs_hvec = vec2hvec(rhs); basisfactor.btranCall(rhs_hvec, 1.0); if (buffer) { @@ -204,7 +324,7 @@ Vector Basis::btran(const Vector& rhs, bool buffer, HighsInt p) { return hvec2vec(rhs_hvec); } -Vector& Basis::ftran(const Vector& rhs, Vector& target, bool buffer, +QpVector& Basis::ftran(const QpVector& rhs, QpVector& target, bool buffer, HighsInt q) { HVector rhs_hvec = vec2hvec(rhs); basisfactor.ftranCall(rhs_hvec, 1.0); @@ -222,7 +342,7 @@ Vector& Basis::ftran(const Vector& rhs, Vector& target, bool buffer, return hvec2vec(rhs_hvec, target); } -Vector Basis::ftran(const Vector& rhs, bool buffer, HighsInt q) { +QpVector Basis::ftran(const QpVector& rhs, bool buffer, HighsInt q) { HVector rhs_hvec = vec2hvec(rhs); basisfactor.ftranCall(rhs_hvec, 1.0); if (buffer) { @@ -238,16 +358,16 @@ Vector Basis::ftran(const Vector& rhs, bool buffer, HighsInt q) { return hvec2vec(rhs_hvec); } -Vector Basis::recomputex(const Instance& inst) { - assert((HighsInt)activeconstraintidx.size() == inst.num_var); - Vector rhs(inst.num_var); +QpVector Basis::recomputex(const Instance& inst) { + assert((HighsInt)active_constraint_index.size() == inst.num_var); + QpVector rhs(inst.num_var); for (HighsInt i = 0; i < inst.num_var; i++) { - HighsInt con = activeconstraintidx[i]; + HighsInt con = active_constraint_index[i]; if (constraintindexinbasisfactor[con] == -1) { printf("error\n"); } - if (basisstatus[con] == BasisStatus::ActiveAtLower) { + if (basisstatus[con] == BasisStatus::kActiveAtLower) { if (con < inst.num_con) { rhs.value[constraintindexinbasisfactor[con]] = inst.con_lo[con]; } else { @@ -273,46 +393,47 @@ Vector Basis::recomputex(const Instance& inst) { return hvec2vec(rhs_hvec); } -Vector& Basis::Ztprod(const Vector& rhs, Vector& target, bool buffer, +QpVector& Basis::Ztprod(const QpVector& rhs, QpVector& target, bool buffer, HighsInt q) { - Vector res_ = ftran(rhs, buffer, q); + ftran(rhs, Ztprod_res, buffer, q); target.reset(); - for (HighsInt i = 0; i < (HighsInt)nonactiveconstraintsidx.size(); i++) { - HighsInt nonactive = nonactiveconstraintsidx[i]; + for (size_t i = 0; i < non_active_constraint_index.size(); i++) { + HighsInt nonactive = non_active_constraint_index[i]; HighsInt idx = constraintindexinbasisfactor[nonactive]; - target.index[i] = i; - target.value[i] = res_.value[idx]; + target.index[i] = static_cast(i); + target.value[i] = Ztprod_res.value[idx]; } target.resparsify(); return target; } -Vector& Basis::Zprod(const Vector& rhs, Vector& target) { - Vector temp(target.dim); +QpVector& Basis::Zprod(const QpVector& rhs, QpVector& target) { + buffer_Zprod.reset(); + buffer_Zprod.dim = target.dim; for (HighsInt i = 0; i < rhs.num_nz; i++) { HighsInt nz = rhs.index[i]; - HighsInt nonactive = nonactiveconstraintsidx[nz]; + HighsInt nonactive = non_active_constraint_index[nz]; HighsInt idx = constraintindexinbasisfactor[nonactive]; - temp.index[i] = idx; - temp.value[idx] = rhs.value[nz]; + buffer_Zprod.index[i] = idx; + buffer_Zprod.value[idx] = rhs.value[nz]; } - temp.num_nz = rhs.num_nz; - return btran(temp, target); + buffer_Zprod.num_nz = rhs.num_nz; + return btran(buffer_Zprod, target); } // void Basis::write(std::string filename) { // FILE* file = fopen(filename.c_str(), "w"); -// fprintf(file, "%lu %lu\n", activeconstraintidx.size(), -// nonactiveconstraintsidx.size()); for (HighsInt i=0; -// i activeconstraintidx; + std::vector active_constraint_index; // ids of constraints that are in the basis but not active // I need to extract those columns to get Z - std::vector nonactiveconstraintsidx; + std::vector non_active_constraint_index; // ids of constraints that are in the basis std::vector baseindex; @@ -71,11 +73,10 @@ class Basis { std::vector constraintindexinbasisfactor; void build(); - void rebuild(); // buffer to avoid recreating vectors - Vector buffer_column_aq; - Vector buffer_row_ep; + QpVector buffer_column_aq; + QpVector buffer_row_ep; // buffers to prevent multiple btran/ftran HighsInt buffered_q = -1; @@ -83,22 +84,29 @@ class Basis { HVector row_ep; HVector col_aq; + bool reinversion_hint = false; public: + + Basis(Runtime& rt, std::vector active, std::vector atlower, std::vector inactive); + bool getreinversionhint() { return reinversion_hint; } + + void rebuild(); + HighsInt getnupdatessinceinvert() { return updatessinceinvert; } - HighsInt getnumactive() const { return activeconstraintidx.size(); }; + HighsInt getnumactive() const { return active_constraint_index.size(); }; - HighsInt getnuminactive() const { return nonactiveconstraintsidx.size(); }; + HighsInt getnuminactive() const { return non_active_constraint_index.size(); }; const std::vector& getactive() const { - return activeconstraintidx; + return active_constraint_index; }; const std::vector& getinactive() const { - return nonactiveconstraintsidx; + return non_active_constraint_index; }; const std::vector& getindexinfactor() const { @@ -119,24 +127,24 @@ class Basis { void updatebasis(const Settings& settings, HighsInt newactivecon, HighsInt droppedcon, Pricing* pricing); - Vector btran(const Vector& rhs, bool buffer = false, HighsInt p = -1); + QpVector btran(const QpVector& rhs, bool buffer = false, HighsInt p = -1); - Vector ftran(const Vector& rhs, bool buffer = false, HighsInt q = -1); + QpVector ftran(const QpVector& rhs, bool buffer = false, HighsInt q = -1); - Vector& btran(const Vector& rhs, Vector& target, bool buffer = false, + QpVector& btran(const QpVector& rhs, QpVector& target, bool buffer = false, HighsInt p = -1); - Vector& ftran(const Vector& rhs, Vector& target, bool buffer = false, + QpVector& ftran(const QpVector& rhs, QpVector& target, bool buffer = false, HighsInt q = -1); - Vector recomputex(const Instance& inst); + QpVector recomputex(const Instance& inst); void write(std::string filename); - Vector& Ztprod(const Vector& rhs, Vector& target, bool buffer = false, + QpVector& Ztprod(const QpVector& rhs, QpVector& target, bool buffer = false, HighsInt q = -1); - Vector& Zprod(const Vector& rhs, Vector& target); + QpVector& Zprod(const QpVector& rhs, QpVector& target); }; #endif diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 7d2a18e54c..44d06542f8 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -14,29 +14,29 @@ class DantzigPricing : public Pricing { Basis& basis; ReducedCosts& redcosts; - HighsInt chooseconstrainttodrop(const Vector& lambda) { - auto activeconstraintidx = basis.getactive(); + HighsInt chooseconstrainttodrop(const QpVector& lambda) { + auto active_constraint_index = basis.getactive(); auto constraintindexinbasisfactor = basis.getindexinfactor(); HighsInt minidx = -1; double maxabslambda = 0.0; - for (size_t i = 0; i < activeconstraintidx.size(); i++) { + for (size_t i = 0; i < active_constraint_index.size(); i++) { HighsInt indexinbasis = - constraintindexinbasisfactor[activeconstraintidx[i]]; + constraintindexinbasisfactor[active_constraint_index[i]]; if (indexinbasis == -1) { printf("error\n"); } assert(indexinbasis != -1); - if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtLower && + if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtLower && -lambda.value[indexinbasis] > maxabslambda) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxabslambda = -lambda.value[indexinbasis]; - } else if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtUpper && + } else if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtUpper && lambda.value[indexinbasis] > maxabslambda) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxabslambda = lambda.value[indexinbasis]; } else { // TODO @@ -54,13 +54,16 @@ class DantzigPricing : public Pricing { DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) : runtime(rt), basis(bas), redcosts(rc){}; - HighsInt price(const Vector& x, const Vector& gradient) { - // Vector lambda = basis.ftran(gradient); + HighsInt price(const QpVector& x, const QpVector& gradient) { HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); return minidx; } - void update_weights(const Vector& aq, const Vector& ep, HighsInt p, + void recompute() { + // do nothing + } + + void update_weights(const QpVector& aq, const QpVector& ep, HighsInt p, HighsInt q) { // does nothing } diff --git a/src/qpsolver/devexharrispricing.hpp b/src/qpsolver/devexharrispricing.hpp index 2531e65147..e86b6e4291 100644 --- a/src/qpsolver/devexharrispricing.hpp +++ b/src/qpsolver/devexharrispricing.hpp @@ -11,18 +11,18 @@ class DevexHarrisPricing : public Pricing { private: Runtime& runtime; Basis& basis; - + ReducedCosts& redcosts; std::vector weights; - HighsInt chooseconstrainttodrop(const Vector& lambda) { - auto activeconstraintidx = basis.getactive(); + HighsInt chooseconstrainttodrop(const QpVector& lambda) { + auto active_constraint_index = basis.getactive(); auto constraintindexinbasisfactor = basis.getindexinfactor(); HighsInt minidx = -1; double maxabslambda = 0.0; - for (size_t i = 0; i < activeconstraintidx.size(); i++) { + for (size_t i = 0; i < active_constraint_index.size(); i++) { HighsInt indexinbasis = - constraintindexinbasisfactor[activeconstraintidx[i]]; + constraintindexinbasisfactor[active_constraint_index[i]]; if (indexinbasis == -1) { printf("error\n"); } @@ -32,15 +32,15 @@ class DevexHarrisPricing : public Pricing { weights[indexinbasis]; if (val > maxabslambda && fabs(lambda.value[indexinbasis]) > runtime.settings.lambda_zero_threshold) { - if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtLower && + if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtLower && -lambda.value[indexinbasis] > 0) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxabslambda = val; - } else if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtUpper && + } else if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtUpper && lambda.value[indexinbasis] > 0) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxabslambda = val; } else { // TODO @@ -52,18 +52,22 @@ class DevexHarrisPricing : public Pricing { } public: - DevexHarrisPricing(Runtime& rt, Basis& bas) + DevexHarrisPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) : runtime(rt), - basis(bas), - weights(std::vector(rt.instance.num_var, 1.0)){}; + basis(bas), + redcosts(rc), + weights(std::vector(rt.instance.num_var, 1.0)) {}; - HighsInt price(const Vector& x, const Vector& gradient) { - Vector lambda = basis.ftran(gradient); - HighsInt minidx = chooseconstrainttodrop(lambda); + HighsInt price(const QpVector& x, const QpVector& gradient) { + HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); return minidx; } - void update_weights(const Vector& aq, const Vector& ep, HighsInt p, + void recompute() { + // do nothing + } + + void update_weights(const QpVector& aq, const QpVector& ep, HighsInt p, HighsInt q) { HighsInt rowindex_p = basis.getindexinfactor()[p]; double weight_p = weights[rowindex_p]; @@ -77,7 +81,7 @@ class DevexHarrisPricing : public Pricing { (aq.value[rowindex_p] * aq.value[rowindex_p]) * weight_p * weight_p); } - if (weights[i] > 10E6) { + if (weights[i] > 1e7) { weights[i] = 1.0; } } diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index 744ca3f726..ba34ac04e5 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -16,15 +16,15 @@ class DevexPricing : public Pricing { std::vector weights; - HighsInt chooseconstrainttodrop(const Vector& lambda) { - auto activeconstraintidx = basis.getactive(); + HighsInt chooseconstrainttodrop(const QpVector& lambda) { + auto active_constraint_index = basis.getactive(); auto constraintindexinbasisfactor = basis.getindexinfactor(); HighsInt minidx = -1; double maxabslambda = 0.0; - for (size_t i = 0; i < activeconstraintidx.size(); i++) { + for (size_t i = 0; i < active_constraint_index.size(); i++) { HighsInt indexinbasis = - constraintindexinbasisfactor[activeconstraintidx[i]]; + constraintindexinbasisfactor[active_constraint_index[i]]; if (indexinbasis == -1) { printf("error\n"); } @@ -34,15 +34,15 @@ class DevexPricing : public Pricing { weights[indexinbasis]; if (val > maxabslambda && fabs(lambda.value[indexinbasis]) > runtime.settings.lambda_zero_threshold) { - if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtLower && + if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtLower && -lambda.value[indexinbasis] > 0) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxabslambda = val; - } else if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtUpper && + } else if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtUpper && lambda.value[indexinbasis] > 0) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxabslambda = val; } else { // TODO @@ -67,13 +67,17 @@ class DevexPricing : public Pricing { // dual values updated as: // c_N^T += alpha_D * a_p^T (pivotal row) // alpha_D = -c_q / a_pq - HighsInt price(const Vector& x, const Vector& gradient) { - Vector& lambda = redcosts.getReducedCosts(); + HighsInt price(const QpVector& x, const QpVector& gradient) { + QpVector& lambda = redcosts.getReducedCosts(); HighsInt minidx = chooseconstrainttodrop(lambda); return minidx; } - void update_weights(const Vector& aq, const Vector& ep, HighsInt p, + void recompute() { + // do nothing + } + + void update_weights(const QpVector& aq, const QpVector& ep, HighsInt p, HighsInt q) { HighsInt rowindex_p = basis.getindexinfactor()[p]; double weight_p = weights[rowindex_p]; @@ -85,7 +89,7 @@ class DevexPricing : public Pricing { (aq.value[rowindex_p] * aq.value[rowindex_p]) * weight_p * weight_p; } - if (weights[i] > 10E6) { + if (weights[i] > 1e7) { weights[i] = 1.0; } } diff --git a/src/qpsolver/factor.hpp b/src/qpsolver/factor.hpp index f7b151a64f..9a48288aed 100644 --- a/src/qpsolver/factor.hpp +++ b/src/qpsolver/factor.hpp @@ -26,6 +26,33 @@ class CholeskyFactor { bool has_negative_eigenvalue = false; std::vector a; + void resize(HighsInt new_k_max) { + std::vector 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 < 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]; + } + } + current_k_max = new_k_max; + } + + public: + CholeskyFactor(Runtime& rt, Basis& bas) : runtime(rt), basis(bas) { + uptodate = false; + current_k_max = + max(min((HighsInt)ceil(rt.instance.num_var / 16.0), (HighsInt)1000), + basis.getnuminactive()); + L.resize(current_k_max * current_k_max); + } + + void recompute() { std::vector> orig; HighsInt dim_ns = basis.getinactive().size(); @@ -36,8 +63,8 @@ class CholeskyFactor { Matrix temp(dim_ns, 0); - Vector buffer_Qcol(runtime.instance.num_var); - Vector buffer_ZtQi(dim_ns); + QpVector buffer_Qcol(runtime.instance.num_var); + QpVector buffer_ZtQi(dim_ns); for (HighsInt i = 0; i < runtime.instance.num_var; i++) { runtime.instance.Q.mat.extractcol(i, buffer_Qcol); basis.Ztprod(buffer_Qcol, buffer_ZtQi); @@ -70,33 +97,7 @@ class CholeskyFactor { uptodate = true; } - void resize(HighsInt new_k_max) { - std::vector 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 < 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]; - } - } - current_k_max = new_k_max; - } - - public: - CholeskyFactor(Runtime& rt, Basis& bas) : runtime(rt), basis(bas) { - uptodate = false; - current_k_max = - max(min((HighsInt)ceil(rt.instance.num_var / 16.0), (HighsInt)1000), - basis.getnuminactive()); - L.resize(current_k_max * current_k_max); - } - - QpSolverStatus expand(const Vector& yp, Vector& gyp, Vector& l, Vector& m) { + QpSolverStatus expand(const QpVector& yp, QpVector& gyp, QpVector& l, QpVector& m) { if (!uptodate) { return QpSolverStatus::OK; } @@ -115,7 +116,6 @@ class CholeskyFactor { current_k++; } else { - printf("lambda = %lf\n", lambda); return QpSolverStatus::NOTPOSITIVDEFINITE; // |LL' 0| @@ -172,11 +172,16 @@ class CholeskyFactor { return QpSolverStatus::OK; } - void solveL(Vector& rhs) { + void solveL(QpVector& rhs) { if (!uptodate) { recompute(); } + if (current_k != rhs.dim) { + printf("dimension mismatch\n"); + return; + } + for (HighsInt r = 0; r < rhs.dim; r++) { for (HighsInt j = 0; j < r; j++) { rhs.value[r] -= rhs.value[j] * L[j * current_k_max + r]; @@ -187,7 +192,7 @@ class CholeskyFactor { } // solve L' u = v - void solveLT(Vector& rhs) { + void solveLT(QpVector& rhs) { for (HighsInt i = rhs.dim - 1; i >= 0; i--) { double sum = 0.0; for (HighsInt j = rhs.dim - 1; j > i; j--) { @@ -197,8 +202,8 @@ class CholeskyFactor { } } - void solve(Vector& rhs) { - if (!uptodate || (numberofreduces >= runtime.instance.num_con / 2 && + void solve(QpVector& rhs) { + if (!uptodate || (numberofreduces >= runtime.instance.num_var / 2 && !has_negative_eigenvalue)) { recompute(); } @@ -268,7 +273,7 @@ class CholeskyFactor { m[j * kmax + i] = 0.0; } - void reduce(const Vector& buffer_d, const HighsInt maxabsd, bool p_in_v) { + void reduce(const QpVector& buffer_d, const HighsInt maxabsd, bool p_in_v) { if (current_k == 0) { return; } @@ -383,7 +388,7 @@ class CholeskyFactor { HighsInt num_nz = 0; for (HighsInt i = 0; i < current_k; i++) { for (HighsInt j = 0; j < current_k; j++) { - if (fabs(L[i * current_k_max + j]) > 10e-8) { + if (fabs(L[i * current_k_max + j]) > 1e-7) { num_nz++; } } diff --git a/src/qpsolver/feasibility.hpp b/src/qpsolver/feasibility.hpp deleted file mode 100644 index 91265bb97b..0000000000 --- a/src/qpsolver/feasibility.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef __SRC_LIB_FEASIBILITY_HPP__ -#define __SRC_LIB_FEASIBILITY_HPP__ - -#include "runtime.hpp" -#include "crashsolution.hpp" -#include "feasibility_highs.hpp" -#include "feasibility_quass.hpp" - -inline -void computestartingpoint(Runtime& runtime, QpHotstartInformation& result) { - HighsTimer qp_timer = HighsTimer(); - switch (runtime.settings.phase1strategy) { - case Phase1Strategy::HIGHS: - computestartingpoint_highs(runtime.instance, runtime.settings, runtime.statistics, runtime.status, result, qp_timer); - break; - case Phase1Strategy::QUASS: - computestartingpoint_quass(runtime, result); - break; - } -} - -#endif diff --git a/src/qpsolver/feasibility_bounded.hpp b/src/qpsolver/feasibility_bounded.hpp new file mode 100644 index 0000000000..f28f5c5ca0 --- /dev/null +++ b/src/qpsolver/feasibility_bounded.hpp @@ -0,0 +1,105 @@ +#ifndef __SRC_LIB_FEASIBILITYBOUNDED_HPP__ +#define __SRC_LIB_FEASIBILITYBOUNDED_HPP__ + +#include "Highs.h" +#include "qpsolver/a_asm.hpp" +#include "qpsolver/crashsolution.hpp" + +static void computeStartingPointBounded(Instance& instance, + Settings& settings, + Statistics& stats, + QpModelStatus& modelstatus, + QpHotstartInformation& result, + HighsTimer& timer) { + // compute initial feasible point for problems with bounds only (no general linear constraints) + + // compute Qx + c = 0 --> x = Q^-1c + std::vector L; + L.resize(instance.num_var * instance.num_var); + + // compute cholesky factorization of Q + for (HighsInt col = 0; col < static_cast(instance.num_var); col++) { + for (HighsInt idx = instance.Q.mat.start[col]; idx < static_cast(instance.Q.mat.start[col+1]); idx++) { + double sum = 0; + HighsInt row = instance.Q.mat.index[idx]; + if (row == col) { + for (HighsInt k = 0; k < row; k++) + sum += L[k * instance.num_var + row] * L[k * instance.num_var + row]; + L[row * instance.num_var + row] = sqrt(instance.Q.mat.value[idx] - sum); + } else { + for (HighsInt k = 0; k < row; k++) + sum += (L[k * instance.num_var + col] * L[k * instance.num_var + row]); + L[row * instance.num_var + col] = + (instance.Q.mat.value[idx] - sum) / L[row * instance.num_var + row]; + } + } + } + + // solve for c + QpVector res = -instance.c; + for (HighsInt r = 0; r = 0; i--) { + double sum = 0.0; + for (HighsInt j = res.dim - 1; j > i; j--) { + sum += res.value[j] * L[i * instance.num_var + j]; + } + res.value[i] = (res.value[i] - sum) / L[i * instance.num_var + i]; + } + + // project solution to bounds and collect active bounds + QpVector x0(instance.num_var); + QpVector ra(instance.num_con); + std::vector initialactive; + std::vector initialinactive; + std::vector atlower; + + for (int i=0; i 0.5/settings.hessianregularizationfactor + && instance.var_up[i] == std::numeric_limits::infinity() + && instance.c.value[i] < 0.0) { + modelstatus = QpModelStatus::kUnbounded; + return; + } else if (res.value[i] < 0.5/settings.hessianregularizationfactor + && instance.var_lo[i] == std::numeric_limits::infinity() + && instance.c.value[i] > 0.0) { + modelstatus = QpModelStatus::kUnbounded; + return; + } else if (res.value[i] <= instance.var_lo[i]) { + res.value[i] = instance.var_lo[i]; + initialactive.push_back(i + instance.num_con); + atlower.push_back(BasisStatus::kActiveAtLower); + } else if (res.value[i] >= instance.var_up[i]) { + res.value[i] = instance.var_up[i]; + initialactive.push_back(i + instance.num_con); + atlower.push_back(BasisStatus::kActiveAtUpper); + } else { + initialinactive.push_back(i + instance.num_con); + } + if (fabs(res.value[i]) > 1e-4) { + x0.value[i] = res.value[i]; + x0.index[x0.num_nz++] = i; + } + } + + // if no bounds are active, solution lies in the interior -> optimal + if (initialactive.size() == 0) { + modelstatus = QpModelStatus::kOptimal; + } + + assert((HighsInt)(initialactive.size() + initialinactive.size()) == + instance.num_var); + + result.status = atlower; + result.active = initialactive; + result.inactive = initialinactive; + result.primal = x0; + result.rowact = ra; +} + +#endif diff --git a/src/qpsolver/feasibility_highs.hpp b/src/qpsolver/feasibility_highs.hpp index 3f2c6d2577..7f8daf3da4 100644 --- a/src/qpsolver/feasibility_highs.hpp +++ b/src/qpsolver/feasibility_highs.hpp @@ -5,172 +5,264 @@ #include "qpsolver/a_asm.hpp" #include "qpsolver/crashsolution.hpp" -static void computestartingpoint_highs(Instance& instance, Settings& settings, Statistics& stats, QpModelStatus& modelstatus, QpHotstartInformation& result, HighsTimer& timer) { - // compute initial feasible point - Highs highs; - - // set HiGHS to be silent - highs.setOptionValue("output_flag", false); - highs.setOptionValue("presolve", "on"); - highs.setOptionValue("time_limit", settings.timelimit - - timer.readRunHighsClock()); - - HighsLp lp; - lp.a_matrix_.index_ = - *((std::vector*)&instance.A.mat.index); - lp.a_matrix_.start_ = - *((std::vector*)&instance.A.mat.start); - lp.a_matrix_.value_ = instance.A.mat.value; - lp.a_matrix_.format_ = MatrixFormat::kColwise; - lp.col_cost_.assign(instance.num_var, 0.0); - // lp.col_cost_ = runtime.instance.c.value; - lp.col_lower_ = instance.var_lo; - lp.col_upper_ = instance.var_up; - lp.row_lower_ = instance.con_lo; - lp.row_upper_ = instance.con_up; - lp.num_col_ = instance.num_var; - lp.num_row_ = instance.num_con; - - // create artificial bounds for free variables - if (settings.phase1boundfreevars) { - for (HighsInt i=0; i::infinity() && - instance.var_up[i] == - std::numeric_limits::infinity()) { - // free variable - basis.col_status.push_back(HighsBasisStatus::kBasic); - } else { - basis.col_status.push_back(HighsBasisStatus::kNonbasic); + highs.setOptionValue("time_limit", settings.time_limit - + timer.readRunHighsClock()); + + HighsLp lp; + lp.a_matrix_.index_ = instance.A.mat.index; + lp.a_matrix_.start_ = instance.A.mat.start; + lp.a_matrix_.value_ = instance.A.mat.value; + lp.a_matrix_.format_ = MatrixFormat::kColwise; + lp.col_cost_.assign(instance.num_var, 0.0); + // lp.col_cost_ = runtime.instance.c.value; + lp.col_lower_ = instance.var_lo; + lp.col_upper_ = instance.var_up; + lp.row_lower_ = instance.con_lo; + lp.row_upper_ = instance.con_up; + lp.num_col_ = instance.num_var; + lp.num_row_ = instance.num_con; + + // create artificial bounds for free variables: false by default + assert(!settings.phase1boundfreevars); + if (settings.phase1boundfreevars) { + for (HighsInt i=0; i 10E-5) { - x0.value[i] = sol.col_value[i]; + if (fabs(use_solution.col_value[i]) > zero_activity_tolerance) { + x0.value[i] = use_solution.col_value[i]; x0.index[x0.num_nz++] = i; + } else if (fabs(use_solution.col_value[i]) > 0) { + num_small_x0++; } } for (HighsInt i = 0; i < ra.dim; i++) { - if (fabs(sol.row_value[i]) > 10E-5) { - ra.value[i] = sol.row_value[i]; + if (fabs(use_solution.row_value[i]) > zero_activity_tolerance) { + ra.value[i] = use_solution.row_value[i]; ra.index[ra.num_nz++] = i; + } else if (fabs(use_solution.row_value[i]) > 0) { + num_small_ra++; } } + if (debug_report && num_small_x0+num_small_ra) + printf("feasibility_highs has %d small col values and %d small row values\n", + int(num_small_x0), int(num_small_ra)); + std::vector initial_active; + std::vector initial_inactive; + std::vector initial_status; - std::vector initialactive; - std::vector initialinactive; - std::vector atlower; - for (HighsInt i = 0; i < (HighsInt)bas.row_status.size(); i++) { - if (bas.row_status[i] == HighsBasisStatus::kLower) { - initialactive.push_back(i); - atlower.push_back(BasisStatus::ActiveAtLower); - } else if (bas.row_status[i] == HighsBasisStatus::kUpper) { - initialactive.push_back(i); - atlower.push_back(BasisStatus::ActiveAtUpper); - } else if (bas.row_status[i] != HighsBasisStatus::kBasic) { - // printf("row %d nonbasic\n", i); - initialinactive.push_back(instance.num_con + i); + const HighsInt num_highs_basis_status = HighsInt(HighsBasisStatus::kNonbasic)+1; + std::vector debug_row_status_count; + debug_row_status_count.assign(num_highs_basis_status, 0); + for (HighsInt i = 0; i < HighsInt(use_basis.row_status.size()); i++) { + HighsBasisStatus status = use_basis.row_status[i]; + debug_row_status_count[HighsInt(status)]++; + if (status == HighsBasisStatus::kLower) { + initial_active.push_back(i); + initial_status.push_back(BasisStatus::kActiveAtLower); + } else if (status == HighsBasisStatus::kUpper) { + initial_active.push_back(i); + initial_status.push_back(BasisStatus::kActiveAtUpper); + } else if (status == HighsBasisStatus::kZero) { + // Shouldn't happen, since free rows are basic in a logical + // basis and remain basic, or are removed by presolve and + // restored as basic in postsolve + assert(111==222); + // That said, a free row that is nonbasic in the Highs basis + // must be counted as inactive in the QP basis for accounting + // purposes + initial_inactive.push_back(i); + } else if (status != HighsBasisStatus::kBasic) { + assert(status == HighsBasisStatus::kNonbasic); + // Surely an error, but not a problem before, since simplex + // solver cannot return a HighsBasisStatus::kNonbasic + // variable. Does matter now, since a saved QP basis will + // generally have such variables. + // + // initial_inactive.push_back(instance.num_con + i); + // + // A HighsBasisStatus::kNonbasic variable corresponds one-to-one + // with being inactive in the QP basis + initial_inactive.push_back(i); } else { - assert(bas.row_status[i] == HighsBasisStatus::kBasic); + assert(status == HighsBasisStatus::kBasic); } } - for (HighsInt i = 0; i < (HighsInt)bas.col_status.size(); i++) { - if (bas.col_status[i] == HighsBasisStatus::kLower) { + std::vector debug_col_status_count; + debug_col_status_count.assign(num_highs_basis_status, 0); + for (HighsInt i = 0; i < HighsInt(use_basis.col_status.size()); i++) { + HighsBasisStatus status = use_basis.col_status[i]; + debug_col_status_count[HighsInt(status)]++; + if (status == HighsBasisStatus::kLower) { if (isfreevar(instance, i)) { - initialinactive.push_back(instance.num_con + i); + initial_inactive.push_back(instance.num_con + i); } else { - initialactive.push_back(i + instance.num_con); - atlower.push_back(BasisStatus::ActiveAtLower); + initial_active.push_back(instance.num_con + i); + initial_status.push_back(BasisStatus::kActiveAtLower); } - } else if (bas.col_status[i] == HighsBasisStatus::kUpper) { + } else if (status == HighsBasisStatus::kUpper) { if (isfreevar(instance, i)) { - initialinactive.push_back(instance.num_con + i); + initial_inactive.push_back(instance.num_con + i); } else { - initialactive.push_back(i + instance.num_con); - atlower.push_back(BasisStatus::ActiveAtUpper); + initial_active.push_back(instance.num_con + i); + initial_status.push_back(BasisStatus::kActiveAtUpper); } - } else if (bas.col_status[i] == HighsBasisStatus::kZero) { - // printf("col %" HIGHSINT_FORMAT " free and set to 0 %" HIGHSINT_FORMAT - // "\n", i, (HighsInt)bas.col_status[i]); - initialinactive.push_back(instance.num_con + i); - } else if (bas.col_status[i] != HighsBasisStatus::kBasic) { - // printf("Column %" HIGHSINT_FORMAT " basis stus %" HIGHSINT_FORMAT "\n", - // i, (HighsInt)bas.col_status[i]); + } else if (status == HighsBasisStatus::kZero) { + initial_inactive.push_back(instance.num_con + i); + } else if (status != HighsBasisStatus::kBasic) { + assert(status == HighsBasisStatus::kNonbasic); + initial_inactive.push_back(instance.num_con + i); } else { - assert(bas.col_status[i] == HighsBasisStatus::kBasic); + assert(status == HighsBasisStatus::kBasic); } } - assert((HighsInt)(initialactive.size() + initialinactive.size()) == - instance.num_var); + if (debug_report) { + printf("QP solver initial basis: (Lo / Bs / Up / Ze / Nb) for cols ("); + for (HighsInt k = 0; k < num_highs_basis_status; k++) + printf("%s%d", k==0 ? "" : " / ", int(debug_col_status_count[k])); + printf(") and rows ("); + for (HighsInt k = 0; k < num_highs_basis_status; k++) + printf("%s%d", k==0 ? "" : " / ", int(debug_row_status_count[k])); + printf(")\n"); + } - for (HighsInt ia : initialinactive) { - if (ia < instance.num_con) { - printf("free row %d\n", (int)ia); - assert(instance.con_lo[ia] == - -std::numeric_limits::infinity()); - assert(instance.con_up[ia] == - std::numeric_limits::infinity()); - } else { - // printf("free col %d\n", (int)ia); - assert(instance.var_lo[ia - instance.num_con] == - -std::numeric_limits::infinity()); - assert(instance.var_up[ia - instance.num_con] == - std::numeric_limits::infinity()); + assert((HighsInt)(initial_active.size() + initial_inactive.size()) == + instance.num_var); + + if (!have_starting_point) { + // When starting from a feasible basis, there will generally be + // inactive variables in the basis that aren't free + for (HighsInt ia : initial_inactive) { + if (ia < instance.num_con) { + // printf("free row %d\n", (int)ia); + assert(instance.con_lo[ia] == -kHighsInf); + assert(instance.con_up[ia] == kHighsInf); + } else { + // printf("free col %d\n", (int)ia); + assert(instance.var_lo[ia - instance.num_con] == -kHighsInf); + assert(instance.var_up[ia - instance.num_con] == kHighsInf); + } } } - result.status = atlower; - result.active = initialactive; - result.inactive = initialinactive; + result.status = initial_status; + result.active = initial_active; + result.inactive = initial_inactive; result.primal = x0; result.rowact = ra; } diff --git a/src/qpsolver/feasibility_quass.hpp b/src/qpsolver/feasibility_quass.hpp deleted file mode 100644 index 8472570334..0000000000 --- a/src/qpsolver/feasibility_quass.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef __SRC_LIB_FEASIBILITYQUASS_HPP__ -#define __SRC_LIB_FEASIBILITYQUASS_HPP__ - -#include "qpsolver/crashsolution.hpp" -#include "qpsolver/basis.hpp" -#include "qpsolver/runtime.hpp" - -inline -void computestartingpoint_quass(Runtime& runtime, QpHotstartInformation& result) { - /* - creates and solves the feasibility problem - min - gamma - s.t. - b - gamma <= Ax <= b + gamma - l - gamma <= x <= u + gamma - gamma >= 0 - - with initial values: - compute x = l or b if possible, x = 0 for free variables - compute Ax = ra - compute initial gamma (large enough to make above system feasible) - initialactive: constraint at which gamma attains maximum, all x for bounded x - initialinactive: all x for free x - - - FTRAN: B lambda = c (== 1, 00000) - PRICE (find most negative/positive lambda not corresponding to gamma) - RATIOTEST - UPDATE (BTRAN) - */ - //Basis basis(); - - - - - - - - - - // // create artificial bounds for free variables - // if (runtime.settings.phase1boundfreevars) { - // for (HighsInt i=0; i::infinity()); - // assert(runtime.instance.con_up[ia] == - // std::numeric_limits::infinity()); - // } else { - // // printf("free col %d\n", (int)ia); - // assert(runtime.instance.var_lo[ia - runtime.instance.num_con] == - // -std::numeric_limits::infinity()); - // assert(runtime.instance.var_up[ia - runtime.instance.num_con] == - // std::numeric_limits::infinity()); - // } - // } - - // result.rowstatus = atlower; - // result.active = initialactive; - // result.inactive = initialinactive; - // result.primal = x0; - // result.rowact = ra; -} - -#endif diff --git a/src/qpsolver/gradient.hpp b/src/qpsolver/gradient.hpp index fea6a3616e..c0ec9d0807 100644 --- a/src/qpsolver/gradient.hpp +++ b/src/qpsolver/gradient.hpp @@ -2,15 +2,19 @@ #define __SRC_LIB_GRADIENT_HPP__ #include "runtime.hpp" -#include "vector.hpp" +#include "qpvector.hpp" class Gradient { Runtime& runtime; - Vector gradient; + QpVector gradient; bool uptodate; HighsInt numupdates = 0; + public: + Gradient(Runtime& rt) + : runtime(rt), gradient(QpVector(rt.instance.num_var)), uptodate(false) {} + void recompute() { runtime.instance.Q.vec_mat(runtime.primal, gradient); gradient += runtime.instance.c; @@ -18,11 +22,7 @@ class Gradient { numupdates = 0; } - public: - Gradient(Runtime& rt) - : runtime(rt), gradient(Vector(rt.instance.num_var)), uptodate(false) {} - - Vector& getGradient() { + QpVector& getGradient() { if (!uptodate || numupdates >= runtime.settings.gradientrecomputefrequency) { recompute(); @@ -30,7 +30,7 @@ class Gradient { return gradient; } - void update(Vector& buffer_Qp, double stepsize) { + void update(QpVector& buffer_Qp, double stepsize) { gradient.saxpy(stepsize, buffer_Qp); numupdates++; } diff --git a/src/qpsolver/instance.hpp b/src/qpsolver/instance.hpp index 04a9dc0b4e..060667307a 100644 --- a/src/qpsolver/instance.hpp +++ b/src/qpsolver/instance.hpp @@ -4,7 +4,7 @@ #include #include "matrix.hpp" -#include "vector.hpp" +#include "qpvector.hpp" struct SumNum { double sum = 0.0; @@ -12,10 +12,11 @@ struct SumNum { }; struct Instance { + HighsInt sense = 1; // Minimization HighsInt num_var = 0; HighsInt num_con = 0; double offset = 0; - Vector c; + QpVector c; Matrix Q; std::vector con_lo; std::vector con_up; @@ -26,16 +27,16 @@ struct Instance { Instance(HighsInt nv = 0, HighsInt nc = 0) : num_var(nv), num_con(nc), - c(Vector(nv)), + c(QpVector(nv)), Q(Matrix(nv, nv)), A(Matrix(nc, nv)) {} - double objval(const Vector& x) { + double objval(const QpVector& x) { return c * x + 0.5 * (Q.vec_mat(x) * x) + offset; } - SumNum sumnumprimalinfeasibilities(const Vector& x, - const Vector& rowactivity) { + SumNum sumnumprimalinfeasibilities(const QpVector& x, + const QpVector& rowactivity) { SumNum res; for (HighsInt row = 0; row < num_con; row++) { if (rowactivity.value[row] < con_lo[row]) { diff --git a/src/qpsolver/matrix.hpp b/src/qpsolver/matrix.hpp index d3887b606c..d727c94157 100644 --- a/src/qpsolver/matrix.hpp +++ b/src/qpsolver/matrix.hpp @@ -4,11 +4,7 @@ #include #include -#include "vector.hpp" - -#ifdef OPENMP -#include "omp.h" -#endif +#include "qpvector.hpp" struct MatrixBase { HighsInt num_row; @@ -17,11 +13,11 @@ struct MatrixBase { std::vector index; std::vector value; - Vector& mat_vec(const Vector& other, Vector& target) const { + QpVector& mat_vec(const QpVector& other, QpVector& target) const { return mat_vec_seq(other, target); } - Vector& mat_vec_seq(const Vector& other, Vector& target) const { + QpVector& mat_vec_seq(const QpVector& other, QpVector& target) const { target.reset(); for (HighsInt i = 0; i < other.num_nz; i++) { HighsInt col = other.index[i]; @@ -34,14 +30,14 @@ struct MatrixBase { return target; } - Vector mat_vec(const Vector& other) { - Vector result(num_row); + QpVector mat_vec(const QpVector& other) { + QpVector result(num_row); mat_vec(other, result); return result; } - Vector vec_mat(HighsInt* idx, double* val, HighsInt nnz) { - Vector result(num_col); + QpVector vec_mat(HighsInt* idx, double* val, HighsInt nnz) { + QpVector result(num_col); for (HighsInt i = 0; i < num_col; i++) { double dot = 0.0; // HighsInt idx_other = 0; @@ -78,11 +74,11 @@ struct MatrixBase { return result; } - Vector& vec_mat(const Vector& other, Vector& target) const { + QpVector& vec_mat(const QpVector& other, QpVector& target) const { return vec_mat_1(other, target); } - Vector& vec_mat_1(const Vector& other, Vector& target) const { + QpVector& vec_mat_1(const QpVector& other, QpVector& target) const { target.reset(); for (HighsInt col = 0; col < num_col; col++) { double dot = 0.0; @@ -96,8 +92,8 @@ struct MatrixBase { return target; } - Vector vec_mat(const Vector& other) const { - Vector result(num_col); + QpVector vec_mat(const QpVector& other) const { + QpVector result(num_col); return vec_mat(other, result); } @@ -109,8 +105,8 @@ struct MatrixBase { res.num_col = other.num_col; res.start.push_back(0); - Vector buffer_col(other.num_row); - Vector buffer_col_res(num_col); + QpVector buffer_col(other.num_row); + QpVector buffer_col_res(num_col); for (HighsInt r = 0; r < other.num_col; r++) { other.extractcol(r, buffer_col); @@ -125,7 +121,7 @@ struct MatrixBase { return res; } - Vector& extractcol(HighsInt col, Vector& target) const { + QpVector& extractcol(HighsInt col, QpVector& target) const { assert(target.dim == num_row); target.reset(); @@ -144,8 +140,8 @@ struct MatrixBase { return target; } - Vector extractcol(HighsInt col) const { - Vector res(num_row); + QpVector extractcol(HighsInt col) const { + QpVector res(num_row); return extractcol(col, res); } @@ -207,7 +203,7 @@ struct Matrix { // } } - void append(const Vector& vec) { + void append(const QpVector& vec) { if (mat.num_col == 0 && mat.start.size() == 0) { mat.start.push_back(0); } @@ -274,8 +270,8 @@ struct Matrix { Matrix mat_mat(Matrix& other) { Matrix res(mat.num_row, 0); - Vector buffer(other.mat.num_row); - Vector buffer2(mat.num_col); + QpVector buffer(other.mat.num_row); + QpVector buffer2(mat.num_col); for (HighsInt col = 0; col < other.mat.num_col; col++) { res.append(vec_mat(other.mat.extractcol(col, buffer), buffer2)); } @@ -286,27 +282,27 @@ struct Matrix { Matrix tran_mat(Matrix& other) { Matrix res(mat.num_col, 0); - Vector buffer(other.mat.num_row); - Vector buffer2(mat.num_row); + QpVector buffer(other.mat.num_row); + QpVector buffer2(mat.num_row); for (HighsInt col = 0; col < other.mat.num_col; col++) { res.append(mat_vec(other.mat.extractcol(col, buffer), buffer2)); } return res; } - Vector& mat_vec(const Vector& other, Vector& target) { + QpVector& mat_vec(const QpVector& other, QpVector& target) { return mat.mat_vec(other, target); } - Vector mat_vec(const Vector& other) { return mat.mat_vec(other); } + QpVector mat_vec(const QpVector& other) { return mat.mat_vec(other); } - Vector vec_mat(const Vector& other) const { return mat.vec_mat(other); } + QpVector vec_mat(const QpVector& other) const { return mat.vec_mat(other); } - Vector& vec_mat(const Vector& other, Vector& target) const { + QpVector& vec_mat(const QpVector& other, QpVector& target) const { return mat.vec_mat(other, target); } - Vector vec_mat(HighsInt* index, double* value, HighsInt num_nz) { + QpVector vec_mat(HighsInt* index, double* value, HighsInt num_nz) { return mat.vec_mat(index, value, num_nz); } diff --git a/src/qpsolver/perturbation.cpp b/src/qpsolver/perturbation.cpp index 153ea7f0b9..467ab3f466 100644 --- a/src/qpsolver/perturbation.cpp +++ b/src/qpsolver/perturbation.cpp @@ -8,7 +8,7 @@ void perturb(Runtime& rt) { return; } - std::uniform_real_distribution randomperturb(10E-6, 10E-5); + std::uniform_real_distribution randomperturb(1e-5, 1e-4); std::default_random_engine re; for (HighsInt i = 0; i < rt.perturbed.num_con; i++) { @@ -31,4 +31,4 @@ void perturb(Runtime& rt) { } } } -} \ No newline at end of file +} diff --git a/src/qpsolver/pricing.hpp b/src/qpsolver/pricing.hpp index 293bc505d0..884fab2e56 100644 --- a/src/qpsolver/pricing.hpp +++ b/src/qpsolver/pricing.hpp @@ -1,13 +1,14 @@ #ifndef __SRC_LIB_PRICING_HPP__ #define __SRC_LIB_PRICING_HPP__ -#include "vector.hpp" +#include "qpvector.hpp" class Pricing { public: - virtual HighsInt price(const Vector& x, const Vector& gradient) = 0; - virtual void update_weights(const Vector& aq, const Vector& ep, HighsInt p, + virtual HighsInt price(const QpVector& x, const QpVector& gradient) = 0; + virtual void update_weights(const QpVector& aq, const QpVector& ep, HighsInt p, HighsInt q) = 0; + virtual void recompute() = 0; virtual ~Pricing() {} }; diff --git a/src/qpsolver/qpconst.hpp b/src/qpsolver/qpconst.hpp index 0d8b75f60c..633ef2de37 100644 --- a/src/qpsolver/qpconst.hpp +++ b/src/qpsolver/qpconst.hpp @@ -5,20 +5,22 @@ enum class QpSolverStatus { OK, NOTPOSITIVDEFINITE, DEGENERATE }; enum class QpModelStatus { - INDETERMINED, - OPTIMAL, - UNBOUNDED, - INFEASIBLE, - ITERATIONLIMIT, - TIMELIMIT, - ERROR + kNotset, // 0 + kUndetermined, + kOptimal, + kUnbounded, + kInfeasible, + kIterationLimit, + kTimeLimit, + kLargeNullspace, + kError }; enum class BasisStatus { - Inactive, - ActiveAtLower = 1, - ActiveAtUpper, - InactiveInBasis + kInactive, + kActiveAtLower = 1, + kActiveAtUpper, + kInactiveInBasis }; diff --git a/src/qpsolver/vector.hpp b/src/qpsolver/qpvector.hpp similarity index 79% rename from src/qpsolver/vector.hpp rename to src/qpsolver/qpvector.hpp index 8aed647aa7..0c987b08fb 100644 --- a/src/qpsolver/vector.hpp +++ b/src/qpsolver/qpvector.hpp @@ -8,19 +8,19 @@ #include #include -struct Vector { +struct QpVector { HighsInt num_nz; HighsInt dim; std::vector index; std::vector value; - Vector(HighsInt d) : dim(d) { + QpVector(HighsInt d) : dim(d) { index.resize(dim); value.resize(dim, 0.0); num_nz = 0; } - Vector(const Vector& vec) + QpVector(const QpVector& vec) : num_nz(vec.num_nz), dim(vec.dim), index(vec.index), value(vec.value) {} void reset() { @@ -31,7 +31,7 @@ struct Vector { num_nz = 0; } - Vector& repopulate(const Vector& other) { + QpVector& repopulate(const QpVector& other) { reset(); for (HighsInt i = 0; i < other.num_nz; i++) { index[i] = other.index[i]; @@ -41,7 +41,7 @@ struct Vector { return *this; } - Vector& operator=(const Vector& other) { + QpVector& operator=(const QpVector& other) { num_nz = other.num_nz; dim = other.dim; index = other.index; @@ -49,7 +49,7 @@ struct Vector { return *this; } - static Vector& unit(HighsInt dim, HighsInt u, Vector& target) { + static QpVector& unit(HighsInt dim, HighsInt u, QpVector& target) { target.reset(); target.index[0] = u; target.value[u] = 1.0; @@ -57,15 +57,15 @@ struct Vector { return target; } - static Vector unit(HighsInt dim, HighsInt u) { - Vector vec(dim); + static QpVector unit(HighsInt dim, HighsInt u) { + QpVector vec(dim); vec.index[0] = u; vec.value[u] = 1.0; vec.num_nz = 1; return vec; } - void report(std::string name = "") { + void report(std::string name = "") const { if (name != "") { printf("%s: ", name.c_str()); } @@ -85,7 +85,7 @@ struct Vector { return val; } - void sanitize(double threshold = 10E-15) { + void sanitize(double threshold = 1e-14) { HighsInt new_idx = 0; for (HighsInt i = 0; i < num_nz; i++) { @@ -108,20 +108,20 @@ struct Vector { } } - Vector& scale(double a) { + QpVector& scale(double a) { for (HighsInt i = 0; i < num_nz; i++) { value[index[i]] *= a; } return *this; } - Vector& saxpy(double a, double b, const Vector& x) { + QpVector& saxpy(double a, double b, const QpVector& x) { scale(a); saxpy(b, x); return *this; } - Vector& saxpy(double a, const Vector& x) { + QpVector& saxpy(double a, const QpVector& x) { sanitize(0.0); for (HighsInt i = 0; i < x.num_nz; i++) { if (value[x.index[i]] == 0.0) { @@ -141,8 +141,8 @@ struct Vector { // resparsify(); // } - Vector operator+(const Vector& other) const { - Vector result(dim); + QpVector operator+(const QpVector& other) const { + QpVector result(dim); for (HighsInt i = 0; i < dim; i++) { result.value[i] = value[i] + other.value[i]; @@ -154,8 +154,8 @@ struct Vector { return result; } - Vector operator-(const Vector& other) const { - Vector result(dim); + QpVector operator-(const QpVector& other) const { + QpVector result(dim); for (HighsInt i = 0; i < dim; i++) { result.value[i] = value[i] - other.value[i]; @@ -167,8 +167,8 @@ struct Vector { return result; } - Vector operator-() const { - Vector result(dim); + QpVector operator-() const { + QpVector result(dim); for (HighsInt i = 0; i < num_nz; i++) { result.index[i] = index[i]; @@ -179,8 +179,8 @@ struct Vector { return result; } - Vector operator*(const double d) const { - Vector result(dim); + QpVector operator*(const double d) const { + QpVector result(dim); for (HighsInt i = 0; i < num_nz; i++) { result.index[i] = index[i]; @@ -191,7 +191,7 @@ struct Vector { return result; } - double dot(const Vector& other) const { + double dot(const QpVector& other) const { double dot = 0.0; for (HighsInt i = 0; i < num_nz; i++) { dot += value[index[i]] * other.value[index[i]]; @@ -200,7 +200,7 @@ struct Vector { return dot; } - double operator*(const Vector& other) const { return dot(other); } + double operator*(const QpVector& other) const { return dot(other); } double dot(const HighsInt* idx, const double* val, HighsInt nnz) const { double dot = 0.0; @@ -211,7 +211,7 @@ struct Vector { return dot; } - Vector& operator+=(const Vector& other) { + QpVector& operator+=(const QpVector& other) { // sanitize(); for (HighsInt i = 0; i < other.num_nz; i++) { // if (value[other.index[i]] == 0.0) { @@ -223,7 +223,7 @@ struct Vector { return *this; } - Vector& operator*=(const double d) { + QpVector& operator*=(const double d) { for (HighsInt i = 0; i < num_nz; i++) { value[index[i]] *= d; } diff --git a/src/qpsolver/quass.cpp b/src/qpsolver/quass.cpp index 6aa5b47eee..2b0f825e69 100644 --- a/src/qpsolver/quass.cpp +++ b/src/qpsolver/quass.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "Highs.h" #include "qpsolver/basis.hpp" @@ -37,7 +38,7 @@ static void loginformation(Runtime& rt, Basis& basis, CholeskyFactor& factor, Hi rt.statistics.density_nullspace.push_back(0.0); } -static void tidyup(Vector& p, Vector& rowmove, Basis& basis, Runtime& runtime) { +static void tidyup(QpVector& p, QpVector& rowmove, Basis& basis, Runtime& runtime) { for (unsigned acon : basis.getactive()) { if ((HighsInt)acon >= runtime.instance.num_con) { p.value[acon - runtime.instance.num_con] = 0.0; @@ -47,8 +48,8 @@ static void tidyup(Vector& p, Vector& rowmove, Basis& basis, Runtime& runtime) { } } -static void computerowmove(Runtime& runtime, Basis& basis, Vector& p, - Vector& rowmove) { +static void computerowmove(Runtime& runtime, Basis& basis, QpVector& p, + QpVector& rowmove) { runtime.instance.A.mat_vec(p, rowmove); return; // rowmove.reset(); @@ -62,7 +63,7 @@ static void computerowmove(Runtime& runtime, Basis& basis, Vector& p, // double val = // p.dot(&Atran.index[Atran.start[i]], &Atran.value[Atran.start[i]], // Atran.start[i + 1] - Atran.start[i]); - // // Vector col = Atran.extractcol(i); + // // QpVector col = Atran.extractcol(i); // // val = col * p; // // // assert(rowmove.value[i] == val); @@ -75,10 +76,10 @@ static void computerowmove(Runtime& runtime, Basis& basis, Vector& p, } // VECTOR -static Vector& computesearchdirection_minor(Runtime& rt, Basis& bas, +static QpVector& computesearchdirection_minor(Runtime& rt, Basis& bas, CholeskyFactor& cf, - ReducedGradient& redgrad, Vector& p) { - Vector g2 = -redgrad.get(); + ReducedGradient& redgrad, QpVector& p) { + QpVector g2 = -redgrad.get(); // TODO PERF: buffer QpVector g2.sanitize(); cf.solve(g2); @@ -88,11 +89,11 @@ static Vector& computesearchdirection_minor(Runtime& rt, Basis& bas, } // VECTOR -static Vector& computesearchdirection_major(Runtime& runtime, Basis& basis, - CholeskyFactor& factor, const Vector& yp, - Gradient& gradient, Vector& gyp, Vector& l, - Vector& m, Vector& p) { - Vector yyp = yp; +static QpVector& computesearchdirection_major(Runtime& runtime, Basis& basis, + CholeskyFactor& factor, const QpVector& yp, + Gradient& gradient, QpVector& gyp, QpVector& l, + QpVector& m, QpVector& p) { + QpVector yyp = yp; // TODO PERF: buffer QpVector // if (gradient.getGradient().dot(yp) > 0.0) { // yyp.scale(-1.0); // } @@ -101,7 +102,7 @@ static Vector& computesearchdirection_major(Runtime& runtime, Basis& basis, basis.Ztprod(gyp, m); l = m; factor.solveL(l); - Vector v = l; + QpVector v = l; // TODO PERF: buffer QpVector factor.solveLT(v); basis.Zprod(v, p); if (gradient.getGradient().dot(yyp) < 0.0) { @@ -116,10 +117,10 @@ static Vector& computesearchdirection_major(Runtime& runtime, Basis& basis, } } -static double computemaxsteplength(Runtime& runtime, const Vector& p, - Gradient& gradient, Vector& buffer_Qp, bool& zcd) { +static double computemaxsteplength(Runtime& runtime, const QpVector& p, + Gradient& gradient, QpVector& buffer_Qp, bool& zcd) { double denominator = p * runtime.instance.Q.mat_vec(p, buffer_Qp); - if (fabs(denominator) > 10E-5) { + if (fabs(denominator) > runtime.settings.pQp_zero_threshold) { double numerator = -(p * gradient.getGradient()); if (numerator < 0.0) { return 0.0; @@ -133,19 +134,19 @@ static double computemaxsteplength(Runtime& runtime, const Vector& p, } static QpSolverStatus reduce(Runtime& rt, Basis& basis, const HighsInt newactivecon, - Vector& buffer_d, HighsInt& maxabsd, + QpVector& buffer_d, HighsInt& maxabsd, HighsInt& constrainttodrop) { HighsInt idx = indexof(basis.getinactive(), newactivecon); if (idx != -1) { maxabsd = idx; constrainttodrop = newactivecon; - Vector::unit(basis.getinactive().size(), idx, buffer_d); + QpVector::unit(basis.getinactive().size(), idx, buffer_d); return QpSolverStatus::OK; // return NullspaceReductionResult(true); } // TODO: this operation is inefficient. - Vector aq = rt.instance.A.t().extractcol(newactivecon); + QpVector aq = rt.instance.A.t().extractcol(newactivecon); // TODO PERF: buffer QpVector basis.Ztprod(aq, buffer_d, true, newactivecon); maxabsd = 0; @@ -170,6 +171,9 @@ static QpSolverStatus reduce(Runtime& rt, Basis& basis, const HighsInt newactive static std::unique_ptr getPricing(Runtime& runtime, Basis& basis, ReducedCosts& redcosts) { switch (runtime.settings.pricing) { + case PricingStrategy::SteepestEdge: + return std::unique_ptr( + new SteepestEdgePricing(runtime, basis, redcosts)); case PricingStrategy::Devex: return std::unique_ptr( new DevexPricing(runtime, basis, redcosts)); @@ -197,7 +201,7 @@ static void regularize(Runtime& rt) { } #if 0 -static void compute_actual_duals(Runtime& rt, Basis& basis, Vector& lambda, Vector& dual_con, Vector& dual_var) { +static void compute_actual_duals(Runtime& rt, Basis& basis, QpVector& lambda, QpVector& dual_con, QpVector& dual_var) { for (auto e : basis.getactive()) { HighsInt indexinbasis = basis.getindexinfactor()[e]; BasisStatus status = basis.getstatus(e); @@ -205,9 +209,9 @@ static void compute_actual_duals(Runtime& rt, Basis& basis, Vector& lambda, Vect // active variable bound HighsInt var = e - rt.instance.num_con; - if (status == BasisStatus::ActiveAtUpper) { + if (status == BasisStatus::kActiveAtUpper) { dual_var.value[var] = -lambda.value[indexinbasis]; - } else if (status == BasisStatus::ActiveAtLower) { + } else if (status == BasisStatus::kActiveAtLower) { dual_var.value[var] = lambda.value[indexinbasis]; } else { assert(lambda.value[indexinbasis] == 0); @@ -215,9 +219,9 @@ static void compute_actual_duals(Runtime& rt, Basis& basis, Vector& lambda, Vect } } else { - if (status == BasisStatus::ActiveAtUpper) { + if (status == BasisStatus::kActiveAtUpper) { dual_con.value[e] = -lambda.value[indexinbasis]; - } else if (status == BasisStatus::ActiveAtLower) { + } else if (status == BasisStatus::kActiveAtLower) { dual_con.value[e] = lambda.value[indexinbasis]; } else { assert(lambda.value[indexinbasis] == 0); @@ -231,7 +235,7 @@ static void compute_actual_duals(Runtime& rt, Basis& basis, Vector& lambda, Vect static double compute_primal_violation(Runtime& rt) { double maxviolation = 0.0; - Vector rowact = rt.instance.A.mat_vec(rt.primal); + QpVector rowact = rt.instance.A.mat_vec(rt.primal); for (HighsInt i = 0; i < rt.instance.num_con; i++) { double violation = rt.instance.con_lo[i] - rowact.value[i]; maxviolation = max(violation, maxviolation); @@ -247,10 +251,10 @@ static double compute_primal_violation(Runtime& rt) { return maxviolation; } -static double compute_dual_violation(Instance& instance, Vector& primal, Vector& dual_con, Vector& dual_var) { +static double compute_dual_violation(Instance& instance, QpVector& primal, QpVector& dual_con, QpVector& dual_var) { double maxviolation = 0.0; - Vector residuals = instance.Q.mat_vec(primal) + instance.c + instance.A.t().mat_vec(dual_con) + dual_var; + QpVector residuals = instance.Q.mat_vec(primal) + instance.c + instance.A.t().mat_vec(dual_con) + dual_var; for (HighsInt i = 0; i < instance.num_var; i++) { double violation = residuals.value[i]; @@ -262,7 +266,24 @@ static double compute_dual_violation(Instance& instance, Vector& primal, Vector& } #endif -void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& timer) { +static bool check_reinvert_due(Basis& basis) { + // reinvert can be triggered by basis + return basis.getreinversionhint(); +} + +static void reinvert(Basis& basis, CholeskyFactor& factor, Gradient& grad, ReducedCosts& rc, ReducedGradient& rg, std::unique_ptr& pricing) { + basis.rebuild(); + factor.recompute(); + grad.recompute(); + rc.recompute(); + rg.recompute(); + //pricing->recompute(); +} + +void Quass::solve(const QpVector& x0, const QpVector& ra, Basis& b0, HighsTimer& timer) { + + //feenableexcept(FE_ALL_EXCEPT & ~FE_INEXACT & ~FE_UNDERFLOW); + runtime.statistics.time_start = std::chrono::high_resolution_clock::now(); Basis& basis = b0; runtime.primal = x0; @@ -278,57 +299,94 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& tim runtime.instance.A.mat_vec(runtime.primal, runtime.rowactivity); std::unique_ptr pricing = getPricing(runtime, basis, redcosts); - Vector p(runtime.instance.num_var); - Vector rowmove(runtime.instance.num_con); + QpVector p(runtime.instance.num_var); + QpVector rowmove(runtime.instance.num_con); - Vector buffer_yp(runtime.instance.num_var); - Vector buffer_gyp(runtime.instance.num_var); - Vector buffer_l(runtime.instance.num_var); - Vector buffer_m(runtime.instance.num_var); + QpVector buffer_yp(runtime.instance.num_var); + QpVector buffer_gyp(runtime.instance.num_var); + QpVector buffer_l(runtime.instance.num_var); + QpVector buffer_m(runtime.instance.num_var); - Vector buffer_Qp(runtime.instance.num_var); + QpVector buffer_Qp(runtime.instance.num_var); // buffers for reduction - Vector buffer_d(runtime.instance.num_var); + QpVector buffer_d(runtime.instance.num_var); regularize(runtime); - bool atfsep = basis.getnumactive() == runtime.instance.num_var; + runtime.relaxed_for_ratiotest = ratiotest_relax_instance(runtime); + + + HighsInt last_logging_iteration = runtime.statistics.num_iterations-1; + double last_logging_time = 0; + double logging_time_interval = 10; + + const HighsInt current_num_active = basis.getnumactive(); + bool atfsep = current_num_active == runtime.instance.num_var; while (true) { // check iteration limit - if (runtime.statistics.num_iterations >= runtime.settings.iterationlimit) { - runtime.status = QpModelStatus::ITERATIONLIMIT; + if (runtime.statistics.num_iterations >= runtime.settings.iteration_limit) { + runtime.status = QpModelStatus::kIterationLimit; break; } // check time limit - if (timer.readRunHighsClock() >= runtime.settings.timelimit) { - runtime.status = QpModelStatus::TIMELIMIT; + if (timer.readRunHighsClock() >= runtime.settings.time_limit) { + runtime.status = QpModelStatus::kTimeLimit; break; } + + if (basis.getnuminactive() > runtime.settings.nullspace_limit) { + runtime.settings.nullspace_limit_log.fire(runtime.settings.nullspace_limit); + runtime.status = QpModelStatus::kLargeNullspace; + return; + } + // LOGGING - if (runtime.statistics.num_iterations % + double run_time = timer.readRunHighsClock(); + if ((runtime.statistics.num_iterations % runtime.settings.reportingfequency == - 0) { - loginformation(runtime, basis, factor, timer); - runtime.settings.endofiterationevent.fire(runtime.statistics); + 0 || + run_time-last_logging_time > logging_time_interval) && + runtime.statistics.num_iterations > last_logging_iteration) { + bool log_report = true; + if (runtime.statistics.num_iterations > 10*runtime.settings.reportingfequency) { + runtime.settings.reportingfequency *= 10; + log_report = false; + } + if (run_time > 10*logging_time_interval) + logging_time_interval *= 2.0; + if (log_report) { + last_logging_time = run_time; + last_logging_iteration = runtime.statistics.num_iterations; + loginformation(runtime, basis, factor, timer); + runtime.settings.iteration_log.fire(runtime.statistics); + } + } + + // REINVERSION + if (check_reinvert_due(basis)) { + reinvert(basis, factor, gradient, redcosts, redgrad, pricing); } - runtime.statistics.num_iterations++; QpSolverStatus status; bool zero_curvature_direction = false; double maxsteplength = 1.0; if (atfsep) { + // Determine a variable to relax from being active. If there is + // none, then basis is optimal HighsInt minidx = pricing->price(runtime.primal, gradient.getGradient()); if (minidx == -1) { - runtime.status = QpModelStatus::OPTIMAL; + runtime.status = QpModelStatus::kOptimal; break; } + // Now perform a real iteration + runtime.statistics.num_iterations++; HighsInt unit = basis.getindexinfactor()[minidx]; - Vector::unit(runtime.instance.num_var, unit, buffer_yp); + QpVector::unit(runtime.instance.num_var, unit, buffer_yp); basis.btran(buffer_yp, buffer_yp, true, minidx); buffer_l.dim = basis.getnuminactive(); @@ -340,27 +398,33 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& tim tidyup(p, rowmove, basis, runtime); maxsteplength = std::numeric_limits::infinity(); // if (runtime.instance.Q.mat.value.size() > 0) { - double denominator = p * runtime.instance.Q.mat_vec(p, buffer_Qp); maxsteplength = computemaxsteplength(runtime, p, gradient, buffer_Qp, zero_curvature_direction); if (!zero_curvature_direction) { status = factor.expand(buffer_yp, buffer_gyp, buffer_l, buffer_m); if (status != QpSolverStatus::OK) { - runtime.status = QpModelStatus::INDETERMINED; + runtime.status = QpModelStatus::kUndetermined; return; } } redgrad.expand(buffer_yp); } else { + // Compute a search direction - which may be zero, in which case + // atfsep is set true and the loop repeats with this + // condition. In particular, this happens when the current basis + // is optimal computesearchdirection_minor(runtime, basis, factor, redgrad, p); computerowmove(runtime, basis, p, rowmove); tidyup(p, rowmove, basis, runtime); + runtime.instance.Q.mat_vec(p, buffer_Qp); } - if (p.norm2() < runtime.settings.pnorm_zero_threshold || - maxsteplength == 0.0 || fabs(gradient.getGradient().dot(p)) < runtime.settings.improvement_zero_threshold) { + maxsteplength == 0.0 || (false && fabs(gradient.getGradient().dot(p)) < runtime.settings.improvement_zero_threshold)) { atfsep = true; } else { + // Now perform a real iteration + runtime.statistics.num_iterations++; + RatiotestResult stepres = ratiotest(runtime, p, rowmove, maxsteplength); if (stepres.limitingconstraint != -1) { HighsInt constrainttodrop; @@ -368,7 +432,7 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& tim status = reduce(runtime, basis, stepres.limitingconstraint, buffer_d, maxabsd, constrainttodrop); if (status != QpSolverStatus::OK) { - runtime.status = QpModelStatus::INDETERMINED; + runtime.status = QpModelStatus::kUndetermined; return; } if (!zero_curvature_direction) { @@ -381,11 +445,11 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& tim status = basis.activate(runtime.settings, stepres.limitingconstraint, stepres.nowactiveatlower - ? BasisStatus::ActiveAtLower - : BasisStatus::ActiveAtUpper, + ? BasisStatus::kActiveAtLower + : BasisStatus::kActiveAtUpper, constrainttodrop, pricing.get()); if (status != QpSolverStatus::OK) { - runtime.status = QpModelStatus::INDETERMINED; + runtime.status = QpModelStatus::kUndetermined; return; } if (basis.getnumactive() != runtime.instance.num_var) { @@ -395,31 +459,31 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& tim if (stepres.alpha == std::numeric_limits::infinity()) { // unbounded - runtime.status = QpModelStatus::UNBOUNDED; + runtime.status = QpModelStatus::kUnbounded; return; } atfsep = false; redgrad.update(stepres.alpha, false); } - gradient.update(buffer_Qp, stepres.alpha); - redcosts.update(); - runtime.primal.saxpy(stepres.alpha, p); runtime.rowactivity.saxpy(stepres.alpha, rowmove); + + gradient.update(buffer_Qp, stepres.alpha); + redcosts.update(); } } loginformation(runtime, basis, factor, timer); - runtime.settings.endofiterationevent.fire(runtime.statistics); + runtime.settings.iteration_log.fire(runtime.statistics); + // basis.report(); runtime.instance.sumnumprimalinfeasibilities( runtime.primal, runtime.instance.A.mat_vec(runtime.primal)); - Vector lambda = redcosts.getReducedCosts(); + QpVector& lambda = redcosts.getReducedCosts(); for (auto e : basis.getactive()) { HighsInt indexinbasis = basis.getindexinfactor()[e]; - BasisStatus status = basis.getstatus(e); if (e >= runtime.instance.num_con) { // active variable bound HighsInt var = e - runtime.instance.num_con; @@ -431,8 +495,8 @@ void Quass::solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& tim runtime.dualcon.resparsify(); runtime.dualvar.resparsify(); - //Vector actual_dual_var(runtime.instance.num_var); - //Vector actual_dual_con(runtime.instance.num_con); + //QpVector actual_dual_var(runtime.instance.num_var); + //QpVector actual_dual_con(runtime.instance.num_con); //compute_actual_duals(runtime, basis, redcosts.getReducedCosts(), actual_dual_con, actual_dual_var); //printf("max primal violation = %.20lf\n", compute_primal_violation(runtime)); //printf("max dual violation = %.20lf\n", compute_dual_violation(runtime.instance, runtime.primal, actual_dual_con, actual_dual_var)); diff --git a/src/qpsolver/quass.hpp b/src/qpsolver/quass.hpp index 003efdb51b..6bffc64192 100644 --- a/src/qpsolver/quass.hpp +++ b/src/qpsolver/quass.hpp @@ -10,7 +10,7 @@ struct Quass { Quass(Runtime& rt); - void solve(const Vector& x0, const Vector& ra, Basis& b0, HighsTimer& timer); + void solve(const QpVector& x0, const QpVector& ra, Basis& b0, HighsTimer& timer); private: Runtime& runtime; diff --git a/src/qpsolver/ratiotest.cpp b/src/qpsolver/ratiotest.cpp index 7baa50f3f7..fc6828a7a3 100644 --- a/src/qpsolver/ratiotest.cpp +++ b/src/qpsolver/ratiotest.cpp @@ -10,8 +10,8 @@ static double step(double x, double p, double l, double u, double t) { } } -static RatiotestResult ratiotest_textbook(Runtime& rt, const Vector& p, - const Vector& rowmove, Instance& instance, +static RatiotestResult ratiotest_textbook(Runtime& rt, const QpVector& p, + const QpVector& rowmove, Instance& instance, const double alphastart) { RatiotestResult result; result.limitingconstraint = -1; @@ -45,8 +45,8 @@ static RatiotestResult ratiotest_textbook(Runtime& rt, const Vector& p, return result; } -static RatiotestResult ratiotest_twopass(Runtime& runtime, const Vector& p, - const Vector& rowmove, Instance& relaxed, +static RatiotestResult ratiotest_twopass(Runtime& runtime, const QpVector& p, + const QpVector& rowmove, Instance& relaxed, const double alphastart) { RatiotestResult res1 = ratiotest_textbook(runtime, p, rowmove, relaxed, alphastart); @@ -94,15 +94,9 @@ static RatiotestResult ratiotest_twopass(Runtime& runtime, const Vector& p, return result; } -RatiotestResult ratiotest(Runtime& runtime, const Vector& p, - const Vector& rowmove, double alphastart) { - switch (runtime.settings.ratiotest) { - case RatiotestStrategy::Textbook: - return ratiotest_textbook(runtime, p, rowmove, runtime.instance, - alphastart); - case RatiotestStrategy::TwoPass: - default: // to fix -Wreturn-type warning - Instance relaxed_instance = runtime.instance; + +Instance ratiotest_relax_instance(Runtime& runtime) { + Instance relaxed_instance = runtime.instance; for (double& bound : relaxed_instance.con_lo) { if (bound != -std::numeric_limits::infinity()) { bound -= runtime.settings.ratiotest_d; @@ -126,7 +120,18 @@ RatiotestResult ratiotest(Runtime& runtime, const Vector& p, bound += runtime.settings.ratiotest_d; } } - return ratiotest_twopass(runtime, p, rowmove, relaxed_instance, + return relaxed_instance; +} + +RatiotestResult ratiotest(Runtime& runtime, const QpVector& p, + const QpVector& rowmove, double alphastart) { + switch (runtime.settings.ratiotest) { + case RatiotestStrategy::Textbook: + return ratiotest_textbook(runtime, p, rowmove, runtime.instance, + alphastart); + case RatiotestStrategy::TwoPass: + default: // to fix -Wreturn-type warning + return ratiotest_twopass(runtime, p, rowmove, runtime.relaxed_for_ratiotest, alphastart); } } diff --git a/src/qpsolver/ratiotest.hpp b/src/qpsolver/ratiotest.hpp index 9a0bd3c3fc..e1aa5667be 100644 --- a/src/qpsolver/ratiotest.hpp +++ b/src/qpsolver/ratiotest.hpp @@ -11,7 +11,9 @@ struct RatiotestResult { bool nowactiveatlower; }; -RatiotestResult ratiotest(Runtime& runtime, const Vector& p, - const Vector& rowmove, double alphastart); +RatiotestResult ratiotest(Runtime& runtime, const QpVector& p, + const QpVector& rowmove, double alphastart); + +Instance ratiotest_relax_instance(Runtime& runtime); #endif diff --git a/src/qpsolver/reducedcosts.hpp b/src/qpsolver/reducedcosts.hpp index 36a165a4d5..e31e9f7ad2 100644 --- a/src/qpsolver/reducedcosts.hpp +++ b/src/qpsolver/reducedcosts.hpp @@ -4,29 +4,29 @@ #include "qpsolver/basis.hpp" #include "qpsolver/gradient.hpp" #include "qpsolver/runtime.hpp" -#include "qpsolver/vector.hpp" +#include "qpsolver/qpvector.hpp" class ReducedCosts { Basis& basis; Gradient& gradient; - Vector reducedcosts; + QpVector reducedcosts; bool uptodate; - void recompute() { - basis.ftran(gradient.getGradient(), reducedcosts); - uptodate = true; - } - public: ReducedCosts(Runtime& rt, Basis& bas, Gradient& grad) : basis(bas), gradient(grad), - reducedcosts(Vector(rt.instance.num_var)), + reducedcosts(QpVector(rt.instance.num_var)), uptodate(false) {} - Vector& getReducedCosts() { + void recompute() { + basis.ftran(gradient.getGradient(), reducedcosts); + uptodate = true; + } + + QpVector& getReducedCosts() { if (!uptodate) { recompute(); } diff --git a/src/qpsolver/reducedgradient.hpp b/src/qpsolver/reducedgradient.hpp index 0ddd11f0de..b124e29c4b 100644 --- a/src/qpsolver/reducedgradient.hpp +++ b/src/qpsolver/reducedgradient.hpp @@ -3,36 +3,36 @@ #include "qpsolver/basis.hpp" #include "qpsolver/runtime.hpp" -#include "qpsolver/vector.hpp" +#include "qpsolver/qpvector.hpp" class ReducedGradient { - Vector rg; + QpVector rg; bool uptodate = false; Gradient& gradient; Basis& basis; - void recompute() { - rg.dim = basis.getinactive().size(); - basis.Ztprod(gradient.getGradient(), rg); - uptodate = true; - } - public: ReducedGradient(Runtime& rt, Basis& bas, Gradient& grad) : rg(rt.instance.num_var), gradient(grad), basis(bas) {} - Vector& get() { + QpVector& get() { if (!uptodate) { recompute(); } return rg; } - void reduce(const Vector& buffer_d, const HighsInt maxabsd) { + void recompute() { + rg.dim = basis.getinactive().size(); + basis.Ztprod(gradient.getGradient(), rg); + uptodate = true; + } + + void reduce(const QpVector& buffer_d, const HighsInt maxabsd) { if (!uptodate) { return; } - // Vector r(rg.dim-1); + // QpVector r(rg.dim-1); // for (HighsInt col=0; col status_var; std::vector status_con; - Runtime(Instance& inst) + Runtime(Instance& inst, Statistics& stats) : instance(inst), - primal(Vector(instance.num_var)), - rowactivity(Vector(instance.num_con)), + statistics(stats), + primal(QpVector(instance.num_var)), + rowactivity(QpVector(instance.num_con)), dualvar(instance.num_var), dualcon(instance.num_con), status_var(instance.num_var), diff --git a/src/qpsolver/settings.hpp b/src/qpsolver/settings.hpp index 72e0ae2034..37d9d12e24 100644 --- a/src/qpsolver/settings.hpp +++ b/src/qpsolver/settings.hpp @@ -2,48 +2,51 @@ #define __SRC_LIB_SETTINGS_HPP__ #include "eventhandler.hpp" +#include "qpconst.hpp" #include "statistics.hpp" enum class RatiotestStrategy { TwoPass, Textbook }; -enum class PricingStrategy { DantzigWolfe, Devex }; +enum class PricingStrategy { SteepestEdge, DantzigWolfe, Devex }; -enum class OutputLevel { LIGHT, MEDIUM, HEAVY }; - -enum class Phase1Strategy { HIGHS, QUASS }; +enum class Phase1Strategy { HIGHS, QUASS, BOUNDED }; struct Settings { RatiotestStrategy ratiotest = RatiotestStrategy::TwoPass; - double ratiotest_t = 1E-9; - double ratiotest_d = 1E-8; + double ratiotest_t = 1e-9; + double ratiotest_d = 1e-8; PricingStrategy pricing = PricingStrategy::Devex; - 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 pnorm_zero_threshold = 1e-11; // if ||p|| < this threshold, p is determined to not be an improving search direction + double improvement_zero_threshold = 1e-4; // if p^t gradient < this threshold, p is determined to not be an improving search direction + double d_zero_threshold = 1e-12; // minimal value for pivot, will declare degeneracy if no larger pivot is found + double lambda_zero_threshold = 1e-9; // used for pricing / optimality checking + double pQp_zero_threshold = 1e-7; // if p'Qp < this, p is determined to not have curvature, a simplex-like iteration is performed. 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 + 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 = 1; - Eventhandler endofiterationevent; - - HighsInt reinvertfrequency = 100; - HighsInt gradientrecomputefrequency = 1; + Eventhandler iteration_log; + Eventhandler qp_model_status_log; + Eventhandler nullspace_limit_log; + + HighsInt nullspace_limit = 4000; + + HighsInt reinvertfrequency = 1000; + HighsInt gradientrecomputefrequency = 100; HighsInt reducedgradientrecomputefrequency = std::numeric_limits::infinity(); HighsInt reducedhessianrecomputefrequency = std::numeric_limits::infinity(); - HighsInt iterationlimit = std::numeric_limits::infinity(); - double timelimit = std::numeric_limits::infinity(); + HighsInt iteration_limit = std::numeric_limits::infinity(); + double time_limit = std::numeric_limits::infinity(); bool rowscaling = true; bool varscaling = true; diff --git a/src/qpsolver/steepestedgepricing.hpp b/src/qpsolver/steepestedgepricing.hpp index 27c7e9823d..0a216394ee 100644 --- a/src/qpsolver/steepestedgepricing.hpp +++ b/src/qpsolver/steepestedgepricing.hpp @@ -11,18 +11,18 @@ class SteepestEdgePricing : public Pricing { private: Runtime& runtime; Basis& basis; - + ReducedCosts& redcosts; std::vector weights; - HighsInt chooseconstrainttodrop(const Vector& lambda) { - auto activeconstraintidx = basis.getactive(); + HighsInt chooseconstrainttodrop(const QpVector& lambda) { + auto active_constraint_index = basis.getactive(); auto constraintindexinbasisfactor = basis.getindexinfactor(); HighsInt minidx = -1; double maxval = 0.0; - for (size_t i = 0; i < activeconstraintidx.size(); i++) { + for (size_t i = 0; i < active_constraint_index.size(); i++) { HighsInt indexinbasis = - constraintindexinbasisfactor[activeconstraintidx[i]]; + constraintindexinbasisfactor[active_constraint_index[i]]; if (indexinbasis == -1) { printf("error\n"); } @@ -32,15 +32,15 @@ class SteepestEdgePricing : public Pricing { weights[indexinbasis]; if (val > maxval && fabs(lambda.value[indexinbasis]) > runtime.settings.lambda_zero_threshold) { - if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtLower && + if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtLower && -lambda.value[indexinbasis] > 0) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxval = val; - } else if (basis.getstatus(activeconstraintidx[i]) == - BasisStatus::ActiveAtUpper && + } else if (basis.getstatus(active_constraint_index[i]) == + BasisStatus::kActiveAtUpper && lambda.value[indexinbasis] > 0) { - minidx = activeconstraintidx[i]; + minidx = active_constraint_index[i]; maxval = val; } else { // TODO @@ -52,35 +52,115 @@ class SteepestEdgePricing : public Pricing { } public: - SteepestEdgePricing(Runtime& rt, Basis& bas) + SteepestEdgePricing(Runtime& rt, Basis& bas, ReducedCosts& rc) : runtime(rt), basis(bas), - weights(std::vector(rt.instance.num_var, 1.0)){}; + redcosts(rc), + weights(std::vector(rt.instance.num_var, 1.0)) { + compute_exact_weights(); + }; - HighsInt price(const Vector& x, const Vector& gradient) { - Vector lambda = basis.ftran(gradient); - HighsInt minidx = chooseconstrainttodrop(lambda); + HighsInt price(const QpVector& x, const QpVector& gradient) { + HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); return minidx; } - void update_weights(const Vector& aq, const Vector& ep, HighsInt p, + + double compute_exact_weight(HighsInt i) { + QpVector y_i = basis.btran(QpVector::unit(runtime.instance.num_var, i)); + return y_i.dot(y_i); + } + + void compute_exact_weights() { + for (int i=0; i 1e-2) { + //printf("weights[%d] = %lf, should be %lf\n", i, weight_is, weight_comp); + return false; + } + return true; + } + + bool check_weights() { + + std::vector correct_weights; + std::vector incorrect_weights; + for (int i=0; i= 1e-2) { + // printf("old weight[p] discrepancy: updated = %lf, computed=%lf\n", old_weight_p_updated, old_weight_p_computed); + //} - Vector v = basis.btran(aq); + double weight_p = old_weight_p_computed; - double weight_p = weights[rowindex_p]; + + double t_p = aq.value[rowindex_p]; for (HighsInt i = 0; i < runtime.instance.num_var; i++) { - if (i == rowindex_p) { - weights[i] = weight_p / (aq.value[rowindex_p] * aq.value[rowindex_p]); - } else { - weights[i] = weights[i] - - 2 * (aq.value[i] / aq.value[rowindex_p]) * (v.value[i]) + - (aq.value[i] * aq.value[i]) / - (aq.value[rowindex_p] * aq.value[rowindex_p]) * - weight_p; + if (i != rowindex_p) { + double t_i = aq.value[i]; + weights[i] = weights[i] + - 2 * (t_i / t_p) * delta.value[i] + + ((t_i * t_i) / (t_p * t_p)) * weight_p; + //printf("weights[%d] = %lf\n", i, weights[i]); } } + //QpVector new_ep = basis.btran(QpVector::unit(runtime.instance.num_var, rowindex_p)); + //double computed_weight = new_ep.dot(new_ep); + double new_weight_p_updated = weight_p / (t_p * t_p); + + //if (fabs(updated_weight - computed_weight) > 1e-4) { + // printf("updated weight %lf vs computed weight %lf. aq[p] = %lf\n", updated_weight, computed_weight, t_p); + // printf("old weight = %lf, aq[p] = %lf, ^2 = %lf, new weight = %lf\n", weight_p, t_p, t_p*t_p, updated_weight); + //} + weights[rowindex_p] = new_weight_p_updated; } }; diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index fe3ac561ec..9e1be51f85 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index 11caaf831f..b94d0959f6 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -632,17 +632,14 @@ HighsStatus HEkk::dualize() { extra_columns.num_row_ = original_num_col_; HighsInt num_upper_bound_col = upper_bound_col_.size(); HighsInt num_upper_bound_row = upper_bound_row_.size(); - HighsInt num_extra_col = 0; double one = 1; for (HighsInt iX = 0; iX < num_upper_bound_col; iX++) { HighsInt iCol = upper_bound_col_[iX]; - const double lower = original_col_lower_[iCol]; const double upper = original_col_upper_[iCol]; extra_columns.addVec(1, &iCol, &one); lp_.col_cost_.push_back(upper); lp_.col_lower_.push_back(-inf); lp_.col_upper_.push_back(0); - num_extra_col++; } if (num_upper_bound_row) { @@ -748,7 +745,7 @@ HighsStatus HEkk::dualize() { lp_.scale_.num_row = dual_num_row; } } - // Change optimzation sense + // Change optimization sense if (lp_.sense_ == ObjSense::kMinimize) { lp_.sense_ = ObjSense::kMaximize; } else { @@ -787,8 +784,8 @@ HighsStatus HEkk::undualize() { HighsInt dual_num_col = lp_.num_col_; HighsInt primal_num_tot = original_num_col_ + original_num_row_; // These two aren't used (yet) - vector& dual_work_dual = info_.workDual_; - vector& primal_work_value = info_.workValue_; + // vector& dual_work_dual = info_.workDual_; + // vector& primal_work_value = info_.workValue_; // Take copies of the nonbasic information for the dual LP, since // its values will be over-written in constructing the corresponding // data for the primal problem @@ -800,7 +797,6 @@ HighsStatus HEkk::undualize() { basis_.nonbasicFlag_.assign(primal_num_tot, kIllegalFlagValue); basis_.nonbasicMove_.assign(primal_num_tot, kIllegalMoveValue); basis_.basicIndex_.resize(0); - const double inf = kHighsInf; // The number of dual rows is the number of primal columns, so all // dual basic variables are nonbasic in the primal problem. // @@ -820,10 +816,9 @@ HighsStatus HEkk::undualize() { // boxed variables/constraints HighsInt upper_bound_col = original_num_row_; for (HighsInt iCol = 0; iCol < original_num_col_; iCol++) { - const double cost = original_col_cost_[iCol]; const double lower = original_col_lower_[iCol]; const double upper = original_col_upper_[iCol]; - HighsInt move = kIllegalMoveValue; + int8_t move = kIllegalMoveValue; HighsInt dual_variable = dual_num_col + iCol; bool dual_basic = dual_nonbasic_flag[dual_variable] == kNonbasicFlagFalse; if (lower == upper) { @@ -878,7 +873,7 @@ HighsStatus HEkk::undualize() { for (HighsInt iRow = 0; iRow < original_num_row_; iRow++) { double lower = original_row_lower_[iRow]; double upper = original_row_upper_[iRow]; - HighsInt move = kIllegalMoveValue; + int8_t move = kIllegalMoveValue; HighsInt dual_variable = iRow; bool dual_basic = dual_nonbasic_flag[dual_variable] == kNonbasicFlagFalse; if (lower == upper) { @@ -949,7 +944,7 @@ HighsStatus HEkk::undualize() { lp_.scale_.num_row = original_num_row_; } } - // Change optimzation sense + // Change optimization sense if (lp_.sense_ == ObjSense::kMinimize) { lp_.sense_ = ObjSense::kMaximize; } else { @@ -967,7 +962,7 @@ HighsStatus HEkk::undualize() { lp_.row_lower_ = original_row_lower_; lp_.row_upper_ = original_row_upper_; // The primal constraint matrix is available row-wise as the first - // original_num_row_ vectors of the dual constratint matrix + // original_num_row_ vectors of the dual constraint matrix HighsSparseMatrix primal_matrix; primal_matrix.start_.resize(original_num_row_ + 1); primal_matrix.index_.resize(original_num_nz_); @@ -1154,7 +1149,6 @@ HighsStatus HEkk::setBasis() { // Set up nonbasicFlag and basicIndex for a logical basis const HighsInt num_col = lp_.num_col_; const HighsInt num_row = lp_.num_row_; - const HighsInt num_tot = num_col + num_row; basis_.setup(num_col, num_row); basis_.debug_origin_name = "HEkk::setBasis - logical"; @@ -1163,7 +1157,7 @@ HighsStatus HEkk::setBasis() { basis_.nonbasicFlag_[iCol] = kNonbasicFlagTrue; double lower = lp_.col_lower_[iCol]; double upper = lp_.col_upper_[iCol]; - HighsInt move = kIllegalMoveValue; + int8_t move = kIllegalMoveValue; if (lower == upper) { // Fixed move = kNonbasicMoveZe; @@ -1224,7 +1218,6 @@ HighsStatus HEkk::setBasis(const HighsBasis& highs_basis) { } HighsInt num_col = lp_.num_col_; HighsInt num_row = lp_.num_row_; - HighsInt num_tot = num_col + num_row; // Set up the basis in case it has not yet been done for this LP basis_.setup(num_col, num_row); basis_.debug_id = highs_basis.debug_id; @@ -1553,7 +1546,7 @@ HighsStatus HEkk::initialiseSimplexLpBasisAndFactor( "Supposed to be a full-rank basis, but incorrect\n"); return HighsStatus::kError; } - // Account for rank deficiency by correcing nonbasicFlag + // Account for rank deficiency by correcting nonbasicFlag handleRankDeficiency(); this->updateStatus(LpAction::kNewBasis); setNonbasicMove(); @@ -1572,7 +1565,6 @@ void HEkk::handleRankDeficiency() { HFactor& factor = simplex_nla_.factor_; HighsInt rank_deficiency = factor.rank_deficiency; vector& row_with_no_pivot = factor.row_with_no_pivot; - vector& col_with_no_pivot = factor.col_with_no_pivot; vector& var_with_no_pivot = factor.var_with_no_pivot; for (HighsInt k = 0; k < rank_deficiency; k++) { HighsInt row_in = row_with_no_pivot[k]; @@ -1764,7 +1756,7 @@ void HEkk::chooseSimplexStrategyThreads(const HighsOptions& options, simplex_strategy = kSimplexStrategyDualMulti; } // - // If parallel stratgies are used, the minimum concurrency will be + // If parallel strategies are used, the minimum concurrency will be // set to be at least the minimum required for the strategy // // All this is independent of the number of threads available, since @@ -1910,8 +1902,8 @@ bool HEkk::getNonsingularInverse(const HighsInt solve_phase) { bool HEkk::getBacktrackingBasis() { if (!info_.valid_backtracking_basis_) return false; basis_ = info_.backtracking_basis_; - info_.costs_shifted = info_.backtracking_basis_costs_shifted_; - info_.costs_perturbed = info_.backtracking_basis_costs_perturbed_; + info_.costs_shifted = (info_.backtracking_basis_costs_shifted_ != 0); + info_.costs_perturbed = (info_.backtracking_basis_costs_perturbed_ != 0); info_.workShift_ = info_.backtracking_basis_workShift_; const HighsInt num_tot = lp_.num_col_ + lp_.num_row_; for (HighsInt iVar = 0; iVar < num_tot; iVar++) @@ -2363,7 +2355,7 @@ void HEkk::setNonbasicMove() { lower = -lp_.row_upper_[iRow]; upper = -lp_.row_lower_[iRow]; } - HighsInt move = kIllegalMoveValue; + int8_t move = kIllegalMoveValue; if (lower == upper) { // Fixed move = kNonbasicMoveZe; @@ -2544,7 +2536,7 @@ void HEkk::initialiseCost(const SimplexAlgorithm algorithm, double upper = lp_.col_upper_[i]; double xpert = (1 + info_.numTotRandomValue_[i]) * (fabs(info_.workCost_[i]) + 1) * cost_perturbation_base_; - const double previous_cost = info_.workCost_[i]; + // const double previous_cost = info_.workCost_[i]; if (lower <= -kHighsInf && upper >= kHighsInf) { // Free - no perturb } else if (upper >= kHighsInf) { // Lower @@ -2740,9 +2732,9 @@ void HEkk::initialiseNonbasicValueAndMove() { // Nonbasic variable const double lower = info_.workLower_[iVar]; const double upper = info_.workUpper_[iVar]; - const HighsInt original_move = basis_.nonbasicMove_[iVar]; + const int8_t original_move = basis_.nonbasicMove_[iVar]; double value; - HighsInt move = kIllegalMoveValue; + int8_t move = kIllegalMoveValue; if (lower == upper) { // Fixed value = lower; @@ -2956,7 +2948,7 @@ void HEkk::computePrimal() { info_.baseLower_[i] = info_.workLower_[iCol]; info_.baseUpper_[i] = info_.workUpper_[iCol]; } - // Indicate that the primal infeasiblility information isn't known + // Indicate that the primal infeasibility information isn't known info_.num_primal_infeasibilities = kHighsIllegalInfeasibilityCount; info_.max_primal_infeasibility = kHighsIllegalInfeasibilityMeasure; info_.sum_primal_infeasibilities = kHighsIllegalInfeasibilityMeasure; @@ -3004,7 +2996,7 @@ void HEkk::computeDual() { debugSimplexDualInfeasible("(new duals)", true); } } - // Indicate that the dual infeasiblility information isn't known + // Indicate that the dual infeasibility information isn't known info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; @@ -3227,16 +3219,16 @@ void HEkk::updateMatrix(const HighsInt variable_in, void HEkk::computeInfeasibilitiesForReporting(const SimplexAlgorithm algorithm, const HighsInt solve_phase) { if (algorithm == SimplexAlgorithm::kPrimal) { - // Report the primal and dual infeasiblities + // Report the primal and dual infeasibilities computeSimplexInfeasible(); } else { - // Report the primal infeasiblities + // Report the primal infeasibilities computeSimplexPrimalInfeasible(); if (solve_phase == kSolvePhase1) { - // In phase 1, report the simplex LP dual infeasiblities + // In phase 1, report the simplex LP dual infeasibilities computeSimplexLpDualInfeasible(); } else { - // In phase 2, report the simplex dual infeasiblities + // In phase 2, report the simplex dual infeasibilities computeSimplexDualInfeasible(); } } @@ -3248,7 +3240,7 @@ void HEkk::computeSimplexInfeasible() { } void HEkk::computeSimplexPrimalInfeasible() { - // Computes num/max/sum of primal infeasibliities according to the + // Computes num/max/sum of primal infeasibilities according to the // simplex bounds. This is used to determine optimality in dual // phase 1 and dual phase 2, albeit using different bounds in // workLower/Upper. @@ -3350,7 +3342,7 @@ void HEkk::computeSimplexDualInfeasible() { } void HEkk::computeSimplexLpDualInfeasible() { - // Compute num/max/sum of dual infeasibliities according to the + // Compute num/max/sum of dual infeasibilities according to the // bounds of the simplex LP. Assumes that boxed variables have // primal variable at the bound corresponding to the sign of the // dual so should only be used in dual phase 1 - where it's only @@ -3537,13 +3529,13 @@ HighsStatus HEkk::returnFromSolve(const HighsStatus return_status) { switch (model_status_) { case HighsModelStatus::kOptimal: { if (info_.num_primal_infeasibilities) { - // Optimal - but not to desired primal feasibilit tolerance + // Optimal - but not to desired primal feasibility tolerance return_primal_solution_status_ = kSolutionStatusInfeasible; } else { return_primal_solution_status_ = kSolutionStatusFeasible; } if (info_.num_dual_infeasibilities) { - // Optimal - but not to desired dual feasibilit tolerance + // Optimal - but not to desired dual feasibility tolerance return_dual_solution_status_ = kSolutionStatusInfeasible; } else { return_dual_solution_status_ = kSolutionStatusFeasible; @@ -3596,7 +3588,7 @@ HighsStatus HEkk::returnFromSolve(const HighsStatus return_status) { case HighsModelStatus::kInterrupt: case HighsModelStatus::kUnknown: { // Simplex has failed to conclude a model property. Either it - // has bailed out due to reaching the objecive bound, target, + // has bailed out due to reaching the objective bound, target, // time, iteration limit or user interrupt, or it has not been // set (cycling is the only reason). Could happen anywhere. // diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index f437cd8ebf..6384e22c10 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -25,7 +25,41 @@ class HighsLpSolverObject; class HEkk { public: - HEkk() {} + HEkk() + : callback_(nullptr), + options_(nullptr), + timer_(nullptr), + lp_name_(""), + model_status_(HighsModelStatus::kNotset), + simplex_in_scaled_space_(false), + cost_scale_(1.0), + cost_perturbation_base_(0.0), + cost_perturbation_max_abs_cost_(0.0), + iteration_count_(0), + dual_simplex_cleanup_level_(0), + dual_simplex_phase1_cleanup_level_(0), + previous_iteration_cycling_detected(-kHighsIInf), + solve_bailout_(false), + called_return_from_solve_(false), + exit_algorithm_(SimplexAlgorithm::kNone), + return_primal_solution_status_(0), + return_dual_solution_status_(0), + original_num_col_(0), + original_num_row_(0), + original_num_nz_(0), + original_offset_(0.0), + edge_weight_error_(0.0), + build_synthetic_tick_(0.0), + total_synthetic_tick_(0.0), + debug_solve_call_num_(0), + debug_basis_id_(0), + time_report_(false), + debug_initial_build_synthetic_tick_(0), + debug_solve_report_(false), + debug_iteration_report_(false), + debug_basis_report_(false), + debug_dual_feasible(false), + debug_max_relative_dual_steepest_edge_weight_error(0) {} /** * @brief Interface to simplex solvers */ @@ -155,14 +189,14 @@ class HEkk { HSimplexNla simplex_nla_; HotStart hot_start_; - double cost_scale_ = 1; + double cost_scale_; double cost_perturbation_base_; double cost_perturbation_max_abs_cost_; - HighsInt iteration_count_ = 0; - HighsInt dual_simplex_cleanup_level_ = 0; - HighsInt dual_simplex_phase1_cleanup_level_ = 0; + HighsInt iteration_count_; + HighsInt dual_simplex_cleanup_level_; + HighsInt dual_simplex_phase1_cleanup_level_; - HighsInt previous_iteration_cycling_detected = -kHighsIInf; + HighsInt previous_iteration_cycling_detected; bool solve_bailout_; bool called_return_from_solve_; @@ -197,17 +231,17 @@ class HEkk { double edge_weight_error_; - double build_synthetic_tick_ = 0; - double total_synthetic_tick_ = 0; - HighsInt debug_solve_call_num_ = 0; - HighsInt debug_basis_id_ = 0; - bool time_report_ = false; - HighsInt debug_initial_build_synthetic_tick_ = 0; - bool debug_solve_report_ = false; - bool debug_iteration_report_ = false; - bool debug_basis_report_ = false; - bool debug_dual_feasible = false; - double debug_max_relative_dual_steepest_edge_weight_error = 0; + double build_synthetic_tick_; + double total_synthetic_tick_; + HighsInt debug_solve_call_num_; + HighsInt debug_basis_id_; + bool time_report_; + HighsInt debug_initial_build_synthetic_tick_; + bool debug_solve_report_; + bool debug_iteration_report_; + bool debug_basis_report_; + bool debug_dual_feasible; + double debug_max_relative_dual_steepest_edge_weight_error; std::vector bad_basis_change_; diff --git a/src/simplex/HEkkControl.cpp b/src/simplex/HEkkControl.cpp index aa32e0b66f..ae714d3a94 100644 --- a/src/simplex/HEkkControl.cpp +++ b/src/simplex/HEkkControl.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HEkkDebug.cpp b/src/simplex/HEkkDebug.cpp index deefab70dc..9bf740cf26 100644 --- a/src/simplex/HEkkDebug.cpp +++ b/src/simplex/HEkkDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -354,7 +354,7 @@ HighsDebugStatus HEkk::debugSimplex(const std::string message, assert(!nonbasicFlag_error); return HighsDebugStatus::kLogicalError; } - bool nonbasicMove_error = basis.nonbasicMove_[iVar]; + bool nonbasicMove_error = (basis.nonbasicMove_[iVar] != 0); if (nonbasicMove_error) { highsLogDev(options.log_options, HighsLogType::kError, "HEkk::debugSimplex - %s: Iteration %" HIGHSINT_FORMAT @@ -397,7 +397,7 @@ HighsDebugStatus HEkk::debugSimplex(const std::string message, if (algorithm == SimplexAlgorithm::kPrimal && phase == 1) { double primal_phase1_cost = bound_violated; if (base) primal_phase1_cost *= 1 + base * info.numTotRandomValue_[iRow]; - bool primal_phase1_cost_error = abs(cost - primal_phase1_cost); + bool primal_phase1_cost_error = (abs(cost - primal_phase1_cost) != 0.0); if (primal_phase1_cost_error) { highsLogDev(options.log_options, HighsLogType::kError, "HEkk::debugSimplex - %s: Iteration %" HIGHSINT_FORMAT @@ -953,7 +953,7 @@ HighsDebugStatus HEkk::debugNonbasicMove(const HighsLp* pass_lp) const { HighsInt num_fixed_variable_move_errors = 0; HighsInt num_col; HighsInt num_row; - const bool use_pass_lp = pass_lp; + const bool use_pass_lp = (pass_lp != nullptr); if (use_pass_lp) { num_col = pass_lp->num_col_; num_row = pass_lp->num_row_; diff --git a/src/simplex/HEkkDual.cpp b/src/simplex/HEkkDual.cpp index 6dcb485ef3..6a6ca9d13e 100644 --- a/src/simplex/HEkkDual.cpp +++ b/src/simplex/HEkkDual.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -67,7 +67,7 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { // Record whether the solution with unperturbed costs is dual feasible const bool dual_feasible_with_unperturbed_costs = info.num_dual_infeasibilities == 0; - // Force phase 2 if dual infeasiblilities without cost perturbation + // Force phase 2 if dual infeasibilities without cost perturbation // involved fixed variables or were (at most) small force_phase2 = pass_force_phase2 || info.max_dual_infeasibility * info.max_dual_infeasibility < @@ -98,24 +98,19 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { } // Determine whether the solution is near-optimal. Values 1000 and // 1e-3 (ensuring sum<1) are unimportant, as the sum of primal - // infeasiblilities for near-optimal solutions is typically many + // infeasibilities for near-optimal solutions is typically many // orders of magnitude smaller than 1, and the sum of primal - // infeasiblilities will be very much larger for non-trivial LPs + // infeasibilities will be very much larger for non-trivial LPs // that are dual feasible for a logical or crash basis. // // Consider there to be no dual infeasibilities if there are none, // or if phase 2 is forced, in which case any dual infeasibilities - // will be shifed + // will be shifted const bool no_simplex_dual_infeasibilities = dual_feasible_with_unperturbed_costs || force_phase2; const bool near_optimal = no_simplex_dual_infeasibilities && info.num_primal_infeasibilities < 1000 && info.max_primal_infeasibility < 1e-3; - // For reporting, save dual infeasibility data for the LP without - // cost perturbations - HighsInt unperturbed_num_infeasibilities = info.num_dual_infeasibilities; - double unperturbed_max_infeasibility = info.max_dual_infeasibility; - double unperturbed_sum_infeasibilities = info.sum_dual_infeasibilities; if (near_optimal) highsLogDev(options.log_options, HighsLogType::kDetailed, "Dual feasible with unperturbed costs and num / max / sum " @@ -126,7 +121,7 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { info.num_primal_infeasibilities, info.max_primal_infeasibility, info.sum_primal_infeasibilities); - // Perturb costs according to whether the solution is near-optimnal + // Perturb costs according to whether the solution is near-optimal const bool perturb_costs = !near_optimal; if (!perturb_costs) highsLogDev(options.log_options, HighsLogType::kDetailed, @@ -204,7 +199,7 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { // Determine the solve phase if (force_phase2) { - // Dual infeasiblilities without cost perturbation involved + // Dual infeasibilities without cost perturbation involved // fixed variables or were (at most) small, so can easily be // removed by flips for fixed variables and shifts for the rest solve_phase = kSolvePhase2; @@ -310,7 +305,7 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { solve_phase == kSolvePhaseOptimalCleanup || solve_phase == kSolvePhasePrimalInfeasibleCleanup); // Can't be solve_phase == kSolvePhase1 since this requires simplex - // solver to have continued after identifying dual infeasiblility. + // solver to have continued after identifying dual infeasibility. if (solve_phase == kSolvePhaseOptimalCleanup || solve_phase == kSolvePhasePrimalInfeasibleCleanup) { ekk_instance_.dual_simplex_cleanup_level_++; @@ -320,7 +315,7 @@ HighsStatus HEkkDual::solve(const bool pass_force_phase2) { // known is that cost shifting was required to get dual // feasibility after removing cost perturbations, and dual // simplex iterations may also have been done. This is unlike - // clean-up of dual infeasiblilties after suspected optimality, + // clean-up of dual infeasibilities after suspected optimality, // when no shifting and dual simplex iterations are done after // removing cost perturbations. // @@ -503,8 +498,6 @@ void HEkkDual::initSlice(const HighsInt initial_num_slice) { // Alias to the matrix 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 @@ -601,7 +594,7 @@ void HEkkDual::solvePhase1() { HighsModelStatus& model_status = ekk_instance_.model_status_; // When starting a new phase the (updated) dual objective function // value isn't known. Indicate this so that when the value computed - // from scratch in build() isn't checked against the the updated + // from scratch in build() isn't checked against the updated // value status.has_primal_objective_value = false; status.has_dual_objective_value = false; @@ -663,7 +656,7 @@ void HEkkDual::solvePhase1() { } if (ekk_instance_.solve_bailout_) break; // If the data are fresh from rebuild(), possibly break out of the - // outer loop to see what's ocurred + // outer loop to see what's occurred // // Deciding whether to rebuild is now more complicated if // refactorization is being avoided, since @@ -729,6 +722,17 @@ void HEkkDual::solvePhase1() { // chooseColumn has failed or excessive primal values have been // created Behave as "Report strange issues" below solve_phase = kSolvePhaseError; + // Solve error is opaque to users, so put in some logging + if (rebuild_reason == kRebuildReasonChooseColumnFail) { + highsLogUser( + ekk_instance_.options_->log_options, HighsLogType::kError, + "Dual simplex ratio test failed due to excessive dual values: " + "consider scaling down the LP objective coefficients\n"); + } else { + highsLogUser(ekk_instance_.options_->log_options, HighsLogType::kError, + "Dual simplex detected excessive primal values: consider " + "scaling down the LP bounds\n"); + } highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, "dual-phase-1-not-solved\n"); model_status = HighsModelStatus::kSolveError; @@ -850,7 +854,7 @@ void HEkkDual::solvePhase2() { HighsModelStatus& model_status = ekk_instance_.model_status_; // When starting a new phase the (updated) dual objective function // value isn't known. Indicate this so that when the value computed - // from scratch in build() isn't checked against the the updated + // from scratch in build() isn't checked against the updated // value status.has_primal_objective_value = false; status.has_dual_objective_value = false; @@ -924,7 +928,7 @@ void HEkkDual::solvePhase2() { } if (ekk_instance_.solve_bailout_) break; // If the data are fresh from rebuild(), possibly break out of the - // outer loop to see what's ocurred + // outer loop to see what's occurred bool finished = status.has_fresh_rebuild && !ekk_instance_.rebuildRefactor(rebuild_reason); // ToDo: Handle the following more elegantly as the first case of @@ -949,7 +953,7 @@ void HEkkDual::solvePhase2() { assert(!ekk_instance_.solve_bailout_); // Assess outcome of dual phase 2 if (dualInfeasCount > 0) { - // There are dual infeasiblities so possibly switch to Phase 1 and + // There are dual infeasibilities so possibly switch to Phase 1 and // return. "Possibly" because, if dual infeasibility has already // been shown, primal simplex is used to distinguish primal // unboundedness from primal infeasibility @@ -963,11 +967,11 @@ void HEkkDual::solvePhase2() { // Remove any cost perturbations and see if basis is still dual feasible cleanup(); if (dualInfeasCount > 0) { - // There are dual infeasiblities, so consider performing primal + // There are dual infeasibilities, so consider performing primal // simplex iterations to get dual feasibility solve_phase = kSolvePhaseOptimalCleanup; } else { - // There are no dual infeasiblities so optimal! + // There are no dual infeasibilities so optimal! solve_phase = kSolvePhaseOptimal; highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed, "problem-optimal\n"); @@ -978,11 +982,22 @@ void HEkkDual::solvePhase2() { // chooseColumn has failed or excessive primal values have been // created Behave as "Report strange issues" below solve_phase = kSolvePhaseError; + // Solve error is opaque to users, so put in some logging + if (rebuild_reason == kRebuildReasonChooseColumnFail) { + highsLogUser( + ekk_instance_.options_->log_options, HighsLogType::kError, + "Dual simplex ratio test failed due to excessive dual values: " + "consider scaling down the LP objective coefficients\n"); + } else { + highsLogUser(ekk_instance_.options_->log_options, HighsLogType::kError, + "Dual simplex detected excessive primal values: consider " + "scaling down the LP bounds\n"); + } highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, "dual-phase-2-not-solved\n"); model_status = HighsModelStatus::kSolveError; } else { - // Can only be that primal infeasiblility has been detected + // Can only be that primal infeasibility has been detected assert(model_status == HighsModelStatus::kInfeasible); assert(solve_phase == kSolvePhaseExit); highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, @@ -1048,7 +1063,7 @@ void HEkkDual::rebuild() { // Note that computePrimalObjectiveValue sets // has_primal_objective_value const bool check_updated_objective_value = status.has_dual_objective_value; - double previous_dual_objective_value; + double previous_dual_objective_value = -kHighsInf; if (check_updated_objective_value) { // debugUpdatedObjectiveValue(ekk_instance_, algorithm, solve_phase, // "Before computeDual"); @@ -1104,10 +1119,10 @@ void HEkkDual::rebuild() { ekk_instance_.resetSyntheticClock(); // Dual simplex doesn't maintain the number of primal - // infeasiblities, so set it to an illegal value now + // infeasibilities, so set it to an illegal value now ekk_instance_.invalidatePrimalInfeasibilityRecord(); // Although dual simplex should always be dual feasible, - // infeasiblilities are only corrected in rebuild + // infeasibilities are only corrected in rebuild ekk_instance_.invalidateDualInfeasibilityRecord(); // Data are fresh from rebuild @@ -1159,10 +1174,10 @@ void HEkkDual::cleanup() { info.updated_dual_objective_value = info.dual_objective_value; if (!info.run_quiet) { - // Report the primal infeasiblities + // Report the primal infeasibilities ekk_instance_.computeSimplexPrimalInfeasible(); - // In phase 1, report the simplex LP dual infeasiblities - // In phase 2, report the simplex dual infeasiblities (known) + // In phase 1, report the simplex LP dual infeasibilities + // In phase 2, report the simplex dual infeasibilities (known) if (solve_phase == kSolvePhase1) ekk_instance_.computeSimplexLpDualInfeasible(); reportRebuild(kRebuildReasonCleanup); @@ -1170,7 +1185,7 @@ void HEkkDual::cleanup() { } void HEkkDual::iterate() { - // This is the main teration loop for dual revised simplex. All the + // This is the main iteration loop for dual revised simplex. All the // methods have as their first line if (rebuild_reason) return;, where // rebuild_reason is, for example, set to 1 when CHUZR finds no // candidate. This causes a break from the inner loop of @@ -1357,7 +1372,7 @@ void HEkkDual::iterationAnalysisData() { } void HEkkDual::iterationAnalysis() { - // Compute the infeasiblility data (expensive) if analysing run-time + // Compute the infeasibility data (expensive) if analysing run-time // data and the log level is at least kIterationReportLogType // (Verbose) const bool make_iteration_report = analysis->analyse_simplex_runtime_data && @@ -1617,7 +1632,7 @@ void HEkkDual::chooseColumn(HVector* row_ep) { // // Sections 3 and 4: Perform (bound-flipping) ratio test. This can // fail if the dual values are excessively large - bool chooseColumnFail = dualRow.chooseFinal(); + bool chooseColumnFail = (dualRow.chooseFinal() != 0); if (chooseColumnFail) { rebuild_reason = kRebuildReasonChooseColumnFail; return; @@ -1640,7 +1655,7 @@ void HEkkDual::chooseColumn(HVector* row_ep) { (int)dualRow.workPivot, dualRow.workAlpha, workDual[dualRow.workPivot], workDual[dualRow.workPivot] / dualRow.workAlpha); - // On the first pass, try to make the povotal row more accurate + // On the first pass, try to make the pivotal row more accurate if (chuzc_pass == 0) { if (debug_small_pivot_issue_report) printf(": improve row\n"); ekk_instance_.analysis_.num_improve_choose_column_row_call++; @@ -1916,7 +1931,7 @@ void HEkkDual::chooseColumnSlice(HVector* row_ep) { for (HighsInt i = 0; i < slice_num; i++) slice_dualRow[i].computeDevexWeight(i); // Accumulate the partial sums - // Initialse with the partial sum for row_ep + // Initialise with the partial sum for row_ep computed_edge_weight = dualRow.computed_edge_weight; // Update with the partial sum for row_ep for (HighsInt i = 0; i < slice_num; i++) @@ -1932,7 +1947,7 @@ void HEkkDual::updateFtran() { // If reinversion is needed then skip this method if (rebuild_reason) return; analysis->simplexTimerStart(FtranClock); - // Clear the picotal column and indicate that its values should be packed + // Clear the pivotal column and indicate that its values should be packed col_aq.clear(); col_aq.packFlag = true; // Get the constraint matrix column by combining just one column @@ -2201,7 +2216,6 @@ void HEkkDual::shiftCost(const HighsInt iCol, const double amount) { void HEkkDual::shiftBack(const HighsInt iCol) { HighsSimplexInfo& info = ekk_instance_.info_; if (!info.workShift_[iCol]) return; - const double shift = fabs(info.workShift_[iCol]); info.workDual_[iCol] -= info.workShift_[iCol]; info.workShift_[iCol] = 0; // Analysis @@ -2286,7 +2300,6 @@ void HEkkDual::initialiseDevexFramework() { void HEkkDual::interpretDualEdgeWeightStrategy( const HighsInt dual_edge_weight_strategy) { - const bool always_initialise_dual_steepest_edge_weights = true; if (dual_edge_weight_strategy == kSimplexEdgeWeightStrategyChoose) { edge_weight_mode = EdgeWeightMode::kSteepestEdge; allow_dual_steepest_edge_to_devex_switch = true; @@ -2327,7 +2340,7 @@ void HEkkDual::possiblyUseLiDualSteepestEdge() { } void HEkkDual::computeDualInfeasibilitiesWithFixedVariableFlips() { - // Computes num/max/sum of dual infeasibliities, ignoring fixed + // Computes num/max/sum of dual infeasibilities, ignoring fixed // variables whose infeasibilities can be corrected by flipping at // the fixed value, so that decisions on the dual simplex phase can // be taken. It is driven by the use of nonbasicMove to identify @@ -2374,7 +2387,7 @@ void HEkkDual::computeDualInfeasibilitiesWithFixedVariableFlips() { } void HEkkDual::correctDualInfeasibilities(HighsInt& free_infeasibility_count) { - // Removes dual infeasiblilities for all but free variables. For + // Removes dual infeasibilities for all but free variables. For // fixed variables, dual infeasibilities are removed by flipping at // the bound. Otherwise, dual infeasibilities are removed by // shifting costs. @@ -2422,9 +2435,9 @@ void HEkkDual::correctDualInfeasibilities(HighsInt& free_infeasibility_count) { } dual_infeasibility = -move * current_dual; if (dual_infeasibility < dual_feasibility_tolerance) continue; - // There is a dual infeasiblity to remove + // There is a dual infeasibility to remove // - // force_phase2 is set true to prevent fipping of non-fixed + // force_phase2 is set true to prevent flipping of non-fixed // (boxed) variables when correcting infeasibilities in the first // set of duals computed after cost perturbation if (fixed || (boxed && !force_phase2)) { @@ -2622,7 +2635,7 @@ void HEkkDual::assessPhase1OptimalityUnperturbed() { // negative, so no conclusions on the primal LP can be deduced // - could be primal unbounded or primal infeasible. // - // Indicate the conclusion of dual infeasiblility by setting + // Indicate the conclusion of dual infeasibility by setting // the scaled model status reportOnPossibleLpDualInfeasibility(); model_status = HighsModelStatus::kUnboundedOrInfeasible; @@ -2862,7 +2875,7 @@ double HEkkDual::computeExactDualObjectiveValue(HVector& dual_col, simplex_nla->btran(dual_col, expected_density); lp.a_matrix_.priceByColumn(quad_precision, dual_row, dual_col); } - // Compute dual infeasiblilities + // Compute dual infeasibilities ekk_instance_.computeSimplexDualInfeasible(); if (info.num_dual_infeasibilities > 0) highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, diff --git a/src/simplex/HEkkDual.h b/src/simplex/HEkkDual.h index 584af97fa0..573abc921d 100644 --- a/src/simplex/HEkkDual.h +++ b/src/simplex/HEkkDual.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HEkkDualMulti.cpp b/src/simplex/HEkkDualMulti.cpp index bad80254c6..76532166eb 100644 --- a/src/simplex/HEkkDualMulti.cpp +++ b/src/simplex/HEkkDualMulti.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HEkkDualRHS.cpp b/src/simplex/HEkkDualRHS.cpp index 1971cfe176..c004de408c 100644 --- a/src/simplex/HEkkDualRHS.cpp +++ b/src/simplex/HEkkDualRHS.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -18,7 +18,7 @@ #include #include -#include "pdqsort/pdqsort.h" +#include "../extern/pdqsort/pdqsort.h" #include "simplex/SimplexTimer.h" using std::fill_n; @@ -29,8 +29,6 @@ using std::pair; void HEkkDualRHS::setup() { const HighsInt numRow = ekk_instance_.lp_.num_row_; - const HighsInt numTot = - ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_; workMark.resize(numRow); workIndex.resize(numRow); work_infeasibility.resize(numRow); diff --git a/src/simplex/HEkkDualRHS.h b/src/simplex/HEkkDualRHS.h index b7a3408ffe..421a09fc0b 100644 --- a/src/simplex/HEkkDualRHS.h +++ b/src/simplex/HEkkDualRHS.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -49,10 +49,9 @@ class HEkkDualRHS { * @brief Choose a set of row indices of good variables to leave the basis * (Multiple CHUZR) */ - void chooseMultiGlobal( - HighsInt* chIndex, //!< Set of indices of chosen rows - HighsInt* chCount, //!< Number of chosen rows - HighsInt chLimit //!< Limit on number of of chosen rows + void chooseMultiGlobal(HighsInt* chIndex, //!< Set of indices of chosen rows + HighsInt* chCount, //!< Number of chosen rows + HighsInt chLimit //!< Limit on number of chosen rows ); /** @@ -62,7 +61,7 @@ class HEkkDualRHS { void chooseMultiHyperGraphAuto( HighsInt* chIndex, //!< Set of indices of chosen rows HighsInt* chCount, //!< Number of chosen rows - HighsInt chLimit //!< Limit on number of of chosen rows + HighsInt chLimit //!< Limit on number of chosen rows ); /** @@ -72,7 +71,7 @@ class HEkkDualRHS { void chooseMultiHyperGraphPart( HighsInt* chIndex, //!< Set of indices of chosen rows HighsInt* chCount, //!< Number of chosen rows - HighsInt chLimit //!< Limit on number of of chosen rows + HighsInt chLimit //!< Limit on number of chosen rows ); /** diff --git a/src/simplex/HEkkDualRow.cpp b/src/simplex/HEkkDualRow.cpp index a9d8e8babe..6f0e00bc6c 100644 --- a/src/simplex/HEkkDualRow.cpp +++ b/src/simplex/HEkkDualRow.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -16,7 +16,7 @@ #include #include -#include "pdqsort/pdqsort.h" +#include "../extern/pdqsort/pdqsort.h" #include "simplex/HSimplexDebug.h" #include "simplex/SimplexTimer.h" #include "util/HighsCDouble.h" @@ -186,7 +186,7 @@ HighsInt HEkkDualRow::chooseFinal() { alt_workCount = workCount; } analysis->simplexTimerStart(Chuzc4Clock); - bool choose_ok; + bool choose_ok = false; if (use_quad_sort) { // Use the O(n^2) quadratic sort for the candidates analysis->simplexTimerStart(Chuzc4a0Clock); @@ -212,10 +212,10 @@ HighsInt HEkkDualRow::chooseFinal() { // 3. Choose large alpha analysis->simplexTimerStart(Chuzc4bClock); - HighsInt breakIndex; - HighsInt breakGroup; - HighsInt alt_breakIndex; - HighsInt alt_breakGroup; + HighsInt breakIndex = -1; + HighsInt breakGroup = -1; + HighsInt alt_breakIndex = -1; + HighsInt alt_breakGroup = -1; if (use_quad_sort) chooseFinalLargeAlpha(breakIndex, breakGroup, workCount, workData, workGroup); @@ -329,12 +329,9 @@ bool HEkkDualRow::chooseFinalWorkGroupQuad() { HighsInt prev_workCount = workCount; double prev_remainTheta = kInitialRemainTheta; double prev_selectTheta = selectTheta; - HighsInt debug_num_loop = 0; while (selectTheta < kMaxSelectTheta) { double remainTheta = kInitialRemainTheta; - debug_num_loop++; - HighsInt debug_loop_ln = 0; for (HighsInt i = workCount; i < fullCount; i++) { HighsInt iCol = workData[i].first; double value = workData[i].second; @@ -346,7 +343,6 @@ bool HEkkDualRow::chooseFinalWorkGroupQuad() { } else if (dual + Td < remainTheta * value) { remainTheta = (dual + Td) / value; } - debug_loop_ln++; } workGroup.push_back(workCount); @@ -391,12 +387,9 @@ bool HEkkDualRow::quadChooseFinalWorkGroupQuad() { HighsInt prev_workCount = workCount; HighsCDouble prev_remainTheta = kInitialRemainTheta; HighsCDouble prev_selectTheta = selectTheta; - HighsInt debug_num_loop = 0; while (selectTheta < kMaxSelectTheta) { HighsCDouble remainTheta = kInitialRemainTheta; - debug_num_loop++; - HighsInt debug_loop_ln = 0; for (HighsInt i = workCount; i < fullCount; i++) { HighsInt iCol = workData[i].first; HighsCDouble value = workData[i].second; @@ -408,7 +401,6 @@ bool HEkkDualRow::quadChooseFinalWorkGroupQuad() { } else if (dual + Td < remainTheta * value) { remainTheta = (dual + Td) / value; } - debug_loop_ln++; } workGroup.push_back(workCount); @@ -488,7 +480,6 @@ bool HEkkDualRow::chooseFinalWorkGroupHeap() { // first entry alt_workGroup.push_back(alt_workCount); this_group_first_entry = alt_workCount; - HighsInt alt_workGroup_size = alt_workGroup.size(); selectTheta = (dual + Td) / value; // End loop if all permitted groups have been identified if (totalChange >= totalDelta) break; @@ -659,7 +650,6 @@ HighsInt HEkkDualRow::debugChooseColumnInfeasibilities() const { HighsInt num_infeasibility = 0; if (ekk_instance_.options_->highs_debug_level < kHighsDebugLevelCheap) return num_infeasibility; - const HighsInt move_out = workDelta < 0 ? -1 : 1; std::vector unpack_value; HighsLp& lp = ekk_instance_.lp_; unpack_value.resize(lp.num_col_ + lp.num_row_); @@ -668,7 +658,6 @@ HighsInt HEkkDualRow::debugChooseColumnInfeasibilities() const { const double Td = ekk_instance_.options_->dual_feasibility_tolerance; for (HighsInt i = 0; i < workCount; i++) { const HighsInt iCol = workData[i].first; - const double delta = workData[i].second; const double value = unpack_value[iCol]; const HighsInt move = workMove[iCol]; const double dual = workDual[iCol]; diff --git a/src/simplex/HEkkDualRow.h b/src/simplex/HEkkDualRow.h index b4e740957d..267cbdc5b9 100644 --- a/src/simplex/HEkkDualRow.h +++ b/src/simplex/HEkkDualRow.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -80,7 +80,7 @@ class HEkkDualRow { * * Can fail when there are excessive dual values due to EXPAND * perturbation not being relatively too small, returns positive if - * dual uboundedness is suspected + * dual unboundedness is suspected */ HighsInt chooseFinal(); diff --git a/src/simplex/HEkkInterface.cpp b/src/simplex/HEkkInterface.cpp index b501118e60..afd39ca028 100644 --- a/src/simplex/HEkkInterface.cpp +++ b/src/simplex/HEkkInterface.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HEkkPrimal.cpp b/src/simplex/HEkkPrimal.cpp index 46ab3ea642..9a258ad90e 100644 --- a/src/simplex/HEkkPrimal.cpp +++ b/src/simplex/HEkkPrimal.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -13,7 +13,7 @@ */ #include "simplex/HEkkPrimal.h" -#include "pdqsort/pdqsort.h" +#include "../extern/pdqsort/pdqsort.h" #include "simplex/HEkkDual.h" #include "simplex/SimplexTimer.h" #include "util/HighsSort.h" @@ -41,7 +41,7 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { if (debugPrimalSimplex("Initialise", true) == HighsDebugStatus::kLogicalError) return ekk_instance_.returnFromSolve(HighsStatus::kError); - // Get the nonabsic free column set + // Get the nonbasic free column set getNonbasicFreeColumnSet(); const bool primal_feasible_with_unperturbed_bounds = @@ -52,14 +52,14 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { options.primal_feasibility_tolerance; // Determine whether the solution is near-optimal. Values 1000 and // 1e-3 (ensuring sum<1) are unimportant, as the sum of dual - // infeasiblilities for near-optimal solutions is typically many + // infeasibilities for near-optimal solutions is typically many // orders of magnitude smaller than 1, and the sum of dual - // infeasiblilities will be very much larger for non-trivial LPs + // infeasibilities will be very much larger for non-trivial LPs // that are primal feasible for a logical or crash basis. // // Consider there to be no primal infeasibilities if there are none, // or if phase 2 is forced, in which case any primal infeasibilities - // will be shifed + // will be shifted const bool no_simplex_primal_infeasibilities = primal_feasible_with_unperturbed_bounds || force_phase2; const bool near_optimal = info.num_dual_infeasibilities < 1000 && @@ -82,7 +82,7 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { info.num_dual_infeasibilities, info.max_dual_infeasibility, info.sum_dual_infeasibilities); - // Perturb bounds according to whether the solution is near-optimnal + // Perturb bounds according to whether the solution is near-optimal const bool perturb_bounds = !near_optimal; if (!perturb_bounds) highsLogDev(options.log_options, HighsLogType::kDetailed, @@ -105,7 +105,7 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { ekk_instance_.info_.num_primal_infeasibilities; solve_phase = num_primal_infeasibility > 0 ? kSolvePhase1 : kSolvePhase2; if (force_phase2) { - // Dual infeasiblilities without cost perturbation involved + // Dual infeasibilities without cost perturbation involved // fixed variables or were (at most) small, so can easily be // removed by flips for and fixed variables shifts for the rest solve_phase = kSolvePhase2; @@ -116,7 +116,7 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { "Solve %d: Forcing phase 2 since near primal feasible with " "unperturbed " "costs\n" - "num / max / sum primal infeasiblitiles\n" + "num / max / sum primal infeasibilities\n" "%d / %11.4g / %11.4g ( perturbed bounds)\n" "%d / %11.4g / %11.4g (unperturbed bounds)\n", (int)ekk_instance_.debug_solve_call_num_, @@ -143,7 +143,7 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { HighsInt it0 = ekk_instance_.iteration_count_; // When starting a new phase the (updated) primal objective function // value isn't known. Indicate this so that when the value - // computed from scratch in rebuild() isn't checked against the the + // computed from scratch in rebuild() isn't checked against the // updated value status.has_primal_objective_value = false; if (solve_phase == kSolvePhaseUnknown) { @@ -202,7 +202,7 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { // been reached // // solve_phase = kSolvePhaseOptimalCleanup if, after removing bound - // shifts, there are primal infeasiblilities to clean up + // shifts, there are primal infeasibilities to clean up // // solve_phase = kSolvePhaseUnknown if backtracking // @@ -425,7 +425,7 @@ void HEkkPrimal::solvePhase1() { HighsSimplexStatus& status = ekk_instance_.status_; // When starting a new phase the (updated) primal objective function // value isn't known. Indicate this so that when the value - // computed from scratch in build() isn't checked against the the + // computed from scratch in build() isn't checked against the // updated value status.has_primal_objective_value = false; status.has_dual_objective_value = false; @@ -462,7 +462,7 @@ void HEkkPrimal::solvePhase1() { } // If the data are fresh from rebuild() and no flips have // occurred, possibly break out of the outer loop to see what's - // ocurred + // occurred bool finished = status.has_fresh_rebuild && num_flip_since_rebuild == 0 && !ekk_instance_.rebuildRefactor(rebuild_reason); if (finished && ekk_instance_.tabooBadBasisChange()) { @@ -479,7 +479,7 @@ void HEkkPrimal::solvePhase1() { // If bailing out, should have returned already assert(!ekk_instance_.solve_bailout_); // Will only have accurate simplex info if moving to phase 2 - but - // should check primal feasiblilty and residual information if LP + // should check primal feasibility and residual information if LP // is primal infeasible if (debugPrimalSimplex("End of solvePhase1") == HighsDebugStatus::kLogicalError) { @@ -487,9 +487,9 @@ void HEkkPrimal::solvePhase1() { return; } if (solve_phase == kSolvePhase1) { - // Determine whether primal infeasiblility has been identified + // Determine whether primal infeasibility has been identified if (variable_in < 0) { - // Optimal in phase 1, so should have primal infeasiblilities + // Optimal in phase 1, so should have primal infeasibilities assert(info.num_primal_infeasibilities > 0); if (ekk_instance_.info_.bounds_perturbed) { // Remove any bound perturbations and return to phase 1 @@ -516,7 +516,7 @@ void HEkkPrimal::solvePhase2() { HighsModelStatus& model_status = ekk_instance_.model_status_; // When starting a new phase the (updated) primal objective function // value isn't known. Indicate this so that when the value - // computed from scratch in build() isn't checked against the the + // computed from scratch in build() isn't checked against the // updated value status.has_primal_objective_value = false; status.has_dual_objective_value = false; @@ -556,7 +556,7 @@ void HEkkPrimal::solvePhase2() { } // If the data are fresh from rebuild() and no flips have // occurred, possibly break out of the outer loop to see what's - // ocurred + // occurred bool finished = status.has_fresh_rebuild && num_flip_since_rebuild == 0 && !ekk_instance_.rebuildRefactor(rebuild_reason); if (finished && ekk_instance_.tabooBadBasisChange()) { @@ -587,11 +587,11 @@ void HEkkPrimal::solvePhase2() { // Remove any bound perturbations and see if basis is still primal feasible cleanup(); if (ekk_instance_.info_.num_primal_infeasibilities > 0) { - // There are primal infeasiblities, so consider performing dual + // There are primal infeasibilities, so consider performing dual // simplex iterations to get primal feasibility solve_phase = kSolvePhaseOptimalCleanup; } else { - // There are no primal infeasiblities so optimal! + // There are no primal infeasibilities so optimal! solve_phase = kSolvePhaseOptimal; highsLogDev(options.log_options, HighsLogType::kDetailed, "problem-optimal\n"); @@ -623,7 +623,7 @@ void HEkkPrimal::solvePhase2() { if (ekk_instance_.info_.bounds_perturbed) { // If the bounds have been perturbed, clean up and return cleanup(); - // If there are primal infeasiblities, go back to phase 1 + // If there are primal infeasibilities, go back to phase 1 if (ekk_instance_.info_.num_primal_infeasibilities > 0) solve_phase = kSolvePhase1; } else { @@ -669,10 +669,10 @@ void HEkkPrimal::cleanup() { info.updated_primal_objective_value = info.primal_objective_value; // if (!info.run_quiet) { - // Report the dual infeasiblities + // Report the dual infeasibilities ekk_instance_.computeSimplexDualInfeasible(); - // In phase 1, report the simplex LP dual infeasiblities - // In phase 2, report the simplex dual infeasiblities (known) + // In phase 1, report the simplex LP dual infeasibilities + // In phase 2, report the simplex dual infeasibilities (known) // if (solve_phase == kSolvePhase1) // computeSimplexLpDualInfeasible(ekk_instance_); reportRebuild(kRebuildReasonCleanup); @@ -697,7 +697,7 @@ void HEkkPrimal::rebuild() { // basic variables, and baseValue only corresponds to the new // ordering once computePrimal has been called const bool check_updated_objective_value = status.has_primal_objective_value; - double previous_primal_objective_value; + double previous_primal_objective_value = -kHighsInf; if (check_updated_objective_value) { // debugUpdatedObjectiveValue(ekk_instance_, algorithm, solve_phase, // "Before INVERT"); @@ -892,10 +892,10 @@ void HEkkPrimal::iterate() { // // rebuild_reason = // kRebuildReasonPrimalInfeasibleInPrimalSimplex is set if a - // primal infeasiblility is found in phase 2 + // primal infeasibility is found in phase 2 // // rebuild_reason = kRebuildReasonPossiblyPhase1Feasible is set in - // phase 1 if the number of primal infeasiblilities is reduced to + // phase 1 if the number of primal infeasibilities is reduced to // zero // // rebuild_reason = kRebuildReasonUpdateLimitReached is set in @@ -1027,7 +1027,7 @@ void HEkkPrimal::chooseColumn(const bool hyper_sparse) { "Full CHUZC: Max measure is %9.4g for column " "%4" HIGHSINT_FORMAT ", and " - "max non-candiate measure of %9.4g\n", + "max non-candidate measure of %9.4g\n", best_measure, variable_in, max_hyper_chuzc_non_candidate_measure); } } @@ -1092,8 +1092,8 @@ bool HEkkPrimal::useVariableIn() { ekk_instance_.debugUpdatedDual(updated_theta_dual, computed_theta_dual); // Feed in the computed dual value. // - // The sum of dual infeasiblilities (and maybe max dual - // infeasiblility) will be wrong, but there's a big tolerance on + // The sum of dual infeasibilities (and maybe max dual + // infeasibility) will be wrong, but there's a big tolerance on // this in debugSimplex. Have to be careful (below) if the computed // dual value is no longer a dual infeasibility info.workDual_[variable_in] = computed_theta_dual; @@ -1105,7 +1105,7 @@ bool HEkkPrimal::useVariableIn() { updated_theta_dual * computed_theta_dual <= 0; // If theta_dual is small, then it's no longer a dual infeasibility, - // so reduce the number of dual infeasiblilities. Otherwise an error + // so reduce the number of dual infeasibilities. Otherwise an error // is identified in debugSimplex if (theta_dual_small) ekk_instance_.info_.num_dual_infeasibilities--; if (theta_dual_small || theta_dual_sign_error) { @@ -1212,7 +1212,7 @@ void HEkkPrimal::phase1ChooseRow() { pdqsort(ph1SorterR.begin(), ph1SorterR.end()); double dMaxTheta = ph1SorterR.at(0).first; double dGradient = fabs(theta_dual); - for (HighsUInt i = 0; i < ph1SorterR.size(); i++) { + for (size_t i = 0; i < ph1SorterR.size(); i++) { double dMyTheta = ph1SorterR.at(i).first; HighsInt index = ph1SorterR.at(i).second; HighsInt iRow = index >= 0 ? index : index + num_row; @@ -1227,8 +1227,8 @@ void HEkkPrimal::phase1ChooseRow() { // Find out the biggest possible alpha for pivot pdqsort(ph1SorterT.begin(), ph1SorterT.end()); double dMaxAlpha = 0.0; - HighsUInt iLast = ph1SorterT.size(); - for (HighsUInt i = 0; i < ph1SorterT.size(); i++) { + size_t iLast = ph1SorterT.size(); + for (size_t i = 0; i < ph1SorterT.size(); i++) { double dMyTheta = ph1SorterT.at(i).first; HighsInt index = ph1SorterT.at(i).second; HighsInt iRow = index >= 0 ? index : index + num_row; @@ -1248,8 +1248,8 @@ void HEkkPrimal::phase1ChooseRow() { row_out = kNoRowChosen; variable_out = -1; move_out = 0; - for (HighsInt i = iLast - 1; i >= 0; i--) { - HighsInt index = ph1SorterT.at(i).second; + for (size_t i = iLast; i > 0; i--) { + HighsInt index = ph1SorterT.at(i - 1).second; HighsInt iRow = index >= 0 ? index : index + num_row; double dAbsAlpha = fabs(col_aq.array[iRow]); if (dAbsAlpha > dMaxAlpha * 0.1) { @@ -1450,7 +1450,7 @@ void HEkkPrimal::update() { // // rebuild_reason = // kRebuildReasonPrimalInfeasibleInPrimalSimplex is set if a - // primal infeasiblility is found + // primal infeasibility is found phase2UpdatePrimal(); } @@ -1477,7 +1477,7 @@ void HEkkPrimal::update() { // // rebuild_reason = // kRebuildReasonPrimalInfeasibleInPrimalSimplex is set in - // phase 2 if a primal infeasiblility is found + // phase 2 if a primal infeasibility is found considerInfeasibleValueIn(); // Update the dual values @@ -1565,7 +1565,8 @@ void HEkkPrimal::hyperChooseColumn() { if (workDual[max_changed_measure_column]) variable_in = max_changed_measure_column; } - const bool consider_nonbasic_free_column = nonbasic_free_col_set.count(); + const bool consider_nonbasic_free_column = + (nonbasic_free_col_set.count() != 0); if (num_hyper_chuzc_candidates) { for (HighsInt iEntry = 1; iEntry <= num_hyper_chuzc_candidates; iEntry++) { HighsInt iCol = hyper_chuzc_candidate[iEntry]; @@ -1943,11 +1944,9 @@ void HEkkPrimal::considerInfeasibleValueIn() { } void HEkkPrimal::phase2UpdatePrimal(const bool initialise) { - static double max_max_local_primal_infeasibility; - static double max_max_ignored_violation; if (initialise) { - max_max_local_primal_infeasibility = 0; - max_max_ignored_violation = 0; + max_max_local_primal_infeasibility_ = 0; + max_max_ignored_violation_ = 0; return; } analysis->simplexTimerStart(UpdatePrimalClock); @@ -1957,7 +1956,7 @@ void HEkkPrimal::phase2UpdatePrimal(const bool initialise) { double max_ignored_violation = 0; // If shifts are only identified in rebuild() the bounds can be // ignored. If they aren't ignored, then violations lead to either - // identification of infeasiblilities (and return to Phase 1) or + // identification of infeasibilities (and return to Phase 1) or // shifting of bounds to accommodate them. const bool ignore_bounds = primal_correction_strategy == kSimplexPrimalCorrectionStrategyInRebuild; @@ -2026,15 +2025,15 @@ void HEkkPrimal::phase2UpdatePrimal(const bool initialise) { if (primal_infeasible) { rebuild_reason = kRebuildReasonPrimalInfeasibleInPrimalSimplex; if (max_local_primal_infeasibility > - max_max_local_primal_infeasibility * 2) { - max_max_local_primal_infeasibility = max_local_primal_infeasibility; + max_max_local_primal_infeasibility_ * 2) { + max_max_local_primal_infeasibility_ = max_local_primal_infeasibility; printf("phase2UpdatePrimal: max_local_primal_infeasibility = %g\n", max_local_primal_infeasibility); } ekk_instance_.invalidatePrimalMaxSumInfeasibilityRecord(); } - if (max_ignored_violation > max_max_ignored_violation * 2) { - max_max_ignored_violation = max_ignored_violation; + if (max_ignored_violation > max_max_ignored_violation_ * 2) { + max_max_ignored_violation_ = max_ignored_violation; printf("phase2UpdatePrimal: max_ignored_violation = %g\n", max_ignored_violation); } @@ -2047,9 +2046,8 @@ void HEkkPrimal::phase2UpdatePrimal(const bool initialise) { bool HEkkPrimal::correctPrimal(const bool initialise) { if (primal_correction_strategy == kSimplexPrimalCorrectionStrategyNone) return true; - static double max_max_primal_correction; if (initialise) { - max_max_primal_correction = 0; + max_max_primal_correction_ = 0; return true; } assert(solve_phase == kSolvePhase2); @@ -2107,7 +2105,7 @@ bool HEkkPrimal::correctPrimal(const bool initialise) { } return false; } - if (max_primal_correction > 2 * max_max_primal_correction) { + if (max_primal_correction > 2 * max_max_primal_correction_) { highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo, "phase2CorrectPrimal: num / max / sum primal corrections = " "%" HIGHSINT_FORMAT @@ -2115,7 +2113,7 @@ bool HEkkPrimal::correctPrimal(const bool initialise) { "%g\n", num_primal_correction, max_primal_correction, sum_primal_correction); - max_max_primal_correction = max_primal_correction; + max_max_primal_correction_ = max_primal_correction; } return true; } @@ -2556,7 +2554,7 @@ void HEkkPrimal::updateVerify() { } void HEkkPrimal::iterationAnalysisData() { - // Possibly compute the infeasiblility data + // Possibly compute the infeasibility data if (analysis->analyse_simplex_runtime_data) ekk_instance_.computeInfeasibilitiesForReporting(SimplexAlgorithm::kPrimal); HighsSimplexInfo& info = ekk_instance_.info_; @@ -2615,16 +2613,15 @@ void HEkkPrimal::localReportIterHeader() { void HEkkPrimal::localReportIter(const bool header) { if (!report_hyper_chuzc) return; - static HighsInt last_header_iteration_count; const HighsSimplexInfo& info = ekk_instance_.info_; HighsInt iteration_count = ekk_instance_.iteration_count_; if (header) { localReportIterHeader(); - last_header_iteration_count = iteration_count; + last_header_iteration_count_ = iteration_count; } else { - if (ekk_instance_.iteration_count_ > last_header_iteration_count + 10) { + if (ekk_instance_.iteration_count_ > last_header_iteration_count_ + 10) { localReportIterHeader(); - last_header_iteration_count = iteration_count; + last_header_iteration_count_ = iteration_count; } if (row_out >= 0) { printf("%5" HIGHSINT_FORMAT " %5" HIGHSINT_FORMAT " %5" HIGHSINT_FORMAT @@ -2741,7 +2738,7 @@ void HEkkPrimal::adjustPerturbedEquationOut() { } void HEkkPrimal::getBasicPrimalInfeasibility() { - // Gets the num/max/sum of basic primal infeasibliities, + // Gets the num/max/sum of basic primal infeasibilities, analysis->simplexTimerStart(ComputePrIfsClock); const double primal_feasibility_tolerance = ekk_instance_.options_->primal_feasibility_tolerance; @@ -2774,7 +2771,7 @@ void HEkkPrimal::getBasicPrimalInfeasibility() { } } if (updated_num_primal_infeasibility >= 0) { - // The number of primal infeasibliities should be correct + // The number of primal infeasibilities should be correct bool num_primal_infeasibility_ok = num_primal_infeasibility == updated_num_primal_infeasibility; // if (!num_primal_infeasibility_ok) diff --git a/src/simplex/HEkkPrimal.h b/src/simplex/HEkkPrimal.h index 4cb8d22ec0..ef5d0c84c1 100644 --- a/src/simplex/HEkkPrimal.h +++ b/src/simplex/HEkkPrimal.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -179,6 +179,11 @@ class HEkkPrimal { HVector col_steepest_edge; HighsRandom random_; // Just for checking PSE weights + double max_max_local_primal_infeasibility_; + double max_max_ignored_violation_; + double max_max_primal_correction_; + HighsInt last_header_iteration_count_; + const HighsInt primal_correction_strategy = kSimplexPrimalCorrectionStrategyAlways; double debug_max_relative_primal_steepest_edge_weight_error = 0; diff --git a/src/simplex/HSimplex.cpp b/src/simplex/HSimplex.cpp index c1c9bf887f..7321ce656a 100644 --- a/src/simplex/HSimplex.cpp +++ b/src/simplex/HSimplex.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -157,17 +157,6 @@ void appendBasicRowsToBasis(HighsLp& lp, SimplexBasis& basis, } } -void unscaleSolution(HighsSolution& solution, const HighsScale scale) { - for (HighsInt iCol = 0; iCol < scale.num_col; iCol++) { - solution.col_value[iCol] *= scale.col[iCol]; - solution.col_dual[iCol] /= (scale.col[iCol] / scale.cost); - } - for (HighsInt iRow = 0; iRow < scale.num_row; iRow++) { - solution.row_value[iRow] /= scale.row[iRow]; - solution.row_dual[iRow] *= (scale.row[iRow] * scale.cost); - } -} - void getUnscaledInfeasibilities(const HighsOptions& options, const HighsScale& scale, const SimplexBasis& basis, @@ -198,7 +187,7 @@ void getUnscaledInfeasibilities(const HighsOptions& options, for (HighsInt iVar = 0; iVar < scale.num_col + scale.num_row; iVar++) { // Look at the dual infeasibilities of nonbasic variables if (basis.nonbasicFlag_[iVar] == kNonbasicFlagFalse) continue; - // No dual infeasiblity for fixed rows and columns + // No dual infeasibility for fixed rows and columns if (info.workLower_[iVar] == info.workUpper_[iVar]) continue; bool col = iVar < scale.num_col; HighsInt iCol = 0; diff --git a/src/simplex/HSimplex.h b/src/simplex/HSimplex.h index f51136f622..a371546729 100644 --- a/src/simplex/HSimplex.h +++ b/src/simplex/HSimplex.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -27,8 +27,6 @@ void appendBasicRowsToBasis(HighsLp& lp, HighsBasis& highs_basis, void appendBasicRowsToBasis(HighsLp& lp, SimplexBasis& basis, HighsInt XnumNewRow); -void unscaleSolution(HighsSolution& solution, const HighsScale scale); - void getUnscaledInfeasibilities(const HighsOptions& options, const HighsScale& scale, const SimplexBasis& basis, diff --git a/src/simplex/HSimplexDebug.cpp b/src/simplex/HSimplexDebug.cpp index e47a8bd9b3..de9fb46efb 100644 --- a/src/simplex/HSimplexDebug.cpp +++ b/src/simplex/HSimplexDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HSimplexDebug.h b/src/simplex/HSimplexDebug.h index 64a9f9652f..ee4d3951e2 100644 --- a/src/simplex/HSimplexDebug.h +++ b/src/simplex/HSimplexDebug.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HSimplexNla.cpp b/src/simplex/HSimplexNla.cpp index 62a4ff67e3..738a16709c 100644 --- a/src/simplex/HSimplexNla.cpp +++ b/src/simplex/HSimplexNla.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -17,8 +17,8 @@ #include +#include "../extern/pdqsort/pdqsort.h" #include "parallel/HighsParallel.h" -#include "pdqsort/pdqsort.h" #include "simplex/HSimplex.h" using std::vector; @@ -177,7 +177,6 @@ double HSimplexNla::rowEp2NormInScaledSpace(const HighsInt iRow, if (scale_ == NULL) { return row_ep.norm2(); } - const vector& col_scale = scale_->col; const vector& row_scale = scale_->row; // Get the 2-norm of row_ep in the scaled space otherwise for // checking @@ -286,7 +285,6 @@ void HSimplexNla::setPivotThreshold(const double new_pivot_threshold) { void HSimplexNla::applyBasisMatrixRowScale(HVector& rhs) const { if (scale_ == NULL) return; - const vector& col_scale = scale_->col; const vector& row_scale = scale_->row; HighsInt to_entry; const bool use_row_indices = @@ -317,7 +315,6 @@ void HSimplexNla::applyBasisMatrixColScale(HVector& rhs) const { void HSimplexNla::unapplyBasisMatrixRowScale(HVector& rhs) const { if (scale_ == NULL) return; - const vector& col_scale = scale_->col; const vector& row_scale = scale_->row; HighsInt to_entry; const bool use_row_indices = @@ -449,7 +446,6 @@ void HSimplexNla::reportPackValue(const std::string message, const HVector* vector, const bool force) const { if (!report_ && !force) return; - const HighsInt num_row = lp_->num_row_; if (vector->packCount > kReportItemLimit) { analyseVectorValues(nullptr, message, vector->packCount, vector->packValue, true); diff --git a/src/simplex/HSimplexNla.h b/src/simplex/HSimplexNla.h index 4cbda269f5..91c4bbd14e 100644 --- a/src/simplex/HSimplexNla.h +++ b/src/simplex/HSimplexNla.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HSimplexNlaDebug.cpp b/src/simplex/HSimplexNlaDebug.cpp index 80d57afd64..d4c2b46c0b 100644 --- a/src/simplex/HSimplexNlaDebug.cpp +++ b/src/simplex/HSimplexNlaDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -23,9 +23,6 @@ const double kResidualExcessiveError = sqrt(kResidualLargeError); const double kSolveLargeError = 1e-8; const double kSolveExcessiveError = sqrt(kSolveLargeError); -const double kInverseLargeError = 1e-8; -const double kInverseExcessiveError = sqrt(kInverseLargeError); - HighsDebugStatus HSimplexNla::debugCheckInvert( const std::string message, const HighsInt alt_debug_level) const { // Sometimes a value other than highs_debug_level is passed as @@ -57,7 +54,7 @@ HighsDebugStatus HSimplexNla::debugCheckInvert( // Make sure that this isn't called between the matrix and LP resizing assert(num_row == this->lp_->a_matrix_.num_row_); assert(num_col == this->lp_->a_matrix_.num_col_); - const bool report = options->log_dev_level; + const bool report = (options->log_dev_level != 0); highsLogDev(options->log_options, HighsLogType::kInfo, "\nCheckINVERT: %s\n", message.c_str()); @@ -140,7 +137,6 @@ HighsDebugStatus HSimplexNla::debugCheckInvert( if (use_debug_level < kHighsDebugLevelExpensive) return return_status; std::string value_adjective; - HighsLogType report_level; expected_density = 0; double inverse_error_norm = 0; double residual_error_norm = 0; @@ -311,7 +307,6 @@ HighsDebugStatus HSimplexNla::debugReportInvertSolutionError( const bool transposed, const HVector& true_solution, const HVector& solution, HVector& residual, const bool force) const { const HighsInt num_row = this->lp_->num_row_; - const HighsOptions* options = this->options_; double solve_error_norm = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) { double solve_error = fabs(solution.array[iRow] - true_solution.array[iRow]); diff --git a/src/simplex/HSimplexNlaFreeze.cpp b/src/simplex/HSimplexNlaFreeze.cpp index 712a00f6f0..b5ea9f46b5 100644 --- a/src/simplex/HSimplexNlaFreeze.cpp +++ b/src/simplex/HSimplexNlaFreeze.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HSimplexNlaProductForm.cpp b/src/simplex/HSimplexNlaProductForm.cpp index e261814c69..3abbc37621 100644 --- a/src/simplex/HSimplexNlaProductForm.cpp +++ b/src/simplex/HSimplexNlaProductForm.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HSimplexReport.cpp b/src/simplex/HSimplexReport.cpp index ce900ce652..e16267cb54 100644 --- a/src/simplex/HSimplexReport.cpp +++ b/src/simplex/HSimplexReport.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HSimplexReport.h b/src/simplex/HSimplexReport.h index fb86c1b393..85104911b4 100644 --- a/src/simplex/HSimplexReport.h +++ b/src/simplex/HSimplexReport.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/HighsSimplexAnalysis.cpp b/src/simplex/HighsSimplexAnalysis.cpp index bf72f26e89..13522088e8 100644 --- a/src/simplex/HighsSimplexAnalysis.cpp +++ b/src/simplex/HighsSimplexAnalysis.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -949,7 +949,7 @@ void HighsSimplexAnalysis::summaryReport() { if (num_correct_dual_primal_flip) { printf( "%12" HIGHSINT_FORMAT - " correct dual primal flips (max = %g) for min dual infeasiblity " + " correct dual primal flips (max = %g) for min dual infeasibility " "= %g\n", num_correct_dual_primal_flip, max_correct_dual_primal_flip, min_correct_dual_primal_flip_dual_infeasibility); @@ -957,7 +957,7 @@ void HighsSimplexAnalysis::summaryReport() { if (num_correct_dual_cost_shift) { printf( "%12" HIGHSINT_FORMAT - " correct dual cost shifts (max = %g) for max dual infeasiblity " + " correct dual cost shifts (max = %g) for max dual infeasibility " "= %g\n", num_correct_dual_cost_shift, max_correct_dual_cost_shift, max_correct_dual_cost_shift_dual_infeasibility); @@ -1090,7 +1090,6 @@ void HighsSimplexAnalysis::summaryReport() { double dlTime = toTime - fmTime; HighsInt iterSpeed = 0; if (dlTime > 0) iterSpeed = dlIter / dlTime; - HighsInt lc_simplex_strategy = lcAnIter.AnIterTrace_simplex_strategy; HighsInt lc_edge_weight_mode = lcAnIter.AnIterTrace_edge_weight_mode; std::string str_edge_weight_mode; if (lc_edge_weight_mode == (HighsInt)EdgeWeightMode::kSteepestEdge) @@ -1112,9 +1111,8 @@ void HighsSimplexAnalysis::summaryReport() { printOneDensity(lcAnIter.AnIterTraceDensity[kSimplexNlaFtran]); printOneDensity(lcAnIter.AnIterTraceDensity[kSimplexNlaBtranEp]); printOneDensity(lcAnIter.AnIterTraceDensity[kSimplexNlaPriceAp]); - double use_DSE_density; - HighsInt local_simplex_strategy = lcAnIter.AnIterTrace_simplex_strategy; if (rp_dual_steepest_edge) { + double use_DSE_density; if (lc_edge_weight_mode == (HighsInt)EdgeWeightMode::kSteepestEdge) { use_DSE_density = lcAnIter.AnIterTraceDensity[kSimplexNlaFtranDse]; } else { @@ -1480,7 +1478,11 @@ void HighsSimplexAnalysis::reportIterationData(const bool header) { void HighsSimplexAnalysis::reportRunTime(const bool header, const double run_time) { if (header) return; - *analysis_log << highsFormatToString(" %ds", (int)run_time); +#ifndef NDEBUG + *analysis_log << highsFormatToString(" %15.8gs", run_time); +#else + *analysis_log << highsFormatToString(" %ds", int(run_time + 0.49)); +#endif } HighsInt HighsSimplexAnalysis::intLog10(const double v) { diff --git a/src/simplex/HighsSimplexAnalysis.h b/src/simplex/HighsSimplexAnalysis.h index ef7482de12..b12dd1c134 100644 --- a/src/simplex/HighsSimplexAnalysis.h +++ b/src/simplex/HighsSimplexAnalysis.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -58,7 +58,135 @@ const HighsLogType kIterationReportLogType = HighsLogType::kVerbose; */ class HighsSimplexAnalysis { public: - HighsSimplexAnalysis() {} + HighsSimplexAnalysis() + : timer_(nullptr), + pointer_serial_factor_clocks(nullptr), + numRow(0), + numCol(0), + numTot(0), + model_name_(""), + lp_name_(""), + analyse_lp_data(false), + analyse_simplex_summary_data(false), + analyse_simplex_runtime_data(false), + analyse_simplex_time(false), + analyse_factor_data(false), + analyse_factor_time(false), + analyse_simplex_data(false), + simplex_strategy(0), + edge_weight_mode(EdgeWeightMode::kSteepestEdge), + solve_phase(0), + simplex_iteration_count(0), + devex_iteration_count(0), + pivotal_row_index(0), + leaving_variable(0), + entering_variable(0), + rebuild_reason(0), + rebuild_reason_string(""), + reduced_rhs_value(0.0), + reduced_cost_value(0.0), + edge_weight(0.0), + edge_weight_error(0.0), + primal_delta(0.0), + primal_step(0.0), + dual_step(0.0), + pivot_value_from_column(0.0), + pivot_value_from_row(0.0), + factor_pivot_threshold(0.0), + numerical_trouble(0.0), + objective_value(0.0), + num_primal_infeasibility(0), + num_dual_infeasibility(0), + sum_primal_infeasibility(0.0), + sum_dual_infeasibility(0.0), + num_dual_phase_1_lp_dual_infeasibility(0), + max_dual_phase_1_lp_dual_infeasibility(0.0), + sum_dual_phase_1_lp_dual_infeasibility(0.0), + num_devex_framework(0), + col_aq_density(0.0), + row_ep_density(0.0), + row_ap_density(0.0), + row_DSE_density(0.0), + col_steepest_edge_density(0.0), + col_basic_feasibility_change_density(0.0), + row_basic_feasibility_change_density(0.0), + col_BFRT_density(0.0), + primal_col_density(0.0), + dual_col_density(0.0), + num_costly_DSE_iteration(0), + costly_DSE_measure(0.0), + multi_iteration_count(0), + multi_chosen(0), + multi_finished(0), + min_concurrency(0), + num_concurrency(0), + max_concurrency(0), + num_col_price(0), + num_row_price(0), + num_row_price_with_switch(0), + num_primal_cycling_detections(0), + num_dual_cycling_detections(0), + num_quad_chuzc(0), + num_heap_chuzc(0), + sum_quad_chuzc_size(0.0), + sum_heap_chuzc_size(0.0), + max_quad_chuzc_size(0), + max_heap_chuzc_size(0), + num_improve_choose_column_row_call(0), + num_remove_pivot_from_pack(0), + num_correct_dual_primal_flip(0), + min_correct_dual_primal_flip_dual_infeasibility(kHighsInf), + max_correct_dual_primal_flip(0.0), + num_correct_dual_cost_shift(0), + max_correct_dual_cost_shift_dual_infeasibility(0.0), + max_correct_dual_cost_shift(0.0), + net_num_single_cost_shift(0), + num_single_cost_shift(0), + max_single_cost_shift(0.0), + sum_single_cost_shift(0.0), + num_dual_steepest_edge_weight_check(0), + num_dual_steepest_edge_weight_reject(0), + num_wrong_low_dual_steepest_edge_weight(0), + num_wrong_high_dual_steepest_edge_weight(0), + average_frequency_low_dual_steepest_edge_weight(0.0), + average_frequency_high_dual_steepest_edge_weight(0.0), + average_log_low_dual_steepest_edge_weight_error(0.0), + average_log_high_dual_steepest_edge_weight_error(0.0), + max_average_frequency_low_dual_steepest_edge_weight(0.0), + max_average_frequency_high_dual_steepest_edge_weight(0.0), + max_sum_average_frequency_extreme_dual_steepest_edge_weight(0.0), + max_average_log_low_dual_steepest_edge_weight_error(0.0), + max_average_log_high_dual_steepest_edge_weight_error(0.0), + max_sum_average_log_extreme_dual_steepest_edge_weight_error(0.0), + num_invert_report_since_last_header(-1), + num_iteration_report_since_last_header(-1), + last_user_log_time(-kHighsInf), + delta_user_log_time(1e0), + average_concurrency(0.0), + average_fraction_of_possible_minor_iterations_performed(0.0), + sum_multi_chosen(0), + sum_multi_finished(0), + num_invert(0), + num_kernel(0), + num_major_kernel(0), + max_kernel_dim(0.0), + sum_kernel_dim(0.0), + running_average_kernel_dim(0.0), + sum_invert_fill_factor(0.0), + sum_kernel_fill_factor(0.0), + sum_major_kernel_fill_factor(0.0), + running_average_invert_fill_factor(1.0), + running_average_kernel_fill_factor(1.0), + running_average_major_kernel_fill_factor(1.0), + AnIterIt0(0), + AnIterPrevIt(0), + AnIterOp{}, + AnIterTraceNumRec(0), + AnIterTraceIterDl(0), + AnIterTrace{}, + AnIterNumInvert{}, + AnIterNumEdWtIt{} {} + // Pointer to timer HighsTimer* timer_; @@ -162,38 +290,38 @@ class HighsSimplexAnalysis { // double dual_steepest_edge_weight_log_error_threshold; // Local copies of simplex data for reporting - HighsInt simplex_strategy = 0; - EdgeWeightMode edge_weight_mode = EdgeWeightMode::kSteepestEdge; - HighsInt solve_phase = 0; - HighsInt simplex_iteration_count = 0; - HighsInt devex_iteration_count = 0; - HighsInt pivotal_row_index = 0; - HighsInt leaving_variable = 0; - HighsInt entering_variable = 0; - HighsInt rebuild_reason = 0; - std::string rebuild_reason_string = ""; - double reduced_rhs_value = 0; - double reduced_cost_value = 0; - double edge_weight = 0; - double edge_weight_error = 0; - double primal_delta = 0; - double primal_step = 0; - double dual_step = 0; - double pivot_value_from_column = 0; - double pivot_value_from_row = 0; - double factor_pivot_threshold = 0; - double numerical_trouble = 0; - double objective_value = 0; - HighsInt num_primal_infeasibility = 0; - HighsInt num_dual_infeasibility = 0; - double sum_primal_infeasibility = 0; - double sum_dual_infeasibility = 0; - // This triple is an original infeasiblility record, so it includes max, + HighsInt simplex_strategy; + EdgeWeightMode edge_weight_mode; + HighsInt solve_phase; + HighsInt simplex_iteration_count; + HighsInt devex_iteration_count; + HighsInt pivotal_row_index; + HighsInt leaving_variable; + HighsInt entering_variable; + HighsInt rebuild_reason; + std::string rebuild_reason_string; + double reduced_rhs_value; + double reduced_cost_value; + double edge_weight; + double edge_weight_error; + double primal_delta; + double primal_step; + double dual_step; + double pivot_value_from_column; + double pivot_value_from_row; + double factor_pivot_threshold; + double numerical_trouble; + double objective_value; + HighsInt num_primal_infeasibility; + HighsInt num_dual_infeasibility; + double sum_primal_infeasibility; + double sum_dual_infeasibility; + // This triple is an original infeasibility record, so it includes max, // but it's only used for reporting - HighsInt num_dual_phase_1_lp_dual_infeasibility = 0; - double max_dual_phase_1_lp_dual_infeasibility = 0; - double sum_dual_phase_1_lp_dual_infeasibility = 0; - HighsInt num_devex_framework = 0; + HighsInt num_dual_phase_1_lp_dual_infeasibility; + double max_dual_phase_1_lp_dual_infeasibility; + double sum_dual_phase_1_lp_dual_infeasibility; + HighsInt num_devex_framework; double col_aq_density; double row_ep_density; double row_ap_density; @@ -208,21 +336,21 @@ class HighsSimplexAnalysis { double costly_DSE_measure; // Local copies of parallel simplex data for reporting - HighsInt multi_iteration_count = 0; - HighsInt multi_chosen = 0; - HighsInt multi_finished = 0; - HighsInt min_concurrency = 0; - HighsInt num_concurrency = 0; - HighsInt max_concurrency = 0; + HighsInt multi_iteration_count; + HighsInt multi_chosen; + HighsInt multi_finished; + HighsInt min_concurrency; + HighsInt num_concurrency; + HighsInt max_concurrency; // Unused // HighsInt multi_num = 0; // Useless // double basis_condition = 0; // Maybe useful // Records of how pivotal row PRICE was done - HighsInt num_col_price = 0; - HighsInt num_row_price = 0; - HighsInt num_row_price_with_switch = 0; + HighsInt num_col_price; + HighsInt num_row_price; + HighsInt num_row_price_with_switch; HighsValueDistribution before_ftran_upper_sparse_density; HighsValueDistribution ftran_upper_sparse_density; @@ -235,29 +363,29 @@ class HighsSimplexAnalysis { HighsValueDistribution cleanup_dual_step_distribution; HighsValueDistribution cleanup_primal_change_distribution; - HighsInt num_primal_cycling_detections = 0; - HighsInt num_dual_cycling_detections = 0; - - HighsInt num_quad_chuzc = 0; - HighsInt num_heap_chuzc = 0; - double sum_quad_chuzc_size = 0; - double sum_heap_chuzc_size = 0; - HighsInt max_quad_chuzc_size = 0; - HighsInt max_heap_chuzc_size = 0; - - HighsInt num_improve_choose_column_row_call = 0; - HighsInt num_remove_pivot_from_pack = 0; - - HighsInt num_correct_dual_primal_flip = 0; - double min_correct_dual_primal_flip_dual_infeasibility = kHighsInf; - double max_correct_dual_primal_flip = 0; - HighsInt num_correct_dual_cost_shift = 0; - double max_correct_dual_cost_shift_dual_infeasibility = 0; - double max_correct_dual_cost_shift = 0; - HighsInt net_num_single_cost_shift = 0; - HighsInt num_single_cost_shift = 0; - double max_single_cost_shift = 0; - double sum_single_cost_shift = 0; + HighsInt num_primal_cycling_detections; + HighsInt num_dual_cycling_detections; + + HighsInt num_quad_chuzc; + HighsInt num_heap_chuzc; + double sum_quad_chuzc_size; + double sum_heap_chuzc_size; + HighsInt max_quad_chuzc_size; + HighsInt max_heap_chuzc_size; + + HighsInt num_improve_choose_column_row_call; + HighsInt num_remove_pivot_from_pack; + + HighsInt num_correct_dual_primal_flip; + double min_correct_dual_primal_flip_dual_infeasibility; + double max_correct_dual_primal_flip; + HighsInt num_correct_dual_cost_shift; + double max_correct_dual_cost_shift_dual_infeasibility; + double max_correct_dual_cost_shift; + HighsInt net_num_single_cost_shift; + HighsInt num_single_cost_shift; + double max_single_cost_shift; + double sum_single_cost_shift; // Tolerances for analysis of TRAN stages - could be needed for // control if this is ever used again! @@ -290,46 +418,46 @@ class HighsSimplexAnalysis { // double AnIterCostlyDseFq; //!< Frequency of iterations when DSE is costly // double AnIterCostlyDseMeasure; - HighsInt num_dual_steepest_edge_weight_check = 0; - HighsInt num_dual_steepest_edge_weight_reject = 0; - HighsInt num_wrong_low_dual_steepest_edge_weight = 0; - HighsInt num_wrong_high_dual_steepest_edge_weight = 0; - double average_frequency_low_dual_steepest_edge_weight = 0; - double average_frequency_high_dual_steepest_edge_weight = 0; - double average_log_low_dual_steepest_edge_weight_error = 0; - double average_log_high_dual_steepest_edge_weight_error = 0; - double max_average_frequency_low_dual_steepest_edge_weight = 0; - double max_average_frequency_high_dual_steepest_edge_weight = 0; - double max_sum_average_frequency_extreme_dual_steepest_edge_weight = 0; - double max_average_log_low_dual_steepest_edge_weight_error = 0; - double max_average_log_high_dual_steepest_edge_weight_error = 0; - double max_sum_average_log_extreme_dual_steepest_edge_weight_error = 0; - - HighsInt num_invert_report_since_last_header = -1; - HighsInt num_iteration_report_since_last_header = -1; - double last_user_log_time = -kHighsInf; - double delta_user_log_time = 1e0; + HighsInt num_dual_steepest_edge_weight_check; + HighsInt num_dual_steepest_edge_weight_reject; + HighsInt num_wrong_low_dual_steepest_edge_weight; + HighsInt num_wrong_high_dual_steepest_edge_weight; + double average_frequency_low_dual_steepest_edge_weight; + double average_frequency_high_dual_steepest_edge_weight; + double average_log_low_dual_steepest_edge_weight_error; + double average_log_high_dual_steepest_edge_weight_error; + double max_average_frequency_low_dual_steepest_edge_weight; + double max_average_frequency_high_dual_steepest_edge_weight; + double max_sum_average_frequency_extreme_dual_steepest_edge_weight; + double max_average_log_low_dual_steepest_edge_weight_error; + double max_average_log_high_dual_steepest_edge_weight_error; + double max_sum_average_log_extreme_dual_steepest_edge_weight_error; + + HighsInt num_invert_report_since_last_header; + HighsInt num_iteration_report_since_last_header; + double last_user_log_time; + double delta_user_log_time; double average_concurrency; double average_fraction_of_possible_minor_iterations_performed; - HighsInt sum_multi_chosen = 0; - HighsInt sum_multi_finished = 0; + HighsInt sum_multi_chosen; + HighsInt sum_multi_finished; // Analysis of INVERT form - HighsInt num_invert = 0; - HighsInt num_kernel = 0; - HighsInt num_major_kernel = 0; - double max_kernel_dim = 0; - double sum_kernel_dim = 0; - double running_average_kernel_dim = 0; - double sum_invert_fill_factor = 0; - double sum_kernel_fill_factor = 0; - double sum_major_kernel_fill_factor = 0; - double running_average_invert_fill_factor = 1; - double running_average_kernel_fill_factor = 1; - double running_average_major_kernel_fill_factor = 1; - - HighsInt AnIterIt0 = 0; + HighsInt num_invert; + HighsInt num_kernel; + HighsInt num_major_kernel; + double max_kernel_dim; + double sum_kernel_dim; + double running_average_kernel_dim; + double sum_invert_fill_factor; + double sum_kernel_fill_factor; + double sum_major_kernel_fill_factor; + double running_average_invert_fill_factor; + double running_average_kernel_fill_factor; + double running_average_major_kernel_fill_factor; + + HighsInt AnIterIt0; HighsInt AnIterPrevIt; // Major operation analysis struct diff --git a/src/simplex/SimplexConst.h b/src/simplex/SimplexConst.h index 650203d059..cdb2db6af5 100644 --- a/src/simplex/SimplexConst.h +++ b/src/simplex/SimplexConst.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/simplex/SimplexStruct.h b/src/simplex/SimplexStruct.h index 50f7b26d6e..eb49c8ca96 100644 --- a/src/simplex/SimplexStruct.h +++ b/src/simplex/SimplexStruct.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -25,7 +25,7 @@ struct SimplexBasis { // The basis for the simplex method consists of basicIndex, // nonbasicFlag and nonbasicMove. If HighsSimplexStatus has_basis // is true then it is assumed that basicIndex_ and nonbasicFlag_ are - // self-consistent and correpond to the dimensions of an associated + // self-consistent and correspond to the dimensions of an associated // HighsLp, but the basis matrix B is not necessarily nonsingular. std::vector basicIndex_; std::vector nonbasicFlag_; diff --git a/src/simplex/SimplexTimer.h b/src/simplex/SimplexTimer.h index 4481b6b10a..f7ec194448 100644 --- a/src/simplex/SimplexTimer.h +++ b/src/simplex/SimplexTimer.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index 6e4b65c983..de393f7bf1 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/test/DevKkt.h b/src/test/DevKkt.h index ef5deb1bb2..d57998cf53 100644 --- a/src/test/DevKkt.h +++ b/src/test/DevKkt.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/test/KktCh2.cpp b/src/test/KktCh2.cpp index 54626aafc5..13ed96403e 100644 --- a/src/test/KktCh2.cpp +++ b/src/test/KktCh2.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -222,7 +222,7 @@ void KktChStep::addChange(int type, HighsInt row, HighsInt col, double valC, } } break; - case 11: // empty row from duplucate rows + case 11: // empty row from duplicate rows upd = rLowers.top(); rLowers.pop(); for (size_t i = 0; i < upd.size(); i++) { @@ -236,7 +236,7 @@ void KktChStep::addChange(int type, HighsInt row, HighsInt col, double valC, RrowUpper[ind] = get<1>(upd[i]); } break; - case 12: // doubleton eq from dupliocate rows; + case 12: // doubleton eq from duplicate rows; upd = cLowers.top(); cLowers.pop(); for (size_t i = 0; i < upd.size(); i++) { diff --git a/src/test/KktCh2.h b/src/test/KktCh2.h index 86635a1831..5ac4cb9cfb 100644 --- a/src/test/KktCh2.h +++ b/src/test/KktCh2.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/FactorTimer.h b/src/util/FactorTimer.h index 7dc46fafe1..22ebd0086d 100644 --- a/src/util/FactorTimer.h +++ b/src/util/FactorTimer.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HFactor.cpp b/src/util/HFactor.cpp index e4b163845e..c7436fe55a 100644 --- a/src/util/HFactor.cpp +++ b/src/util/HFactor.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -16,8 +16,8 @@ #include #include +#include "../extern/pdqsort/pdqsort.h" #include "lp_data/HConst.h" -#include "pdqsort/pdqsort.h" #include "util/FactorTimer.h" #include "util/HFactorDebug.h" #include "util/HVector.h" @@ -807,8 +807,8 @@ void HFactor::buildSimple() { if (nworkLast == nwork) break; } if (report_anything) reportLu(kReportLuBoth, false); - t2_store_l = l_index.size() - t2_store_l; - t2_store_u = u_index.size() - t2_store_u; + t2_store_l = static_cast(l_index.size()) - t2_store_l; + t2_store_u = static_cast(u_index.size()) - t2_store_u; t2_store_p = t2_store_p - nwork; build_synthetic_tick += @@ -892,7 +892,7 @@ HighsInt HFactor::buildKernel() { if (nwork == check_nwork) { reportAsm(); } - // Detemine whether to return due to exceeding the time limit + // Determine whether to return due to exceeding the time limit if (check_for_timeout && search_k % timer_frequency == 0) { double current_time = build_timer_->readRunHighsClock(); double time_difference = current_time - previous_iteration_time; @@ -1039,7 +1039,12 @@ HighsInt HFactor::buildKernel() { } } // 1.4. If we found nothing: tell singular - if (!foundPivot) { + if (iRowPivot < 0) { + // To detect the absence of a pivot, it should be sufficient + // that iRowPivot is (still) -1, but add sanity asserts that + // jColPivot is (still) -1 and foundPivot is false + assert(jColPivot < 0); + assert(!foundPivot); rank_deficiency = nwork + 1; highsLogDev(log_options, HighsLogType::kWarning, "Factorization identifies rank deficiency of %d\n", @@ -1274,7 +1279,6 @@ void HFactor::buildHandleRankDeficiency() { // * Less than the rank deficiency of the basis matrix if num_basic < num_row // // - const HighsInt basic_index_rank_deficiency = rank_deficiency; if (num_basic < num_row) { rank_deficiency += num_row - num_basic; } @@ -2113,7 +2117,7 @@ void HFactor::updateCFT(HVector* aq, HVector* ep, HighsInt* iRow dwork[p_row] = value; } - // 3. Store the partial FTRAN result to matirx U + // 3. Store the partial FTRAN result to matrix U double ppaq = dwork[iRow[cp]]; // pivot of the partial aq dwork[iRow[cp]] = 0; HighsInt u_countX = t_start[cp]; diff --git a/src/util/HFactor.h b/src/util/HFactor.h index 45bc295eee..d06a79af67 100644 --- a/src/util/HFactor.h +++ b/src/util/HFactor.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -105,6 +105,35 @@ struct InvertibleRepresentation { */ class HFactor { public: + HFactor() + : build_realTick(0.0), + build_synthetic_tick(0.0), + rank_deficiency(0), + basis_matrix_num_el(0), + invert_num_el(0), + kernel_dim(0), + kernel_num_el(0), + num_row(0), + num_col(0), + num_basic(0), + a_matrix_valid(false), + a_start(nullptr), + a_index(nullptr), + a_value(nullptr), + basic_index(nullptr), + pivot_threshold(0.0), + pivot_tolerance(0.0), + highs_debug_level(0), + time_limit_(0.0), + use_original_HFactor_logic(false), + debug_report_(false), + basis_matrix_limit_size(0), + update_method(0), + build_timer_(nullptr), + nwork(0), + u_merit_x(0), + u_total_x(0){}; + /** * @brief Copy problem size and pointers of constraint matrix, and set * up space for INVERT @@ -304,10 +333,10 @@ class HFactor { RefactorInfo refactor_info_; // Properties of data held in HFactor.h - HighsInt basis_matrix_num_el = 0; - HighsInt invert_num_el = 0; - HighsInt kernel_dim = 0; - HighsInt kernel_num_el = 0; + HighsInt basis_matrix_num_el; + HighsInt invert_num_el; + HighsInt kernel_dim; + HighsInt kernel_num_el; /** * Data of the factor @@ -339,7 +368,7 @@ class HFactor { HighsLogOptions log_options; bool use_original_HFactor_logic; - bool debug_report_ = false; + bool debug_report_; HighsInt basis_matrix_limit_size; HighsInt update_method; diff --git a/src/util/HFactorConst.h b/src/util/HFactorConst.h index 34c54c4723..5e78afbbb1 100644 --- a/src/util/HFactorConst.h +++ b/src/util/HFactorConst.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HFactorDebug.cpp b/src/util/HFactorDebug.cpp index ba1188333e..61849b4c92 100644 --- a/src/util/HFactorDebug.cpp +++ b/src/util/HFactorDebug.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HFactorDebug.h b/src/util/HFactorDebug.h index 410230c55e..090f5530db 100644 --- a/src/util/HFactorDebug.h +++ b/src/util/HFactorDebug.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HFactorExtend.cpp b/src/util/HFactorExtend.cpp index 4ecfd271d5..fe43bc3654 100644 --- a/src/util/HFactorExtend.cpp +++ b/src/util/HFactorExtend.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HFactorRefactor.cpp b/src/util/HFactorRefactor.cpp index 866227674d..559f1e4eab 100644 --- a/src/util/HFactorRefactor.cpp +++ b/src/util/HFactorRefactor.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -32,7 +32,7 @@ void RefactorInfo::clear() { HighsInt HFactor::rebuild(HighsTimerClock* factor_timer_clock_pointer) { const bool report_lu = false; - // Check that the refactorzation information should be used + // Check that the refactorization information should be used assert(refactor_info_.use); /** * 0. Clear L and U factor diff --git a/src/util/HFactorUtils.cpp b/src/util/HFactorUtils.cpp index fdc49f6138..7eb25048b7 100644 --- a/src/util/HFactorUtils.cpp +++ b/src/util/HFactorUtils.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HSet.cpp b/src/util/HSet.cpp index 269e866ea6..deb9394c1e 100644 --- a/src/util/HSet.cpp +++ b/src/util/HSet.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HSet.h b/src/util/HSet.h index f3dba8ca9f..4f83308f9d 100644 --- a/src/util/HSet.h +++ b/src/util/HSet.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HVector.h b/src/util/HVector.h index ad4a29de1b..3300a91658 100644 --- a/src/util/HVector.h +++ b/src/util/HVector.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HVectorBase.cpp b/src/util/HVectorBase.cpp index f0bf17624a..8ef41f12fb 100644 --- a/src/util/HVectorBase.cpp +++ b/src/util/HVectorBase.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HVectorBase.h b/src/util/HVectorBase.h index c6b3c6fcc1..9db206d809 100644 --- a/src/util/HVectorBase.h +++ b/src/util/HVectorBase.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsCDouble.h b/src/util/HighsCDouble.h index c7274abb4f..b02622d162 100644 --- a/src/util/HighsCDouble.h +++ b/src/util/HighsCDouble.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -32,7 +32,7 @@ class HighsCDouble { // Proceedings of. 2005. /// performs an exact transformation such that x + y = a + b - /// and x = double(a + b). The operation uses 6 flops (addition/substraction). + /// and x = double(a + b). The operation uses 6 flops (addition/subtraction). static void two_sum(double& x, double& y, double a, double b) { x = a + b; double z = x - a; @@ -50,7 +50,7 @@ class HighsCDouble { /// performs an exact transformation such that x + y = a * b /// and x = double(a * b). The operation uses 10 flops for - /// addition/substraction and 7 flops for multiplication. + /// addition/subtraction and 7 flops for multiplication. static void two_product(double& x, double& y, double a, double b) { x = a * b; double a1, a2, b1, b2; diff --git a/src/util/HighsComponent.h b/src/util/HighsComponent.h index 7244a3cda0..47cf8d2d55 100644 --- a/src/util/HighsComponent.h +++ b/src/util/HighsComponent.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsDataStack.h b/src/util/HighsDataStack.h index 02dc367a94..3f8423e8f8 100644 --- a/src/util/HighsDataStack.h +++ b/src/util/HighsDataStack.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -29,7 +29,7 @@ class HighsDataStack { std::vector data; - HighsInt position; + std::size_t position; public: void resetPosition() { position = data.size(); } @@ -78,9 +78,9 @@ class HighsDataStack { } } - void setPosition(HighsInt position_) { this->position = position_; } + void setPosition(size_t position_) { this->position = position_; } - HighsInt getCurrentDataSize() const { return data.size(); } + size_t getCurrentDataSize() const { return data.size(); } }; #endif diff --git a/src/util/HighsDisjointSets.h b/src/util/HighsDisjointSets.h index c9515adc59..db5257e230 100644 --- a/src/util/HighsDisjointSets.h +++ b/src/util/HighsDisjointSets.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsHash.cpp b/src/util/HighsHash.cpp index 39b2c261c7..8594a491fe 100644 --- a/src/util/HighsHash.cpp +++ b/src/util/HighsHash.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsHash.h b/src/util/HighsHash.h index 8f91ab47b8..620953dd0d 100644 --- a/src/util/HighsHash.h +++ b/src/util/HighsHash.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -130,7 +130,7 @@ struct HighsHashHelpers { static int popcnt(uint64_t x) { #ifdef _WIN64 - return __popcnt64(x); + return static_cast(__popcnt64(x)); #else return __popcnt(x & 0xffffffffu) + __popcnt(x >> 32); #endif @@ -253,7 +253,7 @@ struct HighsHashHelpers { u64 result = u64(a) * u64(b); result = (result >> 31) + (result & M31()); if (result >= M31()) result -= M31(); - return result; + return static_cast(result); } static u32 modexp_M31(u32 a, u64 e) { @@ -277,7 +277,8 @@ struct HighsHashHelpers { template static u64 pair_hash(u32 a, u32 b) { - return (a + c[2 * k]) * (b + c[2 * k + 1]); + return (static_cast(a) + c[2 * k]) * + (static_cast(b) + c[2 * k + 1]); } static void sparse_combine(u64& hash, HighsInt index, u64 value) { @@ -383,18 +384,20 @@ struct HighsHashHelpers { // which we evaluate at the random vector of 16. // make sure input value is never zero and at most 31bits are used - value = (pair_hash<0>(value, value >> 32) >> 33) | 1; + value = (pair_hash<0>(static_cast(value), value >> 32) >> 33) | 1; // make sure that the constant has at most 31 bits, as otherwise the modulo // algorithm for multiplication mod M31 might not work properly due to // overflow - u32 a = c[index & 63] & M31(); + u32 a = static_cast(c[index & 63] & M31()); HighsInt degree = (index >> 6) + 1; - hash += multiply_modM31(value, modexp_M31(a, degree)); - hash = (hash >> 31) + (hash & M31()); - if (hash >= M31()) hash -= M31(); - assert(hash < M31()); + u64 result = hash; + result += multiply_modM31(static_cast(value), modexp_M31(a, degree)); + result = (result >> 31) + (result & M31()); + if (result >= M31()) result -= M31(); + assert(result < M31()); + hash = static_cast(result); } static void sparse_inverse_combine32(u32& hash, HighsInt index, u64 value) { @@ -407,16 +410,19 @@ struct HighsHashHelpers { // procedure. // make sure input value is never zero and at most 31bits are used - value = (pair_hash<0>(value, value >> 32) >> 33) | 1; + value = (pair_hash<0>(static_cast(value), value >> 32) >> 33) | 1; - u32 a = c[index & 63] & M31(); + u32 a = static_cast(c[index & 63] & M31()); HighsInt degree = (index >> 6) + 1; // add the additive inverse (M31() - hashvalue) instead of the hash value // itself - hash += M31() - multiply_modM31(value, modexp_M31(a, degree)); - hash = (hash >> 31) + (hash & M31()); - if (hash >= M31()) hash -= M31(); - assert(hash < M31()); + u64 result = hash; + result += + M31() - multiply_modM31(static_cast(value), modexp_M31(a, degree)); + result = (result >> 31) + (result & M31()); + if (result >= M31()) result -= M31(); + assert(result < M31()); + hash = static_cast(result); } static constexpr u64 fibonacci_muliplier() { return u64{0x9e3779b97f4a7c15}; } @@ -746,7 +752,8 @@ struct HighsHashHelpers { // now be different values which exhibit the same pattern as the 0.5 case, // but they do not have a small denominator like 1/2 in their rational // representation but are power of two multiples of the golden ratio and - // therefore irrational, which we do not expect in non-artifical input data. + // therefore irrational, which we do not expect in non-artificial input + // data. int exponent; double hashbits = std::frexp(val * golden_ratio_reciprocal(), &exponent); @@ -813,7 +820,7 @@ struct HighsHashTableEntry { // and the value as default // the enable if statement makes sure this overload is never selected // when the type of the single argument is HighsHashTableEntry so that - // the default move and copy constructures are preferred when they match + // the default move and copy constructors are preferred when they match // and this is only used to initialize the key type from a single argument. template < typename K_, @@ -868,7 +875,7 @@ struct HighsHashTableEntry { // Add a constructor to accept an arbitrary argument pack for initialize the // underlying value of type T. The enable if statement makes sure this // overload is never selected when the type of the single argument is - // HighsHashTableEntry so that the default move and copy constructures + // HighsHashTableEntry so that the default move and copy constructors // are preferred when they match and this is only used to initialize the value // of type from a set of arguments which are properly forwarded. // The std::tuple usage in enable_if is a work-around to make the statement @@ -981,8 +988,8 @@ class HighsHashTable { HighsHashTable() { makeEmptyTable(128); } HighsHashTable(u64 minCapacity) { - u64 initCapacity = u64{1} << (u64)std::ceil( - std::log2(std::max(128.0, 8 * minCapacity / 7.0))); + u64 initCapacity = u64{1} << (u64)std::ceil(std::log2(std::max( + 128.0, 8 * static_cast(minCapacity) / 7))); makeEmptyTable(initCapacity); } diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index b2afcc7876..c42bc271e9 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -143,39 +143,22 @@ class HighsHashTree { --pos; while (hashes[pos] > hash) ++pos; - while (pos != size && hashes[pos] == hash) { - if (entry.key() == entries[pos].key()) - return std::make_pair(&entries[pos].value(), false); + if (find_key(entry.key(), hash, pos)) + return std::make_pair(&entries[pos].value(), false); - ++pos; - } - - if (pos < size) { - std::move_backward(&entries[pos], &entries[size], &entries[size + 1]); - memmove(&hashes[pos + 1], &hashes[pos], - sizeof(hashes[0]) * (size - pos)); - } - - entries[pos] = std::move(entry); - hashes[pos] = hash; - ++size; - hashes[size] = 0; } else { occupation.set(hashChunk); - if (pos < size) { + if (pos < size) while (hashes[pos] > hash) ++pos; - std::move_backward(&entries[pos], &entries[size], &entries[size + 1]); - memmove(&hashes[pos + 1], &hashes[pos], - sizeof(hashes[0]) * (size - pos)); - } - - entries[pos] = std::move(entry); - hashes[pos] = hash; - ++size; - hashes[size] = 0; } + if (pos < size) move_backward(pos, size); + entries[pos] = std::move(entry); + hashes[pos] = hash; + ++size; + hashes[size] = 0; + return std::make_pair(&entries[pos].value(), true); } @@ -187,10 +170,7 @@ class HighsHashTree { int pos = occupation.num_set_until(hashChunk) - 1; while (hashes[pos] > hash) ++pos; - while (pos != size && hashes[pos] == hash) { - if (key == entries[pos].key()) return &entries[pos].value(); - ++pos; - } + if (find_key(key, hash, pos)) return &entries[pos].value(); return nullptr; } @@ -206,26 +186,20 @@ class HighsHashTree { int pos = startPos; while (hashes[pos] > hash) ++pos; - while (pos != size && hashes[pos] == hash) { - if (key == entries[pos].key()) { - --size; - if (pos < size) { - std::move(&entries[pos + 1], &entries[size + 1], &entries[pos]); - memmove(&hashes[pos], &hashes[pos + 1], - sizeof(hashes[0]) * (size - pos)); - if (get_first_chunk16(hashes[startPos]) != hashChunk) - occupation.flip(hashChunk); - } else if (startPos == pos) - occupation.flip(hashChunk); - - hashes[size] = 0; - return true; - } + if (!find_key(key, hash, pos)) return false; - ++pos; - } + --size; + if (pos < size) { + std::move(&entries[pos + 1], &entries[size + 1], &entries[pos]); + memmove(&hashes[pos], &hashes[pos + 1], + sizeof(hashes[0]) * (size - pos)); + if (get_first_chunk16(hashes[startPos]) != hashChunk) + occupation.flip(hashChunk); + } else if (startPos == pos) + occupation.flip(hashChunk); - return false; + hashes[size] = 0; + return true; } void rehash(int hashPos) { @@ -268,17 +242,31 @@ class HighsHashTree { // make space at that position, otherwise nothing needs to be done but // incrementing i increasing the sorted range by 1. if (pos < i) { - uint16_t hash = hashes[i]; + uint64_t hash = hashes[i]; auto entry = std::move(entries[i]); - std::move_backward(&entries[pos], &entries[i], &entries[i + 1]); - memmove(&hashes[pos + 1], &hashes[pos], - sizeof(hashes[0]) * (size - pos)); + move_backward(pos, i); hashes[pos] = hash; entries[pos] = std::move(entry); } ++i; } } + + void move_backward(const int& first, const int& last) { + // move elements backwards + std::move_backward(&entries[first], &entries[last], &entries[last + 1]); + memmove(&hashes[first + 1], &hashes[first], + sizeof(hashes[0]) * (last - first)); + } + + bool find_key(const K& key, const uint16_t& hash, int& pos) const { + // find key + while (pos != size && hashes[pos] == hash) { + if (key == entries[pos].key()) return true; + ++pos; + } + return false; + } }; struct ListNode { @@ -509,13 +497,15 @@ class HighsHashTree { int pos = HighsHashHelpers::log2i(matchMask); matchMask ^= (uint64_t{1} << pos); - int i = leaf1->occupation.num_set_until(pos) + offset1; + int i = + leaf1->occupation.num_set_until(static_cast(pos)) + offset1; while (get_first_chunk16(leaf1->hashes[i]) != pos) { ++i; ++offset1; } - int j = leaf2->occupation.num_set_until(pos) + offset2; + int j = + leaf2->occupation.num_set_until(static_cast(pos)) + offset2; while (get_first_chunk16(leaf2->hashes[j]) != pos) { ++j; ++offset2; @@ -568,13 +558,15 @@ class HighsHashTree { int pos = HighsHashHelpers::log2i(matchMask); matchMask ^= (uint64_t{1} << pos); - int i = leaf->occupation.num_set_until(pos) + offset; + int i = leaf->occupation.num_set_until(static_cast(pos)) + + offset; while (get_first_chunk16(leaf->hashes[i]) != pos) { ++i; ++offset; } - int j = branch->occupation.num_set_until(pos) - 1; + int j = + branch->occupation.num_set_until(static_cast(pos)) - 1; do { if (find_recurse(branch->child[j], @@ -654,7 +646,11 @@ class HighsHashTree { newNode = newLeafSize4; for (int i = 0; i <= newNumChild; ++i) mergeIntoLeaf(newLeafSize4, hashPos, branch->child[i]); + break; } + default: + // Unexpected result from 'entries_to_size_class' + assert(false); } destroyBranchingNode(branch); @@ -815,11 +811,11 @@ class HighsHashTree { // maxsize in one bucket = number of items - (num buckets-1) // since each bucket has at least 1 item the largest one can only // have all remaining ones After adding the item: If it does not - // collid + // collide int maxEntriesPerLeaf = 2 + leaf->size - branchSize; if (maxEntriesPerLeaf <= InnerLeaf<1>::capacity()) { - // all items can go into the smalles leaf size + // all items can go into the smallest leaf size for (int i = 0; i < branchSize; ++i) branch->child[i] = new InnerLeaf<1>; @@ -840,8 +836,7 @@ class HighsHashTree { hash, hashPos + 1, entry); } else { // there are many collisions, determine the exact sizes first - uint8_t sizes[InnerLeaf<4>::capacity() + 1]; - memset(sizes, 0, branchSize); + uint8_t sizes[InnerLeaf<4>::capacity() + 1] = {}; sizes[occupation.num_set_until(hashChunk) - 1] += 1; for (int i = 0; i < leaf->size; ++i) { int pos = @@ -864,6 +859,9 @@ class HighsHashTree { case 4: branch->child[i] = new InnerLeaf<4>; break; + default: + // Unexpected result from 'entries_to_size_class' + assert(false); } } @@ -1139,8 +1137,10 @@ class HighsHashTree { assert(((matchMask >> pos) & 1) == 0); - int location1 = branch1->occupation.num_set_until(pos) - 1; - int location2 = branch2->occupation.num_set_until(pos) - 1; + int location1 = + branch1->occupation.num_set_until(static_cast(pos)) - 1; + int location2 = + branch2->occupation.num_set_until(static_cast(pos)) - 1; const HighsHashTableEntry* match = find_common_recurse(branch1->child[location1], diff --git a/src/util/HighsInt.h b/src/util/HighsInt.h index 2930d4985a..df18aafcb0 100644 --- a/src/util/HighsInt.h +++ b/src/util/HighsInt.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsIntegers.h b/src/util/HighsIntegers.h index 5814e43b34..85244fc64f 100644 --- a/src/util/HighsIntegers.h +++ b/src/util/HighsIntegers.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -29,8 +29,7 @@ class HighsIntegers { } static double mod(double a, double m) { - int64_t r = std::fmod(a, m); - return r + (a < 0) * m; + return std::trunc(std::fmod(a, m)) + (a < 0) * m; } static int64_t nearestInteger(double x) { @@ -38,7 +37,7 @@ class HighsIntegers { } static bool isIntegral(double x, double eps) { - double y = std::fabs(x - (int64_t)x); + double y = std::fabs(x - std::trunc(x)); return std::min(y, 1.0 - y) <= eps; } @@ -118,8 +117,8 @@ class HighsIntegers { m[1] += m[0] * ai; m[3] += m[2] * ai; - double x0 = m[0] / (double)m[2]; - double x1 = m[1] / (double)m[3]; + double x0 = static_cast(m[0]) / static_cast(m[2]); + double x1 = static_cast(m[1]) / static_cast(m[3]); x = std::abs(x); double err0 = std::abs(x - x0); double err1 = std::abs(x - x1); @@ -147,7 +146,7 @@ class HighsIntegers { expshift = std::max(-expshift, 0) + 3; // guard against making the largest value too big which may cause overflows - // with intermdediate gcd values + // with intermediate gcd values int expMaxVal; std::frexp(maxval, &expMaxVal); expMaxVal = std::min(expMaxVal, 32); @@ -195,7 +194,7 @@ class HighsIntegers { currgcd = gcd(currgcd, (int64_t) double(downval)); // if the denominator is large, divide by the current gcd to prevent - // unecessary overflows + // unnecessary overflows if (denom > std::numeric_limits::max()) { denom /= currgcd; if (startdenom != 1) startdenom /= gcd(currgcd, startdenom); @@ -204,7 +203,7 @@ class HighsIntegers { } } - return denom / (double)currgcd; + return static_cast(denom) / static_cast(currgcd); } static double integralScale(const std::vector& vals, double deltadown, diff --git a/src/util/HighsLinearSumBounds.cpp b/src/util/HighsLinearSumBounds.cpp index dc929b712e..ca9e03c363 100644 --- a/src/util/HighsLinearSumBounds.cpp +++ b/src/util/HighsLinearSumBounds.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsLinearSumBounds.h b/src/util/HighsLinearSumBounds.h index c684824308..1bb3e59461 100644 --- a/src/util/HighsLinearSumBounds.h +++ b/src/util/HighsLinearSumBounds.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsMatrixPic.cpp b/src/util/HighsMatrixPic.cpp index b982921795..a7716d26e3 100644 --- a/src/util/HighsMatrixPic.cpp +++ b/src/util/HighsMatrixPic.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsMatrixPic.h b/src/util/HighsMatrixPic.h index 117019b7cc..034ca1ef35 100644 --- a/src/util/HighsMatrixPic.h +++ b/src/util/HighsMatrixPic.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsMatrixSlice.h b/src/util/HighsMatrixSlice.h index 2a5eea4a54..3da09ce402 100644 --- a/src/util/HighsMatrixSlice.h +++ b/src/util/HighsMatrixSlice.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -196,10 +196,11 @@ class HighsMatrixSlice { using pointer = const HighsSliceNonzero*; using reference = const HighsSliceNonzero&; - iterator(HighsInt node) : currentNode(node) {} + iterator(HighsInt node) : pos_(), nodeNext(nullptr), currentNode(node) {} iterator(const HighsInt* nodeIndex, const double* nodeValue, const HighsInt* nodeNext, HighsInt node) - : pos_(nodeIndex + node, nodeValue + node), + : pos_(node == -1 ? nullptr : nodeIndex + node, + node == -1 ? nullptr : nodeValue + node), nodeNext(nodeNext), currentNode(node) {} iterator() = default; @@ -275,7 +276,8 @@ class HighsMatrixSlice { using pointer = const HighsSliceNonzero*; using reference = const HighsSliceNonzero&; - iterator(HighsInt node) : currentNode(node) {} + iterator(HighsInt node) + : pos_(), nodeLeft(nullptr), nodeRight(nullptr), currentNode(node) {} iterator(const HighsInt* nodeIndex, const double* nodeValue, const HighsInt* nodeLeft, const HighsInt* nodeRight, HighsInt node) : pos_(nodeIndex + node, nodeValue + node), @@ -373,7 +375,8 @@ class HighsMatrixSlice { using pointer = const HighsSliceNonzero*; using reference = const HighsSliceNonzero&; - iterator(HighsInt node) : currentNode(node) {} + iterator(HighsInt node) + : pos_(), nodeLeft(nullptr), nodeRight(nullptr), currentNode(node) {} iterator(const HighsInt* nodeIndex, const double* nodeValue, const HighsInt* nodeLeft, const HighsInt* nodeRight, HighsInt node) : pos_(nodeIndex, nodeValue), @@ -476,7 +479,7 @@ class HighsMatrixSlice { using pointer = const HighsSliceNonzero*; using reference = const HighsSliceNonzero&; - iterator(const HighsInt* node) : node(node) {} + iterator(const HighsInt* node) : pos_(), node(node), currentNode(0) {} iterator(const HighsInt* nodeIndex, const double* nodeValue, const HighsInt* node) : pos_(nodeIndex, nodeValue), node(node), currentNode(0) {} diff --git a/src/util/HighsMatrixUtils.cpp b/src/util/HighsMatrixUtils.cpp index 1089698993..42f4375063 100644 --- a/src/util/HighsMatrixUtils.cpp +++ b/src/util/HighsMatrixUtils.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -82,7 +82,6 @@ HighsStatus assessMatrix( if (partitioned) this_p_end = matrix_p_end[0]; for (HighsInt ix = 0; ix < num_vec; ix++) { this_start = matrix_start[ix]; - HighsInt next_start = matrix_start[ix + 1]; bool this_start_too_small = this_start < previous_start; if (this_start_too_small) { highsLogUser(log_options, HighsLogType::kError, @@ -176,7 +175,7 @@ HighsStatus assessMatrix( matrix_name.c_str(), ix, el, component, vec_dim); return HighsStatus::kError; } - // Check that the index has not already ocurred. + // Check that the index has not already occurred. legal_component = index_set.find(component) == nullptr; if (!legal_component) { highsLogUser(log_options, HighsLogType::kError, diff --git a/src/util/HighsMatrixUtils.h b/src/util/HighsMatrixUtils.h index 414e716df9..9cf27d929f 100644 --- a/src/util/HighsMatrixUtils.h +++ b/src/util/HighsMatrixUtils.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsMemoryAllocation.h b/src/util/HighsMemoryAllocation.h new file mode 100644 index 0000000000..be4c0769c4 --- /dev/null +++ b/src/util/HighsMemoryAllocation.h @@ -0,0 +1,55 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file HighsMemoryAllocation.h + * @brief Utilities for memory allocation that return true if successful + */ + +#ifndef UTIL_HIGHS_MEMORY_ALLOCATION_H_ +#define UTIL_HIGHS_MEMORY_ALLOCATION_H_ + +#include + +#include "util/HighsInt.h" + +template +bool okResize(std::vector& use_vector, HighsInt dimension, T value = T{}) { + try { + use_vector.resize(dimension, value); + } catch (const std::bad_alloc& e) { + printf("HighsMemoryAllocation::okResize fails with %s\n", e.what()); + return false; + } + return true; +} + +template +bool okReserve(std::vector& use_vector, HighsInt dimension) { + try { + use_vector.reserve(dimension); + } catch (const std::bad_alloc& e) { + printf("HighsMemoryAllocation::okReserve fails with %s\n", e.what()); + return false; + } + return true; +} + +template +bool okAssign(std::vector& use_vector, HighsInt dimension, T value = T{}) { + try { + use_vector.assign(dimension, value); + } catch (const std::bad_alloc& e) { + printf("HighsMemoryAllocation::okAssign fails with %s\n", e.what()); + return false; + } + return true; +} + +#endif diff --git a/src/util/HighsRandom.h b/src/util/HighsRandom.h index 61fe84dbe6..3b590bbcb1 100644 --- a/src/util/HighsRandom.h +++ b/src/util/HighsRandom.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -32,13 +32,14 @@ class HighsRandom { assert(sup <= uint32_t{1} << nbits); while (true) { advance(); - uint32_t lo = state; + uint32_t lo = static_cast(state); uint32_t hi = state >> 32; - uint64_t val; + uint32_t val; -#define HIGHS_RAND_TRY_OUTPUT(n) \ - val = HighsHashHelpers::pair_hash(lo, hi) >> (64 - nbits); \ +#define HIGHS_RAND_TRY_OUTPUT(n) \ + val = static_cast(HighsHashHelpers::pair_hash(lo, hi) >> \ + (64 - nbits)); \ if (val < sup) return val; HIGHS_RAND_TRY_OUTPUT(0); @@ -86,7 +87,7 @@ class HighsRandom { assert(sup <= uint64_t{1} << nbits); while (true) { advance(); - uint32_t lo = state; + uint32_t lo = static_cast(state); uint32_t hi = state >> 32; uint64_t val; @@ -128,7 +129,8 @@ class HighsRandom { void initialise(HighsUInt seed = 0) { state = seed; do { - state = HighsHashHelpers::pair_hash<0>(state, state >> 32); + state = HighsHashHelpers::pair_hash<0>(static_cast(state), + state >> 32); state ^= (HighsHashHelpers::pair_hash<1>(state >> 32, seed) >> 32); } while (state == 0); } @@ -154,7 +156,9 @@ class HighsRandom { (HighsHashHelpers::pair_hash<1>(state, state >> 32) >> 32); #else // use 31 bits of the 64 bit result - return HighsHashHelpers::pair_hash<0>(state, state >> 32) >> 33; + return HighsHashHelpers::pair_hash<0>(static_cast(state), + state >> 32) >> + 33; #endif } @@ -181,9 +185,12 @@ class HighsRandom { double fraction() { advance(); // 52 bit output is in interval [0,2^52-1] - uint64_t output = - (HighsHashHelpers::pair_hash<0>(state, state >> 32) >> (64 - 52)) ^ - (HighsHashHelpers::pair_hash<1>(state, state >> 32) >> (64 - 26)); + uint64_t output = (HighsHashHelpers::pair_hash<0>( + static_cast(state), state >> 32) >> + (64 - 52)) ^ + (HighsHashHelpers::pair_hash<1>( + static_cast(state), state >> 32) >> + (64 - 26)); // compute (1+output) / (2^52+1) which is strictly between 0 and 1 return (1 + output) * 2.2204460492503125e-16; } @@ -194,9 +201,12 @@ class HighsRandom { double closedFraction() { advance(); // 53 bit result is in interval [0,2^53-1] - uint64_t output = - (HighsHashHelpers::pair_hash<0>(state, state >> 32) >> (64 - 53)) ^ - (HighsHashHelpers::pair_hash<1>(state, state >> 32) >> 32); + uint64_t output = (HighsHashHelpers::pair_hash<0>( + static_cast(state), state >> 32) >> + (64 - 53)) ^ + (HighsHashHelpers::pair_hash<1>( + static_cast(state), state >> 32) >> + 32); // compute output / (2^53-1) in double precision which is in the closed // interval [0,1] return output * 1.1102230246251566e-16; @@ -212,7 +222,9 @@ class HighsRandom { */ bool bit() { advance(); - return HighsHashHelpers::pair_hash<0>(state, state >> 32) >> 63; + return HighsHashHelpers::pair_hash<0>(static_cast(state), + state >> 32) >> + 63; } /** diff --git a/src/util/HighsRbTree.h b/src/util/HighsRbTree.h index 5b3738306f..6903d45d74 100644 --- a/src/util/HighsRbTree.h +++ b/src/util/HighsRbTree.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -141,7 +141,7 @@ class RbTree { } void setColor(LinkType node, HighsUInt color) { - static_cast(this)->getRbTreeLinks(node).setColor(color); + static_cast(this)->getRbTreeLinks(node).setColor(color != 0); } HighsUInt getColor(LinkType node) const { diff --git a/src/util/HighsSort.cpp b/src/util/HighsSort.cpp index 1bf59cf923..e4c08d1543 100644 --- a/src/util/HighsSort.cpp +++ b/src/util/HighsSort.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsSort.h b/src/util/HighsSort.h index 7dbbccc932..6e2defb425 100644 --- a/src/util/HighsSort.h +++ b/src/util/HighsSort.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -36,7 +36,7 @@ void maxheapsort(HighsInt* heap_v, //!< HighsInt values to be sorted */ void maxheapsort( HighsInt* heap_v, //!< Values to be sorted - HighsInt* heap_i, //!< Indices corrresponding to (sorted) values + HighsInt* heap_i, //!< Indices corresponding to (sorted) values HighsInt n //!< Number of values to be sorted ); /** @@ -45,7 +45,7 @@ void maxheapsort( */ void maxheapsort( double* heap_v, //!< Values to be sorted - HighsInt* heap_i, //!< Indices corrresponding to (sorted) values + HighsInt* heap_i, //!< Indices corresponding to (sorted) values HighsInt n //!< Number of values to be sorted ); /** @@ -61,7 +61,7 @@ void buildMaxheap(HighsInt* heap_v, //!< HighsInt values to be sorted */ void buildMaxheap( HighsInt* heap_v, //!< Values to be sorted - HighsInt* heap_i, //!< Indices corrresponding to (sorted) values + HighsInt* heap_i, //!< Indices corresponding to (sorted) values HighsInt n //!< Number of values to be sorted ); /** @@ -70,7 +70,7 @@ void buildMaxheap( */ void buildMaxheap( double* heap_v, //!< Values to be sorted - HighsInt* heap_i, //!< Indices corrresponding to (sorted) values + HighsInt* heap_i, //!< Indices corresponding to (sorted) values HighsInt n //!< Number of values to be sorted ); /** @@ -84,7 +84,7 @@ void maxHeapsort(HighsInt* heap_v, //!< HighsInt values to be sorted */ void maxHeapsort( HighsInt* heap_v, //!< Values to be sorted - HighsInt* heap_i, //!< Indices corrresponding to (sorted) values + HighsInt* heap_i, //!< Indices corresponding to (sorted) values HighsInt n //!< Number of values to be sorted ); /** @@ -92,7 +92,7 @@ void maxHeapsort( */ void maxHeapsort( double* heap_v, //!< Values to be sorted - HighsInt* heap_i, //!< Indices corrresponding to (sorted) values + HighsInt* heap_i, //!< Indices corresponding to (sorted) values HighsInt n //!< Number of values to be sorted ); /** diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index efdb0cb92d..ce8e37695b 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -171,7 +171,6 @@ void HighsSparseMatrix::ensureRowwise() { assert(num_nz >= 0); assert((HighsInt)this->index_.size() >= num_nz); assert((HighsInt)this->value_.size() >= num_nz); - bool empty_matrix = num_col == 0 || num_row == 0; if (num_nz == 0) { // Empty matrix, so just ensure that there are enough zero starts // for the new orientation @@ -529,6 +528,58 @@ void HighsSparseMatrix::addRows(const HighsSparseMatrix new_rows, this->num_row_ += num_new_row; } +void HighsSparseMatrix::getCol(const HighsInt iCol, HighsInt& num_nz, + HighsInt* index, double* value) const { + assert(iCol >= 0 && iCol < this->num_row_); + num_nz = 0; + if (this->isColwise()) { + for (HighsInt iEl = this->start_[iCol]; iEl < this->start_[iCol + 1]; + iEl++) { + index[num_nz] = this->index_[iEl]; + value[num_nz] = this->value_[iEl]; + num_nz++; + } + } else { + for (HighsInt iRow = 0; iRow < this->num_row_; iRow++) { + for (HighsInt iEl = this->start_[iRow]; iEl < this->start_[iRow + 1]; + iEl++) { + if (this->index_[iEl] == iCol) { + index[num_nz] = iRow; + value[num_nz] = this->value_[iEl]; + num_nz++; + break; + } + } + } + } +} + +void HighsSparseMatrix::getRow(const HighsInt iRow, HighsInt& num_nz, + HighsInt* index, double* value) const { + assert(iRow >= 0 && iRow < this->num_row_); + num_nz = 0; + if (this->isRowwise()) { + for (HighsInt iEl = this->start_[iRow]; iEl < this->start_[iRow + 1]; + iEl++) { + index[num_nz] = this->index_[iEl]; + value[num_nz] = this->value_[iEl]; + num_nz++; + } + } else { + for (HighsInt iCol = 0; iCol < this->num_col_; iCol++) { + for (HighsInt iEl = this->start_[iCol]; iEl < this->start_[iCol + 1]; + iEl++) { + if (this->index_[iEl] == iRow) { + index[num_nz] = iCol; + value[num_nz] = this->value_[iEl]; + num_nz++; + break; + } + } + } + } +} + void HighsSparseMatrix::deleteCols( const HighsIndexCollection& index_collection) { assert(this->formatOk()); @@ -559,9 +610,9 @@ void HighsSparseMatrix::deleteCols( } // Ensure that the starts of the deleted columns are zeroed to // avoid redundant start information for columns whose indices - // are't used after the deletion takes place. In particular, if + // aren't used after the deletion takes place. In particular, if // all columns are deleted then something must be done to ensure - // that the matrix isn't magially recreated by increasing the + // that the matrix isn't magically recreated by increasing the // number of columns from zero when there are no rows in the // matrix. for (HighsInt col = delete_from_col; col <= delete_to_col; col++) @@ -903,9 +954,7 @@ void HighsSparseMatrix::createSlice(const HighsSparseMatrix& matrix, assert(matrix.formatOk()); assert(matrix.isColwise()); assert(this->formatOk()); - HighsInt num_col = matrix.num_col_; HighsInt num_row = matrix.num_row_; - HighsInt num_nz = matrix.numNz(); const vector& a_start = matrix.start_; const vector& a_index = matrix.index_; const vector& a_value = matrix.value_; @@ -1032,8 +1081,10 @@ void HighsSparseMatrix::alphaProductPlusY(const double alpha, const std::vector& x, std::vector& y, const bool transpose) const { - assert(int(x.size()) >= transpose ? this->num_row_ : this->num_col_); - assert(int(y.size()) >= transpose ? this->num_col_ : this->num_row_); + assert(x.size() >= static_cast(transpose) ? this->num_row_ + : this->num_col_); + assert(y.size() >= static_cast(transpose) ? this->num_col_ + : this->num_row_); if (this->isColwise()) { if (transpose) { for (int iCol = 0; iCol < this->num_col_; iCol++) @@ -1328,7 +1379,7 @@ void HighsSparseMatrix::priceByRowWithSwitch( // 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 + // that result.index is still of correct size assert(HighsInt(result.size) == this->num_col_); assert(HighsInt(result.index.size()) == this->num_col_); if (expected_density <= kHyperPriceDensity) { diff --git a/src/util/HighsSparseMatrix.h b/src/util/HighsSparseMatrix.h index d99afe1416..22fef9ea40 100644 --- a/src/util/HighsSparseMatrix.h +++ b/src/util/HighsSparseMatrix.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -58,7 +58,10 @@ class HighsSparseMatrix { const int8_t* in_partition = NULL); void addRows(const HighsSparseMatrix new_rows, const int8_t* in_partition = NULL); - + void getRow(const HighsInt iRow, HighsInt& num_nz, HighsInt* index, + double* value) const; + void getCol(const HighsInt iCol, HighsInt& num_nz, HighsInt* index, + double* value) const; void deleteCols(const HighsIndexCollection& index_collection); void deleteRows(const HighsIndexCollection& index_collection); HighsStatus assessDimensions(const HighsLogOptions& log_options, diff --git a/src/util/HighsSparseVectorSum.h b/src/util/HighsSparseVectorSum.h index 307d5cd97e..bfd34694f4 100644 --- a/src/util/HighsSparseVectorSum.h +++ b/src/util/HighsSparseVectorSum.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -62,7 +62,7 @@ class HighsSparseVectorSum { double getValue(HighsInt index) const { return double(values[index]); } void clear() { - if (nonzeroinds.size() < 0.3 * values.size()) + if (10 * nonzeroinds.size() < 3 * values.size()) for (HighsInt i : nonzeroinds) values[i] = 0.0; else values.assign(values.size(), false); diff --git a/src/util/HighsSplay.h b/src/util/HighsSplay.h index 4963caa5ee..1403d1dc3e 100644 --- a/src/util/HighsSplay.h +++ b/src/util/HighsSplay.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ diff --git a/src/util/HighsTimer.h b/src/util/HighsTimer.h index abfe39361f..13b1617eff 100644 --- a/src/util/HighsTimer.h +++ b/src/util/HighsTimer.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -177,6 +177,16 @@ class HighsTimer { return read_time; } + /** + * @brief Return whether a clock is running + */ + bool running(HighsInt i_clock //!< Index of the clock to be read + ) { + assert(i_clock >= 0); + assert(i_clock < num_clock); + return clock_start[i_clock] < 0; + } + /** * @brief Start the RunHighs clock */ @@ -195,7 +205,7 @@ class HighsTimer { /** * @brief Test whether the RunHighs clock is running */ - bool runningRunHighsClock() { return clock_start[run_highs_clock] < 0; } + bool runningRunHighsClock() { return running(run_highs_clock); } /** * @brief Report timing information for the clock indices in the list @@ -219,7 +229,7 @@ class HighsTimer { 0 //!< Lower bound on percentage of total time //!< before an individual clock is reported ) { - HighsInt num_clock_list_entries = clock_list.size(); + size_t num_clock_list_entries = clock_list.size(); double current_run_highs_time = readRunHighsClock(); bool non_null_report = false; @@ -228,7 +238,7 @@ class HighsTimer { // determine the total clock times HighsInt sum_calls = 0; double sum_clock_times = 0; - for (HighsInt i = 0; i < num_clock_list_entries; i++) { + for (size_t i = 0; i < num_clock_list_entries; i++) { HighsInt iClock = clock_list[i]; assert(iClock >= 0); assert(iClock < num_clock); @@ -243,7 +253,7 @@ class HighsTimer { std::vector percent_sum_clock_times(num_clock_list_entries); double max_percent_sum_clock_times = 0; - for (HighsInt i = 0; i < num_clock_list_entries; i++) { + for (size_t i = 0; i < num_clock_list_entries; i++) { HighsInt iClock = clock_list[i]; percent_sum_clock_times[i] = 100.0 * clock_time[iClock] / sum_clock_times; max_percent_sum_clock_times = @@ -261,7 +271,7 @@ class HighsTimer { printf("; Local): Calls Time/Call\n"); // Convert approximate seconds double sum_time = 0; - for (HighsInt i = 0; i < num_clock_list_entries; i++) { + for (size_t i = 0; i < num_clock_list_entries; i++) { HighsInt iClock = clock_list[i]; double time = clock_time[iClock]; double percent_run_highs = 100.0 * time / current_run_highs_time; diff --git a/src/util/HighsUtils.cpp b/src/util/HighsUtils.cpp index c17312ab1b..36c4746c91 100644 --- a/src/util/HighsUtils.cpp +++ b/src/util/HighsUtils.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -21,36 +21,43 @@ #include "util/HighsSort.h" -bool create(HighsIndexCollection& index_collection, const HighsInt from_col, - const HighsInt to_col, const HighsInt dimension) { - if (from_col < 0) return false; - if (to_col >= dimension) return false; +HighsInt create(HighsIndexCollection& index_collection, const HighsInt from_col, + const HighsInt to_col, const HighsInt dimension) { + if (from_col < 0) return kIndexCollectionCreateIllegalInterval; + if (to_col >= dimension) return kIndexCollectionCreateIllegalInterval; index_collection.dimension_ = dimension; index_collection.is_interval_ = true; index_collection.from_ = from_col; index_collection.to_ = to_col; - return true; + return kIndexCollectionCreateOk; } -bool create(HighsIndexCollection& index_collection, - const HighsInt num_set_entries, const HighsInt* set, - const HighsInt dimension) { +HighsInt create(HighsIndexCollection& index_collection, + const HighsInt num_set_entries, const HighsInt* set, + const HighsInt dimension) { // Create an index collection for the given set - so long as it is strictly // ordered + if (num_set_entries < 0) return kIndexCollectionCreateIllegalSetSize; + if (dimension < 0) return kIndexCollectionCreateIllegalSetDimension; index_collection.dimension_ = dimension; index_collection.is_set_ = true; index_collection.set_ = {set, set + num_set_entries}; index_collection.set_num_entries_ = num_set_entries; - if (!increasingSetOk(index_collection.set_, 1, 0, true)) return false; - return true; + if (!increasingSetOk(index_collection.set_, 1, 0, true)) + return kIndexCollectionCreateIllegalSetOrder; + for (HighsInt ix = 0; ix < num_set_entries; ix++) + if (set[ix] < 0 || set[ix] >= dimension) return -(ix + 1); + return kIndexCollectionCreateOk; } -void create(HighsIndexCollection& index_collection, const HighsInt* mask, - const HighsInt dimension) { +HighsInt create(HighsIndexCollection& index_collection, const HighsInt* mask, + const HighsInt dimension) { // Create an index collection for the given mask + if (dimension < 0) return kIndexCollectionCreateIllegalMaskSize; index_collection.dimension_ = dimension; index_collection.is_mask_ = true; index_collection.mask_ = {mask, mask + dimension}; + return kIndexCollectionCreateOk; } void highsSparseTranspose(HighsInt numRow, HighsInt numCol, @@ -482,13 +489,9 @@ void analyseVectorValues(const HighsLogOptions* log_options, const std::string message, HighsInt vecDim, const std::vector& vec, std::string model_name) { - const bool analyseValueList = true; if (vecDim == 0) return; const HighsInt VLsMxZ = 10; std::vector> VLs; - // Ensure that 1.0 and -1.0 are counted - const HighsInt PlusOneIx = 0; - const HighsInt MinusOneIx = 1; bool excessVLsV = false; HighsInt VLsZ = 0; HighsInt min_value = kHighsIInf; diff --git a/src/util/HighsUtils.h b/src/util/HighsUtils.h index a09a0cb5a5..27791ff2be 100644 --- a/src/util/HighsUtils.h +++ b/src/util/HighsUtils.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -20,6 +20,13 @@ #include "lp_data/HighsOptions.h" +const HighsInt kIndexCollectionCreateOk = 0; +const HighsInt kIndexCollectionCreateIllegalInterval = 1; +const HighsInt kIndexCollectionCreateIllegalSetSize = 1; +const HighsInt kIndexCollectionCreateIllegalSetDimension = 2; +const HighsInt kIndexCollectionCreateIllegalSetOrder = 3; +const HighsInt kIndexCollectionCreateIllegalMaskSize = 1; + void highsSparseTranspose(HighsInt numRow, HighsInt numCol, const std::vector& Astart, const std::vector& Aindex, @@ -81,15 +88,15 @@ const double awful_regression_error = 2.0; const double bad_regression_error = 0.2; const double fair_regression_error = 0.02; -bool create(HighsIndexCollection& index_collection, const HighsInt from_col, - const HighsInt to_col, const HighsInt dimension); +HighsInt create(HighsIndexCollection& index_collection, const HighsInt from_col, + const HighsInt to_col, const HighsInt dimension); -bool create(HighsIndexCollection& index_collection, - const HighsInt num_set_entries, const HighsInt* set, - const HighsInt dimension); +HighsInt create(HighsIndexCollection& index_collection, + const HighsInt num_set_entries, const HighsInt* set, + const HighsInt dimension); -void create(HighsIndexCollection& index_collection, const HighsInt* mask, - const HighsInt dimension); +HighsInt create(HighsIndexCollection& index_collection, const HighsInt* mask, + const HighsInt dimension); bool ok(const HighsIndexCollection& index_collection); diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 6925fb70c1..5b0de58e73 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -10,8 +10,10 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "util/stringutil.h" +#include // for std::transform #include +/* void strRemoveWhitespace(char* str) { char* dest = str; do @@ -59,6 +61,19 @@ void strTrim(char* str) { str[i - begin] = '\0'; // Null terminate string. } +*/ + +// std::string& str_tolower(std::string str) { +// std::transform(str.begin(), str.end(), str.begin(), +// [](unsigned char c) { return std::tolower(c); } // correct +// ); +// return str; +// } + +void tolower(std::string& str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::tolower(c); }); +} std::string& ltrim(std::string& str, const std::string& chars) { str.erase(0, str.find_first_not_of(chars)); @@ -75,38 +90,39 @@ std::string& trim(std::string& str, const std::string& chars) { } bool is_empty(char c, const std::string& chars) { - int pos = chars.find_first_of(c); - if (pos == -1 || pos == (int)chars.size()) return false; + size_t pos = chars.find_first_of(c); + if (pos == std::string::npos || pos == chars.size()) return false; return true; } bool is_empty(std::string& str, const std::string& chars) { - int pos = str.find_first_not_of(chars); - if (pos == -1 || pos == (int)str.size()) return true; + size_t pos = str.find_first_not_of(chars); + if (pos == std::string::npos || pos == str.size()) return true; return false; } -bool is_end(std::string& str, int end, const std::string& chars) { - int pos = str.find_first_not_of(chars, end); - if (pos == -1 || pos == (int)str.size()) return true; +bool is_end(std::string& str, size_t end, const std::string& chars) { + size_t pos = str.find_first_not_of(chars, end); + if (pos == std::string::npos || pos == str.size()) return true; return false; } -int first_word_end(std::string& str, int start) { +size_t first_word_end(std::string& str, size_t start) { 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); - if (next_word_end < 0 || next_word_end > (int)str.size()) return str.size(); + size_t next_word_start = str.find_first_not_of(chars, start); + size_t next_word_end = str.find_first_of(chars, next_word_start); + if (next_word_end == std::string::npos || next_word_end > str.size()) + return str.size(); return next_word_end; } -std::string first_word(std::string& str, int start) { +std::string first_word(std::string& str, size_t 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 ""; + if (start >= 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); + size_t next_word_start = str.find_first_not_of(chars, start); + size_t next_word_end = str.find_first_of(chars, next_word_start); + assert(next_word_start != std::string::npos); 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 bd0032c1c6..4fa072485b 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -2,7 +2,7 @@ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ -/* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ /* Leona Gottwald and Michael Feldmeier */ /* */ /* Available as open-source under the MIT License */ @@ -16,11 +16,16 @@ #include #include +/* void strRemoveWhitespace(char* str); char* strClone(const char* str); int strIsWhitespace(const char* str); void strToLower(char* str); void strTrim(char* str); +*/ +// std::string& str_tolower(std::string s); + +void tolower(std::string& str); const std::string non_chars = "\t\n\v\f\r "; std::string& ltrim(std::string& str, const std::string& chars = non_chars); @@ -29,11 +34,11 @@ std::string& trim(std::string& str, const std::string& chars = non_chars); bool is_empty(std::string& str, const std::string& chars = non_chars); bool is_empty(char c, const std::string& chars = non_chars); -bool is_end(std::string& str, int end, const std::string& chars = non_chars); +bool is_end(std::string& str, size_t end, const std::string& chars = non_chars); // todo: replace with pair of references rather than string ret value to avoid // copy and also using function below. or do it properly with iterators. -std::string first_word(std::string& str, int start); -int first_word_end(std::string& str, int start); +std::string first_word(std::string& str, size_t start); +size_t first_word_end(std::string& str, size_t start); #endif diff --git a/highspy/tests/test_highspy.py b/tests/test_highspy.py similarity index 79% rename from highspy/tests/test_highspy.py rename to tests/test_highspy.py index c693f6da8b..7991f91fd6 100644 --- a/highspy/tests/test_highspy.py +++ b/tests/test_highspy.py @@ -3,6 +3,7 @@ import highspy import numpy as np from io import StringIO +from sys import platform class TestHighsPy(unittest.TestCase): @@ -53,33 +54,33 @@ def get_example_model(self): h.passModel(lp) return h - def test_example_model_builder(self): - """ - minimize f = x0 + x1 - subject to x1 <= 7 - 5 <= x0 + 2x1 <= 15 - 6 <= 3x0 + 2x1 - 0 <= x0 <= 4; 1 <= x1 - """ - h = highspy.Highs() - - x0 = h.addVar(lb=0, ub=4, obj=1) - x1 = h.addVar(lb=1, ub=7, obj=1) - - h.addConstr(5 <= x0 + 2*x1 <= 15) - h.addConstr(6 <= 3*x0 + 2*x1) - - lp = h.getLp() - - self.assertEqual(lp.num_col_, 2) - self.assertEqual(lp.num_row_, 2) - self.assertAlmostEqual(lp.col_cost_[0], 1) - self.assertAlmostEqual(lp.col_lower_[0], 0) - self.assertAlmostEqual(lp.col_upper_[0], 4) - self.assertAlmostEqual(lp.row_lower_[0], 5) - self.assertAlmostEqual(lp.row_upper_[0], 15) - self.assertAlmostEqual(lp.row_lower_[1], 6) - self.assertAlmostEqual(lp.row_upper_[1], h.inf) + # def test_example_model_builder(self): + # """ + # minimize f = x0 + x1 + # subject to x1 <= 7 + # 5 <= x0 + 2x1 <= 15 + # 6 <= 3x0 + 2x1 + # 0 <= x0 <= 4; 1 <= x1 + # """ + # h = highspy.Highs() + + # x0 = h.addVariable(lb=0, ub=4, obj=1) + # x1 = h.addVariable(lb=1, ub=7, obj=1) + + # h.addConstr(5 <= x0 + 2*x1 <= 15) + # h.addConstr(6 <= 3*x0 + 2*x1) + + # lp = h.getLp() + + # self.assertEqual(lp.num_col_, 2) + # self.assertEqual(lp.num_row_, 2) + # self.assertAlmostEqual(lp.col_cost_[0], 1) + # self.assertAlmostEqual(lp.col_lower_[0], 0) + # self.assertAlmostEqual(lp.col_upper_[0], 4) + # self.assertAlmostEqual(lp.row_lower_[0], 5) + # self.assertAlmostEqual(lp.row_upper_[0], 15) + # self.assertAlmostEqual(lp.row_lower_[1], 6) + # self.assertAlmostEqual(lp.row_upper_[1], highspy.kHighsInf) def get_infeasible_model(self): inf = highspy.kHighsInf @@ -94,7 +95,7 @@ def get_infeasible_model(self): 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; + lp.offset_ = 0 h = highspy.Highs() h.setOptionValue('output_flag', False) status = h.passModel(lp) @@ -104,10 +105,10 @@ def get_infeasible_model(self): def test_version(self): h = self.get_basic_model() - self.assertEqual(h.version(), "1.6.0") + self.assertEqual(h.version(), "1.7.2") self.assertEqual(h.versionMajor(), 1) - self.assertEqual(h.versionMinor(), 6) - self.assertEqual(h.versionPatch(), 0) + self.assertEqual(h.versionMinor(), 7) + self.assertEqual(h.versionPatch(), 2) def test_basics(self): h = self.get_basic_model() @@ -432,10 +433,12 @@ def test_ranging(self): self.assertEqual(ranging.row_bound_up.value_[1], inf); self.assertEqual(ranging.row_bound_up.objective_[1], inf); + # Temporarily disable modelling tests until language is included properly. + def test_constraint_removal(self): h = highspy.Highs() - x = h.addVar(lb=-h.inf) - y = h.addVar(lb=-h.inf) + x = h.addVariable(lb=-highspy.kHighsInf) + y = h.addVariable(lb=-highspy.kHighsInf) c1 = h.addConstr(-x + y >= 2) c2 = h.addConstr(x + y >= 0) self.assertEqual(h.numConstrs, 2) @@ -447,8 +450,8 @@ def test_infeasible_model(self): h.setOptionValue('output_flag', False) h.setOptionValue('presolve', 'off') - x = h.addVar() - y = h.addVar() + x = h.addVariable() + y = h.addVariable() h.addConstr(x + y == 3) h.addConstr(x + y == 1) @@ -459,113 +462,105 @@ def test_infeasible_model(self): status = h.getModelStatus() self.assertEqual(status, highspy.HighsModelStatus.kInfeasible) - def test_basics_builder(self): - h = highspy.Highs() - h.setOptionValue('output_flag', False) - - x = h.addVar(lb=-h.inf) - y = h.addVar(lb=-h.inf) - - c1 = h.addConstr(-x + y >= 2) - c2 = h.addConstr(x + y >= 0) - - h.minimize(y) - - self.assertAlmostEqual(h.val(x), -1) - self.assertAlmostEqual(h.val(y), 1) - - """ - min y - s.t. - -x + y >= 3 - x + y >= 0 - """ - h.changeRowBounds(0, 3, h.inf) - h.run() - - self.assertAlmostEqual(h.val(x), -1.5) - self.assertAlmostEqual(h.val(y), 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 + # failing? - min y - s.t. - x + y >= 0 - -x + y >= 0 - """ - h.removeConstr(c1) + # def test_basics_builder(self): + # h = highspy.Highs() + # h.setOptionValue('output_flag', False) + + # x = h.addVariable(lb=highspy.kHighsInf) + # y = h.addVariable(lb=highspy.kHighsInf) + + # c1 = h.addConstr(-x + y >= 2) + # c2 = h.addConstr(x + y >= 0) + + # h.minimize(y) + + # self.assertAlmostEqual(h.val(x), -1) + # self.assertAlmostEqual(h.val(y), 1) + + # """ + # min y + # s.t. + # -x + y >= 3 + # x + y >= 0 + # """ + # h.changeRowBounds(0, 3, highspy.kHighsInf) + # h.run() + + # self.assertAlmostEqual(h.val(x), -1.5) + # self.assertAlmostEqual(h.val(y), 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.removeConstr(c1) - c1 = h.addConstr(-x + y >= 0) + # c1 = h.addConstr(-x + y >= 0) - h.run() + # h.run() - self.assertAlmostEqual(h.val(x), 0) - self.assertAlmostEqual(h.val(y), 0) + # self.assertAlmostEqual(h.val(x), 0) + # self.assertAlmostEqual(h.val(y), 0) - # change the upper bound of x to -5 - h.changeColsBounds(1, np.array([0]), np.array([-h.inf], dtype=np.double), - np.array([-5], dtype=np.double)) - h.run() - self.assertAlmostEqual(h.val(x), -5) - self.assertAlmostEqual(h.val(y), 5) + # # change the upper bound of x to -5 + # h.changeColsBounds(1, np.array([0]), np.array([-highspy.kHighsInf], dtype=np.double), + # np.array([-5], dtype=np.double)) + # h.run() + # self.assertAlmostEqual(h.val(x), -5) + # self.assertAlmostEqual(h.val(y), 5) - # now maximize - h.changeRowBounds(1, -h.inf, 0) - h.changeRowBounds(0, -h.inf, 0) - h.minimize(-y) + # # now maximize + # h.changeRowBounds(1, -highspy.kHighsInf, 0) + # h.changeRowBounds(0, -highspy.kHighsInf, 0) + # h.minimize(-y) - self.assertAlmostEqual(h.val(x), -5) - self.assertAlmostEqual(h.val(y), -5) + # self.assertAlmostEqual(h.val(x), -5) + # self.assertAlmostEqual(h.val(y), -5) - self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMinimize) - h.maximize(y) - self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMaximize) + # self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMinimize) + # h.maximize(y) + # self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMaximize) - self.assertAlmostEqual(h.val(x), -5) - self.assertAlmostEqual(h.val(y), -5) + # self.assertAlmostEqual(h.val(x), -5) + # self.assertAlmostEqual(h.val(y), -5) - self.assertAlmostEqual(h.getObjectiveValue(), -5) + # self.assertAlmostEqual(h.getObjectiveValue(), -5) - h.maximize(y + 1) - self.assertAlmostEqual(h.getObjectiveOffset()[1], 1) - self.assertAlmostEqual(h.getObjectiveValue(), -4) + # h.maximize(y + 1) + # self.assertAlmostEqual(h.getObjectiveOffset()[1], 1) + # self.assertAlmostEqual(h.getObjectiveValue(), -4) - def test_addVar(self): + def test_addVariable(self): h = highspy.Highs() - h.addVar() + h.addVariable() h.update() - self.assertEqual(h.numVars, 1) + self.assertEqual(h.numVariables, 1) - def test_removeVar(self): - h = highspy.Highs() - x = [h.addVar(), h.addVar()] - - h.update() - self.assertEqual(h.numVars, 2) - - h.removeVar(x[0]) - self.assertEqual(h.numVars, 1) - def test_addConstr(self): h = highspy.Highs() - x = h.addVar() - y = h.addVar() + x = h.addVariable() + y = h.addVariable() h.addConstr(2*x + 3*y <= 10) - self.assertEqual(h.numVars, 2) + self.assertEqual(h.numVariables, 2) self.assertEqual(h.numConstrs, 1) self.assertEqual(h.getNumNz(), 2) lp = h.getLp() - self.assertAlmostEqual(lp.row_lower_[0], -h.inf) + self.assertAlmostEqual(lp.row_lower_[0], -highspy.kHighsInf) self.assertAlmostEqual(lp.row_upper_[0], 10) self.assertEqual(lp.a_matrix_.index_[0], 0) @@ -576,20 +571,20 @@ def test_addConstr(self): def test_removeConstr(self): h = highspy.Highs() - x = h.addVar() - y = h.addVar() + x = h.addVariable() + y = h.addVariable() c = h.addConstr(2*x + 3*y <= 10) self.assertEqual(h.numConstrs, 1) h.removeConstr(c) - self.assertEqual(h.numVars, 2) + self.assertEqual(h.numVariables, 2) self.assertEqual(h.numConstrs, 0) def test_val(self): h = highspy.Highs() h.setOptionValue('output_flag', False) - x = [h.addVar(), h.addVar()] + x = [h.addVariable(), h.addVariable()] h.addConstr(2*x[0] + 3*x[1] <= 10) h.maximize(x[0]) @@ -603,7 +598,7 @@ def test_var_name(self): h = highspy.Highs() # name set, but not in the model - x = h.addVar(name='x') + x = h.addVariable(name='x') self.assertEqual(x.name, 'x') # change name before adding to the model @@ -612,7 +607,7 @@ def test_var_name(self): # add to the model h.update() - self.assertEqual(h.numVars, 1) + self.assertEqual(h.numVariables, 1) self.assertEqual(h.getLp().col_names_[0], 'y') # change name after adding to the model @@ -654,7 +649,7 @@ def test_integer(self): h = highspy.Highs() h.setOptionValue('output_flag', False) - x = [h.addIntegral(), h.addVar()] + x = [h.addIntegral(), h.addVariable()] h.addConstr(2*x[0] + 3*x[1] <= 10.6) h.maximize(x[0]+x[1]) @@ -672,7 +667,7 @@ def test_objective(self): h = highspy.Highs() h.setOptionValue('output_flag', False) - x = [h.addVar(), h.addVar()] + x = [h.addVariable(), h.addVariable()] h.addConstr(2*x[0] + 3*x[1] <= 10) self.assertRaises(Exception, h.maximize, x[0]+x[1] <= 3) @@ -680,31 +675,31 @@ def test_objective(self): def test_constraint_builder(self): h = highspy.Highs() - (x,y) = [h.addVar(), h.addVar()] + (x,y) = [h.addVariable(), h.addVariable()] # -inf <= 2x + 3y <= inf c1 = 2*x + 3*y - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, h.inf, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, highspy.kHighsInf, 0)) # -inf <= 2x + 3y <= 2x c1 = 2*x + 3*y <= 2*x - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, 0, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, 0, 0)) # -inf <= 2x + 3y <= 2x c1 = 2*x >= 2*x + 3*y - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, 0, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, 0, 0)) # max{1,4} <= 2x + 3y <= inf c1 = 1 <= (4 <= 2*x + 3*y) - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (4, h.inf, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (4, highspy.kHighsInf, 0)) # -inf <= 2x + 3y <= min{1,4} c1 = 2 >= (4 >= 2*x + 3*y) - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, 2, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, 2, 0)) c1 = 2*x + 3*y <= (2 <= 4) - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, True, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, True, 0)) c1 = (2*x + 3*y <= 2) <= 4 - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, 2, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, 2, 0)) # 1 <= 2x + 3y <= 5 c1 = (1 <= 2*x + 3*y) <= 5 @@ -732,7 +727,7 @@ def test_constraint_builder(self): # failure, order matters when having variables on both sides of inequality # -inf <= 4*x - t <= min{0, 5} c1 = (4*x <= 2*x + 3*y) <= 5 - self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-h.inf, 0, 0)) + self.assertAlmostEqual((c1.LHS, c1.RHS, c1.constant), (-highspy.kHighsInf, 0, 0)) #4*x <= (2*x + 3*y <= 5) self.assertRaises(Exception, lambda: 4*x <= (2*x + 3*y <= 5), None) @@ -761,37 +756,42 @@ def test_constraint_builder(self): h.addConstr(c1) self.assertAlmostEqual((h.getLp().row_lower_[0], h.getLp().row_upper_[0]), (4.5, 4.5)) + + # r/w basis tests below works on unix but not windows? 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') + if (platform == 'linux' or platform == 'darwin'): + 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' - ) + if (platform == 'linux' or platform == 'darwin'): + 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) + if (platform == 'linux' or platform == 'darwin'): + # 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/version.rc.in b/version.rc.in new file mode 100644 index 0000000000..e5ff567a99 --- /dev/null +++ b/version.rc.in @@ -0,0 +1,50 @@ + + +#ifndef DEBUG +#define VER_DEBUG 0 +#else +#define VER_DEBUG VS_FF_DEBUG +#endif + +#define VS_FF_DEBUG 0x1L +#define VS_VERSION_INFO 0x1L +#define VS_FFI_FILEFLAGSMASK 0x17L +#define VOS__WINDOWS32 0x4L +#define VFT_DLL 0x2L + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @PROJECT_RC_VERSION@ + PRODUCTVERSION @PROJECT_VERSION@ + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VER_DEBUG + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "HIGHS\0" + VALUE "FileDescription", "Commit @GITHASH@ compiled with @CMAKE_CXX_COMPILER_ID@ @CMAKE_CXX_COMPILER_VERSION@\0" + VALUE "FileVersion", "@PROJECT_VERSION@\0" + VALUE "InternalName", "highs\0" + VALUE "LegalCopyright", "Copyright (c) 2024 HiGHS. All rights reserved.\0" + VALUE "Licence", "MIT\0" + VALUE "Info", "https://highs.dev/\0" + VALUE "ProductName", "Highs\0" + VALUE "ProductVersion", "@PROJECT_VERSION@\0" + END + END + + BLOCK "VarFileInfo" + BEGIN + /* The following line should only be modified for localized versions. */ + /* It consists of any number of WORD,WORD pairs, with each pair */ + /* describing a language,codepage combination supported by the file. */ + /* */ + /* For example, a file might have values "0x409,1252" indicating that it */ + /* supports English language (0x409) in the Windows ANSI codepage (1252). */ + + VALUE "Translation", 0x409, 1252 + END +END