From 3815bf9af3ecb56308f95994f7e257d6c01d7299 Mon Sep 17 00:00:00 2001 From: Peter Mitri <47452063+pet-mit@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:48:59 +0100 Subject: [PATCH] First patch version (#1) --- .github/ISSUE_TEMPLATE/bug_report.md | 28 + .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.md | 18 + .github/pull_request_template.md | 12 + .github/workflows/centos.yml | 174 ++ .github/workflows/oracle.yml | 252 +++ .github/workflows/ubuntu.yml | 300 ++++ .github/workflows/windows-cpp.yml | 175 ++ .github/workflows/windows-java-dotnet.yml | 201 +++ .github/workflows/windows-python.yml | 167 ++ README.md | 145 ++ ortools/linear_solver/sirius_interface.cc | 1417 +++++++++++++++++ .../linear_solver/sirius_interface_test.cc | 883 ++++++++++ ortools/xpress/parse_header_xpress.py | 330 ++++ patch.py | 184 +++ patch_utils.py | 17 + 16 files changed, 4311 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/centos.yml create mode 100644 .github/workflows/oracle.yml create mode 100644 .github/workflows/ubuntu.yml create mode 100644 .github/workflows/windows-cpp.yml create mode 100644 .github/workflows/windows-java-dotnet.yml create mode 100644 .github/workflows/windows-python.yml create mode 100644 README.md create mode 100644 ortools/linear_solver/sirius_interface.cc create mode 100644 ortools/linear_solver/sirius_interface_test.cc create mode 100644 ortools/xpress/parse_header_xpress.py create mode 100644 patch.py create mode 100644 patch_utils.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..cc1eda2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +--- + + +**What version of OR-Tools and what language are you using?** +Version: main/v9.5/v9.4 etc. +Language: C++/Java/Python/C# + +**Which solver are you using (e.g. CP-SAT, Routing Solver, GLOP, BOP, Gurobi)** + +**What operating system (Linux, Windows, ...) and version?** + +**What did you do?** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**What did you expect to see** + +**What did you see instead?** + +Make sure you include information that can help us debug (full error message, model Proto). + +**Anything else we should know about your project / environment** diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e8155a1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true + +contact_links: +- name: Modeling/Usage problem + url: https://groups.google.com/forum/#!forum/or-tools-discuss + about: Need help creating your model ? + Please use the mailing list for modeling issues. + Github issues have limited audience and answers on the github page will not benefit the rest of the users. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..99ad00e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest an idea for this project +--- + + +**What language and solver does this apply to?** +All, Python, Java, C#, C++ / CP-SAT, Routing, Linear Solver + +**Describe the problem you are trying to solve.** + +**Describe the solution you'd like** + +**Describe alternatives you've considered** + +**Additional context** +Add any other context or screenshots about the feature request here. + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..10a6bde --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ + diff --git a/.github/workflows/centos.yml b/.github/workflows/centos.yml new file mode 100644 index 0000000..4a43290 --- /dev/null +++ b/.github/workflows/centos.yml @@ -0,0 +1,174 @@ +name: CentOS + +on: + workflow_dispatch: + inputs: + rtefrance_ortools_branch: + description: 'rte-france/or-tools branch name' + required: true + default: 'main' + push: + branches: + - main + - feature/* + - merge* + - fix/* + - release/* + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} + +jobs: + + build: + name: shrd=${{ matrix.shared }} sirius=${{ matrix.sirius }} extras=${{ matrix.extras }} java=${{ matrix.java }} dotnet=${{ matrix.dotnet }} python=${{ matrix.python }}-${{ matrix.python-version }} + runs-on: ubuntu-latest + container: 'centos:centos7' + env: + SIRIUS_RELEASE_TAG: antares-integration-v1.4 + XPRESS_INSTALL_DIR: xpressmp + strategy: + fail-fast: false + matrix: + sirius: [ON, OFF] + shared: [ON, OFF] + extras: [OFF] + include: + - extras: OFF + python: OFF + java: OFF + dotnet: OFF + - sirius: ON + shared: OFF + extras: OFF + python: OFF + java: OFF + dotnet: OFF + + steps: + - name: set name variables + id: names + run: | + SHARED=${{ matrix.shared }} + [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" + SIRIUS=${{ matrix.sirius }} + [ $SIRIUS == "ON" ] && WITH_SIRIUS="_sirius" || WITH_SIRIUS="" + OS="_centos7" + APPENDIX="${OS}${WITH_SIRIUS}" + APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}${WITH_SIRIUS}" + echo "appendix=$APPENDIX" >> $GITHUB_OUTPUT + echo "appendix_with_shared=$APPENDIX_WITH_SHARED" >> $GITHUB_OUTPUT + + # Fill variable ${BRANCH_NAME} + - uses: nelonoel/branch-name@v1.0.1 + + - name: Install requirements (yum) + run: | + yum install -y epel-release + yum install -y git redhat-lsb-core make wget centos-release-scl scl-utils python3 + yum install -y devtoolset-11 + python3 -m pip install --upgrade pip + python3 -m pip install dataclasses + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v1.13 + with: + cmake-version: '3.22.x' + + - name: Checkout OR-Tools + run: | + git clone $GITHUB_SERVER_URL/rte-france/or-tools.git -b '${{ github.event.inputs.rtefrance_ortools_branch || 'main' }}' . + + - name: Checkout this repository + run: | + git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b ${BRANCH_NAME} "patch" + + - name: Apply patch + run: | + cp -r patch/* . + python3 patch.py + + - name: Set-up Xpress from wheel + run: | + python3 -m pip install --upgrade pip + mkdir xpress + cd xpress + python3 -m pip download --only-binary=:all: --python-version 310 "xpress>=9.2,<9.3" + unzip xpr*.whl + XPRESS_DIR=$PWD/xpress + echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV + echo "XPAUTH_PATH=$XPRESS_DIR/license/community-xpauth.xpr" >> $GITHUB_ENV + ln -s $XPRESS_DIR/lib/libxprs.so.42 $XPRESS_DIR/lib/libxprs.so + + + - name: Download Sirius + if : ${{ matrix.sirius == 'ON' }} + run: | + zipfile=centos-7_sirius-solver.zip + wget https://github.com/rte-france/sirius-solver/releases/download/${{ env.SIRIUS_RELEASE_TAG }}/$zipfile + unzip $zipfile + mv centos-7_sirius-solver-install sirius_install + echo "LD_LIBRARY_PATH=$PWD/sirius_install/lib" >> $GITHUB_ENV + echo "SIRIUS_CMAKE_DIR=$PWD/sirius_install/cmake" >> $GITHUB_ENV + + - name: Configure OR-Tools + run: | + source /opt/rh/devtoolset-11/enable + cmake --version + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=${{ matrix.shared }} \ + -DBUILD_PYTHON=${{ matrix.python }} \ + -DBUILD_JAVA=${{ matrix.java }} \ + -DBUILD_DOTNET=${{ matrix.dotnet }} \ + -DBUILD_EXAMPLES=${{ env.RELEASE_CREATED == 'true' && 'OFF' || 'ON' }} \ + -DBUILD_DEPS=ON \ + -DUSE_SIRIUS=${{ matrix.sirius }} \ + -Dsirius_solver_DIR="${{ env.SIRIUS_CMAKE_DIR }}" \ + -DCMAKE_INSTALL_PREFIX="build/install" \ + -DBUILD_SAMPLES=OFF \ + -DBUILD_FLATZINC=OFF + + - name: Build OR-Tools Linux + run: | + source /opt/rh/devtoolset-11/enable + cmake --build build --config Release --target all install -j4 + + - name: run tests not xpress + if: ${{ matrix.shared == 'ON' }} + run: | + cd build + ctest -C Release --output-on-failure -E "_xpress" + + - name: run tests xpress + run: | + cd build + ctest -V -C Release --output-on-failure -R "_xpress" + + - name: run tests sirius + run: | + cd build + ctest -V -C Release --output-on-failure -R "sirius" + + - name: Prepare OR-Tools install + id: or-install + run: | + cd build + ARCHIVE_NAME="ortools_cxx${{ steps.names.outputs.appendix_with_shared }}.zip" + ARCHIVE_PATH="$PWD/${ARCHIVE_NAME}" + zip -r $ARCHIVE_PATH ./install + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools install artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.or-install.outputs.archive_name }} + path: ${{ steps.or-install.outputs.archive_path }} + - name: Publish OR-Tools install asset + if: ${{ env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.or-install.outputs.archive_path }} diff --git a/.github/workflows/oracle.yml b/.github/workflows/oracle.yml new file mode 100644 index 0000000..3106566 --- /dev/null +++ b/.github/workflows/oracle.yml @@ -0,0 +1,252 @@ +name: OracleLinux + +on: + workflow_dispatch: + inputs: + rtefrance_ortools_branch: + description: 'rte-france/or-tools branch name' + required: true + default: 'main' + push: + branches: + - main + - feature/* + - merge* + - fix/* + - release/* + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} + +jobs: + + build: + name: sirius=${{ matrix.sirius }} shrd=${{ matrix.cmake.shared }} java=${{ matrix.cmake.java }} python=${{ matrix.cmake.python }} + runs-on: ubuntu-latest + container: 'oraclelinux:8' + env: + SIRIUS_RELEASE_TAG: antares-integration-v1.4 + strategy: + fail-fast: false + matrix: + sirius: [ON, OFF] + cmake: [ + { shared: OFF, java: OFF, dotnet: OFF, python: OFF}, + { shared: ON, java: ON, dotnet: OFF, python: ON}, + ] + steps: + - name: set name variables + id: names + run: | + SHARED=${{ matrix.cmake.shared }} + [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" + SIRIUS=${{ matrix.sirius }} + [ $SIRIUS == "ON" ] && WITH_SIRIUS="_sirius" || WITH_SIRIUS="" + OS="_oraclelinux-8" + APPENDIX="${OS}${WITH_SIRIUS}" + echo "appendix=$APPENDIX" >> $GITHUB_OUTPUT + APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}${WITH_SIRIUS}" + echo "appendix_with_shared=$APPENDIX_WITH_SHARED" >> $GITHUB_OUTPUT + + # Fill variable ${BRANCH_NAME} + - uses: nelonoel/branch-name@v1.0.1 + + - name: Install requirements (dnf) + run: | + dnf -y update + dnf -y install git wget openssl-devel + dnf -y groupinstall "Development Tools" + dnf -y install gcc-toolset-11 + dnf clean all + rm -rf /var/cache/dnf + - name: Install swig (dnf) + run: | + dnf -y update + dnf -y install pcre-devel + dnf clean all + rm -rf /var/cache/dnf + wget -q "https://downloads.sourceforge.net/project/swig/swig/swig-4.1.1/swig-4.1.1.tar.gz" + tar xvf swig-4.1.1.tar.gz + rm swig-4.1.1.tar.gz + cd swig-4.1.1 + ./configure --prefix=/usr + make -j 4 + make install + cd .. + rm -rf swig-4.1.1 + - name: Install java (jdk) + if: ${{ matrix.cmake.java == 'ON' }} + run: | + dnf -y update + dnf -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel maven + dnf clean all + rm -rf /var/cache/dnf + - name: Install python + run: | + export PATH=/root/.local/bin:$PATH + dnf -y update + dnf -y install python39-devel python39-pip python39-numpy + dnf remove python3.11 + dnf clean all + echo "/root/.local/bin" >> $GITHUB_PATH + echo "$HOME/.local/bin" >> $GITHUB_PATH + python3 -m pip install --upgrade pip + python3 -m pip install protobuf mypy-protobuf absl-py setuptools wheel pandas virtualenv + + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v1.13 + with: + cmake-version: '3.26.x' + + - name: Checkout or-tools + uses: actions/checkout@v4 + with: + repository: rte-france/or-tools + ref: ${{ github.event.inputs.rtefrance_ortools_branch || 'main' }} + + - name: Checkout this repository + uses: actions/checkout@v4 + with: + path: "patch" + + - name: Apply patch + run: | + cp -r patch/* . + python3 patch.py + + - name: Set-up Xpress with pip + run: | + python3 -m pip install "xpress>=9.2,<9.3" + XPRESS_DIR=/usr/local/lib64/python3.9/site-packages/xpress + echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV + echo "XPAUTH_PATH=$XPRESS_DIR/license/community-xpauth.xpr" >> $GITHUB_ENV + ln -s $XPRESS_DIR/lib/libxprs.so.42 $XPRESS_DIR/lib/libxprs.so + + - name: Download Sirius + if: ${{ matrix.sirius == 'ON' }} + run: | + zipfile=oraclelinux-8_sirius-solver.zip + wget https://github.com/rte-france/sirius-solver/releases/download/${{ env.SIRIUS_RELEASE_TAG }}/$zipfile + unzip $zipfile + mv oraclelinux-8_sirius-solver-install sirius_install + echo "LD_LIBRARY_PATH=$PWD/sirius_install/lib" >> $GITHUB_ENV + echo "SIRIUS_CMAKE_DIR=$PWD/sirius_install/cmake" >> $GITHUB_ENV + + - name: Configure OR-Tools + run: | + source /opt/rh/gcc-toolset-11/enable + cmake --version + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=${{ matrix.cmake.shared }} \ + -DBUILD_PYTHON=${{ matrix.cmake.python }} \ + -DBUILD_JAVA=${{ matrix.cmake.java }} \ + -DBUILD_DOTNET=${{ matrix.cmake.dotnet }} \ + -DBUILD_EXAMPLES=${{ env.RELEASE_CREATED == 'true' && 'OFF' || 'ON' }} \ + -DBUILD_DEPS=ON \ + -DUSE_SIRIUS=${{ matrix.sirius }} \ + -Dsirius_solver_DIR="${{ env.SIRIUS_CMAKE_DIR }}" \ + -DCMAKE_INSTALL_PREFIX="build/install" \ + -DBUILD_SAMPLES=OFF \ + -DBUILD_FLATZINC=OFF + + - name: Build OR-Tools Linux + run: | + source /opt/rh/gcc-toolset-11/enable + cmake --build build --config Release --target all install -j4 + + - name: run tests not xpress + run: | + cd build + ctest -C Release --output-on-failure -E "(_xpress|sirius)" + + - name: run tests xpress + run: | + cd build + ctest -V -C Release --output-on-failure -R "_xpress" + + - name: run test sirius + if: ${{ matrix.sirius == 'ON' }} + run: | + cd build + ctest -V -C Release --output-on-failure -R 'sirius' + + - name: Prepare OR-Tools install + id: or-install + run: | + cd build + ARCHIVE_NAME="ortools_cxx${{ steps.names.outputs.appendix_with_shared }}.zip" + ARCHIVE_PATH="$PWD/${ARCHIVE_NAME}" + zip -r $ARCHIVE_PATH ./install + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools install artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.or-install.outputs.archive_name }} + path: ${{ steps.or-install.outputs.archive_path }} + - name: Publish OR-Tools install asset + if: ${{ env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.or-install.outputs.archive_path }} + + - name: prepare OR-Tools wheel + if: ${{ matrix.cmake.python == 'ON' }} + id: wheel + run: | + MY_DIR="ortools_python-3.9${{ steps.names.outputs.appendix }}" + mkdir $MY_DIR + cp build/python/dist/*.whl $MY_DIR + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="$PWD/${ARCHIVE_NAME}" + zip -r ${ARCHIVE_PATH} ${MY_DIR} + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools wheel artifact + if: ${{ matrix.cmake.python == 'ON' }} + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.wheel.outputs.archive_name }} + path: ${{ steps.wheel.outputs.archive_path }} + - name: Publish OR-Tools wheel asset + if: ${{ matrix.cmake.python == 'ON' && env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.wheel.outputs.archive_path }} + + - name: prepare OR-Tools jar + if: ${{ matrix.cmake.java == 'ON' }} + id: jar + run: | + MY_DIR="ortools_java${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp build/java/ortools-*/target/*.jar $MY_DIR + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="$PWD/${ARCHIVE_NAME}" + df -h + pwd + ls -ltr + ls -ltr ${MY_DIR} + echo "ARCHIVE_PATH=${ARCHIVE_PATH}" + echo "MY_DIR=${MY_DIR}" + echo "PWD=$PWD" + zip -r $ARCHIVE_PATH $MY_DIR + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + - name: Upload OR-Tools jar artifact + if: ${{ matrix.cmake.java == 'ON' }} + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.jar.outputs.archive_name }} + path: ${{ steps.jar.outputs.archive_path }} + - name: Publish OR-Tools jar asset + if: ${{ matrix.cmake.java == 'ON' && env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.jar.outputs.archive_path }} \ No newline at end of file diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml new file mode 100644 index 0000000..2ab2738 --- /dev/null +++ b/.github/workflows/ubuntu.yml @@ -0,0 +1,300 @@ +name: Ubuntu + +on: + workflow_dispatch: + inputs: + rtefrance_ortools_branch: + description: 'rte-france/or-tools branch name' + required: true + default: 'main' + push: + branches: + - main + - feature/* + - merge* + - fix/* + - release/* + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + +jobs: + build: + name: ${{ matrix.os }} shrd=${{ matrix.cmake.shared }} sirius=${{ matrix.sirius }} java=${{ matrix.cmake.java }} dotnet=${{ matrix.cmake.dotnet }} python=${{ matrix.cmake.python }}-${{ matrix.cmake.python-version }} + runs-on: ${{ matrix.os }} + env: + XPRESSDIR: ${{ github.workspace }}/xpressmp + XPAUTH_PATH: ${{ github.workspace }}/xpressmp/bin/xpauth.xpr + SIRIUS_RELEASE_TAG: ${{ matrix.sirius-release-tag }} + SIRIUS_INSTALL_DIR: ${{ github.workspace }}/sirius_install + SIRIUS: ${{ github.workspace }}/sirius_install/bin + RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-20.04"] + cmake: [ + {shared: OFF, java: OFF, dotnet: OFF, python: OFF, python-version: "3.8", publish-cxx-or: ON}, + {shared: ON, java: ON, dotnet: ON, python: OFF, python-version: "3.8", publish-cxx-or: ON}, + {shared: ON, java: OFF, dotnet: OFF, python: ON, python-version: "3.8", publish-cxx-or: OFF}, + {shared: ON, java: OFF, dotnet: OFF, python: ON, python-version: "3.9", publish-cxx-or: OFF}, + {shared: ON, java: OFF, dotnet: OFF, python: ON, python-version: "3.10", publish-cxx-or: OFF}, + ] + sirius: [ON, OFF] + sirius-release-tag: [antares-integration-v1.4] + steps: + - name: Checkout or-tools + uses: actions/checkout@v4 + with: + repository: rte-france/or-tools + ref: ${{ github.event.inputs.rtefrance_ortools_branch || 'main' }} + + - name: Checkout this repository + uses: actions/checkout@v4 + with: + path: "patch" + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.cmake.python-version }} + + - name: Apply patch + run: | + cp -r patch/* . + python patch.py + + - name: Install GCC11 and others + shell: bash + run: | + sudo apt update + sudo apt install gcc-11 g++-11 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 --slave /usr/bin/g++ g++ /usr/bin/g++-11 --slave /usr/bin/gcov gcov /usr/bin/gcov-11 + sudo apt install -yq build-essential lsb-release zlib1g-dev + + - name: Swig install + run: | + sudo apt install -y swig + swig -version + - name: Update Path + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: install python packages + run: > + python -m pip install absl-py mypy mypy-protobuf + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v1.13 + with: + cmake-version: '3.26.x' + + - name: Setup .NET 6.0 + if: ${{ matrix.cmake.dotnet == 'ON' }} + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + - name: Set-up Xpress with pip for Ubuntu + shell: bash + run: | + python -m pip install "xpress>=9.2,<9.3" + echo ${{ env.pythonLocation }} + XPRESS_DIR=${{ env.pythonLocation }}/lib/python${{ matrix.cmake.python-version }}/site-packages/xpress + echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV + echo "XPAUTH_PATH=$XPRESS_DIR/license/community-xpauth.xpr" >> $GITHUB_ENV + echo "Create symbolic link for XPRESS library file because it is missing in the Python installation" + ln -s $XPRESS_DIR/lib/libxprs.so.42 $XPRESS_DIR/lib/libxprs.so + + - name: Download Sirius + if: ${{ matrix.sirius == 'ON' }} + shell: bash + run: | + zipfile=${{ matrix.os }}_sirius-solver.zip + wget https://github.com/rte-france/sirius-solver/releases/download/${{ env.SIRIUS_RELEASE_TAG }}/$zipfile + unzip $zipfile + mv "${{ matrix.os }}_sirius-solver-install" sirius_install + echo "LD_LIBRARY_PATH=$PWD/sirius_install/lib" >> $GITHUB_ENV + echo "SIRIUS_CMAKE_DIR=$PWD/sirius_install/cmake" >> $GITHUB_ENV + + - name: ccache + if: ${{ startsWith(matrix.os, 'ubuntu') }} + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ matrix.os }}-${{ matrix.cmake.shared }}-${{ matrix.sirius }}-${{ matrix.cmake.dotnet }}-${{ matrix.cmake.java }}-${{ matrix.cmake.python }}-${{ matrix.cmake.python-version }} + + - name: Check cmake + run: cmake --version + - name: Configure OR-Tools + run: > + cmake -S . -B build + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=${{ matrix.cmake.shared }} + -DBUILD_PYTHON=${{ matrix.cmake.python }} + -DBUILD_JAVA=${{ matrix.cmake.java }} + -DBUILD_DOTNET=${{ matrix.cmake.dotnet }} + -DBUILD_EXAMPLES=${{ env.RELEASE_CREATED == 'true' && 'OFF' || 'ON' }} + -DBUILD_DEPS=ON + -DUSE_SIRIUS=${{ matrix.sirius }} + -Dsirius_solver_DIR="${{ env.SIRIUS_CMAKE_DIR }}" + -DBUILD_SAMPLES=OFF + -DCMAKE_INSTALL_PREFIX="build/install" + + - name: Build OR-Tools Linux + run: > + cmake + --build build + --config Release + --target all install -j4 + + - name: run tests not xpress + if: ${{ matrix.cmake.shared == 'ON' && (matrix.cmake.python != 'ON' || matrix.cmake.python-version != '3.8' ) }} + working-directory: ./build/ + run: > + ctest + -C Release + --output-on-failure + -E "(_xpress|_sirius|python_init_)" + + - name: run tests xpress + working-directory: ./build/ + run: > + ctest + -V + -C Release + --output-on-failure + -R "_xpress" + + - name: run tests sirius + working-directory: ./build/ + run: > + ctest + -V + -C Release + --output-on-failure + -R "sirius" + + - name: set name variables + id: names + shell: bash + run: | + SHARED=${{ matrix.cmake.shared }} + [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" + SIRIUS=${{ matrix.sirius }} + [ $SIRIUS == "ON" ] && WITH_SIRIUS="_sirius" || WITH_SIRIUS="" + OS="_${{ matrix.os }}" + APPENDIX="${OS}${WITH_SIRIUS}" + echo "appendix=$APPENDIX" >> $GITHUB_OUTPUT + APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}${WITH_SIRIUS}" + echo "appendix_with_shared=$APPENDIX_WITH_SHARED" >> $GITHUB_OUTPUT + + - name: Get release + if: ${{ env.RELEASE_CREATED == 'true' }} + id: get_release + uses: + bruceadams/get-release@v1.3.2 + + - name: Prepare OR-Tools install + if: ${{ matrix.cmake.publish-cxx-or == 'ON' }} + id: or-install + shell: bash + run: | + cd build + ARCHIVE_NAME="ortools_cxx${{ steps.names.outputs.appendix_with_shared }}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "$ARCHIVE_PATH" ./install + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools install artifact + uses: actions/upload-artifact@v4 + if: ${{ matrix.cmake.publish-cxx-or == 'ON' }} + with: + name: ${{ steps.or-install.outputs.archive_name }} + path: ${{ steps.or-install.outputs.archive_path }} + - name: Publish OR-Tools install asset + if: ${{ env.RELEASE_CREATED == 'true' && matrix.cmake.publish-cxx-or == 'ON' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.or-install.outputs.archive_path }} + + - name: prepare OR-Tools wheel + if: ${{ matrix.cmake.python == 'ON' }} + id: wheel + shell: bash + run: | + cd ./build/python/dist + MY_DIR="ortools_python-${{ matrix.cmake.python-version }}${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp *.whl "${MY_DIR}" + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "${ARCHIVE_PATH}" "${MY_DIR}" + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools wheel artifact + if: ${{ matrix.cmake.python == 'ON' }} + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.wheel.outputs.archive_name }} + path: ${{ steps.wheel.outputs.archive_path }} + - name: Publish OR-Tools wheel asset + if: ${{ env.RELEASE_CREATED == 'true' && matrix.cmake.python == 'ON' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.wheel.outputs.archive_path }} + + - name: prepare OR-Tools jar + if: ${{ matrix.cmake.java == 'ON' }} + id: jar + shell: bash + run: | + cd ./build/java + MY_DIR="ortools_java${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp ortools-*/target/*.jar "${MY_DIR}" + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "${ARCHIVE_PATH}" "${MY_DIR}" + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools jar artifact + if: ${{ matrix.cmake.java == 'ON' }} + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.jar.outputs.archive_name }} + path: ${{ steps.jar.outputs.archive_path }} + - name: Publish OR-Tools jar asset + if: ${{ env.RELEASE_CREATED == 'true' && matrix.cmake.java == 'ON' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.jar.outputs.archive_path }} + + - name: prepare OR-Tools dotnet + if: ${{ matrix.cmake.dotnet == 'ON' }} + id: dotnet + shell: bash + run: | + cd ./build/dotnet/packages/ + MY_DIR="ortools_dotnet${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp Google.OrTools.*.nupkg "${MY_DIR}" + cp Google.OrTools.runtime.*.nupkg "${MY_DIR}" + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "${ARCHIVE_PATH}" "${MY_DIR}" + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools dotnet artifact + if: ${{ matrix.cmake.dotnet == 'ON' }} + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.dotnet.outputs.archive_name }} + path: ${{ steps.dotnet.outputs.archive_path }} + - name: Publish OR-Tools dotnet asset + if: ${{ env.RELEASE_CREATED == 'true' && matrix.cmake.dotnet == 'ON' }} + uses: softprops/action-gh-release@v1 + with: + files: ${{ steps.dotnet.outputs.archive_path }} diff --git a/.github/workflows/windows-cpp.yml b/.github/workflows/windows-cpp.yml new file mode 100644 index 0000000..c9d06b1 --- /dev/null +++ b/.github/workflows/windows-cpp.yml @@ -0,0 +1,175 @@ +name: Windows-cpp + +on: + workflow_dispatch: + inputs: + rtefrance_ortools_branch: + description: 'rte-france/or-tools branch name' + required: true + default: 'main' + push: + branches: + - main + - feature/* + - merge* + - fix/* + - release/* + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} + +jobs: + build: + name: Windows cpp sirius=${{ matrix.sirius }} sirius-release-tag=${{ matrix.sirius-release-tag }} + runs-on: ${{ matrix.os }} + env: + SIRIUS_RELEASE_TAG: ${{ matrix.sirius-release-tag }} + SIRIUS_INSTALL_PATH: ${{ github.workspace }}/sirius_install + SIRIUS: ${{ github.workspace }}/sirius_install/bin + strategy: + fail-fast: false + matrix: + sirius: [ON, OFF] + os: [windows-latest] + sirius-release-tag: [ antares-integration-v1.4 ] + steps: + - name: Checkout or-tools + uses: actions/checkout@v4 + with: + repository: rte-france/or-tools + ref: ${{ github.event.inputs.rtefrance_ortools_branch || 'main' }} + + - name: Checkout this repository + uses: actions/checkout@v4 + with: + path: "patch" + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.8" + + - name: Apply patch + shell: bash + run: | + cp -r patch/* . + python patch.py + + - name: Set-up Xpress with pip + shell: bash + run: | + python -m pip install --no-cache-dir "xpress>=9.2,<9.3" + XPRESS_DIR="${{ env.pythonLocation }}\Lib\site-packages\xpress" + cp -r $XPRESS_DIR/lib $XPRESS_DIR/bin + echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV + echo "$XPRESS_DIR/bin" >> $GITHUB_PATH + + - name: Download Sirius + if: ${{ matrix.sirius == 'ON' }} + run: | + (New-Object System.Net.WebClient).DownloadFile("https://github.com/rte-france/sirius-solver/releases/download/${{ env.SIRIUS_RELEASE_TAG }}/${{ matrix.os }}_sirius-solver.zip","sirius-solver.zip"); + Expand-Archive .\sirius-solver.zip .; + Remove-Item .\sirius-solver.zip + - name: Install Sirius + if: ${{ matrix.sirius == 'ON' }} + shell: bash + run: | + mv "${{ matrix.os }}_sirius-solver-install" "${{ env.SIRIUS_INSTALL_PATH }}" + echo "${{ env.SIRIUS }}" >> $GITHUB_PATH + + - name: Check cmake + run: cmake --version + - name: Configure + run: > + cmake -S. -Bbuild + -G "Visual Studio 17 2022" + -DCMAKE_BUILD_TYPE=Release + -DBUILD_DEPS=ON + -DBUILD_EXAMPLES=${{ env.RELEASE_CREATED == 'true' && 'OFF' || 'ON' }} + -DBUILD_SAMPLES=OFF + -DCMAKE_INSTALL_PREFIX=install + -DUSE_SIRIUS=${{ matrix.sirius }} + + - name: Build + run: > + cmake --build build + --config Release + --target ALL_BUILD + -v -j2 + + - name: Tests not xpress + working-directory: ./build/ + run: > + ctest -C Release + --output-on-failure + -E "(_xpress|_sirius)" + + - name: Tests xpress + working-directory: ./build/ + run: | + $env:XPRESSDIR + Get-ChildItem -Path $env:XPRESSDIR + ctest -V -C Release --output-on-failure -R "_xpress" + + - name: Tests sirius + working-directory: ./build/ + run: > + ctest -V -C Release --output-on-failure -R "sirius" + + - name: Install + run: > + cmake --build build + --config Release + --target INSTALL + -v + + - name: set name variables + id: names + shell: bash + run: | + SHARED=${{ matrix.shared }} + [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" + SIRIUS_TAG=${{ matrix.sirius-release-tag }} + [ $SIRIUS_TAG == "metrix-integration-v0.1" ] && SIRIUS_BRANCH="-metrix" || SIRIUS_BRANCH="" + SIRIUS=${{ matrix.sirius }} + [ $SIRIUS == "ON" ] && WITH_SIRIUS="_sirius$SIRIUS_BRANCH" || WITH_SIRIUS="" + OS="_${{ matrix.os }}" + APPENDIX="${OS}${WITH_SIRIUS}" + echo "appendix=$APPENDIX" >> $GITHUB_OUTPUT + APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}${WITH_SIRIUS}" + echo "appendix_with_shared=$APPENDIX_WITH_SHARED" >> $GITHUB_OUTPUT + + - name: Get release + if: ${{ env.RELEASE_CREATED == 'true' }} + id: get_release + uses: + bruceadams/get-release@v1.3.2 + + - name: install zip + shell: cmd + run: | + choco install zip --no-progress + + - name: Prepare OR-Tools install + id: or-install + shell: bash + run: | + ARCHIVE_NAME="ortools_cxx${{ steps.names.outputs.appendix_with_shared }}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "$ARCHIVE_PATH" ./install + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools install artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.or-install.outputs.archive_name }} + path: ${{ steps.or-install.outputs.archive_path }} + - name: Publish OR-Tools install asset + if: ${{ env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: build/${{ steps.or-install.outputs.archive_name }} diff --git a/.github/workflows/windows-java-dotnet.yml b/.github/workflows/windows-java-dotnet.yml new file mode 100644 index 0000000..e6469aa --- /dev/null +++ b/.github/workflows/windows-java-dotnet.yml @@ -0,0 +1,201 @@ +name: Windows Java Dotnet + +on: + workflow_dispatch: + inputs: + rtefrance_ortools_branch: + description: 'rte-france/or-tools branch name' + required: true + default: 'main' + push: + branches: + - main + - feature/* + - merge* + - fix/* + - release/* + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} + +jobs: + build: + name: Windows java and dotnet + runs-on: windows-latest + env: + XPRESSDIR: ${{ github.workspace }}\xpressmp + XPRESS: ${{ github.workspace }}\xpressmp\\bin + XPAUTH_PATH: ${{ github.workspace }}\xpressmp\bin\xpauth.xpr + strategy: + fail-fast: false + steps: + - name: Checkout or-tools + uses: actions/checkout@v4 + with: + repository: rte-france/or-tools + ref: ${{ github.event.inputs.rtefrance_ortools_branch || 'main' }} + + - name: Checkout this repository + uses: actions/checkout@v4 + with: + path: "patch" + + - name: Setup .NET 6.0 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + + - name: Apply patch + shell: bash + run: | + cp -r patch/* . + python patch.py + + - name: Install SWIG 4.1.1 + run: | + (New-Object System.Net.WebClient).DownloadFile("http://prdownloads.sourceforge.net/swig/swigwin-4.1.1.zip","swigwin-4.1.1.zip"); + Expand-Archive .\swigwin-4.1.1.zip .; + echo "$((Get-Item .).FullName)/swigwin-4.1.1" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + Remove-Item .\swigwin-4.1.1.zip + - name: Check swig + run: swig -version + + - name: Set-up Xpress with pip + shell: bash + run: | + python -m pip install --no-cache-dir "xpress>=9.2,<9.3" + XPRESS_DIR="${{ env.pythonLocation }}\Lib\site-packages\xpress" + cp -r $XPRESS_DIR/lib $XPRESS_DIR/bin + echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV + echo "XPAUTH_PATH=$XPRESS_DIR\license\community-xpauth.xpr" >> $GITHUB_ENV + echo "$XPRESS_DIR/bin" >> $GITHUB_PATH + + - name: Check cmake + run: cmake --version + - name: Configure + run: > + cmake -S. -Bbuild + -G "Visual Studio 17 2022" + -DCMAKE_BUILD_TYPE=Release + -DBUILD_JAVA=ON + -DBUILD_DOTNET=ON + -DBUILD_EXAMPLES=${{ env.RELEASE_CREATED == 'true' && 'OFF' || 'ON' }} + -DBUILD_CXX_SAMPLES=OFF + -DBUILD_SAMPLES=OFF + -DCMAKE_INSTALL_PREFIX=install + -DBUILD_FLATZINC=OFF + + - name: Build + run: > + cmake --build build + --config Release + --target ALL_BUILD + -v -j2 + + - name: Tests not xpress + working-directory: ./build/ + run: > + ctest -C Release + --output-on-failure + -E "_xpress" + + - name: Check xpress installation + shell: bash + run: | + echo "ls -l $XPRESSDIR" + ls -l $XPRESSDIR + echo "ls -l $XPRESSDIR/bin" + ls -l $XPRESSDIR/bin + echo "ls -l $XPRESSDIR/lib" + ls -l $XPRESSDIR/lib + echo $XPAUTH_PATH + cat $XPAUTH_PATH + + - name: Tests xpress + working-directory: ./build/ + run: | + $env:XPRESSDIR + Get-ChildItem -Path $env:XPRESSDIR + ctest -V -C Release --output-on-failure -R "_xpress" + + - name: set name variables + id: names + shell: bash + run: | + SHARED=${{ matrix.shared }} + [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" + OS="_windows-latest" + APPENDIX="${OS}" + echo "appendix=$APPENDIX" >> $GITHUB_OUTPUT + APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}" + echo "appendix_with_shared=$APPENDIX_WITH_SHARED" >> $GITHUB_OUTPUT + + - name: Get release + if: ${{ env.RELEASE_CREATED == 'true' }} + id: get_release + uses: + bruceadams/get-release@v1.3.2 + + - name: install zip + shell: cmd + run: | + choco install zip --no-progress + + - name: prepare OR-Tools jar + id: jar + shell: bash + run: | + cd ./build/java + MY_DIR="ortools_java${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp ortools-*/target/*.jar "${MY_DIR}" + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "${ARCHIVE_PATH}" "${MY_DIR}" + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools jar artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.jar.outputs.archive_name }} + path: ${{ steps.jar.outputs.archive_path }} + - name: Publish OR-Tools jar asset + if: ${{ env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: build/${{ steps.jar.outputs.archive_name }} + + - name: prepare OR-Tools dotnet + id: dotnet + shell: bash + run: | + cd ./build/dotnet/packages/ + MY_DIR="ortools_dotnet${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp Google.OrTools.*.nupkg "${MY_DIR}" + cp Google.OrTools.runtime.*.nupkg "${MY_DIR}" + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "${ARCHIVE_PATH}" "${MY_DIR}" + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools dotnet artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.dotnet.outputs.archive_name }} + path: ${{ steps.dotnet.outputs.archive_path }} + - name: Publish OR-Tools dotnet asset + if: ${{ env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: build/${{ steps.dotnet.outputs.archive_name }} diff --git a/.github/workflows/windows-python.yml b/.github/workflows/windows-python.yml new file mode 100644 index 0000000..22d7946 --- /dev/null +++ b/.github/workflows/windows-python.yml @@ -0,0 +1,167 @@ +name: Windows-python + +on: + workflow_dispatch: + inputs: + rtefrance_ortools_branch: + description: 'rte-france/or-tools branch name' + required: true + default: 'main' + push: + branches: + - main + - feature/* + - merge* + - fix/* + - release/* + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} + +jobs: + build: + name: Windows python ${{ matrix.python.version }} + runs-on: windows-latest + env: + XPRESSDIR: ${{ github.workspace }}\xpressmp + XPRESS: ${{ github.workspace }}\xpressmp\bin + XPAUTH_PATH: ${{ github.workspace }}\xpressmp\bin\xpauth.xpr + strategy: + fail-fast: false + matrix: + python: [ + { version: "3.9", dir: Python309 }, + { version: "3.10", dir: Python310 }, + { version: "3.11", dir: Python311 }, + ] + steps: + - name: Checkout or-tools + uses: actions/checkout@v4 + with: + repository: rte-france/or-tools + ref: ${{ github.event.inputs.rtefrance_ortools_branch || 'main' }} + + - name: Checkout this repository + uses: actions/checkout@v4 + with: + path: "patch" + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python.version }} + + - name: Install python3 + run: python3 -m pip install --user mypy-protobuf absl-py setuptools wheel numpy pandas + + - name: Apply patch + shell: bash + run: | + cp -r patch/* . + python patch.py + + - name: Install SWIG 4.1.1 + run: | + (New-Object System.Net.WebClient).DownloadFile("http://prdownloads.sourceforge.net/swig/swigwin-4.1.1.zip","swigwin-4.1.1.zip"); + Expand-Archive .\swigwin-4.1.1.zip .; + echo "$((Get-Item .).FullName)/swigwin-4.1.1" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + Remove-Item .\swigwin-4.1.1.zip + - name: Check swig + run: swig -version + - name: Add Python binaries to path Windows + run: > + echo "$((Get-Item ~).FullName)/AppData/Roaming/Python/${{ matrix.python.dir }}/Scripts" | + Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Set-up Xpress with pip + shell: bash + run: | + python -m pip install --no-cache-dir "xpress>=9.2,<9.3" + XPRESS_DIR="${{ env.pythonLocation }}\Lib\site-packages\xpress" + cp -r $XPRESS_DIR/lib $XPRESS_DIR/bin + echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV + echo "XPAUTH_PATH=$XPRESS_DIR\license\community-xpauth.xpr" >> $GITHUB_ENV + echo "$XPRESS_DIR/bin" >> $GITHUB_PATH + + - name: Check cmake + run: cmake --version + - name: Configure + run: > + cmake -S. -Bbuild + -G "Visual Studio 17 2022" + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SAMPLES=OFF + -DBUILD_EXAMPLES=${{ env.RELEASE_CREATED == 'true' && 'OFF' || 'ON' }} + -DBUILD_CXX_SAMPLES=OFF + -DBUILD_PYTHON=ON + -DBUILD_SAMPLES=OFF + + - name: Build + run: > + cmake --build build + --config Release + --target ALL_BUILD + -v -j2 + + - name: Tests not xpress + working-directory: ./build/ + run: > + ctest -C Release + --output-on-failure + -E "_xpress" + + - name: Tests xpress + working-directory: ./build/ + run: | + ctest -V -C Release --output-on-failure -R "_xpress" + + - name: set name variables + id: names + shell: bash + run: | + SHARED=${{ matrix.shared }} + [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" + OS="_windows-latest" + APPENDIX="${OS}" + echo "appendix=$APPENDIX" >> $GITHUB_OUTPUT + APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}" + echo "appendix_with_shared=$APPENDIX_WITH_SHARED" >> $GITHUB_OUTPUT + + - name: Get release + if: ${{ env.RELEASE_CREATED == 'true' }} + id: get_release + uses: + bruceadams/get-release@v1.3.2 + + - name: install zip + shell: cmd + run: | + choco install zip --no-progress + + - name: prepare OR-Tools wheel + id: wheel + shell: bash + run: | + cd ./build/python/dist + MY_DIR="ortools_python-${{ matrix.python.version }}${{ steps.names.outputs.appendix }}" + mkdir ${MY_DIR} + cp *.whl "${MY_DIR}" + ARCHIVE_NAME="${MY_DIR}.zip" + ARCHIVE_PATH="${{ github.workspace }}/build/${ARCHIVE_NAME}" + zip -r "${ARCHIVE_PATH}" "${MY_DIR}" + echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT + echo "archive_path=$ARCHIVE_PATH" >> $GITHUB_OUTPUT + + - name: Upload OR-Tools wheel artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.wheel.outputs.archive_name }} + path: ${{ steps.wheel.outputs.archive_path }} + - name: Publish OR-Tools wheel asset + if: ${{ env.RELEASE_CREATED == 'true' }} + uses: softprops/action-gh-release@v1 + with: + files: build/${{ steps.wheel.outputs.archive_name }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..54dfa84 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# OR-Tools - Google Optimization Tools + +[![PyPI version](https://img.shields.io/pypi/v/ortools.svg)](https://pypi.org/project/ortools/) +[![PyPI download](https://img.shields.io/pypi/dm/ortools.svg)](https://pypi.org/project/ortools/#files) +[![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/google/or-tools/main) +\ +[![NuGet version](https://img.shields.io/nuget/v/Google.OrTools.svg)](https://www.nuget.org/packages/Google.OrTools) +[![NuGet download](https://img.shields.io/nuget/dt/Google.OrTools.svg)](https://www.nuget.org/packages/Google.OrTools) +\ +[![Maven Central](https://img.shields.io/maven-central/v/com.google.ortools/ortools-java)](https://mvnrepository.com/artifact/com.google.ortools/ortools-java) +\ +[![Discord](https://img.shields.io/discord/693088862481678374?color=7289DA&logo=discord&style=plastic)](https://discord.gg/ENkQrdf) + +Google's software suite for combinatorial optimization. + +## Table of Contents + +* [About OR-Tools](#about) +* [Codemap](#codemap) +* [Installation](#installation) +* [Quick Start](#quick-start) +* [Documentation](#documentation) +* [Contributing](#contributing) +* [License](#license) + + +## About OR-Tools + +Google Optimization Tools (a.k.a., OR-Tools) is an open-source, fast and +portable software suite for solving combinatorial optimization problems. + +The suite contains: + +* Two constraint programming solver (CP* and CP-SAT); +* Two linear programming solvers (Glop and PDLP); +* Wrappers around commercial and other open source solvers, including mixed + integer solvers; +* Bin packing and knapsack algorithms; +* Algorithms for the Traveling Salesman Problem and Vehicle Routing Problem; +* Graph algorithms (shortest paths, min cost flow, max flow, linear sum + assignment). + +We wrote OR-Tools in C++, but provide wrappers in Python, C# and Java. + +## Codemap + +This software suite is composed of the following components: + +* [Makefile](Makefile) Top-level for + [GNU Make](https://www.gnu.org/software/make/manual/make.html) based build. +* [makefiles](makefiles) Subsidiary Make files, CI and build system documentation. +* [CMakeLists.txt](CMakeLists.txt) Top-level for + [CMake](https://cmake.org/cmake/help/latest/) based build. +* [cmake](cmake) Subsidiary CMake files, CI and build system documentation. +* [WORKSPACE](WORKSPACE) Top-level for + [Bazel](https://bazel.build/start/bazel-intro) based build. +* [bazel](bazel) Subsidiary Bazel files, CI and build system documentation. +* [ortools](ortools) Root directory for source code. + * [base](ortools/base) Basic utilities. + * [algorithms](ortools/algorithms) Basic algorithms. + * [samples](ortools/algorithms/samples) Carefully crafted samples. + * [graph](ortools/graph) Graph algorithms. + * [samples](ortools/graph/samples) Carefully crafted samples. + * [linear_solver](ortools/linear_solver) Linear solver wrapper. + * [samples](ortools/linear_solver/samples) Carefully crafted samples. + * [glop](ortools/glop) Simplex-based linear programming solver. + * [samples](ortools/glop/samples) Carefully crafted samples. + * [pdlp](ortools/pdlp) First-order linear programming solver. + * [samples](ortools/pdlp/samples) Carefully crafted samples. + * [lp_data](ortools/lp_data) Data structures for linear models. + * [constraint_solver](ortools/constraint_solver) Constraint and Routing + solver. + * [docs](ortools/constraint_solver/docs) Documentation of the component. + * [samples](ortools/constraint_solver/samples) Carefully crafted samples. + * [sat](ortools/sat) SAT solver. + * [docs](ortools/sat/docs) Documentation of the component. + * [samples](ortools/sat/samples) Carefully crafted samples. + * [bop](ortools/bop) Boolean solver based on SAT. + * [util](ortools/util) Utilities needed by the constraint solver +* [examples](examples) Root directory for all examples. + * [contrib](examples/contrib) Examples from the community. + * [cpp](examples/cpp) C++ examples. + * [dotnet](examples/dotnet) .Net examples. + * [java](examples/java) Java examples. + * [python](examples/python) Python examples. + * [notebook](examples/notebook) Jupyter/IPython notebooks. + * [flatzinc](examples/flatzinc) FlatZinc examples. + * [tests](examples/tests) Unit tests and bug reports. +* [tools](tools) Delivery Tools (e.g. Windows GNU binaries, scripts, release dockers) + +## Installation + +This software suite has been tested under: + +* Ubuntu 18.04 LTS and up (64-bit); +* Apple macOS Mojave with Xcode 9.x (64-bit); +* Microsoft Windows with Visual Studio 2022 (64-bit). + +OR-Tools currently builds with a Makefile, but also provides Bazel and CMake +support. + +For installation instructions (both source and binary), please visit +https://developers.google.com/optimization/introduction/installing. + +### Build from source using Make (legacy) + +We provide a Make based build.
Please check the +[Make build instructions](makefiles/README.md). + +### Build from source using CMake + +We provide a CMake based build.
Please check the +[CMake build instructions](cmake/README.md). + +### Build from source using Bazel + +We provide a Bazel based build.
Please check the +[Bazel build instructions](bazel/README.md). + +## Quick Start + +The best way to learn how to use OR-Tools is to follow the tutorials in our +developer guide: + +https://developers.google.com/optimization/introduction/get_started + +If you want to learn from code examples, take a look at the examples in the +[examples](examples) directory. + +## Documentation + +The complete documentation for OR-Tools is available at: +https://developers.google.com/optimization/ + +## Contributing + +The [CONTRIBUTING.md](CONTRIBUTING.md) file contains instructions on how to +submit the Contributor License Agreement before sending any pull requests (PRs). +Of course, if you're new to the project, it's usually best to discuss any +proposals and reach consensus before sending your first PR. + +## License + +The OR-Tools software suite is licensed under the terms of the Apache License 2.0. +
See [LICENSE](LICENSE) for more information. diff --git a/ortools/linear_solver/sirius_interface.cc b/ortools/linear_solver/sirius_interface.cc new file mode 100644 index 0000000..65443d8 --- /dev/null +++ b/ortools/linear_solver/sirius_interface.cc @@ -0,0 +1,1417 @@ +// Copyright 2019 RTE +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Initial version of this code was provided by RTE + +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "ortools/base/logging.h" +#include "ortools/base/timer.h" +#include "ortools/linear_solver/linear_solver.h" + +#if defined(USE_SIRIUS) + +extern "C" { +#include "srs_api.h" +} + +#define SRS_INTEGER 2 +#define SRS_CONTINUOUS 1 + +//# define EN_BASE 0 +//# define EN_BASE_LIBRE 1 +//# define EN_BASE_SUR_BORNE_INF 2 +//# define EN_BASE_SUR_BORNE_SUP 3 +//# define HORS_BASE_SUR_BORNE_INF 4 +//# define HORS_BASE_SUR_BORNE_SUP 5 +//# define HORS_BASE_A_ZERO 6 /* Pour les variables non bornees qui restent hors base */ + +//FREE = 0, +//AT_LOWER_BOUND, +//AT_UPPER_BOUND, +//FIXED_VALUE, +//BASIC + +enum SRS_BASIS_STATUS { + SRS_BASIC = EN_BASE, + SRS_BASIC_FREE = EN_BASE_LIBRE, + SRS_AT_LOWER = EN_BASE_SUR_BORNE_INF, + SRS_AT_UPPER = EN_BASE_SUR_BORNE_SUP, + SRS_FREE_LOWER = HORS_BASE_SUR_BORNE_INF, + SRS_FREE_UPPER = HORS_BASE_SUR_BORNE_SUP, + SRS_FREE_ZERO = HORS_BASE_A_ZERO, +}; + +// In case we need to return a double but don't have a value for that +// we just return a NaN. +#if !defined(CPX_NAN) +#define SRS_NAN std::numeric_limits::quiet_NaN() +#endif + +// The argument to this macro is the invocation of a SRS function that +// returns a status. If the function returns non-zero the macro aborts +// the program with an appropriate error message. +#define CHECK_STATUS(s) \ + do { \ + int const status_ = s; \ + CHECK_EQ(0, status_); \ + } while (0) + +namespace operations_research { + + using std::unique_ptr; + + // For a model that is extracted to an instance of this class there is a + // 1:1 corresponence between MPVariable instances and SIRIUS columns: the + // index of an extracted variable is the column index in the SIRIUS model. + // Similiar for instances of MPConstraint: the index of the constraint in + // the model is the row index in the SIRIUS model. + class SiriusInterface : public MPSolverInterface { + public: + // NOTE: 'mip' specifies the type of the problem (either continuous or + // mixed integer. This type is fixed for the lifetime of the + // instance. There are no dynamic changes to the model type. + explicit SiriusInterface(MPSolver *const solver, bool mip); + ~SiriusInterface(); + + // Sets the optimization direction (min/max). + virtual void SetOptimizationDirection(bool maximize); + + // ----- Solve ----- + // Solve the problem using the parameter values specified. + virtual MPSolver::ResultStatus Solve(MPSolverParameters const ¶m); + + // Writes the model. + void Write(const std::string& filename) override; + + // ----- Model modifications and extraction ----- + // Resets extracted model + virtual void Reset(); + + virtual void SetVariableBounds(int var_index, double lb, double ub); + virtual void SetVariableInteger(int var_index, bool integer); + virtual void SetConstraintBounds(int row_index, double lb, double ub); + + virtual void AddRowConstraint(MPConstraint *const ct); + virtual void AddVariable(MPVariable *const var); + virtual void SetCoefficient(MPConstraint *const constraint, + MPVariable const *const variable, + double new_value, double old_value); + + // Clear a constraint from all its terms. + virtual void ClearConstraint(MPConstraint *const constraint); + // Change a coefficient in the linear objective + virtual void SetObjectiveCoefficient(MPVariable const *const variable, + double coefficient); + // Change the constant term in the linear objective. + virtual void SetObjectiveOffset(double value); + // Clear the objective from all its terms. + virtual void ClearObjective(); + + // ------ Query statistics on the solution and the solve ------ + // Number of simplex iterations + virtual int64_t iterations() const; + // Number of branch-and-bound nodes. Only available for discrete problems. + virtual int64_t nodes() const; + + // Returns the basis status of a row. + virtual MPSolver::BasisStatus row_status(int constraint_index) const; + // Returns the basis status of a column. + virtual MPSolver::BasisStatus column_status(int variable_index) const; + + bool SetSolverSpecificParametersAsString(const std::string& parameters) override; + + // ----- Misc ----- + + // Query problem type. + // Remember that problem type is a static property that is set + // in the constructor and never changed. + virtual bool IsContinuous() const { return IsLP(); } + virtual bool IsLP() const { return !mMip; } + virtual bool IsMIP() const { return mMip; } + + virtual void ExtractNewVariables(); + virtual void ExtractNewConstraints(); + virtual void ExtractObjective(); + + virtual std::string SolverVersion() const; + + virtual void *underlying_solver() { return reinterpret_cast(mLp); } + + virtual double ComputeExactConditionNumber() const { + if (!IsContinuous()) { + LOG(DFATAL) << "ComputeExactConditionNumber not implemented for" + << " SIRIUS_MIXED_INTEGER_PROGRAMMING"; + return 0.0; + } + + // TODO(user,user): Not yet working. + LOG(DFATAL) << "ComputeExactConditionNumber not implemented for" + << " SIRIUS_LINEAR_PROGRAMMING"; + return 0.0; + } + + protected: + // Set all parameters in the underlying solver. + virtual void SetParameters(MPSolverParameters const ¶m); + // Set each parameter in the underlying solver. + virtual void SetRelativeMipGap(double value); + virtual void SetPrimalTolerance(double value); + virtual void SetDualTolerance(double value); + virtual void SetPresolveMode(int value) override; + virtual void SetScalingMode(int value); + virtual void SetLpAlgorithm(int value); + + virtual bool ReadParameterFile(std::string const &filename); + virtual absl::Status SetNumThreads(int num_threads) override; + virtual std::string ValidFileExtensionForParameterFile() const; + + private: + // Mark modeling object "out of sync". This implicitly invalidates + // solution information as well. It is the counterpart of + // MPSolverInterface::InvalidateSolutionSynchronization + void InvalidateModelSynchronization() { + mCstat = 0; + mRstat = 0; + sync_status_ = MUST_RELOAD; + } + + // Transform SIRIUS basis status to MPSolver basis status. + static MPSolver::BasisStatus xformBasisStatus(char sirius_basis_status); + + private: + SRS_PROBLEM * mLp; + bool const mMip; + // Incremental extraction. + // Without incremental extraction we have to re-extract the model every + // time we perform a solve. Due to the way the Reset() function is + // implemented, this will lose MIP start or basis information from a + // previous solve. On the other hand, if there is a significant changes + // to the model then just re-extracting everything is usually faster than + // keeping the low-level modeling object in sync with the high-level + // variables/constraints. + // Note that incremental extraction is particularly expensive in function + // ExtractNewVariables() since there we must scan _all_ old constraints + // and update them with respect to the new variables. + bool const supportIncrementalExtraction; + + // Use slow and immediate updates or try to do bulk updates. + // For many updates to the model we have the option to either perform + // the update immediately with a potentially slow operation or to + // just mark the low-level modeling object out of sync and re-extract + // the model later. + enum SlowUpdates { + SlowSetCoefficient = 0x0001, + SlowClearConstraint = 0x0002, + SlowSetObjectiveCoefficient = 0x0004, + SlowClearObjective = 0x0008, + SlowSetConstraintBounds = 0x0010, + SlowSetVariableInteger = 0x0020, + SlowSetVariableBounds = 0x0040, + SlowUpdatesAll = 0xffff + } const slowUpdates; + // SIRIUS has no method to query the basis status of a single variable. + // Hence we query the status only once and cache the array. This is + // much faster in case the basis status of more than one row/column + // is required. + unique_ptr mutable mCstat; + unique_ptr mutable mRstat; + + // Setup the right-hand side of a constraint from its lower and upper bound. + static void MakeRhs(double lb, double ub, double &rhs, char &sense, + double &range); + + std::map > > fixedOrderCoefficientsPerConstraint; + + // vector to store TypeDeBorneDeLaVariable values + std::vector varBoundsTypeValues; + }; + + // Creates a LP/MIP instance. + SiriusInterface::SiriusInterface(MPSolver *const solver, bool mip) + : MPSolverInterface(solver), + mLp(NULL), + mMip(mip), + slowUpdates(static_cast(SlowSetObjectiveCoefficient | + SlowClearObjective)), + supportIncrementalExtraction(false), + mCstat(), + mRstat() { + //google::InitGoogleLogging("Sirius"); + int status; + + char const *name = solver_->name_.c_str(); + mLp = SRScreateprob(); + DCHECK(mLp != nullptr); // should not be NULL if status=0 + //FIXME CHECK_STATUS(SRSloadlp(mLp, "newProb", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + //FIXME CHECK_STATUS(SRSchgobjsense(mLp, maximize_ ? SRS_OBJ_MAXIMIZE : SRS_OBJ_MINIMIZE)); + } + + SiriusInterface::~SiriusInterface() { + CHECK_STATUS(SRSfreeprob(mLp)); + //google::ShutdownGoogleLogging(); + } + + std::string SiriusInterface::SolverVersion() const { + // We prefer SRSversionnumber() over SRSversion() since the + // former will never pose any encoding issues. + return absl::StrFormat("SIRIUS library version %s", SRSversion()); + } + + // ------ Model modifications and extraction ----- + + void SiriusInterface::Reset() { + // Instead of explicitly clearing all modeling objects we + // just delete the problem object and allocate a new one. + CHECK_STATUS(SRSfreeprob(mLp)); + + const char *const name = solver_->name_.c_str(); + mLp = SRScreateprob(); + DCHECK(mLp != nullptr); // should not be NULL if status=0 + //FIXME CHECK_STATUS(SRSloadlp(mLp, "newProb", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + //FIXME CHECK_STATUS(SRSchgobjsense(mLp, maximize_ ? SRS_OBJ_MAXIMIZE : SRS_OBJ_MINIMIZE)); + + ResetExtractionInformation(); + mCstat = 0; + mRstat = 0; + } + + void SiriusInterface::SetOptimizationDirection(bool maximize) { + InvalidateSolutionSynchronization(); + //FIXME SRSchgobjsense(mLp, maximize ? SRS_OBJ_MAXIMIZE : SRS_OBJ_MINIMIZE); + } + + void SiriusInterface::SetVariableBounds(int var_index, double lb, double ub) { + InvalidateSolutionSynchronization(); + + // Changing the bounds of a variable is fast. However, doing this for + // many variables may still be slow. So we don't perform the update by + // default. However, if we support incremental extraction + // (supportIncrementalExtraction is true) then we MUST perform the + // update here or we will lose it. + + //if (!supportIncrementalExtraction && !(slowUpdates & SlowSetVariableBounds)) { + // InvalidateModelSynchronization(); + //} + //else + { + if (variable_is_extracted(var_index)) { + // Variable has already been extracted, so we must modify the + // modeling object. + DCHECK_LT(var_index, last_variable_index_); + int const idx[1] = { var_index }; + double lb_l = (lb == -MPSolver::infinity() ? -SRS_infinite : lb); + double ub_l = (ub == MPSolver::infinity() ? SRS_infinite : ub); + CHECK_STATUS(SRSchgbds(mLp, 1, idx, &lb_l, &ub_l)); + } + else { + // Variable is not yet extracted. It is sufficient to just mark + // the modeling object "out of sync" + InvalidateModelSynchronization(); + } + } + } + + // Modifies integrality of an extracted variable. + void SiriusInterface::SetVariableInteger(int var_index, bool integer) { + InvalidateSolutionSynchronization(); + + // NOTE: The type of the model (continuous or mixed integer) is + // defined once and for all in the constructor. There are no + // dynamic changes to the model type. + + // Changing the type of a variable should be fast. Still, doing all + // updates in one big chunk right before solve() is usually faster. + // However, if we support incremental extraction + // (supportIncrementalExtraction is true) then we MUST change the + // type of extracted variables here. + + if (!supportIncrementalExtraction && + !(slowUpdates & SlowSetVariableInteger)) { + InvalidateModelSynchronization(); + } + else { + if (mMip) { + if (variable_is_extracted(var_index)) { + // Variable is extracted. Change the type immediately. + // TODO: Should we check the current type and don't do anything + // in case the type does not change? + DCHECK_LE(var_index, SRSgetnbcols(mLp)); + char const type = integer ? SRS_INTEGER : SRS_CONTINUOUS; + throw std::logic_error("Not implemented"); + //FIXME CHECK_STATUS(SRSchgcoltype(mLp, 1, &var_index, &type)); + } + else + InvalidateModelSynchronization(); + } + else { + LOG(DFATAL) + << "Attempt to change variable to integer in non-MIP problem!"; + } + } + } + + // Setup the right-hand side of a constraint. + void SiriusInterface::MakeRhs(double lb, double ub, double &rhs, char &sense, + double &range) { + if (lb == ub) { + // Both bounds are equal -> this is an equality constraint + rhs = lb; + range = 0.0; + sense = '='; + } + else if (lb > (-SRS_infinite) && ub < SRS_infinite) { + // Both bounds are finite -> this is a ranged constraint + throw std::logic_error("Sirius does not handle ranged constraint."); + if (ub < lb) { + CHECK_STATUS(-1); + } + CHECK_STATUS(-1); + } + else if (ub < SRS_infinite || + (std::abs(ub) == SRS_infinite && std::abs(lb) > SRS_infinite)) { + // Finite upper, infinite lower bound -> this is a <= constraint + rhs = ub; + range = 0.0; + sense = '<'; + } + else if (lb > (-SRS_infinite) || + (std::abs(lb) == SRS_infinite && std::abs(ub) > SRS_infinite)) { + // Finite lower, infinite upper bound -> this is a >= constraint + rhs = lb; + range = 0.0; + sense = '>'; + } + else { + // Lower and upper bound are both infinite. + // This is used for example in .mps files to specify alternate + // objective functions. + // Note that the case lb==ub was already handled above, so we just + // pick the bound with larger magnitude and create a constraint for it. + // Note that we replace the infinite bound by SRS_infinite since + // bounds with larger magnitude may cause other SIRIUS functions to + // fail (for example the export to LP files). + DCHECK_GT(std::abs(lb), SRS_infinite); + DCHECK_GT(std::abs(ub), SRS_infinite); + if (std::abs(lb) > std::abs(ub)) { + rhs = (lb < 0) ? -SRS_infinite : SRS_infinite; + sense = '>'; + } + else { + rhs = (ub < 0) ? -SRS_infinite : SRS_infinite; + sense = '<'; + } + range = 0.0; + } + } + + void SiriusInterface::SetConstraintBounds(int index, double lb, double ub) { + InvalidateSolutionSynchronization(); + + // Changing rhs, sense, or range of a constraint is not too slow. + // Still, doing all the updates in one large operation is faster. + // Note however that if we do not want to re-extract the full model + // for each solve (supportIncrementalExtraction is true) then we MUST + // update the constraint here, otherwise we lose this update information. + + //if (!supportIncrementalExtraction && + // !(slowUpdates & SlowSetConstraintBounds)) { + // InvalidateModelSynchronization(); + //} + //else + { + if (constraint_is_extracted(index)) { + // Constraint is already extracted, so we must update its bounds + // and its type. + DCHECK(mLp != NULL); + char sense; + double range, rhs; + MakeRhs(lb, ub, rhs, sense, range); + CHECK_STATUS(SRSchgrhs(mLp, 1, &index, &rhs)); + CHECK_STATUS(SRSchgsens(mLp, 1, &index, &sense)); + CHECK_STATUS(SRSchgrangeval(mLp, 1, &index, &range)); + } + else { + // Constraint is not yet extracted. It is sufficient to mark the + // modeling object as "out of sync" + InvalidateModelSynchronization(); + } + } + } + + void SiriusInterface::AddRowConstraint(MPConstraint *const ct) { + // This is currently only invoked when a new constraint is created, + // see MPSolver::MakeRowConstraint(). + // At this point we only have the lower and upper bounds of the + // constraint. We could immediately call SRSaddrows() here but it is + // usually much faster to handle the fully populated constraint in + // ExtractNewConstraints() right before the solve. + InvalidateModelSynchronization(); + } + + void SiriusInterface::AddVariable(MPVariable *const ct) { + // This is currently only invoked when a new variable is created, + // see MPSolver::MakeVar(). + // At this point the variable does not appear in any constraints or + // the objective function. We could invoke SRSaddcols() to immediately + // create the variable here but it is usually much faster to handle the + // fully setup variable in ExtractNewVariables() right before the solve. + InvalidateModelSynchronization(); + } + + void SiriusInterface::SetCoefficient(MPConstraint *const constraint, + MPVariable const *const variable, + double new_value, double) { + InvalidateSolutionSynchronization(); + + fixedOrderCoefficientsPerConstraint[constraint->index()].push_back(std::make_pair(variable->index(), new_value)); + + // Changing a single coefficient in the matrix is potentially pretty + // slow since that coefficient has to be found in the sparse matrix + // representation. So by default we don't perform this update immediately + // but instead mark the low-level modeling object "out of sync". + // If we want to support incremental extraction then we MUST perform + // the modification immediately or we will lose it. + + if (!supportIncrementalExtraction && !(slowUpdates & SlowSetCoefficient)) { + InvalidateModelSynchronization(); + } + else { + int const row = constraint->index(); + int const col = variable->index(); + if (constraint_is_extracted(row) && variable_is_extracted(col)) { + // If row and column are both extracted then we can directly + // update the modeling object + DCHECK_LE(row, last_constraint_index_); + DCHECK_LE(col, last_variable_index_); + //FIXME CHECK_STATUS(SRSchgcoef(mLp, row, col, new_value)); + } + else { + // If either row or column is not yet extracted then we can + // defer the update to ExtractModel() + InvalidateModelSynchronization(); + } + } + } + + void SiriusInterface::ClearConstraint(MPConstraint *const constraint) { + int const row = constraint->index(); + if (!constraint_is_extracted(row)) + // There is nothing to do if the constraint was not even extracted. + return; + + // Clearing a constraint means setting all coefficients in the corresponding + // row to 0 (we cannot just delete the row since that would renumber all + // the constraints/rows after it). + // Modifying coefficients in the matrix is potentially pretty expensive + // since they must be found in the sparse matrix representation. That is + // why by default we do not modify the coefficients here but only mark + // the low-level modeling object "out of sync". + + if (!(slowUpdates & SlowClearConstraint)) { + InvalidateModelSynchronization(); + } + else { + InvalidateSolutionSynchronization(); + + int const len = constraint->coefficients_.size(); + unique_ptr rowind(new int[len]); + unique_ptr colind(new int[len]); + unique_ptr val(new double[len]); + int j = 0; + const auto& coeffs = constraint->coefficients_; + for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { + int const col = it->first->index(); + if (variable_is_extracted(col)) { + rowind[j] = row; + colind[j] = col; + val[j] = 0.0; + ++j; + } + } + if (j) { + //FIXME CHECK_STATUS(SRSchgmcoef(mLp, j, rowind.get(), colind.get(), val.get())); + } + } + } + + void SiriusInterface::SetObjectiveCoefficient(MPVariable const *const variable, + double coefficient) { + int const col = variable->index(); + if (!variable_is_extracted(col)) + // Nothing to do if variable was not even extracted + return; + + InvalidateSolutionSynchronization(); + + // The objective function is stored as a dense vector, so updating a + // single coefficient is O(1). So by default we update the low-level + // modeling object here. + // If we support incremental extraction then we have no choice but to + // perform the update immediately. + + if (supportIncrementalExtraction || + (slowUpdates & SlowSetObjectiveCoefficient)) { + CHECK_STATUS(SRSchgobj(mLp, 1, &col, &coefficient)); + } + else + InvalidateModelSynchronization(); + } + + void SiriusInterface::SetObjectiveOffset(double value) { + // Changing the objective offset is O(1), so we always do it immediately. + InvalidateSolutionSynchronization(); + throw std::logic_error("Not implemented"); + //FIXME CHECK_STATUS(SRSsetobjoffset(mLp, value)); + } + + void SiriusInterface::ClearObjective() { + InvalidateSolutionSynchronization(); + + // Since the objective function is stored as a dense vector updating + // it is O(n), so we usually perform the update immediately. + // If we want to support incremental extraction then we have no choice + // but to perform the update immediately. + + if (supportIncrementalExtraction || (slowUpdates & SlowClearObjective)) { + int const cols = SRSgetnbcols(mLp); + unique_ptr ind(new int[cols]); + unique_ptr zero(new double[cols]); + int j = 0; + const auto& coeffs = solver_->objective_->coefficients_; + for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { + int const idx = it->first->index(); + // We only need to reset variables that have been extracted. + if (variable_is_extracted(idx)) { + DCHECK_LT(idx, cols); + ind[j] = idx; + zero[j] = 0.0; + ++j; + } + } + if (j > 0) CHECK_STATUS(SRSchgobj(mLp, j, ind.get(), zero.get())); + //FIXME CHECK_STATUS(SRSsetobjoffset(mLp, 0.0)); + } + else + InvalidateModelSynchronization(); + } + + // ------ Query statistics on the solution and the solve ------ + + int64_t SiriusInterface::iterations() const { + int iter = 0; + if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations; + CHECK_STATUS(SRSgetspxitercount(mLp, &iter)); + return static_cast(iter); + } + + int64_t SiriusInterface::nodes() const { + if (mMip) { + int nodes = 0; + if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes; + CHECK_STATUS(SRSgetmipnodecount(mLp, &nodes)); + return static_cast(nodes); + } + else { + LOG(DFATAL) << "Number of nodes only available for discrete problems"; + return kUnknownNumberOfNodes; + } + } + + // Transform a SIRIUS basis status to an MPSolver basis status. + MPSolver::BasisStatus SiriusInterface::xformBasisStatus(char sirius_basis_status) { + switch (sirius_basis_status) { + case SRS_AT_LOWER: + return MPSolver::AT_LOWER_BOUND; + case SRS_BASIC: + return MPSolver::BASIC; + case SRS_AT_UPPER: + return MPSolver::AT_UPPER_BOUND; + case SRS_FREE_LOWER: + case SRS_FREE_UPPER: + case SRS_FREE_ZERO: + case SRS_BASIC_FREE: + return MPSolver::FREE; + default: + LOG(DFATAL) << "Unknown SIRIUS basis status"; + return MPSolver::FREE; + } + } + + // Returns the basis status of a row. + MPSolver::BasisStatus SiriusInterface::row_status(int constraint_index) const { + if (mMip) { + LOG(FATAL) << "Basis status only available for continuous problems"; + return MPSolver::FREE; + } + + if (CheckSolutionIsSynchronized()) { + if (!mRstat) { + int const rows = SRSgetnbrows(mLp); + unique_ptr data(new char[rows]); + char * ptrToData = data.get(); + mRstat.swap(data); + CHECK_STATUS(SRSgetrowbasisstatus(mLp, &ptrToData)); + } + } + else + mRstat = 0; + + if (mRstat) { + return xformBasisStatus(mRstat[constraint_index]); + } + else { + LOG(FATAL) << "Row basis status not available"; + return MPSolver::FREE; + } + } + + // Returns the basis status of a column. + MPSolver::BasisStatus SiriusInterface::column_status(int variable_index) const { + if (mMip) { + LOG(FATAL) << "Basis status only available for continuous problems"; + return MPSolver::FREE; + } + + if (CheckSolutionIsSynchronized()) { + if (!mCstat) { + int const cols = SRSgetnbcols(mLp); + unique_ptr data(new char[cols]); + char * ptrToData = data.get(); + mCstat.swap(data); + CHECK_STATUS(SRSgetcolbasisstatus(mLp, &ptrToData)); + } + } + else + mCstat = 0; + + if (mCstat) { + return xformBasisStatus(mCstat[variable_index]); + } + else { + LOG(FATAL) << "Column basis status not available"; + return MPSolver::FREE; + } + } + + // Extract all variables that have not yet been extracted. + void SiriusInterface::ExtractNewVariables() { + // NOTE: The code assumes that a linear expression can never contain + // non-zero duplicates. + + InvalidateSolutionSynchronization(); + + if (!supportIncrementalExtraction) { + // Without incremental extraction ExtractModel() is always called + // to extract the full model. + CHECK(last_variable_index_ == 0 || + last_variable_index_ == solver_->variables_.size()); + CHECK(last_constraint_index_ == 0 || + last_constraint_index_ == solver_->constraints_.size()); + } + + int const last_extracted = last_variable_index_; + int const var_count = solver_->variables_.size(); + int newcols = var_count - last_extracted; + if (newcols > 0) { + // There are non-extracted variables. Extract them now. + + unique_ptr obj(new double[newcols]); + unique_ptr lb(new double[newcols]); + unique_ptr ub(new double[newcols]); + unique_ptr ctype(new int[newcols]); + unique_ptr colname(new const char *[newcols]); + + bool have_names = false; + for (int j = 0, varidx = last_extracted; j < newcols; ++j, ++varidx) { + MPVariable const *const var = solver_->variables_[varidx]; + lb[j] = var->lb(); + ub[j] = var->ub(); + ctype[j] = var->integer() ? SRS_INTEGER : SRS_CONTINUOUS; + colname[j] = var->name().empty() ? 0 : var->name().c_str(); + have_names = have_names || var->name().empty(); + obj[j] = solver_->objective_->GetCoefficient(var); + } + + // Arrays for modifying the problem are setup. Update the index + // of variables that will get extracted now. Updating indices + // _before_ the actual extraction makes things much simpler in + // case we support incremental extraction. + // In case of error we just reset the indeces. + std::vector const &variables = solver_->variables(); + for (int j = last_extracted; j < var_count; ++j) { + CHECK(!variable_is_extracted(variables[j]->index())); + set_variable_as_extracted(variables[j]->index(), true); + } + + try { + bool use_newcols = true; + + if (supportIncrementalExtraction) { + // If we support incremental extraction then we must + // update existing constraints with the new variables. + // To do that we use SRSaddcols() to actually create the + // variables. This is supposed to be faster than combining + // SRSnewcols() and SRSchgcoeflist(). + + // For each column count the size of the intersection with + // existing constraints. + unique_ptr collen(new int[newcols]); + for (int j = 0; j < newcols; ++j) collen[j] = 0; + int nonzeros = 0; + // TODO: Use a bitarray to flag the constraints that actually + // intersect new variables? + for (int i = 0; i < last_constraint_index_; ++i) { + MPConstraint const *const ct = solver_->constraints_[i]; + CHECK(constraint_is_extracted(ct->index())); + const auto& coeffs = ct->coefficients_; + for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { + int const idx = it->first->index(); + if (variable_is_extracted(idx) && idx > last_variable_index_) { + collen[idx - last_variable_index_]++; + ++nonzeros; + } + } + } + + if (nonzeros > 0) { + // At least one of the new variables did intersect with an + // old constraint. We have to create the new columns via + // SRSaddcols(). + use_newcols = false; + unique_ptr begin(new int[newcols + 2]); + unique_ptr cmatind(new int[nonzeros]); + unique_ptr cmatval(new double[nonzeros]); + + // Here is how cmatbeg[] is setup: + // - it is initialized as + // [ 0, 0, collen[0], collen[0]+collen[1], ... ] + // so that cmatbeg[j+1] tells us where in cmatind[] and + // cmatval[] we need to put the next nonzero for column + // j + // - after nonzeros have been setup the array looks like + // [ 0, collen[0], collen[0]+collen[1], ... ] + // so that it is the correct input argument for SRSaddcols + int *cmatbeg = begin.get(); + cmatbeg[0] = 0; + cmatbeg[1] = 0; + ++cmatbeg; + for (int j = 0; j < newcols; ++j) + cmatbeg[j + 1] = cmatbeg[j] + collen[j]; + + for (int i = 0; i < last_constraint_index_; ++i) { + MPConstraint const *const ct = solver_->constraints_[i]; + int const row = ct->index(); + const auto& coeffs = ct->coefficients_; + for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { + int const idx = it->first->index(); + if (variable_is_extracted(idx) && idx > last_variable_index_) { + int const nz = cmatbeg[idx]++; + cmatind[nz] = row; + cmatval[nz] = it->second; + } + } + } + --cmatbeg; + //FIXME CHECK_STATUS(SRScreatecols(mLp, newcols, nonzeros, obj.get(), cmatbeg, cmatind.get(), cmatval.get(),lb.get(), ub.get())); + } + } + + if (use_newcols) { + // Either incremental extraction is not supported or none of + // the new variables did intersect an existing constraint. + // We can just use SRSnewcols() to create the new variables. + std::vector collen(newcols, 0); + std::vector cmatbeg(newcols, 0); + unique_ptr cmatind(new int[1]); + unique_ptr cmatval(new double[1]); + cmatind[0] = 0; + cmatval[0] = 1.0; + + CHECK_STATUS( + SRScreatecols(mLp, newcols, obj.get(), ctype.get(), lb.get(), ub.get(), colname.get()) + ); + //int const cols = SRSgetnbcols(mLp); + //unique_ptr ind(new int[newcols]); + //for (int j = 0; j < cols; ++j) + // ind[j] = j; + //CHECK_STATUS( + // SRSchgcoltype(mLp, cols - last_extracted, ind.get(), ctype.get()) + //); + } + else { + // Incremental extraction: we must update the ctype of the + // newly created variables (SRSaddcols() does not allow + // specifying the ctype) + if (mMip && SRSgetnbcols(mLp) > 0) { + // Query the actual number of columns in case we did not + // manage to extract all columns. + int const cols = SRSgetnbcols(mLp); + unique_ptr ind(new int[newcols]); + for (int j = last_extracted; j < cols; ++j) + ind[j - last_extracted] = j; + //FIXME CHECK_STATUS(SRSchgcoltype(mLp, cols - last_extracted, ind.get(),ctype.get())); + } + } + } + catch (...) { + // Undo all changes in case of error. + int const cols = SRSgetnbcols(mLp); + if (cols > last_extracted) + { + std::vector colsToDelete; + for (int i = last_extracted; i < cols; ++i) + colsToDelete.push_back(i); + //FIXME (void)SRSdelcols(mLp, colsToDelete.size(), colsToDelete.data()); + } + std::vector const &variables = solver_->variables(); + int const size = variables.size(); + for (int j = last_extracted; j < size; ++j) + set_variable_as_extracted(j, false); + throw; + } + } + } + + // Extract constraints that have not yet been extracted. + void SiriusInterface::ExtractNewConstraints() { + // NOTE: The code assumes that a linear expression can never contain + // non-zero duplicates. + if (!supportIncrementalExtraction) { + // Without incremental extraction ExtractModel() is always called + // to extract the full model. + CHECK(last_variable_index_ == 0 || + last_variable_index_ == solver_->variables_.size()); + CHECK(last_constraint_index_ == 0 || + last_constraint_index_ == solver_->constraints_.size()); + } + + int const offset = last_constraint_index_; + int const total = solver_->constraints_.size(); + + if (total > offset) { + // There are constraints that are not yet extracted. + + InvalidateSolutionSynchronization(); + + int newCons = total - offset; + int const cols = SRSgetnbcols(mLp); + //DCHECK_EQ(last_variable_index_, cols); + + // Update indices of new constraints _before_ actually extracting + // them. In case of error we will just reset the indices. + for (int c = offset; c < total; ++c) + set_constraint_as_extracted(c, true); + + try { + int nbTerms = 0; + for (int c = 0; c < newCons; ++c) { + MPConstraint const *const ct = solver_->constraints_[offset + c]; + nbTerms += ct->coefficients_.size(); + } + unique_ptr rmatbeg(new int[newCons]); + unique_ptr rmatrownbterms(new int[newCons]); + unique_ptr rmatind(new int[nbTerms]); + unique_ptr rmatval(new double[nbTerms]); + + unique_ptr sense(new char[newCons]); + unique_ptr rhs(new double[newCons]); + unique_ptr name(new char const *[newCons]); + unique_ptr rngval(new double[newCons]); + unique_ptr rngind(new int[newCons]); + bool haveRanges = false; + + // Loop over the new constraints, collecting rows for up to + // CHUNK constraints into the arrays so that adding constraints + // is faster. + int nextNz = 0; + for (int c = 0; c < newCons; ++c) { + // Collect up to CHUNK constraints into the arrays. + MPConstraint const *const ct = solver_->constraints_[offset + c]; + + // Setup right-hand side of constraint. + MakeRhs(ct->lb(), ct->ub(), rhs[c], sense[c], + rngval[c]); + haveRanges = haveRanges || (rngval[c] != 0.0); + rngind[c] = offset + c; + + // Setup left-hand side of constraint. + rmatbeg[c] = nextNz; + //const auto& coeffs = ct->coefficients_; + const auto& coeffs = fixedOrderCoefficientsPerConstraint[ct->index()]; + for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { + int const idxVar = it->first; + if (variable_is_extracted(idxVar)) { + //DCHECK_LT(nextNz, cols); + //DCHECK_LT(idxVar, cols); + rmatind[nextNz] = idxVar; + rmatval[nextNz] = it->second; + ++nextNz; + } + } + rmatrownbterms[c] = nextNz - rmatbeg[c]; + //std::cout + // << c << " " + // << rmatbeg[c] << " " + // << rmatrownbterms[c] << " " + // << rmatind[c] << " " + // << rmatval[c] << " " + // << std::endl; + + // Finally the name of the constraint. + name[c] = ct->name().empty() ? NULL : ct->name().c_str(); + } + + CHECK_STATUS( + //SRSaddrows(mLp, nextRow, nextNz, sense.get(), rhs.get(), rngval.get(), rmatbeg.get(), rmatind.get(), rmatval.get()) + SRScreaterows(mLp, newCons, rhs.get(), rngval.get(), sense.get(), name.get()) + ); + SRSsetcoefs(mLp, rmatbeg.get(), rmatrownbterms.get(), rmatind.get(), rmatval.get()); + } + catch (...) { + // Undo all changes in case of error. + int const rows = SRSgetnbrows(mLp); + std::vector rowsToDelete; + for (int i = offset; i < rows; ++i) + rowsToDelete.push_back(i); + //FIXME if (rows > offset) (void)SRSdelrows(mLp, rowsToDelete.size(), rowsToDelete.data()); + std::vector const &constraints = solver_->constraints(); + int const size = constraints.size(); + for (int i = offset; i < size; ++i) set_constraint_as_extracted(i, false); + throw; + } + } + } + + // Extract the objective function. + void SiriusInterface::ExtractObjective() { + // NOTE: The code assumes that the objective expression does not contain + // any non-zero duplicates. + + int const cols = SRSgetnbcols(mLp); + //DCHECK_EQ(last_variable_index_, cols); + + unique_ptr ind(new int[cols]); + unique_ptr val(new double[cols]); + for (int j = 0; j < cols; ++j) { + ind[j] = j; + val[j] = 0.0; + } + + const auto& coeffs = solver_->objective_->coefficients_; + for (auto it = coeffs.begin(); it != coeffs.end(); ++it) { + int const idx = it->first->index(); + if (variable_is_extracted(idx)) { + //DCHECK_LT(idx, cols); + val[idx] = it->second; + } + } + + CHECK_STATUS(SRSchgobj(mLp, cols, ind.get(), val.get())); + //FIXME CHECK_STATUS(SRSsetobjoffset(mLp, solver_->Objective().offset())); + } + + // ------ Parameters ----- + + // WIP : Use SetSolverSpecificParametersAsString to pass TypeDeBorneDeLaVariable + bool SiriusInterface::SetSolverSpecificParametersAsString(const std::string& parameters) + { + std::stringstream ss(parameters); + std::string paramName; + std::getline(ss, paramName, ' '); + if (paramName == "VAR_BOUNDS_TYPE") { + std::string paramValue; + varBoundsTypeValues.clear(); + while (std::getline(ss, paramValue, ' ')) { + varBoundsTypeValues.push_back(std::stoi(paramValue)); + } + return true; + } + else { + // unknow paramName + return false; + } + } + + void SiriusInterface::SetParameters(const MPSolverParameters ¶m) { + SetCommonParameters(param); + if (mMip) SetMIPParameters(param); + } + + void SiriusInterface::SetRelativeMipGap(double value) { + if (mMip) { + LOG(WARNING) << "SetRelativeMipGap not implemented for sirius_interface"; + //FIXME CHECK_STATUS(SRSsetdblcontrol(mLp, SRS_MIPRELSTOP, value)); + } + else { + LOG(WARNING) << "The relative MIP gap is only available " + << "for discrete problems."; + } + } + + void SiriusInterface::SetPrimalTolerance(double value) { + LOG(WARNING) << "SetPrimalTolerance not implemented for sirius_interface"; + //FIXME CHECK_STATUS(SRSsetdblcontrol(mLp, SRS_FEASTOL, value)); + } + + void SiriusInterface::SetDualTolerance(double value) { + LOG(WARNING) << "SetDualTolerance not implemented for sirius_interface"; + //FIXME CHECK_STATUS(SRSsetdblcontrol(mLp, SRS_OPTIMALITYTOL, value)); + } + + void SiriusInterface::SetPresolveMode(int value) { + MPSolverParameters::PresolveValues const presolve = + static_cast(value); + + switch (presolve) { + case MPSolverParameters::PRESOLVE_OFF: + SRSsetintparams(mLp, SRS_PARAM_PRESOLVE, 0); + return; + case MPSolverParameters::PRESOLVE_ON: + SRSsetintparams(mLp, SRS_PARAM_PRESOLVE, 1); + return; + } + SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, value); + } + + // Sets the scaling mode. + void SiriusInterface::SetScalingMode(int value) { + MPSolverParameters::ScalingValues const scaling = + static_cast(value); + + switch (scaling) { + case MPSolverParameters::SCALING_OFF: + LOG(WARNING) << "SetScalingMode not implemented for sirius_interface"; + //FIXME CHECK_STATUS(SRSsetintcontrol(mLp, SRS_SCALING, 0)); + break; + case MPSolverParameters::SCALING_ON: + LOG(WARNING) << "SetScalingMode not implemented for sirius_interface"; + //FIXME CHECK_STATUS(SRSsetintcontrol(mLp, SRS_SCALING, 1)); + break; + } + } + + // Sets the LP algorithm : primal, dual or barrier. Note that SIRIUS offers other + // LP algorithm (e.g. network) and automatic selection + void SiriusInterface::SetLpAlgorithm(int value) { + MPSolverParameters::LpAlgorithmValues const algorithm = + static_cast(value); + + int alg = 1; + + switch (algorithm) { + case MPSolverParameters::DUAL: + alg = 2; + break; + case MPSolverParameters::PRIMAL: + alg = 3; + break; + case MPSolverParameters::BARRIER: + alg = 4; + break; + } + + if (alg == 1) + SetIntegerParamToUnsupportedValue(MPSolverParameters::LP_ALGORITHM, value); + else { + LOG(WARNING) << "SetLpAlgorithm not implemented for sirius_interface"; + //FIXME CHECK_STATUS(SRSsetintcontrol(mLp, SRS_DEFAULTALG, alg)); + } + } + + bool SiriusInterface::ReadParameterFile(std::string const &filename) { + // Return true on success and false on error. + return true; + // LOG(DFATAL) << "ReadParameterFile not implemented for Sirius interface"; + // return false; + } + + absl::Status SiriusInterface::SetNumThreads(int num_threads) + { + // sirius does not support mt + LOG(WARNING) << "SetNumThreads: sirius does not support multithreading"; + return absl::OkStatus(); + } + + std::string SiriusInterface::ValidFileExtensionForParameterFile() const { + return ".prm"; + } + + MPSolver::ResultStatus SiriusInterface::Solve(MPSolverParameters const ¶m) { + int status; + + // Delete chached information + mCstat = 0; + mRstat = 0; + + WallTimer timer; + timer.Start(); + + // Set incrementality + MPSolverParameters::IncrementalityValues const inc = + static_cast( + param.GetIntegerParam(MPSolverParameters::INCREMENTALITY)); + switch (inc) { + case MPSolverParameters::INCREMENTALITY_OFF: + Reset(); /* This should not be required but re-extracting everything + * may be faster, so we do it. */ + break; + case MPSolverParameters::INCREMENTALITY_ON: + //FIXME SRSsetintcontrol(mLp, SRS_CRASH, 0); + break; + } + + // Extract the model to be solved. + // If we don't support incremental extraction and the low-level modeling + // is out of sync then we have to re-extract everything. Note that this + // will lose MIP starts or advanced basis information from a previous + // solve. + if (!supportIncrementalExtraction && sync_status_ == MUST_RELOAD) Reset(); + ExtractModel(); + VLOG(1) << absl::StrFormat("Model build in %.3f seconds.", timer.Get()); + + // Set log level. + CHECK_STATUS(SRSsetintparams(mLp, SRS_PARAM_VERBOSE_SPX, quiet() ? 0 : 1)); + CHECK_STATUS(SRSsetintparams(mLp, SRS_PARAM_VERBOSE_PNE, quiet() ? 0 : 1)); + + // Set parameters. + // NOTE: We must invoke SetSolverSpecificParametersAsString() _first_. + // Its current implementation invokes ReadParameterFile() which in + // turn invokes SRSreadcopyparam(). The latter will _overwrite_ + // all current parameter settings in the environment. + solver_->SetSolverSpecificParametersAsString(solver_->solver_specific_parameter_string_); + SetParameters(param); + if (solver_->time_limit()) { + VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms."; + CHECK_STATUS(SRSsetdoubleparams(mLp, SRS_PARAM_MAX_TIME, solver_->time_limit() * 1e-3)); + } + + // Solve. + // Do not CHECK_STATUS here since some errors (for example CPXERR_NO_MEMORY) + // still allow us to query useful information. + timer.Restart(); + int problemStatus = 0; + if (maximize_) { + SRSsetintparams(mLp, SRS_PARAM_MAXIMIZE, 1); + } + else { + SRSsetintparams(mLp, SRS_PARAM_MAXIMIZE, 0); + } + + //std::cout + // << "nbVar " << mLp->problem_mps->NbVar << std::endl + // << "nbRow " << mLp->problem_mps->NbCnt << std::endl; + // + //std::cout << "Xmins "; + //for (int i = 0; i < mLp->problem_mps->NbVar; ++i) + // std::cout << mLp->problem_mps->Umin[i] << " "; + //std::cout << std::endl; + // + //std::cout << "Xmaxs "; + //for (int i = 0; i < mLp->problem_mps->NbVar; ++i) + // std::cout << mLp->problem_mps->Umax[i] << " "; + //std::cout << std::endl; + // + //std::cout << "rhs" << std::endl; + //for (int i = 0; i < mLp->problem_mps->NbCnt; ++i) + // std::cout << mLp->problem_mps->SensDeLaContrainte[i] << " " << mLp->problem_mps->Rhs[i] << std::endl; + // + //exit(0); + + // set variables's bound's type if any + if (!varBoundsTypeValues.empty()) { + SRScopyvarboundstype(mLp, varBoundsTypeValues.data()); + } + + // set solution hints if any + if (!solver_->solution_hint_.empty()) { + // store X values + for (std::pair& solution_hint_elt : solver_->solution_hint_) { + SRSsetxvalue(mLp, solution_hint_elt.first->index(), solution_hint_elt.second); + } + + } + if (IsMIP()) + SRSsetintparams(mLp, SRS_FORCE_PNE, 1); + + status = SRSoptimize(mLp); + + if (status) { + VLOG(1) << absl::StrFormat("Failed to optimize MIP. Error %d", status); + // NOTE: We do not return immediately since there may be information + // to grab (for example an incumbent) + } + else { + VLOG(1) << absl::StrFormat("Solved in %.3f seconds.", timer.Get()); + } + + problemStatus = SRSgetproblemstatus(mLp); + VLOG(1) << absl::StrFormat("SIRIUS solution status %d.", problemStatus); + + // Figure out what solution we have. + bool const feasible = !(problemStatus == SRS_STATUS_UNFEASIBLE); + + // Get problem dimensions for solution queries below. + int const rows = SRSgetnbrows(mLp); + int const cols = SRSgetnbcols(mLp); + DCHECK_EQ(rows, solver_->constraints_.size()); + DCHECK_EQ(cols, solver_->variables_.size()); + + // Capture objective function value. + objective_value_ = SRS_NAN; + if (feasible) { + (SRSgetobjval(mLp, &objective_value_)); + objective_value_ += solver_->Objective().offset(); + } + VLOG(1) << "objective = " << objective_value_; + + // Capture primal and dual solutions + if (mMip) { + // If there is a primal feasible solution then capture it. + if (feasible) { + if (cols > 0) { + double * x = new double[cols]; + CHECK_STATUS(SRSgetx(mLp, &x)); + for (int i = 0; i < solver_->variables_.size(); ++i) { + MPVariable *const var = solver_->variables_[i]; + var->set_solution_value(x[i]); + VLOG(3) << var->name() << ": value =" << x[i]; + } + delete[] x; + } + } + else { + for (int i = 0; i < solver_->variables_.size(); ++i) + solver_->variables_[i]->set_solution_value(SRS_NAN); + } + + // MIP does not have duals + for (int i = 0; i < solver_->variables_.size(); ++i) + solver_->variables_[i]->set_reduced_cost(SRS_NAN); + unique_ptr pi(new double[rows]); + if (feasible) { + double * dualValues = pi.get(); + CHECK_STATUS(SRSgetdualvalues(mLp, &dualValues)); + } + for (int i = 0; i < solver_->constraints_.size(); ++i) { + MPConstraint *const ct = solver_->constraints_[i]; + bool dual = false; + if (feasible) { + ct->set_dual_value(pi[i]); + dual = true; + } + else + ct->set_dual_value(SRS_NAN); + VLOG(4) << "row " << ct->index() << ":" + << (dual ? absl::StrFormat(" dual = %f", pi[i]) : ""); + } + } + else { + // Continuous problem. + if (cols > 0) { + double * dj = new double[cols]; + double * x = new double[cols]; + CHECK_STATUS(SRSgetx(mLp, &x)); + + if (feasible) { + CHECK_STATUS(SRSgetreducedcosts(mLp, &dj)); + } + for (int i = 0; i < solver_->variables_.size(); ++i) { + MPVariable *const var = solver_->variables_[i]; + var->set_solution_value(x[i]); + bool value = false, dual = false; + + if (feasible) { + var->set_solution_value(x[i]); + value = true; + } + else + var->set_solution_value(SRS_NAN); + if (feasible) { + var->set_reduced_cost(dj[i]); + dual = true; + } + else + var->set_reduced_cost(SRS_NAN); + VLOG(3) << var->name() << ":" + << (value ? absl::StrFormat(" value = %f", x[i]) : "") + << (dual ? absl::StrFormat(" reduced cost = %f", dj[i]) : ""); + } + delete[] x; + delete[] dj; + } + + if (rows > 0) { + unique_ptr pi(new double[rows]); + if (feasible) { + double * dualValues = pi.get(); + CHECK_STATUS(SRSgetdualvalues(mLp, &dualValues)); + } + for (int i = 0; i < solver_->constraints_.size(); ++i) { + MPConstraint *const ct = solver_->constraints_[i]; + bool dual = false; + if (feasible) { + ct->set_dual_value(pi[i]); + dual = true; + } + else + ct->set_dual_value(SRS_NAN); + VLOG(4) << "row " << ct->index() << ":" + << (dual ? absl::StrFormat(" dual = %f", pi[i]) : ""); + } + } + } + + // Map SIRIUS status to more generic solution status in MPSolver + switch (problemStatus) { + case SRS_STATUS_OPTIMAL: + result_status_ = MPSolver::OPTIMAL; + break; + case SRS_STATUS_UNFEASIBLE: + result_status_ = MPSolver::INFEASIBLE; + break; + case SRS_STATUS_UNBOUNDED: + result_status_ = MPSolver::UNBOUNDED; + break; + default: + result_status_ = feasible ? MPSolver::FEASIBLE : MPSolver::ABNORMAL; + break; + } + + sync_status_ = SOLUTION_SYNCHRONIZED; + return result_status_; + } + + void SiriusInterface::Write(const std::string& filename) { + if (sync_status_ == MUST_RELOAD) { + Reset(); + } + ExtractModel(); + VLOG(1) << "Writing Sirius MPS \"" << filename << "\"."; + const int status = SRSwritempsprob(mLp->problem_mps, filename.c_str()); + if (status) { + LOG(ERROR) << "Sirius: Failed to write MPS!"; + } + } + + MPSolverInterface *BuildSiriusInterface(bool mip, MPSolver *const solver) { + return new SiriusInterface(solver, mip); + } + +} // namespace operations_research +#endif // #if defined(USE_SIRUS) diff --git a/ortools/linear_solver/sirius_interface_test.cc b/ortools/linear_solver/sirius_interface_test.cc new file mode 100644 index 0000000..081c400 --- /dev/null +++ b/ortools/linear_solver/sirius_interface_test.cc @@ -0,0 +1,883 @@ +//#include "ortools/linear_solver/sirius_interface.cc" +#include "ortools/linear_solver/linear_solver.h" +#include "gtest/gtest.h" +extern "C" { +#include "srs_api.h" +} +#include + +namespace operations_research { + + class SRSGetter { + public: + SRSGetter(MPSolver* solver) : solver_(solver) {} + + bool isMip() { + return prob()->is_mip; + } + + int getNumVariables() { + return SRSgetnbcols(prob()); + } + + std::string getVariableName(int n) { + return prob()->problem_mps->LabelDeLaVariable[n]; + } + + int getNumConstraints() { return SRSgetnbrows(prob()); } + + std::string getConstraintName(int n) { + return prob()->problem_mps->LabelDeLaContrainte[n]; + } + + double getLb(int n) { + EXPECT_LT(n, getNumVariables()); + return prob()->problem_mps->Umin[n]; + } + + double getUb(int n) { + EXPECT_LT(n, getNumVariables()); + return prob()->problem_mps->Umax[n]; + } + + int getVariableType(int n) { + EXPECT_LT(n, getNumVariables()); + return prob()->problem_mps->TypeDeVariable[n]; + } + + char getConstraintType(int n) { + EXPECT_LT(n, getNumConstraints()); + return prob()->problem_mps->SensDeLaContrainte[n]; + } + + double getConstraintRhs(int n) { + EXPECT_LT(n, getNumConstraints()); + return prob()->problem_mps->B[n]; + } + + double getConstraintCoef(int row, int col) { + EXPECT_LT(col, getNumVariables()); + EXPECT_LT(row, getNumConstraints()); + PROBLEME_MPS* problem_mps = prob()->problem_mps; + int rowBeg = problem_mps->Mdeb[row]; + for (int i = 0; i < problem_mps->NbTerm[row]; ++i) { + if (problem_mps->Nuvar[rowBeg + i] == col) { + return problem_mps->A[rowBeg + i]; + } + } + return 0.; + } + + double getObjectiveCoef(int n) { + EXPECT_LT(n, getNumVariables()); + return prob()->problem_mps->L[n]; + } + + bool getObjectiveSense() { + return prob()->maximize; + } + + int getPresolve() { + return prob()->presolve; + } + + int getScaling() { + return prob()->scaling; + } + + double getRelativeMipGap() { + return prob()->relativeGap; + } + + int getVarBoundType(int n) { + EXPECT_LT(n, getNumVariables()); + return prob()->problem_mps->TypeDeBorneDeLaVariable[n]; + } + + private: + MPSolver* solver_; + + SRS_PROBLEM* prob() { + return (SRS_PROBLEM*)solver_->underlying_solver(); + } + }; + +#define UNITTEST_INIT_MIP() \ + MPSolver solver("SIRIUS_MIP", MPSolver::SIRIUS_MIXED_INTEGER_PROGRAMMING);\ + SRSGetter getter(&solver) +#define UNITTEST_INIT_LP() \ + MPSolver solver("SIRIUS_LP", MPSolver::SIRIUS_LINEAR_PROGRAMMING);\ + SRSGetter getter(&solver) + + void _unittest_verify_var(SRSGetter* getter, MPVariable* x, int type, double lb, double ub) { + EXPECT_EQ(getter->getVariableType(x->index()), type); + EXPECT_EQ(getter->getLb(x->index()), lb); + EXPECT_EQ(getter->getUb(x->index()), ub); + } + + void _unittest_verify_constraint(SRSGetter* getter, MPConstraint* c, char type, double lb, double ub) { + int idx = c->index(); + EXPECT_EQ(getter->getConstraintType(idx), type); + switch (type) { + case SRS_LESSER_THAN: + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + break; + case SRS_GREATER_THAN: + EXPECT_EQ(getter->getConstraintRhs(idx), lb); + break; + case SRS_EQUAL: + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + EXPECT_EQ(getter->getConstraintRhs(idx), lb); + break; + } + } + + TEST(TestSiriusInterface, isMIP) { + UNITTEST_INIT_MIP(); + EXPECT_EQ(solver.IsMIP(), true); + } + + TEST(TestSiriusInterface, isLP) { + UNITTEST_INIT_LP(); + EXPECT_EQ(solver.IsMIP(), false); + } + + TEST(TestSiriusInterface, NumVariables) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPVariable* x1 = solver.MakeNumVar(-1., 5.1, "x1"); + MPVariable* x2 = solver.MakeNumVar(3.14, 5.1, "x2"); + std::vector xs; + solver.MakeBoolVarArray(500, "xs", &xs); + solver.Solve(); + EXPECT_EQ(getter.getNumVariables(), 502); + } + + TEST(TestSiriusInterface, VariablesName) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + std::string pi("Pi"); + std::string secondVar("Name"); + MPVariable* x1 = solver.MakeNumVar(-1., 5.1, pi); + MPVariable* x2 = solver.MakeNumVar(3.14, 5.1, secondVar); + solver.Solve(); + EXPECT_EQ(getter.getVariableName(0), pi); + EXPECT_EQ(getter.getVariableName(1), secondVar); + } + + TEST(TestSiriusInterface, NumConstraints) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(100.0, 100.0); + solver.MakeRowConstraint(-solver.infinity(), 13.1); + solver.MakeRowConstraint(12.1, solver.infinity()); + solver.Solve(); + EXPECT_EQ(getter.getNumConstraints(), 3); + } + + TEST(TestSiriusInterface, ConstraintsName) { + UNITTEST_INIT_MIP(); + + std::string phi("Phi"); + std::string otherCnt("constraintName"); + solver.MakeRowConstraint(100.0, 100.0, phi); + solver.MakeRowConstraint(-solver.infinity(), 13.1, otherCnt); + solver.Solve(); + EXPECT_EQ(getter.getConstraintName(0), phi); + EXPECT_EQ(getter.getConstraintName(1), otherCnt); + } + + TEST(TestSiriusInterface, Reset) { + UNITTEST_INIT_MIP(); + solver.MakeBoolVar("x1"); + solver.MakeBoolVar("x2"); + solver.MakeRowConstraint(-solver.infinity(), 100.0); + solver.Solve(); + EXPECT_EQ(getter.getNumConstraints(), 1); + EXPECT_EQ(getter.getNumVariables(), 2); + solver.Reset(); + EXPECT_EQ(getter.getNumConstraints(), 0); + EXPECT_EQ(getter.getNumVariables(), 0); + } + + TEST(TestSiriusInterface, MakeIntVar) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + int lb = 0, ub = 10; + MPVariable* x = solver.MakeIntVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, SRS_INTEGER_VAR, lb, ub); + } + + TEST(TestSiriusInterface, MakeNumVar) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + double lb = 1.5, ub = 158.2; + MPVariable* x = solver.MakeNumVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, SRS_CONTINUOUS_VAR, lb, ub); + } + + TEST(TestSiriusInterface, MakeBoolVar) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPVariable* x = solver.MakeBoolVar("x"); + solver.Solve(); + _unittest_verify_var(&getter, x, SRS_INTEGER_VAR, 0, 1); + } + + TEST(TestSiriusInterface, MakeIntVarArray) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + int n1 = 25, lb1 = -7, ub1 = 18; + solver.MakeRowConstraint(-solver.infinity(), 0); + std::vector xs1; + solver.MakeIntVarArray(n1, lb1, ub1, "xs1", &xs1); + int n2 = 37, lb2 = 19, ub2 = 189; + std::vector xs2; + solver.MakeIntVarArray(n2, lb2, ub2, "xs2", &xs2); + solver.Solve(); + for (int i = 0; i < n1; ++i) { + _unittest_verify_var(&getter, xs1[i], SRS_INTEGER_VAR, lb1, ub1); + } + for (int i = 0; i < n2; ++i) { + _unittest_verify_var(&getter, xs2[i], SRS_INTEGER_VAR, lb2, ub2); + } + } + + TEST(TestSiriusInterface, MakeNumVarArray) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + int n1 = 1; + double lb1 = 5.1, ub1 = 8.1; + std::vector xs1; + solver.MakeNumVarArray(n1, lb1, ub1, "xs1", &xs1); + int n2 = 13; + double lb2 = -11.5, ub2 = 189.9; + std::vector xs2; + solver.MakeNumVarArray(n2, lb2, ub2, "xs2", &xs2); + solver.Solve(); + for (int i = 0; i < n1; ++i) { + _unittest_verify_var(&getter, xs1[i], SRS_CONTINUOUS_VAR, lb1, ub1); + } + for (int i = 0; i < n2; ++i) { + _unittest_verify_var(&getter, xs2[i], SRS_CONTINUOUS_VAR, lb2, ub2); + } + } + + TEST(TestSiriusInterface, MakeBoolVarArray) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + double n = 43; + std::vector xs; + solver.MakeBoolVarArray(n, "xs", &xs); + solver.Solve(); + for (int i = 0; i < n; ++i) { + _unittest_verify_var(&getter, xs[i], SRS_INTEGER_VAR, 0, 1); + } + } + + TEST(TestSiriusInterface, SetVariableBounds) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + int lb1 = 3, ub1 = 4; + MPVariable* x1 = solver.MakeIntVar(lb1, ub1, "x1"); + double lb2 = 3.7, ub2 = 4; + MPVariable* x2 = solver.MakeNumVar(lb2, ub2, "x2"); + solver.Solve(); + _unittest_verify_var(&getter, x1, SRS_INTEGER_VAR, lb1, ub1); + _unittest_verify_var(&getter, x2, SRS_CONTINUOUS_VAR, lb2, ub2); + lb1 = 12, ub1 = 15; + x1->SetBounds(lb1, ub1); + lb2 = -1.1, ub2 = 0; + x2->SetBounds(lb2, ub2); + solver.Solve(); + _unittest_verify_var(&getter, x1, SRS_INTEGER_VAR, lb1, ub1); + _unittest_verify_var(&getter, x2, SRS_CONTINUOUS_VAR, lb2, ub2); + } + + TEST(TestSiriusInterface, DISABLED_SetVariableInteger) { + // Here we test a badly definied behaviour + // depending on the sirius version the sirius-workflow breaks at: + // either the call of x->SetInteger(false) like the test suggest + // or at solver.Solve() because integer variables are not supported + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + int lb = -1, ub = 7; + MPVariable* x = solver.MakeIntVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, SRS_INTEGER_VAR, lb, ub); + EXPECT_THROW(x->SetInteger(false), std::logic_error); + } + + TEST(TestSiriusInterface, ConstraintL) { + UNITTEST_INIT_MIP(); + double lb = -solver.infinity(), ub = 10.; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_LESSER_THAN, lb, ub); + } + + TEST(TestSiriusInterface, ConstraintR) { + UNITTEST_INIT_MIP(); + double lb = -2, ub = -1; + solver.MakeRowConstraint(lb, ub); + EXPECT_THROW(solver.Solve(), std::logic_error); + } + + TEST(TestSiriusInterface, ConstraintG) { + UNITTEST_INIT_MIP(); + double lb = 8.1, ub = solver.infinity(); + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_GREATER_THAN, lb, ub); + } + + TEST(TestSiriusInterface, ConstraintE) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_EQUAL, lb, ub); + } + + TEST(TestSiriusInterface, SetConstraintBoundsL) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_EQUAL, lb, ub); + lb = -solver.infinity(), ub = 16.6; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_LESSER_THAN, lb, ub); + } + + TEST(TestSiriusInterface, SetConstraintBoundsG) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_EQUAL, lb, ub); + lb = 5, ub = solver.infinity(); + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_GREATER_THAN, lb, ub); + } + + TEST(TestSiriusInterface, SetConstraintBoundsE) { + UNITTEST_INIT_MIP(); + double lb = -1, ub = solver.infinity(); + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_GREATER_THAN, lb, ub); + lb = 128, ub = lb; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, SRS_EQUAL, lb, ub); + } + + TEST(TestSiriusInterface, DISABLED_ConstraintCoef) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeBoolVar("x1"); + MPVariable* x2 = solver.MakeBoolVar("x2"); + MPConstraint* c1 = solver.MakeRowConstraint(4.1, solver.infinity()); + MPConstraint* c2 = solver.MakeRowConstraint(-solver.infinity(), 0.1); + double c11 = -15.6, c12 = 0.4, c21 = -11, c22 = 4.5; + c1->SetCoefficient(x1, c11); + c1->SetCoefficient(x2, c12); + c2->SetCoefficient(x1, c21); + c2->SetCoefficient(x2, c22); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), c11); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), c12); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), c21); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), c22); + + // Next part causes sirius to crash ("free(): invalid next size (fast)") + c11 = 0.11, c12 = 0.12, c21 = 0.21, c22 = 0.22; + c1->SetCoefficient(x1, c11); + c1->SetCoefficient(x2, c12); + c2->SetCoefficient(x1, c21); + c2->SetCoefficient(x2, c22); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), c11); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), c12); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), c21); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), c22); + } + + TEST(TestSiriusInterface, DISABLED_ClearConstraint) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeBoolVar("x1"); + MPVariable* x2 = solver.MakeBoolVar("x2"); + MPConstraint* c1 = solver.MakeRowConstraint(4.1, solver.infinity()); + MPConstraint* c2 = solver.MakeRowConstraint(-solver.infinity(), 0.1); + double c11 = -1533.6, c12 = 3.4, c21 = -11000, c22 = 0.0001; + c1->SetCoefficient(x1, c11); + c1->SetCoefficient(x2, c12); + c2->SetCoefficient(x1, c21); + c2->SetCoefficient(x2, c22); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), c11); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), c12); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), c21); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), c22); + c1->Clear(); + c2->Clear(); + + // next part causes sirius to crash ("free(): invalid next size (fast)") + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), 0); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), 0); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), 0); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), 0); + } + + TEST(TestSiriusInterface, ObjectiveCoef) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double coef = 3112.4; + obj->SetCoefficient(x, coef); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), coef); + coef = 0.2; + obj->SetCoefficient(x, coef); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), coef); + } + + TEST(TestSiriusInterface, DISABLED_ObjectiveOffset) { + // ObjectiveOffset not implemented for sirius_interface + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double offset = 4.3; + obj->SetOffset(offset); + solver.Solve(); + // EXPECT_EQ(getter.getObjectiveOffset(), offset); + offset = 3.6; + obj->SetOffset(offset); + solver.Solve(); + // EXPECT_EQ(getter.getObjectiveOffset(), offset); + } + + TEST(TestSiriusInterface, ObjectiveOffset) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double offset = 4.3; + EXPECT_THROW(obj->SetOffset(offset), std::logic_error); + } + + TEST(TestSiriusInterface, ClearObjective) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double coef = -15.6; + obj->SetCoefficient(x, coef); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), coef); + obj->Clear(); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), 0); + } + + TEST(TestSiriusInterface, ObjectiveSense) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPObjective* const objective = solver.MutableObjective(); + objective->SetMinimization(); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveSense(), false); + objective->SetMaximization(); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveSense(), true); + } + + TEST(TestSiriusInterface, interations) { + UNITTEST_INIT_LP(); + int nc = 100, nv = 100; + std::vector cs(nc); + for (int ci = 0; ci < nc; ++ci) { + cs[ci] = solver.MakeRowConstraint(-solver.infinity(), ci + 1); + } + MPObjective* const objective = solver.MutableObjective(); + for (int vi = 0; vi < nv; ++vi) { + MPVariable* v = solver.MakeNumVar(0, nv, "x" + std::to_string(vi)); + for (int ci = 0; ci < nc; ++ci) { + cs[ci]->SetCoefficient(v, vi + ci); + } + objective->SetCoefficient(v, 1); + } + solver.Solve(); + EXPECT_GT(solver.iterations(), 0); + } + + TEST(TestSiriusInterface, DISABLED_nodes) { + // The problem seems to be incorrectly returned as infeasible + UNITTEST_INIT_MIP(); + int nc = 100, nv = 100; + std::vector cs(2*nc); + for (int ci = 0; ci < nc; ++ci) { + cs[2*ci ] = solver.MakeRowConstraint(-solver.infinity(), ci + 1); + cs[2*ci + 1] = solver.MakeRowConstraint(ci, solver.infinity()); + } + MPObjective* const objective = solver.MutableObjective(); + for (int vi = 0; vi < nv; ++vi) { + MPVariable* v = solver.MakeIntVar(0, nv, "x" + std::to_string(vi)); + for (int ci = 0; ci < nc; ++ci) { + cs[2*ci ]->SetCoefficient(v, vi + ci); + cs[2*ci + 1]->SetCoefficient(v, vi + ci); + } + objective->SetCoefficient(v, 1); + } + VLOG(0) << solver.Solve(); + EXPECT_GT(solver.nodes(), 0); + } + + TEST(TestSiriusInterface, SolverVersion) { + UNITTEST_INIT_MIP(); + EXPECT_GE(solver.SolverVersion().size(), 36); + } + + TEST(TestSiriusInterface, DISABLED_Write) { + // SRSwritempsprob has different implementations on the metrix branch + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeIntVar(-1.2, 9.3, "x1"); + MPVariable* x2 = solver.MakeNumVar(-1, 5, "x2"); + MPConstraint* c1 = solver.MakeRowConstraint(-solver.infinity(), 1); + c1->SetCoefficient(x1, 3); + c1->SetCoefficient(x2, 1.5); + MPConstraint* c2 = solver.MakeRowConstraint(3, solver.infinity()); + c2->SetCoefficient(x2, -1.1); + MPObjective* obj = solver.MutableObjective(); + obj->SetMaximization(); + obj->SetCoefficient(x1, 1); + obj->SetCoefficient(x2, 2); + + std::string tmpName = std::string(std::tmpnam(nullptr)) + ".mps"; + solver.Write(tmpName); + + std::ifstream tmpFile(tmpName); + std::stringstream tmpBuffer; + tmpBuffer << tmpFile.rdbuf(); + tmpFile.close(); + std::remove(tmpName.c_str()); + + EXPECT_EQ(tmpBuffer.str(), R"(* Number of variables: 2 +* Number of constraints: 2 +NAME Pb Solve +ROWS + N OBJECTIF + L R0000000 + G R0000001 +COLUMNS + C0000000 OBJECTIF 1.0000000000 + C0000000 R0000000 3.0000000000 + C0000001 OBJECTIF 2.0000000000 + C0000001 R0000000 1.5000000000 + C0000001 R0000001 -1.1000000000 +RHS + RHSVAL R0000000 1.000000000 + RHSVAL R0000001 3.000000000 +BOUNDS + LI BNDVALUE C0000000 -1 + UI BNDVALUE C0000000 9 + LO BNDVALUE C0000001 -1.000000000 + UP BNDVALUE C0000001 5.000000000 +ENDATA +)"); + } + + TEST(TestSiriusInterface, DISABLED_SetPrimalTolerance) { + // SetPrimalTolerance not implemented for sirius_interface + UNITTEST_INIT_LP(); + MPConstraint* c = solver.MakeRowConstraint(-solver.infinity(), 0.5); + MPVariable* x = solver.MakeNumVar(0, 1, "x"); + MPObjective* obj = solver.MutableObjective(); + c->SetCoefficient(x, 1); + obj->SetCoefficient(x, 1); + + MPSolverParameters params; + double tol = 1e-4; + params.SetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE, tol); + solver.Solve(params); + // EXPECT_EQ(getter.getPrimalTolerance(), tol); + } + + + TEST(TestSiriusInterface, SetPrimalTolerance) { + UNITTEST_INIT_LP(); + MPConstraint* c = solver.MakeRowConstraint(-solver.infinity(), 0.5); + MPVariable* x = solver.MakeNumVar(0, 1, "x"); + MPObjective* obj = solver.MutableObjective(); + c->SetCoefficient(x, 1); + obj->SetCoefficient(x, 1); + + MPSolverParameters params; + double tol = 1e-4; + params.SetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE, tol); + solver.Solve(params); + } + + TEST(TestSiriusInterface, DISABLED_SetDualTolerance) { + // SetDualTolerance not implemented for sirius_interface + UNITTEST_INIT_LP(); + MPConstraint* c = solver.MakeRowConstraint(-solver.infinity(), 0.5); + MPVariable* x = solver.MakeNumVar(0, 1, "x"); + MPObjective* obj = solver.MutableObjective(); + c->SetCoefficient(x, 1); + obj->SetCoefficient(x, 1); + + MPSolverParameters params; + double tol = 1e-2; + params.SetDoubleParam(MPSolverParameters::DUAL_TOLERANCE, tol); + solver.Solve(params); + // EXPECT_EQ(getter.getDualTolerance(), tol) << "Not available"; + } + + TEST(TestSiriusInterface, SetDualTolerance) { + UNITTEST_INIT_LP(); + MPConstraint* c = solver.MakeRowConstraint(-solver.infinity(), 0.5); + MPVariable* x = solver.MakeNumVar(0, 1, "x"); + MPObjective* obj = solver.MutableObjective(); + c->SetCoefficient(x, 1); + obj->SetCoefficient(x, 1); + + MPSolverParameters params; + double tol = 1e-2; + params.SetDoubleParam(MPSolverParameters::DUAL_TOLERANCE, tol); + solver.Solve(params); + } + + TEST(TestSiriusInterface, SetPresolveMode) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::PRESOLVE, MPSolverParameters::PRESOLVE_OFF); + solver.Solve(params); + EXPECT_EQ(getter.getPresolve(), 0); + params.SetIntegerParam(MPSolverParameters::PRESOLVE, MPSolverParameters::PRESOLVE_ON); + solver.Solve(params); + EXPECT_EQ(getter.getPresolve(), 1); + } + + TEST(TestSiriusInterface, DISABLED_SetLpAlgorithm) { + // SetLpAlgorithm not implemented for sirius_interface + UNITTEST_INIT_LP(); + MPConstraint* c = solver.MakeRowConstraint(-solver.infinity(), 0.5); + MPVariable* x = solver.MakeNumVar(0, 1, "x"); + MPObjective* obj = solver.MutableObjective(); + c->SetCoefficient(x, 1); + obj->SetCoefficient(x, 1); + + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::DUAL); + solver.Solve(params); + // EXPECT_EQ(getter.getLpAlgorithm(), 2); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::PRIMAL); + solver.Solve(params); + // EXPECT_EQ(getter.getLpAlgorithm(), 3); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::BARRIER); + solver.Solve(params); + // EXPECT_EQ(getter.getLpAlgorithm(), 4); + } + + TEST(TestSiriusInterface, SetLpAlgorithm) { + UNITTEST_INIT_LP(); + MPConstraint* c = solver.MakeRowConstraint(-solver.infinity(), 0.5); + MPVariable* x = solver.MakeNumVar(0, 1, "x"); + MPObjective* obj = solver.MutableObjective(); + c->SetCoefficient(x, 1); + obj->SetCoefficient(x, 1); + + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::DUAL); + solver.Solve(params); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::PRIMAL); + solver.Solve(params); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::BARRIER); + solver.Solve(params); + } + + TEST(TestSiriusInterface, DISABLED_SetScaling) { + // SetScaling not implemented for sirius_interface + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::SCALING, MPSolverParameters::SCALING_OFF); + solver.Solve(params); + EXPECT_EQ(getter.getScaling(), 0); + params.SetIntegerParam(MPSolverParameters::SCALING, MPSolverParameters::SCALING_ON); + solver.Solve(params); + EXPECT_EQ(getter.getScaling(), 1); + } + + TEST(TestSiriusInterface, SetScaling) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::SCALING, MPSolverParameters::SCALING_OFF); + solver.Solve(params); + params.SetIntegerParam(MPSolverParameters::SCALING, MPSolverParameters::SCALING_ON); + solver.Solve(params); + } + + TEST(TestSiriusInterface, DISABLED_SetRelativeMipGap) { + // SetRelativeMipGap not implemented for sirius_interface + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPSolverParameters params; + double relativeMipGap = 1e-3; + params.SetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP, relativeMipGap); + solver.Solve(params); + EXPECT_EQ(getter.getRelativeMipGap(), relativeMipGap); + } + + TEST(TestSiriusInterface, SetRelativeMipGap) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + + MPSolverParameters params; + double relativeMipGap = 1e-3; + params.SetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP, relativeMipGap); + solver.Solve(params); + } + + TEST(TestSiriusInterface, setVarBoundType) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(-solver.infinity(), 0); + double infty = solver.infinity(); + solver.MakeIntVar(2, 2, "VARIABLE_FIXE"); + solver.MakeIntVar(-10, -1, "VARIABLE_BORNEE_DES_DEUX_COTES"); + solver.MakeIntVar(3, infty, "VARIABLE_BORNEE_INFERIEUREMENT"); + solver.MakeIntVar(-infty, -1, "VARIABLE_BORNEE_SUPERIEUREMENT"); + solver.MakeIntVar(-infty, infty, "VARIABLE_NON_BORNEE"); + + std::array varBoundTypes = { + VARIABLE_FIXE, + VARIABLE_BORNEE_DES_DEUX_COTES, + VARIABLE_BORNEE_INFERIEUREMENT, + VARIABLE_BORNEE_SUPERIEUREMENT, + VARIABLE_NON_BORNEE + }; + std::string xpressParamString = "VAR_BOUNDS_TYPE"; + for (int boundType : varBoundTypes) + xpressParamString += " " + std::to_string(boundType); + + solver.SetSolverSpecificParametersAsString(xpressParamString); + solver.Solve(); + + for (int i = 0; i < varBoundTypes.size(); ++i) + EXPECT_EQ(getter.getVarBoundType(i), varBoundTypes[i]); + } + + TEST(TestSiriusInterface, DISABLED_SolveMIP) { + // The problem seems to incorrectly be returned as infeasible + UNITTEST_INIT_MIP(); + + // max x + 2y + // st. -x + y <= 1 + // 2x + 3y <= 12 + // 3x + 2y <= 12 + // x , y >= 0 + // x , y \in Z + + double inf = solver.infinity(); + MPVariable* x = solver.MakeIntVar(0, inf, "x"); + MPVariable* y = solver.MakeIntVar(0, inf, "y"); + MPObjective* obj = solver.MutableObjective(); + obj->SetCoefficient(x, 1); + obj->SetCoefficient(y, 2); + obj->SetMaximization(); + MPConstraint* c1 = solver.MakeRowConstraint(-inf, 1); + c1->SetCoefficient(x, -1); + c1->SetCoefficient(y, 1); + MPConstraint* c2 = solver.MakeRowConstraint(-inf, 12); + c2->SetCoefficient(x, 3); + c2->SetCoefficient(y, 2); + MPConstraint* c3 = solver.MakeRowConstraint(-inf, 12); + c3->SetCoefficient(x, 2); + c3->SetCoefficient(y, 3); + + solver.Solve(); + EXPECT_EQ(obj->Value(), 6); + EXPECT_EQ(obj->BestBound(), 6); + EXPECT_EQ(x->solution_value(), 2); + EXPECT_EQ(y->solution_value(), 2); + } + + TEST(TestSiriusInterface, DISABLED_SolveLP) { + // Sign of dual values seems to be off + // This sign problem occurs with presolve on and presolve off + UNITTEST_INIT_LP(); + + // max x + 2y + // st. -x + y <= 1 + // 2x + 3y <= 12 + // 3x + 2y <= 12 + // x , y \in R+ + + double inf = solver.infinity(); + MPVariable* x = solver.MakeNumVar(0, inf, "x"); + MPVariable* y = solver.MakeNumVar(0, inf, "y"); + MPObjective* obj = solver.MutableObjective(); + obj->SetCoefficient(x, 1); + obj->SetCoefficient(y, 2); + obj->SetMaximization(); + MPConstraint* c1 = solver.MakeRowConstraint(-inf, 1); + c1->SetCoefficient(x, -1); + c1->SetCoefficient(y, 1); + MPConstraint* c2 = solver.MakeRowConstraint(-inf, 12); + c2->SetCoefficient(x, 3); + c2->SetCoefficient(y, 2); + MPConstraint* c3 = solver.MakeRowConstraint(-inf, 12); + c3->SetCoefficient(x, 2); + c3->SetCoefficient(y, 3); + + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::PRESOLVE, MPSolverParameters::PRESOLVE_OFF); + solver.Solve(params); + + EXPECT_NEAR(obj->Value(), 7.4, 1e-8); + EXPECT_NEAR(x->solution_value(), 1.8, 1e-8); + EXPECT_NEAR(y->solution_value(), 2.8, 1e-8); + EXPECT_NEAR(x->reduced_cost(), 0, 1e-8); + EXPECT_NEAR(y->reduced_cost(), 0, 1e-8); + EXPECT_NEAR(c1->dual_value(), 0.2, 1e-8); + EXPECT_NEAR(c2->dual_value(), 0, 1e-8); + EXPECT_NEAR(c3->dual_value(), 0.6, 1e-8); + } + +} // namespace operations_research + +int main(int argc, char** argv) { + absl::SetFlag(&FLAGS_logtostderr, 1); + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/ortools/xpress/parse_header_xpress.py b/ortools/xpress/parse_header_xpress.py new file mode 100644 index 0000000..0745f43 --- /dev/null +++ b/ortools/xpress/parse_header_xpress.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 +"""Xpress header parser script to generate code for the environment.{cc|h}. + +To use, run the script + ./parse_header_xpress.py + +This will printout on the console 9 sections: + +------------------- header ------------------- + +to copy paste in environment.h + +------------------- define ------------------- + +to copy in the define part of environment.cc + +------------------- assign ------------------- + +to copy in the assign part of environment.cc + +------------------- string parameters ------------------- + +to copy in the "getMapStringControls" function of linear_solver/xpress_interface.cc + +------------------- string parameters tests ------------------- + +to copy in the "setStringControls" TEST of linear_solver/unittests/xpress_interface.cc + +------------------- double parameters ------------------- + +to copy in the "getMapDoubleControls" function of linear_solver/xpress_interface.cc + +------------------- double parameters tests ------------------- + +to copy in the "setDoubleControls" TEST of linear_solver/unittests/xpress_interface.cc + +------------------- int parameters ------------------- + +to copy in the "getMapIntControls" function of linear_solver/xpress_interface.cc + +------------------- int parameters tests ------------------- + +to copy in the "setIntControl" TEST of linear_solver/unittests/xpress_interface.cc + +------------------- int64 parameters ------------------- + +to copy in the "getMapInt64Controls" function of linear_solver/xpress_interface.cc + +------------------- int64 parameters tests ------------------- + +to copy in the "setInt64Control" TEST of linear_solver/unittests/xpress_interface.cc +""" + +import argparse +import re +from enum import Enum + + +# from absl import app + +# This enum is used to detect different sections in the xprs.h document +class XprsDocumentSection(Enum): + STRING_PARAMS = 1 + DOUBLE_PARAMS = 2 + INT_PARAMS = 3 + INT64_PARAMS = 4 + OTHER = 5 + + +class XpressHeaderParser(object): + """Converts xprs.h to something pastable in ./environment.h|.cc.""" + + def __init__(self): + self.__header = '' + self.__define = '' + self.__assign = '' + self.__state = 0 + self.__return_type = '' + self.__args = '' + self.__fun_name = '' + self.__string_parameters = '' + self.__string_parameters_unittest = '' + self.__double_parameters = '' + self.__double_parameters_unittest = '' + self.__int_parameters = '' + self.__int_parameters_unittest = '' + self.__int64_parameters = '' + self.__int64_parameters_unittest = '' + # These are the definitions required for compiling the XPRESS interface, excluding control parameters + self.__required_defines = {"XPRS_STOP_USER", "XPRS_TYPE_NOTDEFINED", "XPRS_TYPE_INT", "XPRS_TYPE_INT64", + "XPRS_TYPE_DOUBLE", "XPRS_PLUSINFINITY", "XPRS_MINUSINFINITY", "XPRS_MAXBANNERLENGTH", "XPVERSION", + "XPRS_LPOBJVAL", "XPRS_MIPOBJVAL", "XPRS_BESTBOUND", "XPRS_OBJRHS", "XPRS_OBJSENSE", + "XPRS_ROWS", "XPRS_SIMPLEXITER", "XPRS_LPSTATUS", "XPRS_MIPSTATUS", "XPRS_NODES", + "XPRS_COLS", "XPRS_LP_OPTIMAL", "XPRS_LP_INFEAS", "XPRS_LP_UNBOUNDED", + "XPRS_MIP_SOLUTION", "XPRS_MIP_INFEAS", "XPRS_MIP_OPTIMAL", "XPRS_MIP_UNBOUNDED", + "XPRS_OBJ_MINIMIZE", "XPRS_OBJ_MAXIMIZE", "XPRS_NAMES_ROW", "XPRS_NAMES_COLUMN"} + self.__missing_required_defines = self.__required_defines + # These enum will detect control parameters that will all be imported + self.__doc_section = XprsDocumentSection.OTHER + # These parameters are not supported + self.__excluded_defines = {"XPRS_COMPUTE"} + # These are the functions required for compiling the XPRESS interface + self.__required_functions = {"XPRScreateprob", "XPRSdestroyprob", "XPRSinit", "XPRSfree", "XPRSgetlicerrmsg", + "XPRSlicense", "XPRSgetbanner", "XPRSgetversion", "XPRSsetdefaultcontrol", + "XPRSsetintcontrol", "XPRSsetintcontrol64", "XPRSsetdblcontrol", + "XPRSsetstrcontrol", "XPRSgetintcontrol", "XPRSgetintcontrol64", + "XPRSgetdblcontrol", "XPRSgetstringcontrol", "XPRSgetintattrib", + "XPRSgetdblattrib", "XPRSloadlp", "XPRSloadlp64", "XPRSgetobj", "XPRSgetrhs", + "XPRSgetrhsrange", "XPRSgetlb", "XPRSgetub", "XPRSgetcoef", "XPRSaddrows", + "XPRSdelrows", "XPRSaddcols", "XPRSaddnames", "XPRSgetnames", "XPRSdelcols", "XPRSchgcoltype", "XPRSloadbasis", + "XPRSpostsolve", "XPRSchgobjsense", "XPRSgetlasterror", "XPRSgetbasis", + "XPRSwriteprob", "XPRSgetrowtype", "XPRSgetcoltype", "XPRSgetlpsol", + "XPRSgetmipsol", "XPRSchgbounds", "XPRSchgobj", "XPRSchgcoef", "XPRSchgmcoef", + "XPRSchgrhs", "XPRSchgrhsrange", "XPRSchgrowtype", "XPRSaddcbmessage", "XPRSsetcbmessage", + "XPRSaddmipsol", "XPRSaddcbintsol", "XPRSremovecbintsol", + "XPRSinterrupt", "XPRSlpoptimize", "XPRSmipoptimize"} + self.__missing_required_functions = self.__required_functions + self.__XPRSprob_section = False + + def write_define(self, symbol, value): + if symbol in self.__excluded_defines: + print('skipping ' + symbol) + return + + # If it is a control parameter, import it to expose it to the user + # Else import it only if required + if self.__doc_section in [XprsDocumentSection.STRING_PARAMS, XprsDocumentSection.DOUBLE_PARAMS, + XprsDocumentSection.INT_PARAMS, XprsDocumentSection.INT64_PARAMS]: + self.__header += f'#define {symbol} {value}\n' + ortools_symbol = symbol.replace("XPRS_", "") + if self.__doc_section == XprsDocumentSection.STRING_PARAMS: + self.__string_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__string_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, "default_value"}},\n' + elif self.__doc_section == XprsDocumentSection.DOUBLE_PARAMS: + self.__double_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__double_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1.}},\n' + elif self.__doc_section == XprsDocumentSection.INT_PARAMS: + self.__int_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__int_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1}},\n' + elif self.__doc_section == XprsDocumentSection.INT64_PARAMS: + self.__int64_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__int64_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1}},\n' + elif symbol in self.__required_defines: + self.__header += f'#define {symbol} {value}\n' + self.__missing_required_defines.remove(symbol) + else: + print('skipping ' + symbol) + + def write_fun(self, return_type, name, args): + if name in self.__required_functions: + self.__header += f'extern std::function<{return_type}({args})> {name};\n' + self.__define += f'std::function<{return_type}({args})> {name} = nullptr;\n' + self.__assign += f' xpress_dynamic_library->GetFunction(&{name}, ' + self.__assign += f'"{name}");\n' + self.__missing_required_functions.remove(name) + else: + print('skipping ' + name) + + def parse(self, filepath): + """Main method to parser the Xpress header.""" + + with open(filepath) as fp: + all_lines = fp.read() + + self.__XPRSprob_section = False + + for line in all_lines.splitlines(): + if not line: # Ignore empty lines. + continue + + self.detect_XPRSprob_section(line) + + if re.match(r'/\*', line, re.M): # Comments in xprs.h indicate the section + if self.__XPRSprob_section: + if "string control parameters" in line.lower(): + self.__doc_section = XprsDocumentSection.STRING_PARAMS + elif "double control parameters" in line.lower(): + self.__doc_section = XprsDocumentSection.DOUBLE_PARAMS + elif "integer control parameters" in line.lower() and "64-bit" in line.lower(): + self.__doc_section = XprsDocumentSection.INT64_PARAMS + elif "integer control parameters" in line.lower(): + self.__doc_section = XprsDocumentSection.INT_PARAMS + else: + self.__doc_section = XprsDocumentSection.OTHER + else: + self.__doc_section = XprsDocumentSection.OTHER + + if self.__state == 0: + match_def = re.match(r'#define ([A-Z0-9_]*)\s+([^/]+)', line, + re.M) + if match_def: + self.write_define(match_def.group(1), match_def.group(2)) + continue + + # Single line function definition. + match_fun = re.match( + r'([a-z]+) XPRS_CC (XPRS[A-Za-z0-9_]*)\(([^;]*)\);', line, + re.M) + if match_fun: + self.write_fun(match_fun.group(1), match_fun.group(2), + match_fun.group(3)) + continue + + # Simple type declaration (i.e. int XPRS_CC). + match_fun = re.match(r'([a-z]+) XPRS_CC\s*$', line, re.M) + if match_fun: + self.__return_type = match_fun.group(1) + self.__state = 1 + continue + + # Complex type declaration with pointer. + match_fun = re.match(r'([A-Za-z0-9 ]+)\*\s*XPRS_CC\s*$', line, + re.M) + if match_fun: + self.__return_type = match_fun.group(1) + '*' + self.__state = 1 + continue + + elif self.__state == 1: # The return type was defined at the line before. + # Function definition terminates in this line. + match_fun = re.match(r'\s*(XPRS[A-Za-z0-9_]*)\(([^;]+)\);', line, + re.M) + if match_fun: + self.write_fun(match_fun.group(1), self.__return_type, + match_fun.group(2)) + self.__state = 0 + self.__return_type = '' + continue + + # Function definition does not terminate in this line. + match_fun = re.match(r'\s*(XPRS[A-Za-z0-9_]*)\(([^;]+)$', line, + re.M) + if match_fun: + self.__fun_name = match_fun.group(1) + self.__args = match_fun.group(2) + self.__state = 2 + continue + + elif self.__state == 2: # Extra arguments. + # Arguments end in this line. + match_fun = re.match(r'\s*([^;]+)\);', line, re.M) + if match_fun: + self.__args += match_fun.group(1) + self.write_fun(self.__fun_name, self.__return_type, + self.__args) + self.__args = '' + self.__fun_name = '' + self.__return_type = '' + self.__state = 0 + continue + + # Arguments do not end in this line. + match_fun = re.match(r'\s*([^;]+)$', line, re.M) + if match_fun: + self.__args += match_fun.group(1) + continue + + def detect_XPRSprob_section(self, line): + """This method detects the section between these commented lines: + /***************************************************************************\ + * control parameters for XPRSprob * + ... + /***************************************************************************\ + """ + if " * control parameters for XPRSprob" in line: + self.__XPRSprob_section = True + elif self.__XPRSprob_section and \ + "/***************************************************************************\\" in line: + self.__XPRSprob_section = False + + def output(self): + """Output the 3 generated code on standard out.""" + print('------------------- header (to copy in environment.h) -------------------') + print(self.__header) + + print('------------------- define (to copy in the define part of environment.cc) -------------------') + print(self.__define) + + print('------------------- assign (to copy in the assign part of environment.cc) -------------------') + print(self.__assign) + + print('------------------- string params (to copy in the "getMapStringControls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__string_parameters) + + print('------------------- string params test (to copy in the "setStringControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__string_parameters_unittest) + + print('------------------- double params (to copy in the "getMapDoubleControls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__double_parameters) + + print('------------------- double params test (to copy in the "setDoubleControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__double_parameters_unittest) + + print('------------------- int params (to copy in the "getMapIntControls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__int_parameters) + + print('------------------- int params test (to copy in the "setIntControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__int_parameters_unittest) + + print('------------------- int64 params (to copy in the "getMapInt64Controls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__int64_parameters) + + print('------------------- int64 params test (to copy in the "setInt64Controls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__int64_parameters_unittest) + + def print_missing_elements(self): + if self.__missing_required_defines: + print('------WARNING------ missing required defines -------------------') + print(self.__missing_required_defines) + + if self.__missing_required_functions: + print('------WARNING------ missing required functions -------------------') + print(self.__missing_required_functions) + + if self.__missing_required_defines or self.__missing_required_functions: + raise LookupError("Some required defines or functions are missing (see detail above)") + + +def main(path: str) -> None: + parser = XpressHeaderParser() + parser.parse(path) + parser.output() + parser.print_missing_elements() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Xpress header parser.') + parser.add_argument('filepath', type=str) + args = parser.parse_args() + main(args.filepath) diff --git a/patch.py b/patch.py new file mode 100644 index 0000000..e6bff18 --- /dev/null +++ b/patch.py @@ -0,0 +1,184 @@ +from pathlib import Path +from typing import List +from patch_utils import * + +full_patch: List[Addition] = [] + +# add the USE_SIRIUS configuration flag in CMakeLists.txt +full_patch.append(Addition( + Path.cwd()/'CMakeLists.txt', + '''option(USE_CPLEX "Use the CPLEX solver" OFF) +message(STATUS "CPLEX support: ${USE_CPLEX}") + +''', + '''option(USE_SIRIUS "Build and use SIRIUS interface" OFF) +message(STATUS "SIRIUS support: ${USE_SIRIUS}") + +''')) + +# add the USE_SIRIUS configuration flag in cpp.cmake + +full_patch.append( + Addition( + Path.cwd()/'cmake'/'cpp.cmake', + ' $<$:libscip>\n', + ' $<$:sirius_solver>\n')) +full_patch.append(Addition( + Path.cwd()/'cmake'/'cpp.cmake', + ''' +if(USE_CPLEX) + list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "USE_CPLEX") +endif() +''', + ''' +if(USE_SIRIUS) + list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "USE_SIRIUS") +endif() +''')) + +# add the USE_SIRIUS configuration flag in deps.cmake +full_patch.append(Addition( + Path.cwd()/'cmake'/'deps.cmake', + ''' +if(USE_CPLEX) + find_package(CPLEX REQUIRED) +endif() +''', + ''' +#add SIRIUS +if (USE_SIRIUS) + if(POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) + endif() + find_package(sirius_solver CONFIG REQUIRED) +endif(USE_SIRIUS) +''')) + +# add the USE_SIRIUS configuration flag in ortoolsConfig.cmake.in +full_patch.append(Addition( + Path.cwd()/'cmake'/'ortoolsConfig.cmake.in', + ''' +if(@USE_SCIP@) + if(NOT scip_FOUND AND NOT TARGET libscip) + find_dependency(SCIP REQUIRED) + endif() +endif() +''', + ''' +if(@USE_SIRIUS@) + if(POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) + endif() + if(NOT sirius_solver_FOUND AND NOT TARGET sirius_solver) + find_dependency(sirius_solver REQUIRED ${CONFIG_FLAG}) + endif() +endif() +''')) + +# add SIRIUS execution in example files +full_patch.append(Addition( + Path.cwd()/'examples'/'cpp'/'linear_programming.cc', + ' RunLinearProgrammingExample("XPRESS_LP");\n', + ' RunLinearProgrammingExample("SIRIUS_LP");\n' + )) + +full_patch.append(Addition( + Path.cwd()/'examples'/'dotnet'/'cslinearprogramming.cs', + ' RunLinearProgrammingExample("XPRESS_LP");\n', + ' RunLinearProgrammingExample("SIRIUS_LP");\n' + )) + +full_patch.append(Addition( + Path.cwd()/'examples'/'java'/'LinearProgramming.java', + ''' runLinearProgrammingExample("CLP", false); +''', + ''' System.out.println("---- Linear programming example with Sirius ----"); + runLinearProgrammingExample("SIRIUS_LP", false); +''')) + +full_patch.append(Addition( + Path.cwd()/'examples'/'python'/'linear_programming.py', + ' RunLinearExampleCppStyleAPI("XPRESS_LP")\n', + ' RunLinearExampleCppStyleAPI("SIRIUS_LP")\n')) + +# add the USE_SIRIUS configuration flag in ortools/linear_solver/CMakeLists.txt +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'CMakeLists.txt', + ' $<$:libscip>\n', + ' $<$:sirius_solver>\n')) + +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'CMakeLists.txt', + ''' add_test(NAME cxx_unittests_xpress_interface COMMAND test_xprs_interface) +''', + ''' if (USE_SIRIUS) + add_executable(test_sirius_interface sirius_interface_test.cc) + target_compile_features(test_sirius_interface PRIVATE cxx_std_17) + target_link_libraries(test_sirius_interface PRIVATE ortools::ortools GTest::gtest_main) + + add_test(NAME cxx_unittests_sirius_interface COMMAND test_sirius_interface) + endif() +''')) + +# add the SIRIUS support in ortools/linear_solver/linear_solver.cc & .h +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'linear_solver.cc', + '''extern MPSolverInterface* BuildXpressInterface(bool mip, + MPSolver* const solver); +''', + '''#if defined(USE_SIRIUS) +extern MPSolverInterface* BuildSiriusInterface(bool mip, MPSolver* const solver); +#endif +''')) + +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'linear_solver.cc', + ''' return BuildXpressInterface(false, solver); +''', + '''#if defined(USE_SIRIUS) + case MPSolver::SIRIUS_LINEAR_PROGRAMMING: + return BuildSiriusInterface(false, solver); + case MPSolver::SIRIUS_MIXED_INTEGER_PROGRAMMING: + return BuildSiriusInterface(true, solver); +#endif +''')) + +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'linear_solver.cc', + '''#ifdef USE_CPLEX + if (problem_type == CPLEX_LINEAR_PROGRAMMING || + problem_type == CPLEX_MIXED_INTEGER_PROGRAMMING) { + return true; + } +#endif +''', + '''#ifdef USE_SIRIUS + if (problem_type == SIRIUS_MIXED_INTEGER_PROGRAMMING) return true; + if (problem_type == SIRIUS_LINEAR_PROGRAMMING) return true; +#endif +''')) + +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'linear_solver.cc', + ''' {MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING, "xpress"}, +''', + ''' {MPSolver::SIRIUS_LINEAR_PROGRAMMING, "sirius_lp"}, + {MPSolver::SIRIUS_MIXED_INTEGER_PROGRAMMING, "sirius"}, +''')) + +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'linear_solver.h', + ''' COPT_MIXED_INTEGER_PROGRAMMING = 104, +''', + ''' SIRIUS_LINEAR_PROGRAMMING = 105, + SIRIUS_MIXED_INTEGER_PROGRAMMING = 106, +''')) + +full_patch.append(Addition( + Path.cwd()/'ortools'/'linear_solver'/'linear_solver.h', + ' friend class XpressInterface;\n', + ' friend class SiriusInterface;\n')) + +# run patch +for a in full_patch: + replace_in_file(a.filepath, a.search, a.search+a.add) diff --git a/patch_utils.py b/patch_utils.py new file mode 100644 index 0000000..d5411b7 --- /dev/null +++ b/patch_utils.py @@ -0,0 +1,17 @@ +from pathlib import Path +from dataclasses import dataclass + +def replace_in_file(filepath, search, replace): + with open(filepath, 'r', encoding="utf8") as file: + data = file.read() + data = data.replace(search, replace) + + with open(filepath, 'w', encoding="utf8") as file: + file.write(data) + + +@dataclass +class Addition: + filepath: Path + search: str + add: str \ No newline at end of file