From 6e25f818a8164983d95a655edce45d98edd9c141 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 27 Oct 2023 10:20:22 +0200 Subject: [PATCH 01/21] remove python script --- ortools/xpress/parse_header_xpress.py | 330 -------------------------- 1 file changed, 330 deletions(-) delete mode 100644 ortools/xpress/parse_header_xpress.py diff --git a/ortools/xpress/parse_header_xpress.py b/ortools/xpress/parse_header_xpress.py deleted file mode 100644 index 67898761d81..00000000000 --- a/ortools/xpress/parse_header_xpress.py +++ /dev/null @@ -1,330 +0,0 @@ -#!/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"} - 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", - "XPRSminim", "XPRSmaxim", "XPRSaddmipsol", "XPRSaddcbintsol", "XPRSremovecbintsol", - "XPRSinterrupt"} - 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) From 35c49754d05f074be384f8a2952af40f45a64cc5 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 27 Oct 2023 10:21:34 +0200 Subject: [PATCH 02/21] remove RTE actions --- .github/workflows/centos.yml | 142 -------------- .github/workflows/oracle.yml | 213 --------------------- .github/workflows/setup-env/action.yml | 64 ------- .github/workflows/ubuntu.yml | 253 ------------------------ .github/workflows/windows.yml | 255 ------------------------- 5 files changed, 927 deletions(-) delete mode 100644 .github/workflows/centos.yml delete mode 100644 .github/workflows/oracle.yml delete mode 100644 .github/workflows/setup-env/action.yml delete mode 100644 .github/workflows/ubuntu.yml delete mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/centos.yml b/.github/workflows/centos.yml deleted file mode 100644 index 4c7ca4a9fc6..00000000000 --- a/.github/workflows/centos.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: CentOS - -on: - push: - branches: - - main - - feature/* - - merge* - - fix/* - release: - types: [ created ] - -env: - GITHUB_TOKEN: ${{ github.token }} - RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} - -jobs: - - build: - name: shrd=${{ matrix.shared }} extras=${{ matrix.extras }} java=${{ matrix.java }} dotnet=${{ matrix.dotnet }} python=${{ matrix.python }}-${{ matrix.python-version }} - runs-on: ubuntu-latest - container: 'centos:centos7' - env: - XPRESS_INSTALL_DIR: xpressmp/ - strategy: - fail-fast: false - matrix: - shared: [ON, OFF] - extras: [OFF] - include: - - 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" - OS="_centos7" - APPENDIX="${OS}" - APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}" - 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 - yum install -y devtoolset-9 - - 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/$GITHUB_REPOSITORY.git -b ${BRANCH_NAME} . - - - name: Checkout Xpress linux - uses: actions/checkout@v3 - with: - repository: rte-france/xpress-mp - path: ${{ env.XPRESS_INSTALL_DIR }} - ref: "9.0" - token: ${{ secrets.ACCESS_TOKEN }} - - - name: set Xpress variables - run: | - cd ${{ env.XPRESS_INSTALL_DIR }} - echo "XPRESSDIR=$PWD" >> $GITHUB_ENV - echo "XPAUTH_PATH=$PWD/bin/xpauth.xpr" >> $GITHUB_ENV - - - name: Configure OR-Tools - run: | - source /opt/rh/devtoolset-9/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=ON \ - -DBUILD_XPRESS_TEST_AND_EXAMPLES=ON \ - -DBUILD_DEPS=ON \ - -DCMAKE_INSTALL_PREFIX="build/install" \ - -DBUILD_SAMPLES=OFF \ - -DBUILD_FLATZINC=OFF - - - name: Build OR-Tools Linux - run: | - source /opt/rh/devtoolset-9/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" - - - name: run tests xpress - run: | - cd build - ctest -V -C Release --output-on-failure -R "xpress" - - - 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 }} - - publish_asset: - name: Publish release assets - needs: build - runs-on: ubuntu-latest - steps: - - name: Download artifacts - if: ${{ env.RELEASE_CREATED == 'true' }} - uses: actions/download-artifact@v3 - - - name: Publish assets - if: ${{ env.RELEASE_CREATED == 'true' }} - uses: alexellis/upload-assets@0.4.0 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - asset_paths: '["*/*.zip"]' diff --git a/.github/workflows/oracle.yml b/.github/workflows/oracle.yml deleted file mode 100644 index 46e8987c23c..00000000000 --- a/.github/workflows/oracle.yml +++ /dev/null @@ -1,213 +0,0 @@ -name: OracleLinux - -on: - push: - branches: - - main - - feature/* - - merge* - - fix/* - release: - types: [ created ] - -env: - GITHUB_TOKEN: ${{ github.token }} - RELEASE_CREATED: ${{ github.event_name == 'release' && github.event.action == 'created' }} - -jobs: - - build: - name: shrd=${{ matrix.cmake.shared }} java=${{ matrix.cmake.java }} python=${{ matrix.cmake.python }} - runs-on: ubuntu-latest - container: 'oraclelinux:8' - env: - XPRESS_INSTALL_DIR: xpressmp/ - strategy: - fail-fast: false - matrix: - 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" - OS="_oraclelinux-8" - APPENDIX="${OS}" - echo "::set-output name=appendix::$APPENDIX" - APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}" - echo "::set-output name=appendix_with_shared::$APPENDIX_WITH_SHARED" - - # 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 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 - run: | - git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b ${BRANCH_NAME} . - - - name: Set-up Xpress with pip - run: | - python3 -m pip install xpress==9.0.0 - 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.41 $XPRESS_DIR/lib/libxprs.so - - - 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=ON \ - -DBUILD_XPRESS_TEST_AND_EXAMPLES=ON \ - -DBUILD_DEPS=ON \ - -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" - - - name: run tests xpress - run: | - cd build - ctest -V -C Release --output-on-failure -R "xpress" - - - 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: prepare OR-Tools wheel - if: ${{ matrix.cmake.python == 'ON' }} - id: wheel - run: | - MY_DIR="ortools_python-${{ 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 "::set-output name=archive_name::$ARCHIVE_NAME" - echo "::set-output name=archive_path::$ARCHIVE_PATH" - - name: Upload OR-Tools wheel artifact - if: ${{ matrix.cmake.python == 'ON' }} - uses: actions/upload-artifact@v3 - with: - name: ${{ steps.wheel.outputs.archive_name }} - path: ${{ 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 "::set-output name=archive_name::$ARCHIVE_NAME" - echo "::set-output name=archive_path::$ARCHIVE_PATH" - - name: Upload OR-Tools jar artifact - if: ${{ matrix.cmake.java == 'ON' }} - uses: actions/upload-artifact@v3 - with: - name: ${{ steps.jar.outputs.archive_name }} - path: ${{ steps.jar.outputs.archive_path }} - - publish_asset: - name: Publish release assets - needs: build - runs-on: ubuntu-latest - steps: - - name: Download artifacts - if: ${{ env.RELEASE_CREATED == 'true' }} - uses: actions/download-artifact@v3 - - - name: Publish assets - if: ${{ env.RELEASE_CREATED == 'true' }} - uses: alexellis/upload-assets@0.4.0 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - asset_paths: '["*/*.zip"]' diff --git a/.github/workflows/setup-env/action.yml b/.github/workflows/setup-env/action.yml deleted file mode 100644 index 942b66251f4..00000000000 --- a/.github/workflows/setup-env/action.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: "setup ubuntu environement" -description: "setup dotnet python" -inputs: - os: - description: 'operating system' - required: true - python-version: - description: 'python version' - required: false - default: '3.10' - dotnet: - description: 'should install dotnet ON OFF' - required: false - default: 'OFF' -runs: - using: "composite" - steps: - - name: install zip wget for windows - if: ${{ startsWith(inputs.os, 'windows') }} - shell: cmd - run: | - choco install zip wget --no-progress - - name: Setup .NET 6.0 - if: ${{ inputs.dotnet == 'ON' }} - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ inputs.python-version }} - architecture: x64 - - - name: Update Path for ubuntu - if: ${{ startsWith(inputs.os, 'ubuntu') }} - run: echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Add Python binaries to path Windows - if: ${{ startsWith(inputs.os, 'windows') }} - run: > - echo "$((Get-Item ~).FullName)/AppData/Roaming/Python/${{ matrix.python.dir }}/Scripts" | - Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Pip install reqs - shell: bash - run: | - python -m pip install --upgrade pip - python -m pip install mypy-protobuf absl-py setuptools wheel numpy pandas virtualenv - - - name: Install SWIG 4.1.1 for windows - if: ${{ startsWith(inputs.os, 'windows') }} - shell: pwsh - 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: Install SWIG for Ubuntu - if: ${{ startsWith(inputs.os, 'ubuntu') }} - run: | - sudo apt install -y swig - swig -version - diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml deleted file mode 100644 index 97164ebd655..00000000000 --- a/.github/workflows/ubuntu.yml +++ /dev/null @@ -1,253 +0,0 @@ -name: Ubuntu - -on: - push: - branches: - - main - - feature/* - - merge* - - fix/* - release: - types: [ created ] - -env: - GITHUB_TOKEN: ${{ github.token }} - -jobs: - build: - name: ${{ matrix.os }} shrd=${{ matrix.cmake.shared }} 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 - strategy: - fail-fast: false - matrix: - os: ["ubuntu-20.04", "ubuntu-22.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}, - ] - - steps: - - name: Checkout or-tools - uses: actions/checkout@v4 - - - name: Swig install - run: | - sudo apt install -y swig - swig -version - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.cmake.python-version }} - - name: Update Path - run: echo "$HOME/.local/bin" >> $GITHUB_PATH - - 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.0.0 - 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.41 $XPRESS_DIR/lib/libxprs.so - - - 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=ON - -DBUILD_DEPS=ON - -DBUILD_SAMPLES=OFF - -DBUILD_XPRESS_TEST_AND_EXAMPLES=ON - -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 - working-directory: ./build/ - run: > - ctest - -C Release - --output-on-failure - -E "xpress" - - - name: run 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.cmake.shared }} - [ $SHARED == "ON" ] && WITH_SHARED="_shared" || WITH_SHARED="_static" - OS="_${{ matrix.os }}" - APPENDIX="${OS}" - echo "::set-output name=appendix::$APPENDIX" - APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}" - echo "::set-output name=appendix_with_shared::$APPENDIX_WITH_SHARED" - - - name: is release created - shell: bash - run: | - release_created=${{ github.event_name == 'release' && github.event.action == 'created' }} - echo "RELEASE_CREATED=$release_created" >> $GITHUB_ENV - - - name: Get release - if: ${{ env.RELEASE_CREATED == 'true' }} - id: get_release - uses: - bruceadams/get-release@v1.2.3 - - - name: Prepare OR-Tools install - if: ${{ matrix.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@v3 - if: ${{ env.RELEASE_CREATED == 'false' && matrix.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.publish-cxx-or == 'ON' }} - uses: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.or-install.outputs.archive_path }} - asset_name: ${{ steps.or-install.outputs.archive_name }} - asset_content_type: application/zip - - - 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: ${{ env.RELEASE_CREATED == 'false' && matrix.cmake.python == 'ON' }} - uses: actions/upload-artifact@v3 - 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.wheel.outputs.archive_path }} - asset_name: ${{ steps.wheel.outputs.archive_name }} - asset_content_type: application/zip - - - 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: ${{ env.RELEASE_CREATED == 'false' && matrix.cmake.java == 'ON' }} - uses: actions/upload-artifact@v3 - 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.jar.outputs.archive_path }} - asset_name: ${{ steps.jar.outputs.archive_name }} - asset_content_type: application/zip - - - 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: ${{ env.RELEASE_CREATED == 'false' && matrix.cmake.dotnet == 'ON' }} - uses: actions/upload-artifact@v3 - 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.dotnet.outputs.archive_path }} - asset_name: ${{ steps.dotnet.outputs.archive_name }} - asset_content_type: application/zip diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index fefe230a169..00000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,255 +0,0 @@ -name: Windows - -on: - push: - branches: - - main - - feature/* - - merge* - - fix/* - release: - types: [ created ] - -env: - GITHUB_TOKEN: ${{ github.token }} - -jobs: - build: - name: ${{ matrix.os }} shrd=${{ matrix.shared }} 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 - strategy: - fail-fast: false - matrix: - os: ['windows-latest'] - shared: [OFF] - cmake: [ - { java: ON, dotnet: ON, python: OFF, python-version: "3.8", python-dir: "Python309", publish-cxx-or: ON }, - { java: OFF, dotnet: OFF, python: ON, python-version: "3.8", python-dir: "Python308", publish-cxx-or: OFF }, - { java: OFF, dotnet: OFF, python: ON, python-version: "3.9", python-dir: "Python309", publish-cxx-or: OFF }, - { java: OFF, dotnet: OFF, python: ON, python-version: "3.10", python-dir: "Python310", publish-cxx-or: OFF }, - ] - steps: - - name: Checkout or-tools - uses: actions/checkout@v4 - - - name: Setup .NET 6.0 - if: ${{ matrix.cmake.dotnet == 'ON' }} - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.cmake.python-version }} - architecture: x64 - - name: Install python3 - run: python3 -m pip install --user mypy-protobuf absl-py setuptools wheel numpy pandas - - name: Add Python binaries to path Windows - run: > - echo "$((Get-Item ~).FullName)/AppData/Roaming/Python/${{ matrix.cmake.python-dir }}/Scripts" | - Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Install SWIG 4.1.1 - shell: pwsh - 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: Set-up Xpress with pip - shell: bash - run: | - python -m pip install --no-cache-dir xpress==9.0.0 - XPRESS_DIR="${{ env.pythonLocation }}\Lib\site-packages\xpress" - echo "XPRESSDIR=$XPRESS_DIR" >> $GITHUB_ENV - echo "XPAUTH_PATH=$XPRESS_DIR\license\community-xpauth.xpr" >> $GITHUB_ENV - - - name: Check cmake - run: cmake --version - - name: Configure OR-Tools - run: > - cmake -S . -B build - -DCMAKE_BUILD_TYPE=Release - -DBUILD_SHARED_LIBS=${{ matrix.shared }} - -DBUILD_PYTHON=${{ matrix.cmake.python }} - -DBUILD_JAVA=${{ matrix.cmake.java }} - -DBUILD_DOTNET=${{ matrix.cmake.dotnet }} - -DBUILD_EXAMPLES=ON - -DBUILD_XPRESS_TEST_AND_EXAMPLES=ON - -DBUILD_DEPS=ON - -DBUILD_SAMPLES=OFF - -DCMAKE_INSTALL_PREFIX="build/install" - - - name: Build OR-Tools windows - run: > - cmake - --build build - --config Release - --target ALL_BUILD INSTALL - -v -j2 - - - name: run tests not xpress - working-directory: ./build/ - run: > - ctest - -C Release - --output-on-failure - -E "xpress" - - - name: run 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="_${{ matrix.os }}" - APPENDIX="${OS}" - echo "::set-output name=appendix::$APPENDIX" - APPENDIX_WITH_SHARED="${OS}${WITH_SHARED}" - echo "::set-output name=appendix_with_shared::$APPENDIX_WITH_SHARED" - - - name: is release created - shell: bash - run: | - release_created=${{ github.event_name == 'release' && github.event.action == 'created' }} - echo "RELEASE_CREATED=$release_created" >> $GITHUB_ENV - - - name: Get release - if: ${{ env.RELEASE_CREATED == 'true' }} - id: get_release - uses: - bruceadams/get-release@v1.2.3 - - - 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@v3 - if: ${{ env.RELEASE_CREATED == 'false' && 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.or-install.outputs.archive_path }} - asset_name: ${{ steps.or-install.outputs.archive_name }} - asset_content_type: application/zip - - - 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: ${{ env.RELEASE_CREATED == 'false' && matrix.cmake.python == 'ON' }} - uses: actions/upload-artifact@v3 - 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.wheel.outputs.archive_path }} - asset_name: ${{ steps.wheel.outputs.archive_name }} - asset_content_type: application/zip - - - 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: ${{ env.RELEASE_CREATED == 'false' && matrix.cmake.java == 'ON' }} - uses: actions/upload-artifact@v3 - 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.jar.outputs.archive_path }} - asset_name: ${{ steps.jar.outputs.archive_name }} - asset_content_type: application/zip - - - 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: ${{ env.RELEASE_CREATED == 'false' && matrix.cmake.dotnet == 'ON' }} - uses: actions/upload-artifact@v3 - 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: actions/upload-release-asset@v1.0.2 - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ steps.dotnet.outputs.archive_path }} - asset_name: ${{ steps.dotnet.outputs.archive_name }} - asset_content_type: application/zip From 470c48236a98ef9c56bb8ed2ebb91bfc8af9745f Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 27 Oct 2023 10:59:22 +0200 Subject: [PATCH 03/21] fix test_xpress_interface.cc --- examples/xpress_tests/test_xpress_interface.cc | 12 ++++++------ ortools/linear_solver/xpress_interface.cc | 2 -- ortools/xpress/environment.h | 6 ++++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/xpress_tests/test_xpress_interface.cc b/examples/xpress_tests/test_xpress_interface.cc index 870ea99b0dc..4590b1ae804 100644 --- a/examples/xpress_tests/test_xpress_interface.cc +++ b/examples/xpress_tests/test_xpress_interface.cc @@ -1,10 +1,10 @@ -#include "ortools/linear_solver/xpress_interface.cc" - #include #include #include "gtest/gtest.h" #include "ortools/base/init_google.h" +#include "ortools/linear_solver/linear_solver.h" +#include "ortools/xpress/environment.h" #define XPRS_NAMELENGTH 1028 namespace operations_research { @@ -239,13 +239,13 @@ namespace operations_research { if (should_throw_) { throw std::runtime_error("This is a mocked exception in MyMPCallback"); } - XpressMPCallbackContext* context_ = static_cast(callback_context); + //XpressMPCallbackContext* context_ = static_cast(callback_context); ++nSolutions_; - EXPECT_TRUE(context_->CanQueryVariableValues()); - EXPECT_EQ(context_->Event(), MPCallbackEvent::kMipSolution); + EXPECT_TRUE(callback_context->CanQueryVariableValues()); + EXPECT_EQ(callback_context->Event(), MPCallbackEvent::kMipSolution); last_variable_values_.resize(mpSolver_->NumVariables(), 0.0); for (int i = 0 ; i < mpSolver_->NumVariables(); i++) { - last_variable_values_[i] = context_->VariableValue(mpSolver_->variable(i)); + last_variable_values_[i] = callback_context->VariableValue(mpSolver_->variable(i)); } }; diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index db8bd9a7130..514e5e546f2 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -32,8 +32,6 @@ #define XPRS_INTEGER 'I' #define XPRS_CONTINUOUS 'C' -#define XPRS_NAMES_ROW 1 -#define XPRS_NAMES_COLUMN 2 // The argument to this macro is the invocation of a XPRS function that // returns a status. If the function returns non-zero the macro aborts diff --git a/ortools/xpress/environment.h b/ortools/xpress/environment.h index 3ac9e74fb83..faee0a7e521 100644 --- a/ortools/xpress/environment.h +++ b/ortools/xpress/environment.h @@ -82,6 +82,12 @@ absl::Status LoadXpressDynamicLibrary(std::string &xpresspath); #define XPRS_TYPE_INT64 2 #define XPRS_TYPE_DOUBLE 3 #define XPRS_TYPE_STRING 4 +/***************************************************************************\ + * values related to NAMESPACES * +\***************************************************************************/ +#define XPRS_NAMES_ROW 1 +#define XPRS_NAMES_COLUMN 2 + #define XPRS_PLUSINFINITY 1.0e+20 #define XPRS_MINUSINFINITY -1.0e+20 From a2729ab43ab7c3cfa378ff2f8f0b3a372c92dffb Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 27 Oct 2023 11:08:33 +0200 Subject: [PATCH 04/21] remove callback_xpress.py --- examples/xpress_tests/callback_xpress.py | 80 ------------------------ 1 file changed, 80 deletions(-) delete mode 100644 examples/xpress_tests/callback_xpress.py diff --git a/examples/xpress_tests/callback_xpress.py b/examples/xpress_tests/callback_xpress.py deleted file mode 100644 index 7ac4bc71f8a..00000000000 --- a/examples/xpress_tests/callback_xpress.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2023 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. -"""MIP example/test that shows how to use the callback API.""" - -from ortools.linear_solver import pywraplp -from ortools.linear_solver.pywraplp import MPCallbackContext, MPCallback -import random -import unittest - - -class MyMPCallback(MPCallback): - def __init__(self, mp_solver: pywraplp.Solver): - super().__init__(False, False) - self._mp_solver_ = mp_solver - self._solutions_ = 0 - self._last_var_values_ = [0] * len(mp_solver.variables()) - - def RunCallback(self, ctx: MPCallbackContext): - self._solutions_ += 1 - for i in range(0, len(self._mp_solver_.variables())) : - self._last_var_values_[i] = ctx.VariableValue(self._mp_solver_.variable(i)) - - -class TestSiriusXpress(unittest.TestCase): - def test_callback(self): - """Builds a large MIP that is difficult to solve, in order for us to have time to intercept non-optimal - feasible solutions using callback""" - solver = pywraplp.Solver.CreateSolver('XPRESS_MIXED_INTEGER_PROGRAMMING') - n_vars = 30 - max_time = 30 - if not solver: - return - - random.seed(123) - - objective = solver.Objective() - objective.SetMaximization() - - for i in range(0, n_vars): - x = solver.IntVar(-random.random() * 200, random.random() * 200, 'x_' + str(i)) - objective.SetCoefficient(x, random.random() * 200 - 100) - if i == 0: - continue - rand1 = -random.random() * 2000 - rand2 = random.random() * 2000 - c = solver.Constraint(min(rand1, rand2), max(rand1, rand2)) - c.SetCoefficient(x, random.random() * 200 - 100) - for j in range(0, i): - c.SetCoefficient(solver.variable(j), random.random() * 200 - 100) - - solver.SetSolverSpecificParametersAsString("PRESOLVE 0 MAXTIME " + str(max_time)) - solver.EnableOutput() - - cb = MyMPCallback(solver) - solver.SetCallback(cb) - - solver.Solve() - - # This is a tough MIP, in 30 seconds XPRESS should have found at least 5 - # solutions (tested with XPRESS v9.0, may change in later versions) - self.assertTrue(cb._solutions_ > 5) - # Test that the last solution intercepted by callback is the same as the optimal one retained - for i in range(0, len(solver.variables())): - self.assertAlmostEqual(cb._last_var_values_[i], solver.variable(i).SolutionValue()) - - -if __name__ == '__main__': - unittest.main() - From b8815c0f225070a5575000fb8926b5306ef6a44b Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 27 Oct 2023 14:49:47 +0200 Subject: [PATCH 05/21] revert writing colnames and rownames --- .../xpress_tests/test_xpress_interface.cc | 25 ---------------- ortools/linear_solver/xpress_interface.cc | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/examples/xpress_tests/test_xpress_interface.cc b/examples/xpress_tests/test_xpress_interface.cc index 4590b1ae804..f0bfbf59d7d 100644 --- a/examples/xpress_tests/test_xpress_interface.cc +++ b/examples/xpress_tests/test_xpress_interface.cc @@ -281,19 +281,6 @@ namespace operations_research { EXPECT_EQ(getter.getNumVariables(), 502); } - TEST(XpressInterface, 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.getColName(0), pi); - EXPECT_EQ(getter.getColName(1), secondVar); - } - TEST(XpressInterface, NumConstraints) { UNITTEST_INIT_MIP(); solver.MakeRowConstraint(12., 100.0); @@ -303,18 +290,6 @@ namespace operations_research { EXPECT_EQ(getter.getNumConstraints(), 3); } - TEST(XpressInterface, ConstraintName) { - 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.getRowName(0), phi); - EXPECT_EQ(getter.getRowName(1), otherCnt); - } - TEST(XpressInterface, Reset) { UNITTEST_INIT_MIP(); solver.MakeBoolVar("x1"); diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index 514e5e546f2..d129355f63a 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -1453,10 +1453,12 @@ void XpressInterface::ExtractNewVariables() { CHECK_STATUS(XPRSaddcols(mLp, new_col_count, nonzeros, obj.get(), cmatbeg, cmatind.get(), cmatval.get(), lb.get(), ub.get())); - if (have_names) { - CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, col_names.data(), - 0, new_col_count - 1)); - } + // TODO Fixme + // Writing all names worsen the performance significantly + // Performance are //if (have_names) { + // CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, col_names.data(), + // 0, new_col_count - 1)); + //} } } @@ -1474,10 +1476,12 @@ void XpressInterface::ExtractNewVariables() { CHECK_STATUS(XPRSaddcols(mLp, new_col_count, 0, obj.get(), cmatbeg.data(), cmatind.get(), cmatval.get(), lb.get(), ub.get())); - if (have_names) { - CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, col_names.data(), 0, - new_col_count - 1)); - } + //TODO fixme + // Writing all names worsen the performance significantly + //if (have_names) { + // CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, col_names.data(), 0, + // new_col_count - 1)); + //} int const cols = getnumcols(mLp); unique_ptr ind(new int[new_col_count]); for (int j = 0; j < cols; ++j) ind[j] = j; @@ -1605,10 +1609,12 @@ void XpressInterface::ExtractNewConstraints() { rngval.get(), rmatbeg.get(), rmatind.get(), rmatval.get())); - if (have_names) { - CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_ROW, name.data(), offset, - offset + c - 1)); - } + //TODO fixme + // Writing all names worsen the performance significantly + //if (have_names) { + // CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_ROW, name.data(), offset, + // offset + c - 1)); + //} if (haveRanges) { CHECK_STATUS( XPRSchgrhsrange(mLp, nextRow, rngind.get(), rngval.get())); From 5ee26859d32848250960817452efa32a3addce78 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Mon, 30 Oct 2023 10:40:27 +0100 Subject: [PATCH 06/21] accept suggestion from Mizux --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da4a4738d23..d12b55fa9a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,7 +281,7 @@ message(STATUS "CPLEX support: ${USE_CPLEX}") ## XPRESS # Since it is dynamicaly loaded upon use, OFF is currently not supported. CMAKE_DEPENDENT_OPTION(USE_XPRESS "Use the Xpress solver" ON "BUILD_CXX" OFF) -option(BUILD_XPRESS_TEST_AND_EXAMPLES "Build Xpress-specific tests" OFF) +CMAKE_DEPENDENT_OPTION(BUILD_XPRESS_TEST_AND_EXAMPLES "Build Xpress-specific tests" OFF "USE_XPRESS" OFF) # Language specific options if(BUILD_CXX) From 59bccfb7f83aaa8546dee37510e9517f1822568c Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Tue, 7 Nov 2023 18:33:38 +0100 Subject: [PATCH 07/21] clean --- .../xpress_tests/test_xpress_interface.cc | 1643 +++++++++-------- 1 file changed, 838 insertions(+), 805 deletions(-) diff --git a/examples/xpress_tests/test_xpress_interface.cc b/examples/xpress_tests/test_xpress_interface.cc index f0bfbf59d7d..8529ec126cf 100644 --- a/examples/xpress_tests/test_xpress_interface.cc +++ b/examples/xpress_tests/test_xpress_interface.cc @@ -15,643 +15,653 @@ namespace operations_research { EXPECT_EQ(0, status_) << "Nonzero return status"; \ } while (0) - class XPRSGetter { - public: - XPRSGetter(MPSolver* solver) : solver_(solver) {} - - int getNumVariables() { - int cols; - EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_COLS, &cols)); - return cols; - } +class XPRSGetter { + public: + XPRSGetter(MPSolver* solver) : solver_(solver) {} + + int getNumVariables() { + int cols; + EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_COLS, &cols)); + return cols; + } - int getNumConstraints() { - int cols; - EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_ROWS, &cols)); - return cols; - } + int getNumConstraints() { + int cols; + EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_ROWS, &cols)); + return cols; + } - std::string getRowName(int n) { - EXPECT_LT(n, getNumConstraints()); - return getName(n, XPRS_NAMES_ROW); - } + std::string getRowName(int n) { + EXPECT_LT(n, getNumConstraints()); + return getName(n, XPRS_NAMES_ROW); + } - double getLb(int n) { - EXPECT_LT(n, getNumVariables()); - double lb; - EXPECT_STATUS(XPRSgetlb(prob(), &lb, n, n)); - return lb; - } + double getLb(int n) { + EXPECT_LT(n, getNumVariables()); + double lb; + EXPECT_STATUS(XPRSgetlb(prob(), &lb, n, n)); + return lb; + } - double getUb(int n) { - EXPECT_LT(n, getNumVariables()); - double ub; - EXPECT_STATUS(XPRSgetub(prob(), &ub, n, n)); - return ub; - } + double getUb(int n) { + EXPECT_LT(n, getNumVariables()); + double ub; + EXPECT_STATUS(XPRSgetub(prob(), &ub, n, n)); + return ub; + } - std::string getColName(int n) { - EXPECT_LT(n, getNumVariables()); - return getName(n, XPRS_NAMES_COLUMN); - } + std::string getColName(int n) { + EXPECT_LT(n, getNumVariables()); + return getName(n, XPRS_NAMES_COLUMN); + } - char getVariableType(int n) { - EXPECT_LT(n, getNumVariables()); - char type; - EXPECT_STATUS(XPRSgetcoltype(prob(), &type, n, n)); - return type; - } + char getVariableType(int n) { + EXPECT_LT(n, getNumVariables()); + char type; + EXPECT_STATUS(XPRSgetcoltype(prob(), &type, n, n)); + return type; + } - char getConstraintType(int n) { - EXPECT_LT(n, getNumConstraints()); - char type; - EXPECT_STATUS(XPRSgetrowtype(prob(), &type, n, n)); - return type; - } + char getConstraintType(int n) { + EXPECT_LT(n, getNumConstraints()); + char type; + EXPECT_STATUS(XPRSgetrowtype(prob(), &type, n, n)); + return type; + } - double getConstraintRhs(int n) { - EXPECT_LT(n, getNumConstraints()); - double rhs; - EXPECT_STATUS(XPRSgetrhs(prob(), &rhs, n, n)); - return rhs; - } + double getConstraintRhs(int n) { + EXPECT_LT(n, getNumConstraints()); + double rhs; + EXPECT_STATUS(XPRSgetrhs(prob(), &rhs, n, n)); + return rhs; + } - double getConstraintRange(int n) { - EXPECT_LT(n, getNumConstraints()); - double range; - EXPECT_STATUS(XPRSgetrhsrange(prob(), &range, n, n)); - return range; - } + double getConstraintRange(int n) { + EXPECT_LT(n, getNumConstraints()); + double range; + EXPECT_STATUS(XPRSgetrhsrange(prob(), &range, n, n)); + return range; + } - double getConstraintCoef(int row, int col) { - EXPECT_LT(col, getNumVariables()); - EXPECT_LT(row, getNumConstraints()); - double coef; - EXPECT_STATUS(XPRSgetcoef(prob(), row, col, &coef)); - return coef; - } + double getConstraintCoef(int row, int col) { + EXPECT_LT(col, getNumVariables()); + EXPECT_LT(row, getNumConstraints()); + double coef; + EXPECT_STATUS(XPRSgetcoef(prob(), row, col, &coef)); + return coef; + } - double getObjectiveCoef(int n) { - EXPECT_LT(n, getNumVariables()); - double objCoef; - EXPECT_STATUS(XPRSgetobj(prob(), &objCoef, n, n)); - return objCoef; - } + double getObjectiveCoef(int n) { + EXPECT_LT(n, getNumVariables()); + double objCoef; + EXPECT_STATUS(XPRSgetobj(prob(), &objCoef, n, n)); + return objCoef; + } - double getObjectiveOffset() { - double offset; - EXPECT_STATUS(XPRSgetdblattrib(prob(), XPRS_OBJRHS, &offset)); - return offset; - } + double getObjectiveOffset() { + double offset; + EXPECT_STATUS(XPRSgetdblattrib(prob(), XPRS_OBJRHS, &offset)); + return offset; + } - double getObjectiveSense() { - double sense; - EXPECT_STATUS(XPRSgetdblattrib(prob(), XPRS_OBJSENSE, &sense)); - return sense; - } + double getObjectiveSense() { + double sense; + EXPECT_STATUS(XPRSgetdblattrib(prob(), XPRS_OBJSENSE, &sense)); + return sense; + } - std::string getStringControl(int control) { - std::string value(280, '\0'); - int valueSize; - EXPECT_STATUS(XPRSgetstringcontrol(prob(), control, &value[0], value.size(), &valueSize)); - value.resize(valueSize - 1); - return value; - } + std::string getStringControl(int control) { + std::string value(280, '\0'); + int valueSize; + EXPECT_STATUS(XPRSgetstringcontrol(prob(), control, &value[0], value.size(), + &valueSize)); + value.resize(valueSize - 1); + return value; + } - double getDoubleControl(int control) { - double value; - EXPECT_STATUS(XPRSgetdblcontrol(prob(), control, &value)); - return value; - } + double getDoubleControl(int control) { + double value; + EXPECT_STATUS(XPRSgetdblcontrol(prob(), control, &value)); + return value; + } - int getIntegerControl(int control) { - int value; - EXPECT_STATUS(XPRSgetintcontrol(prob(), control, &value)); - return value; - } + int getIntegerControl(int control) { + int value; + EXPECT_STATUS(XPRSgetintcontrol(prob(), control, &value)); + return value; + } - int getInteger64Control(int control) { - XPRSint64 value; - EXPECT_STATUS(XPRSgetintcontrol64(prob(), control, &value)); - return value; - } + int getInteger64Control(int control) { + XPRSint64 value; + EXPECT_STATUS(XPRSgetintcontrol64(prob(), control, &value)); + return value; + } - private: - MPSolver* solver_; + private: + MPSolver* solver_; - XPRSprob prob() { - return (XPRSprob)solver_->underlying_solver(); - } + XPRSprob prob() { return (XPRSprob)solver_->underlying_solver(); } - std::string getName(int n, int type) { - int namelength; - EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_NAMELENGTH, &namelength)); + std::string getName(int n, int type) { + int namelength; + EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_NAMELENGTH, &namelength)); - std::string name; - name.resize(8 * namelength + 1); - EXPECT_STATUS(XPRSgetnames(prob(), type, name.data(), n, n)); + std::string name; + name.resize(8 * namelength + 1); + EXPECT_STATUS(XPRSgetnames(prob(), type, name.data(), n, n)); - name.erase(std::find_if(name.rbegin(), name.rend(), - [](unsigned char ch) { - return !std::isspace(ch) && ch != '\0'; - }) - .base(), - name.end()); + name.erase(std::find_if(name.rbegin(), name.rend(), + [](unsigned char ch) { + return !std::isspace(ch) && ch != '\0'; + }) + .base(), + name.end()); - return name; - } - }; + return name; + } +}; -#define UNITTEST_INIT_MIP() \ - MPSolver solver("XPRESS_MIP", MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING);\ +#define UNITTEST_INIT_MIP() \ + MPSolver solver("XPRESS_MIP", MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING); \ XPRSGetter getter(&solver) -#define UNITTEST_INIT_LP() \ - MPSolver solver("XPRESS_LP", MPSolver::XPRESS_LINEAR_PROGRAMMING);\ +#define UNITTEST_INIT_LP() \ + MPSolver solver("XPRESS_LP", MPSolver::XPRESS_LINEAR_PROGRAMMING); \ XPRSGetter getter(&solver) - void _unittest_verify_var(XPRSGetter* getter, MPVariable* x, char 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_var(XPRSGetter* getter, MPVariable* x, char 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(XPRSGetter* getter, MPConstraint* c, char type, double lb, double ub) { - int idx = c->index(); - EXPECT_EQ(getter->getConstraintType(idx), type); - switch (type) { - case 'L': - EXPECT_EQ(getter->getConstraintRhs(idx), ub); - break; - case 'U': - EXPECT_EQ(getter->getConstraintRhs(idx), lb); - break; - case 'E': - EXPECT_EQ(getter->getConstraintRhs(idx), ub); - EXPECT_EQ(getter->getConstraintRhs(idx), lb); - break; - case 'R': - EXPECT_EQ(getter->getConstraintRhs(idx), ub); - EXPECT_EQ(getter->getConstraintRange(idx), ub - lb); - break; - } +void _unittest_verify_constraint(XPRSGetter* getter, MPConstraint* c, char type, + double lb, double ub) { + int idx = c->index(); + EXPECT_EQ(getter->getConstraintType(idx), type); + switch (type) { + case 'L': + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + break; + case 'U': + EXPECT_EQ(getter->getConstraintRhs(idx), lb); + break; + case 'E': + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + EXPECT_EQ(getter->getConstraintRhs(idx), lb); + break; + case 'R': + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + EXPECT_EQ(getter->getConstraintRange(idx), ub - lb); + break; } +} - void buildLargeMip(MPSolver &solver, int numVars, int maxTime) { - // Build a random but big and complicated MIP with numVars integer variables - // And every variable has a coupling constraint with all previous ones - srand(123); - MPObjective* obj = solver.MutableObjective(); - obj->SetMaximization(); - for (int i = 0 ; i < numVars; ++i) { - MPVariable* x = solver.MakeIntVar(-rand() % 200, rand() % 200, "x_" + std::to_string(i)); - obj->SetCoefficient(x, rand() % 200 - 100); - if (i == 0) { - continue; - } - int rand1 = -rand() % 2000; - int rand2 = rand() % 2000; - MPConstraint* c = solver.MakeRowConstraint(std::min(rand1, rand2), - std::max(rand1, rand2)); - c->SetCoefficient(x, rand() % 200 - 100); - for (int j = 0; j < i; ++j) { - c->SetCoefficient(solver.variable(j), rand() % 200 - 100); - } +void buildLargeMip(MPSolver& solver, int numVars, int maxTime) { + // Build a random but big and complicated MIP with numVars integer variables + // And every variable has a coupling constraint with all previous ones + srand(123); + MPObjective* obj = solver.MutableObjective(); + obj->SetMaximization(); + for (int i = 0; i < numVars; ++i) { + MPVariable* x = solver.MakeIntVar(-rand() % 200, rand() % 200, + "x_" + std::to_string(i)); + obj->SetCoefficient(x, rand() % 200 - 100); + if (i == 0) { + continue; + } + int rand1 = -rand() % 2000; + int rand2 = rand() % 2000; + int min = std::min(rand1, rand2); + int max = std::max(rand1, rand2); + MPConstraint* c = solver.MakeRowConstraint(min, max); + c->SetCoefficient(x, rand() % 200 - 100); + for (int j = 0; j < i; ++j) { + c->SetCoefficient(solver.variable(j), rand() % 200 - 100); } - solver.SetSolverSpecificParametersAsString("PRESOLVE 0 MAXTIME " + std::to_string(maxTime)); - solver.EnableOutput(); } + solver.SetSolverSpecificParametersAsString("PRESOLVE 0 MAXTIME " + + std::to_string(maxTime)); + solver.EnableOutput(); +} + +class MyMPCallback : public MPCallback { + private: + MPSolver* mpSolver_; + int nSolutions_ = 0; + std::vector last_variable_values_; + bool should_throw_; + + public: + MyMPCallback(MPSolver* mpSolver, bool should_throw) + : MPCallback(false, false), + mpSolver_(mpSolver), + should_throw_(should_throw){}; - class MyMPCallback : public MPCallback { - private: - MPSolver* mpSolver_; - int nSolutions_=0; - std::vector last_variable_values_; - bool should_throw_; - public: - MyMPCallback(MPSolver* mpSolver, bool should_throw) - : MPCallback(false, false), - mpSolver_(mpSolver), - should_throw_(should_throw){}; - - ~MyMPCallback() override{}; - - void RunCallback(MPCallbackContext* callback_context) override { - if (should_throw_) { - throw std::runtime_error("This is a mocked exception in MyMPCallback"); - } - //XpressMPCallbackContext* context_ = static_cast(callback_context); - ++nSolutions_; - EXPECT_TRUE(callback_context->CanQueryVariableValues()); - EXPECT_EQ(callback_context->Event(), MPCallbackEvent::kMipSolution); - last_variable_values_.resize(mpSolver_->NumVariables(), 0.0); - for (int i = 0 ; i < mpSolver_->NumVariables(); i++) { - last_variable_values_[i] = callback_context->VariableValue(mpSolver_->variable(i)); - } - }; - - int getNSolutions() const { return nSolutions_; } - double getLastVariableValue(int index) const { return last_variable_values_[index]; } + ~MyMPCallback() override{}; + + void RunCallback(MPCallbackContext* callback_context) override { + if (should_throw_) { + throw std::runtime_error("This is a mocked exception in MyMPCallback"); + } + // XpressMPCallbackContext* context_ = + // static_cast(callback_context); + ++nSolutions_; + EXPECT_TRUE(callback_context->CanQueryVariableValues()); + EXPECT_EQ(callback_context->Event(), MPCallbackEvent::kMipSolution); + last_variable_values_.resize(mpSolver_->NumVariables(), 0.0); + for (int i = 0; i < mpSolver_->NumVariables(); i++) { + last_variable_values_[i] = + callback_context->VariableValue(mpSolver_->variable(i)); + } }; - MyMPCallback* buildLargeMipWithCallback(MPSolver &solver, int numVars, int maxTime) { - buildLargeMip(solver, numVars, maxTime); - MPCallback* mpCallback = new MyMPCallback(&solver, false); - solver.SetCallback(nullptr); // just to test that this does not cause failure - solver.SetCallback(mpCallback); - return static_cast(mpCallback); + int getNSolutions() const { return nSolutions_; } + double getLastVariableValue(int index) const { + return last_variable_values_[index]; } +}; + +MyMPCallback* buildLargeMipWithCallback(MPSolver& solver, int numVars, + int maxTime) { + buildLargeMip(solver, numVars, maxTime); + MPCallback* mpCallback = new MyMPCallback(&solver, false); + solver.SetCallback(nullptr); // just to test that this does not cause failure + solver.SetCallback(mpCallback); + return static_cast(mpCallback); +} - TEST(XpressInterface, isMIP) { - UNITTEST_INIT_MIP(); - EXPECT_EQ(solver.IsMIP(), true); - } +TEST(XpressInterface, isMIP) { + UNITTEST_INIT_MIP(); + EXPECT_EQ(solver.IsMIP(), true); +} - TEST(XpressInterface, isLP) { - UNITTEST_INIT_LP(); - EXPECT_EQ(solver.IsMIP(), false); - } +TEST(XpressInterface, isLP) { + UNITTEST_INIT_LP(); + EXPECT_EQ(solver.IsMIP(), false); +} - TEST(XpressInterface, NumVariables) { - UNITTEST_INIT_MIP(); - 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(XpressInterface, NumConstraints) { - UNITTEST_INIT_MIP(); - solver.MakeRowConstraint(12., 100.0); - solver.MakeRowConstraint(13., 13.1); - solver.MakeRowConstraint(12.1, 1000.0); - solver.Solve(); - EXPECT_EQ(getter.getNumConstraints(), 3); - } +TEST(XpressInterface, NumVariables) { + UNITTEST_INIT_MIP(); + 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(XpressInterface, Reset) { - UNITTEST_INIT_MIP(); - solver.MakeBoolVar("x1"); - solver.MakeBoolVar("x2"); - solver.MakeRowConstraint(12., 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(XpressInterface, NumConstraints) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(12., 100.0); + solver.MakeRowConstraint(13., 13.1); + solver.MakeRowConstraint(12.1, 1000.0); + solver.Solve(); + EXPECT_EQ(getter.getNumConstraints(), 3); +} - TEST(XpressInterface, MakeIntVar) { - UNITTEST_INIT_MIP(); - int lb = 0, ub = 10; - MPVariable* x = solver.MakeIntVar(lb, ub, "x"); - solver.Solve(); - _unittest_verify_var(&getter, x, 'I', lb, ub); - } +TEST(XpressInterface, Reset) { + UNITTEST_INIT_MIP(); + solver.MakeBoolVar("x1"); + solver.MakeBoolVar("x2"); + solver.MakeRowConstraint(12., 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(XpressInterface, MakeNumVar) { - UNITTEST_INIT_MIP(); - double lb = 1.5, ub = 158.2; - MPVariable* x = solver.MakeNumVar(lb, ub, "x"); - solver.Solve(); - _unittest_verify_var(&getter, x, 'C', lb, ub); - } +TEST(XpressInterface, MakeIntVar) { + UNITTEST_INIT_MIP(); + int lb = 0, ub = 10; + MPVariable* x = solver.MakeIntVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'I', lb, ub); +} - TEST(XpressInterface, MakeBoolVar) { - UNITTEST_INIT_MIP(); - MPVariable* x = solver.MakeBoolVar("x"); - solver.Solve(); - _unittest_verify_var(&getter, x, 'B', 0, 1); - } +TEST(XpressInterface, MakeNumVar) { + UNITTEST_INIT_MIP(); + double lb = 1.5, ub = 158.2; + MPVariable* x = solver.MakeNumVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'C', lb, ub); +} - TEST(XpressInterface, MakeIntVarArray) { - UNITTEST_INIT_MIP(); - int n1 = 25, lb1 = -7, ub1 = 18; - 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], 'I', lb1, ub1); - } - for (int i = 0; i < n2; ++i) { - _unittest_verify_var(&getter, xs2[i], 'I', lb2, ub2); - } - } +TEST(XpressInterface, MakeBoolVar) { + UNITTEST_INIT_MIP(); + MPVariable* x = solver.MakeBoolVar("x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'B', 0, 1); +} - TEST(XpressInterface, MakeNumVarArray) { - UNITTEST_INIT_MIP(); - 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], 'C', lb1, ub1); - } - for (int i = 0; i < n2; ++i) { - _unittest_verify_var(&getter, xs2[i], 'C', lb2, ub2); - } +TEST(XpressInterface, MakeIntVarArray) { + UNITTEST_INIT_MIP(); + int n1 = 25, lb1 = -7, ub1 = 18; + 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], 'I', lb1, ub1); } - - TEST(XpressInterface, MakeBoolVarArray) { - UNITTEST_INIT_MIP(); - 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], 'B', 0, 1); - } + for (int i = 0; i < n2; ++i) { + _unittest_verify_var(&getter, xs2[i], 'I', lb2, ub2); } +} - TEST(XpressInterface, SetVariableBounds) { - UNITTEST_INIT_MIP(); - 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, 'I', lb1, ub1); - _unittest_verify_var(&getter, x2, 'C', 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, 'I', lb1, ub1); - _unittest_verify_var(&getter, x2, 'C', lb2, ub2); +TEST(XpressInterface, MakeNumVarArray) { + UNITTEST_INIT_MIP(); + 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], 'C', lb1, ub1); } - - TEST(XpressInterface, SetVariableInteger) { - UNITTEST_INIT_MIP(); - int lb = -1, ub = 7; - MPVariable* x = solver.MakeIntVar(lb, ub, "x"); - solver.Solve(); - _unittest_verify_var(&getter, x, 'I', lb, ub); - x->SetInteger(false); - solver.Solve(); - _unittest_verify_var(&getter, x, 'C', lb, ub); + for (int i = 0; i < n2; ++i) { + _unittest_verify_var(&getter, xs2[i], 'C', lb2, ub2); } +} - TEST(XpressInterface, ConstraintL) { - UNITTEST_INIT_MIP(); - double lb = -solver.infinity(), ub = 10.; - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'L', lb, ub); +TEST(XpressInterface, MakeBoolVarArray) { + UNITTEST_INIT_MIP(); + 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], 'B', 0, 1); } +} - TEST(XpressInterface, ConstraintR) { - UNITTEST_INIT_MIP(); - double lb = -2, ub = -1; - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'R', lb, ub); - } +TEST(XpressInterface, SetVariableBounds) { + UNITTEST_INIT_MIP(); + 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, 'I', lb1, ub1); + _unittest_verify_var(&getter, x2, 'C', 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, 'I', lb1, ub1); + _unittest_verify_var(&getter, x2, 'C', lb2, ub2); +} - TEST(XpressInterface, ConstraintG) { - UNITTEST_INIT_MIP(); - double lb = 8.1, ub = solver.infinity(); - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'G', lb, ub); - } +TEST(XpressInterface, SetVariableInteger) { + UNITTEST_INIT_MIP(); + int lb = -1, ub = 7; + MPVariable* x = solver.MakeIntVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'I', lb, ub); + x->SetInteger(false); + solver.Solve(); + _unittest_verify_var(&getter, x, 'C', lb, ub); +} - TEST(XpressInterface, ConstraintE) { - UNITTEST_INIT_MIP(); - double lb = 18947.3, ub = lb; - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'E', lb, ub); - } +TEST(XpressInterface, ConstraintL) { + UNITTEST_INIT_MIP(); + double lb = -solver.infinity(), ub = 10.; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'L', lb, ub); +} - TEST(XpressInterface, SetConstraintBoundsL) { - UNITTEST_INIT_MIP(); - double lb = 18947.3, ub = lb; - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'E', lb, ub); - lb = -solver.infinity(), ub = 16.6; - c->SetBounds(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'L', lb, ub); - } +TEST(XpressInterface, ConstraintR) { + UNITTEST_INIT_MIP(); + double lb = -2, ub = -1; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'R', lb, ub); +} - TEST(XpressInterface, SetConstraintBoundsR) { - UNITTEST_INIT_MIP(); - double lb = -solver.infinity(), ub = 15; - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'L', lb, ub); - lb = 0, ub = 0.1; - c->SetBounds(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'R', lb, ub); - } +TEST(XpressInterface, ConstraintG) { + UNITTEST_INIT_MIP(); + double lb = 8.1, ub = solver.infinity(); + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'G', lb, ub); +} - TEST(XpressInterface, SetConstraintBoundsG) { - UNITTEST_INIT_MIP(); - double lb = 1, ub = 2; - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'R', lb, ub); - lb = 5, ub = solver.infinity(); - c->SetBounds(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'G', lb, ub); - } +TEST(XpressInterface, ConstraintE) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'E', lb, ub); +} - TEST(XpressInterface, SetConstraintBoundsE) { - UNITTEST_INIT_MIP(); - double lb = -1, ub = solver.infinity(); - MPConstraint* c = solver.MakeRowConstraint(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'G', lb, ub); - lb = 128, ub = lb; - c->SetBounds(lb, ub); - solver.Solve(); - _unittest_verify_constraint(&getter, c, 'E', lb, ub); - } +TEST(XpressInterface, SetConstraintBoundsL) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'E', lb, ub); + lb = -solver.infinity(), ub = 16.6; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'L', lb, ub); +} - TEST(XpressInterface, 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); - 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(XpressInterface, SetConstraintBoundsR) { + UNITTEST_INIT_MIP(); + double lb = -solver.infinity(), ub = 15; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'L', lb, ub); + lb = 0, ub = 0.1; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'R', lb, ub); +} - TEST(XpressInterface, 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(); - 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(XpressInterface, SetConstraintBoundsG) { + UNITTEST_INIT_MIP(); + double lb = 1, ub = 2; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'R', lb, ub); + lb = 5, ub = solver.infinity(); + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'G', lb, ub); +} - TEST(XpressInterface, ObjectiveCoef) { - UNITTEST_INIT_MIP(); - 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(XpressInterface, SetConstraintBoundsE) { + UNITTEST_INIT_MIP(); + double lb = -1, ub = solver.infinity(); + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'G', lb, ub); + lb = 128, ub = lb; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'E', lb, ub); +} - TEST(XpressInterface, ObjectiveOffset) { - UNITTEST_INIT_MIP(); - 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(XpressInterface, 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); + 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(XpressInterface, ClearObjective) { - UNITTEST_INIT_MIP(); - 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(XpressInterface, 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(); + 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(XpressInterface, ObjectiveSense) { - UNITTEST_INIT_MIP(); - MPObjective* const objective = solver.MutableObjective(); - objective->SetMinimization(); - EXPECT_EQ(getter.getObjectiveSense(), XPRS_OBJ_MINIMIZE); - objective->SetMaximization(); - EXPECT_EQ(getter.getObjectiveSense(), XPRS_OBJ_MAXIMIZE); - } +TEST(XpressInterface, ObjectiveCoef) { + UNITTEST_INIT_MIP(); + 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(XpressInterface, interations) { - UNITTEST_INIT_LP(); - int nc = 100, nv = 100; - std::vector cs(nc); +TEST(XpressInterface, ObjectiveOffset) { + UNITTEST_INIT_MIP(); + 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(XpressInterface, ClearObjective) { + UNITTEST_INIT_MIP(); + 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(XpressInterface, ObjectiveSense) { + UNITTEST_INIT_MIP(); + MPObjective* const objective = solver.MutableObjective(); + objective->SetMinimization(); + EXPECT_EQ(getter.getObjectiveSense(), XPRS_OBJ_MINIMIZE); + objective->SetMaximization(); + EXPECT_EQ(getter.getObjectiveSense(), XPRS_OBJ_MAXIMIZE); +} + +TEST(XpressInterface, interations) { + UNITTEST_INIT_LP(); + int nc = 100, nv = 100; + std::vector cs(nc); + for (int ci = 0; ci < nc; ++ci) { + cs[ci] = solver.MakeRowConstraint(ci, 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] = solver.MakeRowConstraint(ci, 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); + cs[ci]->SetCoefficient(v, vi + ci); } - solver.Solve(); - EXPECT_GT(solver.iterations(), 0); + objective->SetCoefficient(v, 1); } + solver.Solve(); + EXPECT_GT(solver.iterations(), 0); +} - TEST(XpressInterface, nodes) { - UNITTEST_INIT_MIP(); - int nc = 100, nv = 100; - std::vector cs(nc); +TEST(XpressInterface, nodes) { + UNITTEST_INIT_MIP(); + int nc = 100, nv = 100; + std::vector cs(nc); + for (int ci = 0; ci < nc; ++ci) { + cs[ci] = solver.MakeRowConstraint(ci, ci + 1); + } + 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[ci] = solver.MakeRowConstraint(ci, ci + 1); - } - 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[ci]->SetCoefficient(v, vi + ci); - } - objective->SetCoefficient(v, 1); + cs[ci]->SetCoefficient(v, vi + ci); } - solver.Solve(); - EXPECT_GT(solver.nodes(), 0); + objective->SetCoefficient(v, 1); } + solver.Solve(); + EXPECT_GT(solver.nodes(), 0); +} - TEST(XpressInterface, SolverVersion) { - UNITTEST_INIT_MIP(); - EXPECT_GE(solver.SolverVersion().size(), 30); - } +TEST(XpressInterface, SolverVersion) { + UNITTEST_INIT_MIP(); + EXPECT_GE(solver.SolverVersion().size(), 30); +} - TEST(XpressInterface, Write) { - UNITTEST_INIT_MIP(); - MPVariable* x1 = solver.MakeIntVar(-1.2, 9.3, "C1"); - MPVariable* x2 = solver.MakeNumVar(-1, 5.147593849384714, "C2"); - MPConstraint* c1 = solver.MakeRowConstraint(-solver.infinity(), 1, "R1"); - c1->SetCoefficient(x1, 3); - c1->SetCoefficient(x2, 1.5); - MPConstraint* c2 = solver.MakeRowConstraint(3, 5, "R2"); - c2->SetCoefficient(x2, -1.1122334455667788); - 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"(NAME newProb +TEST(XpressInterface, Write) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeIntVar(-1.2, 9.3, "C1"); + MPVariable* x2 = solver.MakeNumVar(-1, 5.147593849384714, "C2"); + MPConstraint* c1 = solver.MakeRowConstraint(-solver.infinity(), 1, "R1"); + c1->SetCoefficient(x1, 3); + c1->SetCoefficient(x2, 1.5); + MPConstraint* c2 = solver.MakeRowConstraint(3, 5, "R2"); + c2->SetCoefficient(x2, -1.1122334455667788); + 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"(NAME newProb OBJSENSE MAXIMIZE ROWS N __OBJ___ @@ -675,73 +685,80 @@ BOUNDS LO BND00001 C2 -1 ENDATA )"); - } +} - TEST(XpressInterface, SetPrimalTolerance) { - UNITTEST_INIT_LP(); - MPSolverParameters params; - double tol = 1e-4; - params.SetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE, tol); - solver.Solve(params); - EXPECT_EQ(getter.getDoubleControl(XPRS_FEASTOL), tol); - } +TEST(XpressInterface, SetPrimalTolerance) { + UNITTEST_INIT_LP(); + MPSolverParameters params; + double tol = 1e-4; + params.SetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE, tol); + solver.Solve(params); + EXPECT_EQ(getter.getDoubleControl(XPRS_FEASTOL), tol); +} - TEST(XpressInterface, SetDualTolerance) { - UNITTEST_INIT_LP(); - MPSolverParameters params; - double tol = 1e-2; - params.SetDoubleParam(MPSolverParameters::DUAL_TOLERANCE, tol); - solver.Solve(params); - EXPECT_EQ(getter.getDoubleControl(XPRS_OPTIMALITYTOL), tol); - } +TEST(XpressInterface, SetDualTolerance) { + UNITTEST_INIT_LP(); + MPSolverParameters params; + double tol = 1e-2; + params.SetDoubleParam(MPSolverParameters::DUAL_TOLERANCE, tol); + solver.Solve(params); + EXPECT_EQ(getter.getDoubleControl(XPRS_OPTIMALITYTOL), tol); +} - TEST(XpressInterface, SetPresolveMode) { - UNITTEST_INIT_MIP(); - MPSolverParameters params; - params.SetIntegerParam(MPSolverParameters::PRESOLVE, MPSolverParameters::PRESOLVE_OFF); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_PRESOLVE), 0); - params.SetIntegerParam(MPSolverParameters::PRESOLVE, MPSolverParameters::PRESOLVE_ON); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_PRESOLVE), 1); - } +TEST(XpressInterface, SetPresolveMode) { + UNITTEST_INIT_MIP(); + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::PRESOLVE, + MPSolverParameters::PRESOLVE_OFF); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_PRESOLVE), 0); + params.SetIntegerParam(MPSolverParameters::PRESOLVE, + MPSolverParameters::PRESOLVE_ON); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_PRESOLVE), 1); +} - TEST(XpressInterface, SetLpAlgorithm) { - UNITTEST_INIT_LP(); - MPSolverParameters params; - params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::DUAL); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 2); - params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::PRIMAL); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 3); - params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, MPSolverParameters::BARRIER); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 4); - } +TEST(XpressInterface, SetLpAlgorithm) { + UNITTEST_INIT_LP(); + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, + MPSolverParameters::DUAL); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 2); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, + MPSolverParameters::PRIMAL); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 3); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, + MPSolverParameters::BARRIER); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 4); +} - TEST(XpressInterface, SetScaling) { - UNITTEST_INIT_MIP(); - MPSolverParameters params; - params.SetIntegerParam(MPSolverParameters::SCALING, MPSolverParameters::SCALING_OFF); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_SCALING), 0); - params.SetIntegerParam(MPSolverParameters::SCALING, MPSolverParameters::SCALING_ON); - solver.Solve(params); - EXPECT_EQ(getter.getIntegerControl(XPRS_SCALING), 163); - } +TEST(XpressInterface, SetScaling) { + UNITTEST_INIT_MIP(); + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::SCALING, + MPSolverParameters::SCALING_OFF); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_SCALING), 0); + params.SetIntegerParam(MPSolverParameters::SCALING, + MPSolverParameters::SCALING_ON); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_SCALING), 163); +} - TEST(XpressInterface, SetRelativeMipGap) { - UNITTEST_INIT_MIP(); - MPSolverParameters params; - double relativeMipGap = 1e-3; - params.SetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP, relativeMipGap); - solver.Solve(params); - EXPECT_EQ(getter.getDoubleControl(XPRS_MIPRELSTOP), relativeMipGap); - } +TEST(XpressInterface, SetRelativeMipGap) { + UNITTEST_INIT_MIP(); + MPSolverParameters params; + double relativeMipGap = 1e-3; + params.SetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP, relativeMipGap); + solver.Solve(params); + EXPECT_EQ(getter.getDoubleControl(XPRS_MIPRELSTOP), relativeMipGap); +} - TEST(XpressInterface, setStringControls) { - std::vector> params = { +TEST(XpressInterface, setStringControls) { + std::vector> params = { {"MPSRHSNAME", XPRS_MPSRHSNAME, "default_value"}, {"MPSOBJNAME", XPRS_MPSOBJNAME, "default_value"}, {"MPSRANGENAME", XPRS_MPSRANGENAME, "default_value"}, @@ -751,17 +768,17 @@ ENDATA {"TUNEROUTPUTPATH", XPRS_TUNEROUTPUTPATH, "default_value"}, {"TUNERSESSIONNAME", XPRS_TUNERSESSIONNAME, "default_value"}, {"COMPUTEEXECSERVICE", XPRS_COMPUTEEXECSERVICE, "default_value"}, - }; - for (const auto& [paramString, control, paramValue] : params) { - UNITTEST_INIT_MIP(); - std::string xpressParamString = paramString + " " + paramValue; - solver.SetSolverSpecificParametersAsString(xpressParamString); - EXPECT_EQ(paramValue, getter.getStringControl(control)); - } + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = paramString + " " + paramValue; + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getStringControl(control)); } +} - TEST(XpressInterface, setDoubleControls) { - std::vector> params = { +TEST(XpressInterface, setDoubleControls) { + std::vector> params = { {"MAXCUTTIME", XPRS_MAXCUTTIME, 1.}, {"MAXSTALLTIME", XPRS_MAXSTALLTIME, 1.}, {"TUNERMAXTIME", XPRS_TUNERMAXTIME, 1.}, @@ -841,17 +858,18 @@ ENDATA {"TIMELIMIT", XPRS_TIMELIMIT, 1.}, {"SOLTIMELIMIT", XPRS_SOLTIMELIMIT, 1.}, {"REPAIRINFEASTIMELIMIT", XPRS_REPAIRINFEASTIMELIMIT, 1.}, - }; - for (const auto& [paramString, control, paramValue] : params) { - UNITTEST_INIT_MIP(); - std::string xpressParamString = paramString + " " + std::to_string(paramValue); - solver.SetSolverSpecificParametersAsString(xpressParamString); - EXPECT_EQ(paramValue, getter.getDoubleControl(control)); - } + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = + paramString + " " + std::to_string(paramValue); + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getDoubleControl(control)); } +} - TEST(XpressInterface, setIntControl) { - std::vector> params = { +TEST(XpressInterface, setIntControl) { + std::vector> params = { {"EXTRAROWS", XPRS_EXTRAROWS, 1}, {"EXTRACOLS", XPRS_EXTRACOLS, 1}, {"LPITERLIMIT", XPRS_LPITERLIMIT, 1}, @@ -1080,196 +1098,211 @@ ENDATA {"CALLBACKCHECKTIMEDELAY", XPRS_CALLBACKCHECKTIMEDELAY, 1}, {"MULTIOBJOPS", XPRS_MULTIOBJOPS, 1}, {"MULTIOBJLOG", XPRS_MULTIOBJLOG, 1}, - {"GLOBALSPATIALBRANCHIFPREFERORIG", XPRS_GLOBALSPATIALBRANCHIFPREFERORIG, 1}, + {"GLOBALSPATIALBRANCHIFPREFERORIG", XPRS_GLOBALSPATIALBRANCHIFPREFERORIG, + 1}, {"PRECONFIGURATION", XPRS_PRECONFIGURATION, 1}, {"FEASIBILITYJUMP", XPRS_FEASIBILITYJUMP, 1}, - }; - for (const auto& [paramString, control, paramValue] : params) { - UNITTEST_INIT_MIP(); - std::string xpressParamString = paramString + " " + std::to_string(paramValue); - solver.SetSolverSpecificParametersAsString(xpressParamString); - EXPECT_EQ(paramValue, getter.getIntegerControl(control)); - } + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = + paramString + " " + std::to_string(paramValue); + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getIntegerControl(control)); } +} - TEST(XpressInterface, setInt64Control) { - std::vector> params = { +TEST(XpressInterface, setInt64Control) { + std::vector> params = { {"EXTRAELEMS", XPRS_EXTRAELEMS, 1}, {"EXTRASETELEMS", XPRS_EXTRASETELEMS, 1}, - }; - for (const auto& [paramString, control, paramValue] : params) { - UNITTEST_INIT_MIP(); - std::string xpressParamString = paramString + " " + std::to_string(paramValue); - solver.SetSolverSpecificParametersAsString(xpressParamString); - EXPECT_EQ(paramValue, getter.getInteger64Control(control)); - } + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = + paramString + " " + std::to_string(paramValue); + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getInteger64Control(control)); } +} - TEST(XpressInterface, SolveMIP) { - UNITTEST_INIT_MIP(); +TEST(XpressInterface, SolveMIP) { + 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); +} - // 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(XpressInterface, SolveLP) { + 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); + solver.Solve(); + + 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); +} - TEST(XpressInterface, SolveLP) { - 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); - solver.Solve(); - - 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); +// WARNING fragile test because it uses +// the random generator is used by +// buildLargeMip(solver, numVars, maxTime); +// called by +// buildLargeMipWithCallback(solver, 60, 2); +// This tests hints a solution to the solver that is only +// usable for the test generated under linux +#if defined(_MSC_VER) +// Ignore this test because the random generator is different +// for windows and linux. +#elif defined(__GNUC__) +TEST(XpressInterface, SetHint) { + UNITTEST_INIT_MIP(); + + // Once a solution is added to XPRESS, it is actually impossible to get it + // back using the API + // In this test we send the (near) optimal solution as a hint (with + // obj=56774). Usually XPRESS finds it in ~3000 seconds but in this case it + // should be able to retain it in juste a few seconds using the hint. Note + // that the logs should mention "User solution (USER_HINT) stored." + buildLargeMipWithCallback(solver, 60, 2); + + std::vector hintValues{ + -2, -3, -19, 8, -1, -1, 7, 9, -20, -17, 7, -7, + 9, -27, 13, 14, -6, -3, -25, -9, 15, 13, -10, 16, + -34, 51, 39, 4, -54, 19, -76, 1, -17, -18, -46, -10, + 0, -36, 9, -29, -6, 4, -16, -45, -12, -45, -25, -70, + -43, -63, 54, -148, 79, -2, 64, 92, 61, -121, -174, -85}; + std::vector> hint; + for (int i = 0; i < solver.NumVariables(); ++i) { + hint.push_back(std::make_pair( + solver.LookupVariableOrNull("x_" + std::to_string(i)), hintValues[i])); } + solver.SetHint(hint); + solver.Solve(); - TEST(XpressInterface, SetHint) { - UNITTEST_INIT_MIP(); + // Test that we have at least the near optimal objective function value + EXPECT_GE(solver.Objective().Value(), 56774.0); +} +#endif - // Once a solution is added to XPRESS, it is actually impossible to get it - // back using the API - // In this test we send the (near) optimal solution as a hint (with obj=56774). - // Usually XPRESS finds it in ~3000 seconds but in this case it should be able - // to retain it in juste a few seconds using the hint. - // Note that the logs should mention "User solution (USER_HINT) stored." - buildLargeMipWithCallback(solver, 60, 2); - - std::vector hintValues{ - -2, -3, -19, 8, -1, -1, 7, 9, -20, -17, 7, -7, - 9, -27, 13, 14, -6, -3, -25, -9, 15, 13, -10, 16, - -34, 51, 39, 4, -54, 19, -76, 1, -17, -18, -46, -10, - 0, -36, 9, -29, -6, 4, -16, -45, -12, -45, -25, -70, - -43, -63, 54, -148, 79, -2, 64, 92, 61, -121, -174, -85}; - std::vector> hint; - for (int i = 0; i < solver.NumVariables(); ++i) { - hint.push_back( - std::make_pair(solver.LookupVariableOrNull("x_" + std::to_string(i)), - hintValues[i])); - } - solver.SetHint(hint); - solver.Solve(); - // Test that we have at least the near optimal objective function value - EXPECT_GE(solver.Objective().Value(), 56774.0); - } +TEST(XpressInterface, SetCallBack) { + UNITTEST_INIT_MIP(); - TEST(XpressInterface, SetCallBack) { - UNITTEST_INIT_MIP(); + auto myMpCallback = buildLargeMipWithCallback(solver, 30, 30); + solver.Solve(); - auto myMpCallback = buildLargeMipWithCallback(solver, 30, 30); - solver.Solve(); + int nSolutions = myMpCallback->getNSolutions(); - int nSolutions = myMpCallback->getNSolutions(); - - // This is a tough MIP, in 30 seconds XPRESS should have found at least 5 - // solutions (tested with XPRESS v9.0, may change in later versions) - EXPECT_GT(nSolutions, 5); - // Test variable values for the last solution found - for (int i = 0; i < solver.NumVariables(); ++i) { - EXPECT_NEAR(myMpCallback->getLastVariableValue(i), - solver.LookupVariableOrNull("x_" + std::to_string(i)) - ->solution_value(), - 1e-10); - } + // This is a tough MIP, in 30 seconds XPRESS should have found at least 5 + // solutions (tested with XPRESS v9.0, may change in later versions) + EXPECT_GT(nSolutions, 5); + // Test variable values for the last solution found + for (int i = 0; i < solver.NumVariables(); ++i) { + EXPECT_NEAR( + myMpCallback->getLastVariableValue(i), + solver.LookupVariableOrNull("x_" + std::to_string(i))->solution_value(), + 1e-10); } +} - TEST(XpressInterface, SetAndUnsetCallBack) { - // Test that when we unset a callback it is not called - UNITTEST_INIT_MIP(); - auto myMpCallback = buildLargeMipWithCallback(solver, 100, 5); - solver.SetCallback(nullptr); - solver.Solve(); - EXPECT_EQ(myMpCallback->getNSolutions(), 0); - } +TEST(XpressInterface, SetAndUnsetCallBack) { + // Test that when we unset a callback it is not called + UNITTEST_INIT_MIP(); + auto myMpCallback = buildLargeMipWithCallback(solver, 100, 5); + solver.SetCallback(nullptr); + solver.Solve(); + EXPECT_EQ(myMpCallback->getNSolutions(), 0); +} - TEST(XpressInterface, SetAndResetCallBack) { - // Test that when we set a new callback then it is called, and old one is not called - UNITTEST_INIT_MIP(); - auto oldMpCallback = buildLargeMipWithCallback(solver, 100, 5); - auto newMpCallback = new MyMPCallback(&solver, false); - solver.SetCallback((MPCallback*) newMpCallback); - solver.Solve(); - EXPECT_EQ(oldMpCallback->getNSolutions(), 0); - EXPECT_GT(newMpCallback->getNSolutions(), 1); - } +TEST(XpressInterface, SetAndResetCallBack) { + // Test that when we set a new callback then it is called, and old one is not + // called + UNITTEST_INIT_MIP(); + auto oldMpCallback = buildLargeMipWithCallback(solver, 100, 5); + auto newMpCallback = new MyMPCallback(&solver, false); + solver.SetCallback((MPCallback*)newMpCallback); + solver.Solve(); + EXPECT_EQ(oldMpCallback->getNSolutions(), 0); + EXPECT_GT(newMpCallback->getNSolutions(), 1); +} - TEST(XpressInterface, CallbackThrowsException) { - // Test that when the callback throws an exception, it is caught and re-thrown - UNITTEST_INIT_MIP(); - auto oldMpCallback = buildLargeMipWithCallback(solver, 30, 30); - auto newMpCallback = new MyMPCallback(&solver, true); - solver.SetCallback((MPCallback*) newMpCallback); - EXPECT_THROW( - { - try { - solver.Solve(); - } catch (const std::runtime_error& e) { - // this tests that it has the correct message - EXPECT_STREQ("This is a mocked exception in MyMPCallback", - e.what()); - throw; - } - }, - std::runtime_error); - } +TEST(XpressInterface, CallbackThrowsException) { + // Test that when the callback throws an exception, it is caught and re-thrown + UNITTEST_INIT_MIP(); + auto oldMpCallback = buildLargeMipWithCallback(solver, 30, 30); + auto newMpCallback = new MyMPCallback(&solver, true); + solver.SetCallback((MPCallback*)newMpCallback); + EXPECT_THROW( + { + try { + solver.Solve(); + } catch (const std::runtime_error& e) { + // this tests that it has the correct message + EXPECT_STREQ("This is a mocked exception in MyMPCallback", e.what()); + throw; + } + }, + std::runtime_error); +} } // namespace operations_research int main(int argc, char** argv) { - InitGoogle(argv[0], &argc, &argv, true); + InitGoogle(argv[0], &argc, &argv, true); absl::SetFlag(&FLAGS_logtostderr, 1); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); From 0065b38149bb982ced9616de5714f1980a695038 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Thu, 9 Nov 2023 17:06:04 +0100 Subject: [PATCH 08/21] change cmake/README.md --- cmake/README.md | 167 +++++++++--------- examples/xpress_tests/CMakeLists.txt | 2 +- .../{ => test}/test_xpress_interface.cc | 0 3 files changed, 80 insertions(+), 89 deletions(-) rename examples/xpress_tests/{ => test}/test_xpress_interface.cc (100%) diff --git a/cmake/README.md b/cmake/README.md index 76c7dcca4cd..e3824dbce04 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -91,7 +91,7 @@ Here the list of supported solvers: * HiGHS\* * PDLP * SCIP -* XPRESS\* +* XPRESS \*: these solvers are disabled by default. @@ -127,7 +127,6 @@ support for the following third-party solvers: note: You must enable the support of GLPK solver by using `-DUSE_GLPK=ON` (`OFF` by default). * CPLEX (`USE_CPLEX`), -* XPRESS (`USE_XPRESS`) **warning: Since these solvers are either proprietary (and require a specific license) or available under the GPL, we can't test them on public CI and their @@ -145,14 +144,6 @@ For ease of migration from legacy `make third_party` builds, CMake will also read the CPLEX installation path from the `UNIX_CPLEX_DIR` environment variable, if defined. -### Enabling XPRESS Support - -To enable XPRESS support, configure with `-DUSE_XPRESS=ON` and -`-DXPRESS_ROOT=/absolute/path/to/XPRESS/root/dir`, replacing -`/absolute/path/to/XPRESS/root/dir` with the path to your XPRESS installation. -`XPRESS_ROOT` can also be defined as an environment variable rather than an -option at configure time. - ## CMake Options There are several options that can be passed to CMake to modify how the code @@ -181,84 +172,84 @@ Following is a list of available options, for the full list run: cmake -S. -Bbuild -LH ``` -| CMake Option | Default Value | Note | -|:-------------|:--------------|:-----| -| `CMAKE_BUILD_TYPE` | Release | see CMake documentation [here](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | -| `BUILD_CXX` | ON | Build C++ | -| `BUILD_DOTNET` | OFF | Build .Net wrapper and packages | -| `BUILD_JAVA` | OFF | Build Java wrapper and packages | -| `BUILD_PYTHON` | OFF | Build Python wrapper and package | -| | | | -| `BUILD_FLATZINC` | ON\* | Build the flatzinc library
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_GLOP` | OFF\* | Build the standalone Glop library
**Forced** to OFF if `BUILD_CXX=ON`, otherwise default to ON | -| | | | -| `BUILD_DEPS` | OFF* | Default to ON if `BUILD_JAVA=ON` or `BUILD_PYTHON=ON` or `BUILD_DOTNET=ON` | -| `BUILD_ZLIB` | OFF* | Static build the zlib library
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_absl` | OFF* | Static build the abseil-cpp libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_Protobuf` | OFF* | Static build the protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_re2` | OFF* | Static build the re2 libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_Eigen3` | OFF* | Static build the Eigen3 libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| | | | -| `USE_COINOR` | ON\* | Enable Coin-OR support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_CoinUtils` | OFF\* | Static build the CoinUtils library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Osi` | OFF\* | Static build the Osi library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Clp` | OFF\* | Static build the Clp library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Cgl` | OFF\* | Static build the Cgl library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Cbc` | OFF\* | Static build the Cbc library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_GLPK` | OFF\* | Enable GLPK support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_GLPK` | OFF\* | Static build the GLPK libraries
**Forced** to ON if `USE_GLPK=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_HIGHS` | ON\* | Enable HIGHS support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_HIGHS` | OFF\* | Static build the HiGHS libraries
**Forced** to ON if `USE_HIGHS=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_SCIP` | ON\* | Enable SCIP support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_SCIP` | OFF\* | Static build the SCIP libraries
**Forced** to ON if `USE_SCIP=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_CPLEX` | OFF | Enable CPLEX support | -| | | | -| `USE_XPRESS` | OFF | Enable XPRESS support | -| | | | -| `BUILD_DOC` | OFF\* | Build all documentations | -| `BUILD_CXX_DOC` | OFF\* | Build C++ documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `BUILD_DOTNET_DOC` | OFF\* | Build .Net documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `BUILD_JAVA_DOC` | OFF\* | Build Java documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `BUILD_PYTHON_DOC` | OFF\* | Build Python documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `INSTALL_DOC` | OFF\* | Install all documentations
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_DOC=OFF` | -| | | | -| `BUILD_SAMPLES` | ON\* | Build all samples
Default to ON if `BUILD_DEPS=ON` | -| `BUILD_CXX_SAMPLES` | ON\* | Build all C++ samples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_DOTNET_SAMPLES` | ON\* | Build all .Net samples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_JAVA_SAMPLES` | ON\* | Build all Java samples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_PYTHON_SAMPLES` | ON\* | Build all Python samples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | -| | | | -| `BUILD_EXAMPLES` | ON\* | Build all examples
Default to ON if `BUILD_DEPS=ON` | -| `BUILD_CXX_EXAMPLES` | ON\* | Build all C++ examples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_DOTNET_EXAMPLES` | ON\* | Build all .Net examples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_JAVA_EXAMPLES` | ON\* | Build all Java examples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_PYTHON_EXAMPLES` | ON\* | Build all Python examples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | -| | | | -| `USE_DOTNET_46` | OFF | Enable .Net Framework 4.6 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_461` | OFF | Enable .Net Framework 4.6.1 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_462` | OFF | Enable .Net Framework 4.6.2 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_48` | OFF | Enable .Net Framework 4.8 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_STD_21` | OFF | Enable .Net Standard 2.1 support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | -| `USE_DOTNET_CORE_31` | OFF | Enable .Net Core 3.1 LTS support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | -| `USE_DOTNET_6` | ON | Enable .Net 6 LTS support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_7` | OFF | Enable .Net 7 support
Only available if `BUILD_DOTNET=ON` | -| `UNIVERSAL_DOTNET_PACKAGE` | OFF | Build a multi platform package (i.e. `Google.OrTools` will depends on all runtime packages)
Only available if `BUILD_DOTNET=ON` | -| | | | -| `SKIP_GPG` | ON | Disable GPG sign
Only available if `BUILD_JAVA=ON` | -| `UNIVERSAL_JAVA_PACKAGE` | OFF | Build a multi platform package (i.e. `ortools-java` will depends on all native packages)
Only available if `BUILD_JAVA=ON` | -| `BUILD_FAT_JAR` | OFF | Build a `ortools-java` .jar that includes all of its own Maven dependencies, including the native package
Only available if `BUILD_JAVA=ON` | -| | | | -| `BUILD_pybind11` | `BUILD_DEPS` | Static build the pybind11 libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | -| `BUILD_pybind11_protobuf` | `BUILD_DEPS` | Static build the pybind11_protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | -| `GENERATE_PYTHON_STUB` | ON | Generate python stub files
Only available if `BUILD_PYTHON=ON` | -| `BUILD_VENV` | `BUILD_TESTING` | Create python venv in `BINARY_DIR/python/venv`
**Forced** to ON if `BUILD_TESTING=ON`
Only available if `BUILD_PYTHON=ON` | -| `VENV_USE_SYSTEM_SITE_PACKAGES` | OFF | Python venv can use system site package (e.g. `py3-numpy` on Alpine)
Only available if `BUILD_PYTHON=ON` and `BUILD_VENV=ON` | -| `FETCH_PYTHON_DEPS` | `BUILD_DEPS` | Fetch python modules needed to build ortools package
Only available if `BUILD_PYTHON=ON` | -| | | | +| CMake Option | Default Value | Note | +|:-------------|:----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `CMAKE_BUILD_TYPE` | Release | see CMake documentation [here](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | +| `BUILD_CXX` | ON | Build C++ | +| `BUILD_DOTNET` | OFF | Build .Net wrapper and packages | +| `BUILD_JAVA` | OFF | Build Java wrapper and packages | +| `BUILD_PYTHON` | OFF | Build Python wrapper and package | +| | | | +| `BUILD_FLATZINC` | ON\* | Build the flatzinc library
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_GLOP` | OFF\* | Build the standalone Glop library
**Forced** to OFF if `BUILD_CXX=ON`, otherwise default to ON | +| | | | +| `BUILD_DEPS` | OFF* | Default to ON if `BUILD_JAVA=ON` or `BUILD_PYTHON=ON` or `BUILD_DOTNET=ON` | +| `BUILD_ZLIB` | OFF* | Static build the zlib library
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_absl` | OFF* | Static build the abseil-cpp libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_Protobuf` | OFF* | Static build the protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_re2` | OFF* | Static build the re2 libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_Eigen3` | OFF* | Static build the Eigen3 libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| | | | +| `USE_COINOR` | ON\* | Enable Coin-OR support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_CoinUtils` | OFF\* | Static build the CoinUtils library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Osi` | OFF\* | Static build the Osi library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Clp` | OFF\* | Static build the Clp library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Cgl` | OFF\* | Static build the Cgl library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Cbc` | OFF\* | Static build the Cbc library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_GLPK` | OFF\* | Enable GLPK support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_GLPK` | OFF\* | Static build the GLPK libraries
**Forced** to ON if `USE_GLPK=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_HIGHS` | ON\* | Enable HIGHS support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_HIGHS` | OFF\* | Static build the HiGHS libraries
**Forced** to ON if `USE_HIGHS=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_SCIP` | ON\* | Enable SCIP support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_SCIP` | OFF\* | Static build the SCIP libraries
**Forced** to ON if `USE_SCIP=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_CPLEX` | OFF | Enable CPLEX support | +| | | | +| `BUILD_DOC` | OFF\* | Build all documentations | +| `BUILD_CXX_DOC` | OFF\* | Build C++ documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `BUILD_DOTNET_DOC` | OFF\* | Build .Net documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `BUILD_JAVA_DOC` | OFF\* | Build Java documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `BUILD_PYTHON_DOC` | OFF\* | Build Python documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `INSTALL_DOC` | OFF\* | Install all documentations
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_DOC=OFF` | +| | | | +| `BUILD_SAMPLES` | ON\* | Build all samples
Default to ON if `BUILD_DEPS=ON` | +| `BUILD_CXX_SAMPLES` | ON\* | Build all C++ samples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_DOTNET_SAMPLES` | ON\* | Build all .Net samples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_JAVA_SAMPLES` | ON\* | Build all Java samples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_PYTHON_SAMPLES` | ON\* | Build all Python samples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | +| | | | +| `BUILD_EXAMPLES` | ON\* | Build all examples
Default to ON if `BUILD_DEPS=ON` | +| `BUILD_CXX_EXAMPLES` | ON\* | Build all C++ examples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_DOTNET_EXAMPLES` | ON\* | Build all .Net examples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_JAVA_EXAMPLES` | ON\* | Build all Java examples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_PYTHON_EXAMPLES` | ON\* | Build all Python examples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | +| | | | +| `BUILD_XPRESS_TEST_AND_EXAMPLES` | OFF\* | Build Xpress specific tests (C++, Java and Python)
tests and example for each language are built **only** if the respective language support is `ON`
These tests and examples don't need Xpress for the compilation but they need a valid Xpress installation for a succesful execution (a cummunity licence is sufficient). | +| | | | +| `USE_DOTNET_46` | OFF | Enable .Net Framework 4.6 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_461` | OFF | Enable .Net Framework 4.6.1 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_462` | OFF | Enable .Net Framework 4.6.2 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_48` | OFF | Enable .Net Framework 4.8 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_STD_21` | OFF | Enable .Net Standard 2.1 support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | +| `USE_DOTNET_CORE_31` | OFF | Enable .Net Core 3.1 LTS support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | +| `USE_DOTNET_6` | ON | Enable .Net 6 LTS support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_7` | OFF | Enable .Net 7 support
Only available if `BUILD_DOTNET=ON` | +| `UNIVERSAL_DOTNET_PACKAGE` | OFF | Build a multi platform package (i.e. `Google.OrTools` will depends on all runtime packages)
Only available if `BUILD_DOTNET=ON` | +| | | | +| `SKIP_GPG` | ON | Disable GPG sign
Only available if `BUILD_JAVA=ON` | +| `UNIVERSAL_JAVA_PACKAGE` | OFF | Build a multi platform package (i.e. `ortools-java` will depends on all native packages)
Only available if `BUILD_JAVA=ON` | +| `BUILD_FAT_JAR` | OFF | Build a `ortools-java` .jar that includes all of its own Maven dependencies, including the native package
Only available if `BUILD_JAVA=ON` | +| | | | +| `BUILD_pybind11` | `BUILD_DEPS` | Static build the pybind11 libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | +| `BUILD_pybind11_protobuf` | `BUILD_DEPS` | Static build the pybind11_protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | +| `GENERATE_PYTHON_STUB` | ON | Generate python stub files
Only available if `BUILD_PYTHON=ON` | +| `BUILD_VENV` | `BUILD_TESTING` | Create python venv in `BINARY_DIR/python/venv`
**Forced** to ON if `BUILD_TESTING=ON`
Only available if `BUILD_PYTHON=ON` | +| `VENV_USE_SYSTEM_SITE_PACKAGES` | OFF | Python venv can use system site package (e.g. `py3-numpy` on Alpine)
Only available if `BUILD_PYTHON=ON` and `BUILD_VENV=ON` | +| `FETCH_PYTHON_DEPS` | `BUILD_DEPS` | Fetch python modules needed to build ortools package
Only available if `BUILD_PYTHON=ON` | +| | | | ## Integrating OR-Tools in your CMake Project diff --git a/examples/xpress_tests/CMakeLists.txt b/examples/xpress_tests/CMakeLists.txt index 430f96a525c..fafb954426c 100644 --- a/examples/xpress_tests/CMakeLists.txt +++ b/examples/xpress_tests/CMakeLists.txt @@ -20,7 +20,7 @@ if (BUILD_CXX_EXAMPLES) set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN:$ORIGIN/../lib:$ORIGIN") endif () - add_executable(test_xprs_interface test_xpress_interface.cc) + add_executable(test_xprs_interface test/test_xpress_interface.cc) target_compile_features(test_xprs_interface PRIVATE cxx_std_17) target_link_libraries(test_xprs_interface PRIVATE ortools::ortools GTest::gtest_main) diff --git a/examples/xpress_tests/test_xpress_interface.cc b/examples/xpress_tests/test/test_xpress_interface.cc similarity index 100% rename from examples/xpress_tests/test_xpress_interface.cc rename to examples/xpress_tests/test/test_xpress_interface.cc From 45718d06161fe60a855d219d271ee91d3201dfe9 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 10 Nov 2023 10:12:23 +0100 Subject: [PATCH 09/21] try fix build bazel --- ortools/linear_solver/BUILD.bazel | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/ortools/linear_solver/BUILD.bazel b/ortools/linear_solver/BUILD.bazel index c0774941c20..9bf36205b6c 100644 --- a/ortools/linear_solver/BUILD.bazel +++ b/ortools/linear_solver/BUILD.bazel @@ -131,18 +131,6 @@ config_setting( }, ) -bool_flag( - name = "with_xpress", - build_setting_default = False, -) - -config_setting( - name = "use_xpress", - flag_values = { - ":with_xpress": "true", - }, -) - proto_library( name = "linear_solver_proto", srcs = ["linear_solver.proto"], @@ -175,6 +163,7 @@ cc_library( "linear_solver_callback.cc", "lpi_glop.cpp", "sat_interface.cc", + "xpress_interface.cc", ] + select({ ":use_bop": ["bop_interface.cc"], "//conditions:default": [], @@ -208,9 +197,6 @@ cc_library( }) + select({ ":use_cplex": ["cplex_interface.cc"], "//conditions:default": [], - }) + select({ - ":use_xpress": ["xpress_interface.cc"], - "//conditions:default": [], }), hdrs = [ "linear_expr.h", @@ -264,6 +250,7 @@ cc_library( "//ortools/base:stl_util", "//ortools/base:timer", "//ortools/gurobi:environment", + "//ortools/xpress:environment", "//ortools/linear_solver/proto_solver", "//ortools/port:file", "//ortools/port:proto_utils", From d4d0fd80cf7f4598e2bdb0c78b44f1f569dc1a3d Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Fri, 10 Nov 2023 15:41:35 +0100 Subject: [PATCH 10/21] try fix build bazel add MPSWriteError.h --- ortools/linear_solver/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/ortools/linear_solver/BUILD.bazel b/ortools/linear_solver/BUILD.bazel index 9bf36205b6c..3bffe2aae6c 100644 --- a/ortools/linear_solver/BUILD.bazel +++ b/ortools/linear_solver/BUILD.bazel @@ -204,6 +204,7 @@ cc_library( "linear_solver_callback.h", "scip_callback.h", "scip_helper_macros.h", + "MPSWriteError.h", ] + select({ ":use_glop": ["glop_utils.h"], "//conditions:default": [], From bdc6e5eae662e24f950f9769601e3313a253c6e5 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Mon, 13 Nov 2023 15:00:53 +0100 Subject: [PATCH 11/21] xpress tests gracefully exit if Xpress not found --- CMakeLists.txt | 4 - cmake/README.md | 154 +++++++++--------- examples/cpp/integer_programming.cc | 1 + examples/{xpress_tests => cpp}/xpress_use.cc | 3 +- examples/xpress_tests/CMakeLists.txt | 28 ---- ortools/linear_solver/CMakeLists.txt | 16 ++ .../linear_solver/xpress_interface_test.cc | 19 ++- ortools/xpress/environment.cc | 2 +- 8 files changed, 111 insertions(+), 116 deletions(-) rename examples/{xpress_tests => cpp}/xpress_use.cc (98%) delete mode 100644 examples/xpress_tests/CMakeLists.txt rename examples/xpress_tests/test/test_xpress_interface.cc => ortools/linear_solver/xpress_interface_test.cc (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d12b55fa9a9..275ff239834 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,7 +281,6 @@ message(STATUS "CPLEX support: ${USE_CPLEX}") ## XPRESS # Since it is dynamicaly loaded upon use, OFF is currently not supported. CMAKE_DEPENDENT_OPTION(USE_XPRESS "Use the Xpress solver" ON "BUILD_CXX" OFF) -CMAKE_DEPENDENT_OPTION(BUILD_XPRESS_TEST_AND_EXAMPLES "Build Xpress-specific tests" OFF "USE_XPRESS" OFF) # Language specific options if(BUILD_CXX) @@ -439,6 +438,3 @@ endforeach() # Add tests in examples/tests add_subdirectory(examples/tests) - -# Add Xpress specific tests -add_subdirectory(examples/xpress_tests) diff --git a/cmake/README.md b/cmake/README.md index e3824dbce04..4d09125fccc 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -172,84 +172,82 @@ Following is a list of available options, for the full list run: cmake -S. -Bbuild -LH ``` -| CMake Option | Default Value | Note | -|:-------------|:----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `CMAKE_BUILD_TYPE` | Release | see CMake documentation [here](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | -| `BUILD_CXX` | ON | Build C++ | -| `BUILD_DOTNET` | OFF | Build .Net wrapper and packages | -| `BUILD_JAVA` | OFF | Build Java wrapper and packages | -| `BUILD_PYTHON` | OFF | Build Python wrapper and package | -| | | | -| `BUILD_FLATZINC` | ON\* | Build the flatzinc library
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_GLOP` | OFF\* | Build the standalone Glop library
**Forced** to OFF if `BUILD_CXX=ON`, otherwise default to ON | -| | | | -| `BUILD_DEPS` | OFF* | Default to ON if `BUILD_JAVA=ON` or `BUILD_PYTHON=ON` or `BUILD_DOTNET=ON` | -| `BUILD_ZLIB` | OFF* | Static build the zlib library
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_absl` | OFF* | Static build the abseil-cpp libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_Protobuf` | OFF* | Static build the protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_re2` | OFF* | Static build the re2 libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| `BUILD_Eigen3` | OFF* | Static build the Eigen3 libraries
**Forced** to ON if `BUILD_DEPS=ON` | -| | | | -| `USE_COINOR` | ON\* | Enable Coin-OR support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_CoinUtils` | OFF\* | Static build the CoinUtils library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Osi` | OFF\* | Static build the Osi library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Clp` | OFF\* | Static build the Clp library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Cgl` | OFF\* | Static build the Cgl library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| `BUILD_Cbc` | OFF\* | Static build the Cbc library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_GLPK` | OFF\* | Enable GLPK support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_GLPK` | OFF\* | Static build the GLPK libraries
**Forced** to ON if `USE_GLPK=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_HIGHS` | ON\* | Enable HIGHS support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_HIGHS` | OFF\* | Static build the HiGHS libraries
**Forced** to ON if `USE_HIGHS=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_SCIP` | ON\* | Enable SCIP support
**Forced** to OFF if `BUILD_CXX=OFF` | -| `BUILD_SCIP` | OFF\* | Static build the SCIP libraries
**Forced** to ON if `USE_SCIP=ON` **and** `BUILD_DEPS=ON` | -| | | | -| `USE_CPLEX` | OFF | Enable CPLEX support | -| | | | -| `BUILD_DOC` | OFF\* | Build all documentations | -| `BUILD_CXX_DOC` | OFF\* | Build C++ documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `BUILD_DOTNET_DOC` | OFF\* | Build .Net documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `BUILD_JAVA_DOC` | OFF\* | Build Java documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `BUILD_PYTHON_DOC` | OFF\* | Build Python documentation
**Forced** to ON if `BUILD_DOC=ON` | -| `INSTALL_DOC` | OFF\* | Install all documentations
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_DOC=OFF` | -| | | | -| `BUILD_SAMPLES` | ON\* | Build all samples
Default to ON if `BUILD_DEPS=ON` | -| `BUILD_CXX_SAMPLES` | ON\* | Build all C++ samples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_DOTNET_SAMPLES` | ON\* | Build all .Net samples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_JAVA_SAMPLES` | ON\* | Build all Java samples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_PYTHON_SAMPLES` | ON\* | Build all Python samples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | -| | | | -| `BUILD_EXAMPLES` | ON\* | Build all examples
Default to ON if `BUILD_DEPS=ON` | -| `BUILD_CXX_EXAMPLES` | ON\* | Build all C++ examples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_DOTNET_EXAMPLES` | ON\* | Build all .Net examples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_JAVA_EXAMPLES` | ON\* | Build all Java examples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | -| `BUILD_PYTHON_EXAMPLES` | ON\* | Build all Python examples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | -| | | | -| `BUILD_XPRESS_TEST_AND_EXAMPLES` | OFF\* | Build Xpress specific tests (C++, Java and Python)
tests and example for each language are built **only** if the respective language support is `ON`
These tests and examples don't need Xpress for the compilation but they need a valid Xpress installation for a succesful execution (a cummunity licence is sufficient). | -| | | | -| `USE_DOTNET_46` | OFF | Enable .Net Framework 4.6 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_461` | OFF | Enable .Net Framework 4.6.1 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_462` | OFF | Enable .Net Framework 4.6.2 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_48` | OFF | Enable .Net Framework 4.8 support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_STD_21` | OFF | Enable .Net Standard 2.1 support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | -| `USE_DOTNET_CORE_31` | OFF | Enable .Net Core 3.1 LTS support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | -| `USE_DOTNET_6` | ON | Enable .Net 6 LTS support
Only available if `BUILD_DOTNET=ON` | -| `USE_DOTNET_7` | OFF | Enable .Net 7 support
Only available if `BUILD_DOTNET=ON` | -| `UNIVERSAL_DOTNET_PACKAGE` | OFF | Build a multi platform package (i.e. `Google.OrTools` will depends on all runtime packages)
Only available if `BUILD_DOTNET=ON` | -| | | | -| `SKIP_GPG` | ON | Disable GPG sign
Only available if `BUILD_JAVA=ON` | -| `UNIVERSAL_JAVA_PACKAGE` | OFF | Build a multi platform package (i.e. `ortools-java` will depends on all native packages)
Only available if `BUILD_JAVA=ON` | -| `BUILD_FAT_JAR` | OFF | Build a `ortools-java` .jar that includes all of its own Maven dependencies, including the native package
Only available if `BUILD_JAVA=ON` | -| | | | -| `BUILD_pybind11` | `BUILD_DEPS` | Static build the pybind11 libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | -| `BUILD_pybind11_protobuf` | `BUILD_DEPS` | Static build the pybind11_protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | -| `GENERATE_PYTHON_STUB` | ON | Generate python stub files
Only available if `BUILD_PYTHON=ON` | -| `BUILD_VENV` | `BUILD_TESTING` | Create python venv in `BINARY_DIR/python/venv`
**Forced** to ON if `BUILD_TESTING=ON`
Only available if `BUILD_PYTHON=ON` | -| `VENV_USE_SYSTEM_SITE_PACKAGES` | OFF | Python venv can use system site package (e.g. `py3-numpy` on Alpine)
Only available if `BUILD_PYTHON=ON` and `BUILD_VENV=ON` | -| `FETCH_PYTHON_DEPS` | `BUILD_DEPS` | Fetch python modules needed to build ortools package
Only available if `BUILD_PYTHON=ON` | -| | | | +| CMake Option | Default Value | Note | +|:-------------|:--------------|:-----| +| `CMAKE_BUILD_TYPE` | Release | see CMake documentation [here](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | +| `BUILD_CXX` | ON | Build C++ | +| `BUILD_DOTNET` | OFF | Build .Net wrapper and packages | +| `BUILD_JAVA` | OFF | Build Java wrapper and packages | +| `BUILD_PYTHON` | OFF | Build Python wrapper and package | +| | | | +| `BUILD_FLATZINC` | ON\* | Build the flatzinc library
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_GLOP` | OFF\* | Build the standalone Glop library
**Forced** to OFF if `BUILD_CXX=ON`, otherwise default to ON | +| | | | +| `BUILD_DEPS` | OFF* | Default to ON if `BUILD_JAVA=ON` or `BUILD_PYTHON=ON` or `BUILD_DOTNET=ON` | +| `BUILD_ZLIB` | OFF* | Static build the zlib library
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_absl` | OFF* | Static build the abseil-cpp libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_Protobuf` | OFF* | Static build the protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_re2` | OFF* | Static build the re2 libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| `BUILD_Eigen3` | OFF* | Static build the Eigen3 libraries
**Forced** to ON if `BUILD_DEPS=ON` | +| | | | +| `USE_COINOR` | ON\* | Enable Coin-OR support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_CoinUtils` | OFF\* | Static build the CoinUtils library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Osi` | OFF\* | Static build the Osi library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Clp` | OFF\* | Static build the Clp library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Cgl` | OFF\* | Static build the Cgl library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| `BUILD_Cbc` | OFF\* | Static build the Cbc library
**Forced** to ON if `USE_COINOR=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_GLPK` | OFF\* | Enable GLPK support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_GLPK` | OFF\* | Static build the GLPK libraries
**Forced** to ON if `USE_GLPK=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_HIGHS` | ON\* | Enable HIGHS support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_HIGHS` | OFF\* | Static build the HiGHS libraries
**Forced** to ON if `USE_HIGHS=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_SCIP` | ON\* | Enable SCIP support
**Forced** to OFF if `BUILD_CXX=OFF` | +| `BUILD_SCIP` | OFF\* | Static build the SCIP libraries
**Forced** to ON if `USE_SCIP=ON` **and** `BUILD_DEPS=ON` | +| | | | +| `USE_CPLEX` | OFF | Enable CPLEX support | +| | | | +| `BUILD_DOC` | OFF\* | Build all documentations | +| `BUILD_CXX_DOC` | OFF\* | Build C++ documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `BUILD_DOTNET_DOC` | OFF\* | Build .Net documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `BUILD_JAVA_DOC` | OFF\* | Build Java documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `BUILD_PYTHON_DOC` | OFF\* | Build Python documentation
**Forced** to ON if `BUILD_DOC=ON` | +| `INSTALL_DOC` | OFF\* | Install all documentations
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_DOC=OFF` | +| | | | +| `BUILD_SAMPLES` | ON\* | Build all samples
Default to ON if `BUILD_DEPS=ON` | +| `BUILD_CXX_SAMPLES` | ON\* | Build all C++ samples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_DOTNET_SAMPLES` | ON\* | Build all .Net samples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_JAVA_SAMPLES` | ON\* | Build all Java samples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_PYTHON_SAMPLES` | ON\* | Build all Python samples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | +| | | | +| `BUILD_EXAMPLES` | ON\* | Build all examples
Default to ON if `BUILD_DEPS=ON` | +| `BUILD_CXX_EXAMPLES` | ON\* | Build all C++ examples
**Forced** to OFF if `BUILD_CXX=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_DOTNET_EXAMPLES` | ON\* | Build all .Net examples
**Forced** to OFF if `BUILD_DOTNET=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_JAVA_EXAMPLES` | ON\* | Build all Java examples
**Forced** to OFF if `BUILD_JAVA=OFF` or `BUILD_SAMPLE=OFF` | +| `BUILD_PYTHON_EXAMPLES` | ON\* | Build all Python examples
**Forced** to OFF if `BUILD_PYTHON=OFF` or `BUILD_SAMPLE=OFF` | +| | | | +| `USE_DOTNET_46` | OFF | Enable .Net Framework 4.6 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_461` | OFF | Enable .Net Framework 4.6.1 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_462` | OFF | Enable .Net Framework 4.6.2 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_48` | OFF | Enable .Net Framework 4.8 support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_STD_21` | OFF | Enable .Net Standard 2.1 support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | +| `USE_DOTNET_CORE_31` | OFF | Enable .Net Core 3.1 LTS support
Only available if `BUILD_DOTNET=ON` and not targeting arm64 platform | +| `USE_DOTNET_6` | ON | Enable .Net 6 LTS support
Only available if `BUILD_DOTNET=ON` | +| `USE_DOTNET_7` | OFF | Enable .Net 7 support
Only available if `BUILD_DOTNET=ON` | +| `UNIVERSAL_DOTNET_PACKAGE` | OFF | Build a multi platform package (i.e. `Google.OrTools` will depends on all runtime packages)
Only available if `BUILD_DOTNET=ON` | +| | | | +| `SKIP_GPG` | ON | Disable GPG sign
Only available if `BUILD_JAVA=ON` | +| `UNIVERSAL_JAVA_PACKAGE` | OFF | Build a multi platform package (i.e. `ortools-java` will depends on all native packages)
Only available if `BUILD_JAVA=ON` | +| `BUILD_FAT_JAR` | OFF | Build a `ortools-java` .jar that includes all of its own Maven dependencies, including the native package
Only available if `BUILD_JAVA=ON` | +| | | | +| `BUILD_pybind11` | `BUILD_DEPS` | Static build the pybind11 libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | +| `BUILD_pybind11_protobuf` | `BUILD_DEPS` | Static build the pybind11_protobuf libraries
**Forced** to ON if `BUILD_DEPS=ON`
Only available if `BUILD_PYTHON=ON` | +| `GENERATE_PYTHON_STUB` | ON | Generate python stub files
Only available if `BUILD_PYTHON=ON` | +| `BUILD_VENV` | `BUILD_TESTING` | Create python venv in `BINARY_DIR/python/venv`
**Forced** to ON if `BUILD_TESTING=ON`
Only available if `BUILD_PYTHON=ON` | +| `VENV_USE_SYSTEM_SITE_PACKAGES` | OFF | Python venv can use system site package (e.g. `py3-numpy` on Alpine)
Only available if `BUILD_PYTHON=ON` and `BUILD_VENV=ON` | +| `FETCH_PYTHON_DEPS` | `BUILD_DEPS` | Fetch python modules needed to build ortools package
Only available if `BUILD_PYTHON=ON` | +| | | | ## Integrating OR-Tools in your CMake Project diff --git a/examples/cpp/integer_programming.cc b/examples/cpp/integer_programming.cc index 86400484f43..58b22fb3320 100644 --- a/examples/cpp/integer_programming.cc +++ b/examples/cpp/integer_programming.cc @@ -87,6 +87,7 @@ void RunAllExamples() { RunIntegerProgrammingExample("GUROBI"); RunIntegerProgrammingExample("GLPK"); RunIntegerProgrammingExample("CPLEX"); + RunIntegerProgrammingExample("XPRESS"); } } // namespace operations_research diff --git a/examples/xpress_tests/xpress_use.cc b/examples/cpp/xpress_use.cc similarity index 98% rename from examples/xpress_tests/xpress_use.cc rename to examples/cpp/xpress_use.cc index 0cc328adf41..600918e7620 100644 --- a/examples/xpress_tests/xpress_use.cc +++ b/examples/cpp/xpress_use.cc @@ -50,7 +50,8 @@ void useXpressSolver(bool solveAsMip, bool useFactory) { } } if (solver == nullptr) { - LOG(FATAL) << "Xpress solver is not available"; + LOG(WARNING) << "Xpress solver is not available"; + return; } // Use the solver /* diff --git a/examples/xpress_tests/CMakeLists.txt b/examples/xpress_tests/CMakeLists.txt deleted file mode 100644 index fafb954426c..00000000000 --- a/examples/xpress_tests/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -if (NOT BUILD_XPRESS_TEST_AND_EXAMPLES) - return() -endif () - -if (BUILD_CXX_EXAMPLES) - add_cxx_example(${CMAKE_CURRENT_SOURCE_DIR}/xpress_use.cc) -endif () - -# disable python callback test -#if (BUILD_PYTHON_EXAMPLES) -# add_python_example(${CMAKE_CURRENT_SOURCE_DIR}/callback_xpress.py) -#endif () - -include(CTest) -if (BUILD_CXX_EXAMPLES) - if (APPLE) - set(CMAKE_INSTALL_RPATH - "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") - elseif (UNIX) - set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN:$ORIGIN/../lib:$ORIGIN") - endif () - - add_executable(test_xprs_interface test/test_xpress_interface.cc) - target_compile_features(test_xprs_interface PRIVATE cxx_std_17) - target_link_libraries(test_xprs_interface PRIVATE ortools::ortools GTest::gtest_main) - - add_test(NAME cxx_unittests_xpress_interface COMMAND test_xprs_interface) -endif () \ No newline at end of file diff --git a/ortools/linear_solver/CMakeLists.txt b/ortools/linear_solver/CMakeLists.txt index 3ae9a6a2629..6a3ccfdeb5a 100644 --- a/ortools/linear_solver/CMakeLists.txt +++ b/ortools/linear_solver/CMakeLists.txt @@ -16,6 +16,7 @@ list(REMOVE_ITEM _SRCS ${CMAKE_CURRENT_SOURCE_DIR}/solve.cc ) list(FILTER _SRCS EXCLUDE REGEX "/model_exporter_main\\.cc") +list(FILTER _SRCS EXCLUDE REGEX ".*/.*_test.cc") if(USE_SCIP) list(APPEND _SRCS ${LPI_GLOP_SRC}) endif() @@ -67,3 +68,18 @@ elseif(UNIX) endif() install(TARGETS solve) + +if (BUILD_CXX_EXAMPLES) + if (APPLE) + set(CMAKE_INSTALL_RPATH + "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif (UNIX) + set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN:$ORIGIN/../lib:$ORIGIN") + endif () + + add_executable(test_xprs_interface xpress_interface_test.cc) + target_compile_features(test_xprs_interface PRIVATE cxx_std_17) + target_link_libraries(test_xprs_interface PRIVATE ortools::ortools GTest::gtest_main) + + add_test(NAME cxx_unittests_xpress_interface COMMAND test_xprs_interface) +endif () diff --git a/examples/xpress_tests/test/test_xpress_interface.cc b/ortools/linear_solver/xpress_interface_test.cc similarity index 98% rename from examples/xpress_tests/test/test_xpress_interface.cc rename to ortools/linear_solver/xpress_interface_test.cc index 8529ec126cf..df56239546e 100644 --- a/examples/xpress_tests/test/test_xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface_test.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -652,14 +653,17 @@ TEST(XpressInterface, Write) { obj->SetCoefficient(x1, 1); obj->SetCoefficient(x2, 2); - std::string tmpName = std::string(std::tmpnam(nullptr)) + ".mps"; + const std::filesystem::path temporary_working_dir = + std::filesystem::temp_directory_path() / "temporary_working_dir"; + std::filesystem::create_directories(temporary_working_dir); + + std::string tmpName = temporary_working_dir / "dummy.mps"; solver.Write(tmpName); std::ifstream tmpFile(tmpName); std::stringstream tmpBuffer; tmpBuffer << tmpFile.rdbuf(); - tmpFile.close(); - std::remove(tmpName.c_str()); + std::filesystem::remove_all(temporary_working_dir); EXPECT_EQ(tmpBuffer.str(), R"(NAME newProb OBJSENSE MAXIMIZE @@ -1305,5 +1309,12 @@ int main(int argc, char** argv) { InitGoogle(argv[0], &argc, &argv, true); absl::SetFlag(&FLAGS_logtostderr, 1); testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + auto solver = operations_research::MPSolver::CreateSolver("XPRESS_LP"); + if (solver == nullptr) { + LOG(ERROR) << "Xpress solver is not available"; + return EXIT_SUCCESS; + } + else{ + return RUN_ALL_TESTS(); + } } diff --git a/ortools/xpress/environment.cc b/ortools/xpress/environment.cc index 924897e0857..0f3fcbba4de 100644 --- a/ortools/xpress/environment.cc +++ b/ortools/xpress/environment.cc @@ -340,7 +340,7 @@ bool initXpressEnv(bool verbose, int xpress_oem_license_key) { } } void log_full_license_error(int code, const std::string& xpress_lib_dir) { - LOG(ERROR) << "XpressInterface: Xpress found at " << xpress_lib_dir + LOG(WARNING) << "XpressInterface: Xpress found at " << xpress_lib_dir << "\n"; char errmsg[256]; XPRSgetlicerrmsg(errmsg, 256); From fddaba48032acc73ae435df7a1be63d2ea8d5510 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Mon, 13 Nov 2023 15:29:08 +0100 Subject: [PATCH 12/21] add integer and linear programming test for dotnet python and java --- examples/dotnet/csintegerprogramming.cs | 4 ++++ examples/dotnet/cslinearprogramming.cs | 1 + examples/java/IntegerProgramming.java | 2 ++ examples/java/LinearProgramming.java | 2 ++ examples/python/integer_programming.py | 2 ++ examples/python/linear_programming.py | 2 ++ 6 files changed, 13 insertions(+) diff --git a/examples/dotnet/csintegerprogramming.cs b/examples/dotnet/csintegerprogramming.cs index d3e1041e99e..ee61c75c124 100644 --- a/examples/dotnet/csintegerprogramming.cs +++ b/examples/dotnet/csintegerprogramming.cs @@ -117,6 +117,8 @@ static void Main() RunIntegerProgrammingExample("SAT"); Console.WriteLine("---- Linear programming example with GUROBI ----"); RunIntegerProgrammingExample("GUROBI"); + Console.WriteLine("---- Linear programming example with XPRESS ----"); + RunIntegerProgrammingExample("XPRESS"); Console.WriteLine("---- Integer programming example (Natural API) with GLPK ----"); RunIntegerProgrammingExampleNaturalApi("GLPK"); Console.WriteLine("---- Linear programming example (Natural API) with CBC ----"); @@ -127,5 +129,7 @@ static void Main() RunIntegerProgrammingExampleNaturalApi("SAT"); Console.WriteLine("---- Linear programming example (Natural API) with GUROBI ----"); RunIntegerProgrammingExampleNaturalApi("GUROBI"); + Console.WriteLine("---- Linear programming example (Natural API) with XPRESS ----"); + RunIntegerProgrammingExampleNaturalApi("XPRESS"); } } diff --git a/examples/dotnet/cslinearprogramming.cs b/examples/dotnet/cslinearprogramming.cs index c3f2188db0d..3872550cc87 100644 --- a/examples/dotnet/cslinearprogramming.cs +++ b/examples/dotnet/cslinearprogramming.cs @@ -160,6 +160,7 @@ static void Main() RunLinearProgrammingExample("GLOP"); RunLinearProgrammingExample("GLPK_LP"); RunLinearProgrammingExample("CLP"); + RunLinearProgrammingExample("XPRESS_LP"); RunLinearProgrammingExampleNaturalApi("GLOP", true); RunLinearProgrammingExampleNaturalApi("GLPK_LP", false); diff --git a/examples/java/IntegerProgramming.java b/examples/java/IntegerProgramming.java index 6e5ede6ee80..7e730f48f85 100644 --- a/examples/java/IntegerProgramming.java +++ b/examples/java/IntegerProgramming.java @@ -84,5 +84,7 @@ public static void main(String[] args) throws Exception { runIntegerProgrammingExample("GLPK"); System.out.println("---- Integer programming example with CP-SAT ----"); runIntegerProgrammingExample("SAT"); + System.out.println("---- Integer programming example with XPRESS ----"); + runIntegerProgrammingExample("XPRESS"); } } diff --git a/examples/java/LinearProgramming.java b/examples/java/LinearProgramming.java index 8457d77435b..b93f06da486 100644 --- a/examples/java/LinearProgramming.java +++ b/examples/java/LinearProgramming.java @@ -116,5 +116,7 @@ public static void main(String[] args) throws Exception { runLinearProgrammingExample("GLOP", true); System.out.println("---- Linear programming example with CLP ----"); runLinearProgrammingExample("CLP", false); + System.out.println("---- Linear programming example with XPRESS ----"); + runLinearProgrammingExample("XPRESS", false); } } diff --git a/examples/python/integer_programming.py b/examples/python/integer_programming.py index 21d62a1985d..c61f90258d6 100755 --- a/examples/python/integer_programming.py +++ b/examples/python/integer_programming.py @@ -102,6 +102,7 @@ def RunAllIntegerExampleNaturalLanguageAPI(): # RunIntegerExampleNaturalLanguageAPI('CBC') RunIntegerExampleNaturalLanguageAPI("SCIP") RunIntegerExampleNaturalLanguageAPI("SAT") + RunIntegerExampleNaturalLanguageAPI("XPRESS") def RunAllIntegerExampleCppStyleAPI(): @@ -110,6 +111,7 @@ def RunAllIntegerExampleCppStyleAPI(): # RunIntegerExampleCppStyleAPI('CBC') RunIntegerExampleCppStyleAPI("SCIP") RunIntegerExampleCppStyleAPI("SAT") + RunIntegerExampleCppStyleAPI("XPRESS") def main(): diff --git a/examples/python/linear_programming.py b/examples/python/linear_programming.py index 291ebe4f59e..b5e9c941c9b 100644 --- a/examples/python/linear_programming.py +++ b/examples/python/linear_programming.py @@ -138,11 +138,13 @@ def main(): RunLinearExampleNaturalLanguageAPI("GLPK_LP") RunLinearExampleNaturalLanguageAPI("CLP") RunLinearExampleNaturalLanguageAPI("PDLP") + RunLinearExampleNaturalLanguageAPI("XPRESS") RunLinearExampleCppStyleAPI("GLOP") RunLinearExampleCppStyleAPI("GLPK_LP") RunLinearExampleCppStyleAPI("CLP") RunLinearExampleCppStyleAPI("PDLP") + RunLinearExampleCppStyleAPI("XPRESS") if __name__ == "__main__": From d750959d135ee3b53fc2394e9c21773182dbd551 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Mon, 13 Nov 2023 15:29:08 +0100 Subject: [PATCH 13/21] remove MPSWriteError --- ortools/linear_solver/BUILD.bazel | 1 - ortools/linear_solver/MPSWriteError.h | 25 ----------------------- ortools/linear_solver/linear_solver.h | 2 +- ortools/linear_solver/xpress_interface.cc | 4 +--- 4 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 ortools/linear_solver/MPSWriteError.h diff --git a/ortools/linear_solver/BUILD.bazel b/ortools/linear_solver/BUILD.bazel index 3bffe2aae6c..9bf36205b6c 100644 --- a/ortools/linear_solver/BUILD.bazel +++ b/ortools/linear_solver/BUILD.bazel @@ -204,7 +204,6 @@ cc_library( "linear_solver_callback.h", "scip_callback.h", "scip_helper_macros.h", - "MPSWriteError.h", ] + select({ ":use_glop": ["glop_utils.h"], "//conditions:default": [], diff --git a/ortools/linear_solver/MPSWriteError.h b/ortools/linear_solver/MPSWriteError.h deleted file mode 100644 index 9fc0e2ca4dc..00000000000 --- a/ortools/linear_solver/MPSWriteError.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2023 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 - -#ifndef ORTOOLS_MPSWRITEERROR_H -#define ORTOOLS_MPSWRITEERROR_H -#include -namespace operations_research { -class MPSWriteError : public std::runtime_error { - public: - MPSWriteError(const std::string& message) : std::runtime_error(message) {} -}; -} // namespace operations_research -#endif // ORTOOLS_MPSWRITEERROR_H diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index a7157e8d963..edcc49ee61e 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -1663,7 +1663,7 @@ class MPSolverInterface { } // Writes the model using the solver internal write function. Currently only - // available for GurobiInterface. + // available for GurobiInterface and XpressInterface. virtual void Write(const std::string& filename); // ----- Model modifications and extraction ----- diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index d129355f63a..c28cab27fdf 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -25,8 +25,6 @@ #include "absl/strings/str_format.h" #include "ortools/base/logging.h" #include "ortools/base/timer.h" -#include "ortools/base/types.h" -#include "ortools/linear_solver/MPSWriteError.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/xpress/environment.h" @@ -2093,7 +2091,7 @@ void XpressInterface::Write(const std::string& filename) { VLOG(1) << "Writing Xpress MPS \"" << filename << "\"."; const int status = XPRSwriteprob(mLp, filename.c_str(), ""); if (status) { - throw MPSWriteError("Failed to write MPS."); + LOG(ERROR) << "Xpress: Failed to write MPS! "; } } From 0fa930e55d25be8b48443ccf7f1b0cd984017492 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Mon, 13 Nov 2023 18:14:18 +0100 Subject: [PATCH 14/21] try fix Window build --- ortools/linear_solver/xpress_interface_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ortools/linear_solver/xpress_interface_test.cc b/ortools/linear_solver/xpress_interface_test.cc index df56239546e..694d56a7b21 100644 --- a/ortools/linear_solver/xpress_interface_test.cc +++ b/ortools/linear_solver/xpress_interface_test.cc @@ -657,7 +657,7 @@ TEST(XpressInterface, Write) { std::filesystem::temp_directory_path() / "temporary_working_dir"; std::filesystem::create_directories(temporary_working_dir); - std::string tmpName = temporary_working_dir / "dummy.mps"; + std::string tmpName = (temporary_working_dir / "dummy.mps").string(); solver.Write(tmpName); std::ifstream tmpFile(tmpName); From 859e3e68bd2248f15bc8f2850a47fd59188ddc24 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Tue, 14 Nov 2023 12:03:49 +0100 Subject: [PATCH 15/21] remove useless line from CMakeLists.txt --- ortools/xpress/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ortools/xpress/CMakeLists.txt b/ortools/xpress/CMakeLists.txt index b2f10a24ea3..bce9253cec7 100644 --- a/ortools/xpress/CMakeLists.txt +++ b/ortools/xpress/CMakeLists.txt @@ -18,5 +18,4 @@ target_link_libraries(${NAME} PRIVATE absl::strings absl::str_format protobuf::libprotobuf - ${PROJECT_NAMESPACE}::${PROJECT_NAME}_proto - $<$:Coin::Cbc>) + ${PROJECT_NAMESPACE}::${PROJECT_NAME}_proto) From ea451e2a4561caf7bf3697dad0949299fd6df159 Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Wed, 15 Nov 2023 10:26:48 +0100 Subject: [PATCH 16/21] try fix test under windows --- ortools/linear_solver/xpress_interface_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/ortools/linear_solver/xpress_interface_test.cc b/ortools/linear_solver/xpress_interface_test.cc index 694d56a7b21..4df44841be8 100644 --- a/ortools/linear_solver/xpress_interface_test.cc +++ b/ortools/linear_solver/xpress_interface_test.cc @@ -663,6 +663,7 @@ TEST(XpressInterface, Write) { std::ifstream tmpFile(tmpName); std::stringstream tmpBuffer; tmpBuffer << tmpFile.rdbuf(); + tmpFile.close(); std::filesystem::remove_all(temporary_working_dir); EXPECT_EQ(tmpBuffer.str(), R"(NAME newProb From 7f48cd9a9af95d7b2440f9af7a597f448d4c3472 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Thu, 16 Nov 2023 13:14:11 +0100 Subject: [PATCH 17/21] reformat --- ortools/sat/colab/flags.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ortools/sat/colab/flags.py b/ortools/sat/colab/flags.py index ec31be8e240..3cc51b04641 100644 --- a/ortools/sat/colab/flags.py +++ b/ortools/sat/colab/flags.py @@ -13,9 +13,11 @@ """Collection of helpers to manage absl flags in colab.""" + class NotebookStringFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:str, doc:str): + + def __init__(self, name: str, value: str, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -31,13 +33,14 @@ def name(self) -> str: return self.__name -def define_string(name:str, value:str, doc:str): +def define_string(name: str, value: str, doc: str): return NotebookStringFlag(name, value, doc) class NotebookIntFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:int, doc:str): + + def __init__(self, name: str, value: int, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -53,13 +56,14 @@ def name(self) -> str: return self.__name -def define_integer(name:str, value:int, doc:str): - return NotebookIntFlag(name, value, doc) +def define_integer(name: str, value: int, doc: str): + return NotebookIntFlag(name, value, doc) class NotebookFloatFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:float, doc:str): + + def __init__(self, name: str, value: float, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -75,13 +79,14 @@ def name(self) -> str: return self.__name -def define_float(name:str, value:bool, doc:str): - return NotebookFloatFlag(name, value, doc) +def define_float(name: str, value: bool, doc: str): + return NotebookFloatFlag(name, value, doc) class NotebookBoolFlag: """Stub for absl flag to be used within a jupyter notebook.""" - def __init__(self, name:str, value:bool, doc:str): + + def __init__(self, name: str, value: bool, doc: str): self.__name = name self.__value = value self.__doc__ = doc @@ -96,6 +101,6 @@ def name(self) -> str: """Returns the name of the parameter.""" return self.__name -def define_bool(name:str, value:bool, doc:str): - return NotebookBoolFlag(name, value, doc) +def define_bool(name: str, value: bool, doc: str): + return NotebookBoolFlag(name, value, doc) From e09439c92f552242b4ea96b300071816381d282b Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Thu, 16 Nov 2023 16:22:45 +0100 Subject: [PATCH 18/21] use XPRESS_LP instead of XPRESS for linear programming examples --- examples/java/LinearProgramming.java | 2 +- examples/python/linear_programming.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/java/LinearProgramming.java b/examples/java/LinearProgramming.java index b93f06da486..34749e4046a 100644 --- a/examples/java/LinearProgramming.java +++ b/examples/java/LinearProgramming.java @@ -117,6 +117,6 @@ public static void main(String[] args) throws Exception { System.out.println("---- Linear programming example with CLP ----"); runLinearProgrammingExample("CLP", false); System.out.println("---- Linear programming example with XPRESS ----"); - runLinearProgrammingExample("XPRESS", false); + runLinearProgrammingExample("XPRESS_LP", false); } } diff --git a/examples/python/linear_programming.py b/examples/python/linear_programming.py index b5e9c941c9b..6b383360dff 100644 --- a/examples/python/linear_programming.py +++ b/examples/python/linear_programming.py @@ -138,13 +138,13 @@ def main(): RunLinearExampleNaturalLanguageAPI("GLPK_LP") RunLinearExampleNaturalLanguageAPI("CLP") RunLinearExampleNaturalLanguageAPI("PDLP") - RunLinearExampleNaturalLanguageAPI("XPRESS") + RunLinearExampleNaturalLanguageAPI("XPRESS_LP") RunLinearExampleCppStyleAPI("GLOP") RunLinearExampleCppStyleAPI("GLPK_LP") RunLinearExampleCppStyleAPI("CLP") RunLinearExampleCppStyleAPI("PDLP") - RunLinearExampleCppStyleAPI("XPRESS") + RunLinearExampleCppStyleAPI("XPRESS_LP") if __name__ == "__main__": From 857c353f710f1390e6af8a35336c7db87fdf8b93 Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Thu, 16 Nov 2023 18:17:12 +0100 Subject: [PATCH 19/21] tools: add --platform arg when possible make script more resilient/cross-platform --- tools/docker/Makefile | 2 +- tools/release/build_delivery_manylinux_amd64.sh | 7 ++++--- tools/release/publish_delivery_manylinux_amd64.sh | 2 +- tools/release/publish_delivery_manylinux_arm64.sh | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/docker/Makefile b/tools/docker/Makefile index dfde496a29a..8b8b3ff281a 100644 --- a/tools/docker/Makefile +++ b/tools/docker/Makefile @@ -212,7 +212,7 @@ define manylinux_inner = .PHONY: python_$1_manylinux_cp$2_$3 python_$1_manylinux_cp$2_$3: python/$1/manylinux.Dockerfile export/python/manylinux/build-manylinux.sh @docker image rm -f ${IMAGE}:$$@ 2>/dev/null - ${DOCKER_BUILD_CMD} \ + ${DOCKER_BUILDX_CMD} --platform linux/$1 \ --tag ${IMAGE}:$$@ \ --build-arg GIT_BRANCH=${OR_TOOLS_BRANCH} \ --build-arg GIT_SHA1=${OR_TOOLS_SHA1} \ diff --git a/tools/release/build_delivery_manylinux_amd64.sh b/tools/release/build_delivery_manylinux_amd64.sh index 5c49e443fea..735b69a018f 100755 --- a/tools/release/build_delivery_manylinux_amd64.sh +++ b/tools/release/build_delivery_manylinux_amd64.sh @@ -73,8 +73,9 @@ function build_delivery() { cd "${RELEASE_DIR}" || exit 2 # Build env + # https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63 echo -n "Build ${ORTOOLS_IMG}:env..." | tee -a "${ROOT_DIR}/build.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":env \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ @@ -84,7 +85,7 @@ function build_delivery() { # Build devel echo -n "Build ${ORTOOLS_IMG}:devel..." | tee -a "${ROOT_DIR}/build.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":devel \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ @@ -94,7 +95,7 @@ function build_delivery() { # Build delivery echo -n "Build ${ORTOOLS_IMG}:${ORTOOLS_DELIVERY}..." | tee -a "${ROOT_DIR}/build.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":"${ORTOOLS_DELIVERY}" \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ diff --git a/tools/release/publish_delivery_manylinux_amd64.sh b/tools/release/publish_delivery_manylinux_amd64.sh index bb694ed92ac..a80d2e95988 100755 --- a/tools/release/publish_delivery_manylinux_amd64.sh +++ b/tools/release/publish_delivery_manylinux_amd64.sh @@ -70,7 +70,7 @@ function publish_delivery() { # Publish delivery echo -n "Build ${ORTOOLS_IMG}:publish_${ORTOOLS_DELIVERY}..." | tee -a "${ROOT_DIR}/publish.log" - docker buildx build \ + docker buildx build --platform linux/amd64 \ --tag "${ORTOOLS_IMG}":"publish_${ORTOOLS_DELIVERY}" \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ diff --git a/tools/release/publish_delivery_manylinux_arm64.sh b/tools/release/publish_delivery_manylinux_arm64.sh index 653b0b89a43..3c5f6488534 100755 --- a/tools/release/publish_delivery_manylinux_arm64.sh +++ b/tools/release/publish_delivery_manylinux_arm64.sh @@ -70,7 +70,7 @@ function publish_delivery() { # Publish delivery echo -n "Build ${ORTOOLS_IMG}:publish_${ORTOOLS_DELIVERY}..." | tee -a "${ROOT_DIR}/publish.log" - docker buildx build \ + docker buildx build --platform linux/arm64 \ --tag "${ORTOOLS_IMG}":"publish_${ORTOOLS_DELIVERY}" \ --build-arg ORTOOLS_GIT_BRANCH="${ORTOOLS_BRANCH}" \ --build-arg ORTOOLS_GIT_SHA1="${ORTOOLS_SHA1}" \ From 5b6c803db3f1461c7ebb7defd39f5aad4d3e73fe Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Thu, 16 Nov 2023 19:46:56 +0100 Subject: [PATCH 20/21] [CP-SAT] convert to PEP8 convention --- examples/python/appointments.py | 47 +- .../python/assignment_with_constraints_sat.py | 35 +- examples/python/balance_group_sat.py | 42 +- examples/python/bus_driver_scheduling_sat.py | 142 +- examples/python/chemical_balance_sat.py | 23 +- examples/python/clustering_sat.py | 16 +- examples/python/cover_rectangle_sat.py | 55 +- examples/python/cryptarithm_sat.py | 58 +- examples/python/flexible_job_shop_sat.py | 56 +- examples/python/gate_scheduling_sat.py | 66 +- examples/python/golomb_sat.py | 32 +- examples/python/hidato_sat.py | 43 +- examples/python/jobshop_ft06_distance_sat.py | 36 +- examples/python/jobshop_ft06_sat.py | 28 +- .../python/jobshop_with_maintenance_sat.py | 39 +- examples/python/knapsack_2d_sat.py | 167 +- examples/python/line_balancing_sat.py | 72 +- examples/python/maze_escape_sat.py | 20 +- .../memory_layout_and_infeasibility_sat.py | 179 ++ .../python/no_wait_baking_scheduling_sat.py | 24 +- examples/python/nqueens_sat.py | 39 +- examples/python/prize_collecting_tsp_sat.py | 31 +- examples/python/prize_collecting_vrp_sat.py | 36 +- examples/python/proto_solve.py | 4 +- examples/python/qubo_sat.py | 18 +- examples/python/rcpsp_sat.py | 186 +- examples/python/shift_scheduling_sat.py | 130 +- ...duling_with_setup_release_due_dates_sat.py | 36 +- examples/python/spread_robots_sat.py | 38 +- examples/python/steel_mill_slab_sat.py | 100 +- examples/python/sudoku_sat.py | 16 +- examples/python/task_allocation_sat.py | 34 +- .../tasks_and_workers_assignment_sat.py | 46 +- examples/python/tsp_sat.py | 12 +- examples/python/vendor_scheduling_sat.py | 34 +- examples/python/wedding_optimal_chart_sat.py | 42 +- .../python/weighted_latency_problem_sat.py | 24 +- examples/python/zebra_sat.py | 130 +- ortools/sat/BUILD.bazel | 874 ++++++--- ortools/sat/clause.cc | 9 +- ortools/sat/cp_model_checker.cc | 7 +- ortools/sat/cp_model_expand.cc | 30 +- ortools/sat/cp_model_presolve.cc | 36 +- ortools/sat/cp_model_presolve.h | 8 +- ortools/sat/cp_model_solver.cc | 11 + ortools/sat/docs/README.md | 16 +- ortools/sat/docs/boolean_logic.md | 40 +- ortools/sat/docs/channeling.md | 57 +- ortools/sat/docs/integer_arithmetic.md | 88 +- ortools/sat/docs/model.md | 48 +- ortools/sat/docs/scheduling.md | 320 ++-- ortools/sat/docs/solver.md | 91 +- ortools/sat/docs/troubleshooting.md | 28 +- ortools/sat/feasibility_jump.cc | 12 +- ortools/sat/lp_utils.cc | 9 + ortools/sat/presolve_context.cc | 10 +- ortools/sat/presolve_context.h | 1 - ortools/sat/python/cp_model.py | 1576 ++++++++++------- ortools/sat/python/cp_model_test.py | 1370 +++++++------- ortools/sat/python/swig_helper.cc | 25 +- ortools/sat/python/swig_helper_test.py | 31 +- ortools/sat/samples/assignment_groups_sat.py | 26 +- ortools/sat/samples/assignment_sat.py | 17 +- .../sat/samples/assignment_task_sizes_sat.py | 16 +- ortools/sat/samples/assignment_teams_sat.py | 20 +- ortools/sat/samples/assumptions_sample_sat.py | 28 +- ortools/sat/samples/bin_packing_sat.py | 23 +- ortools/sat/samples/binpacking_problem_sat.py | 28 +- ortools/sat/samples/bool_or_sample_sat.py | 6 +- .../sat/samples/boolean_product_sample_sat.py | 14 +- ortools/sat/samples/channeling_sample_sat.py | 29 +- ortools/sat/samples/clone_model_sample_sat.py | 28 +- ortools/sat/samples/cp_is_fun_sat.py | 47 +- ortools/sat/samples/cp_sat_example.py | 32 +- .../cumulative_variable_profile_sample_sat.py | 21 +- .../earliness_tardiness_cost_sample_sat.py | 29 +- ortools/sat/samples/interval_sample_sat.py | 10 +- ortools/sat/samples/literal_sample_sat.py | 4 +- ortools/sat/samples/minimal_jobshop_sat.py | 32 +- ortools/sat/samples/multiple_knapsack_sat.py | 20 +- ortools/sat/samples/no_overlap_sample_sat.py | 42 +- ortools/sat/samples/non_linear_sat.py | 22 +- ortools/sat/samples/nqueens_sat.py | 25 +- ortools/sat/samples/nurses_sat.py | 26 +- .../samples/optional_interval_sample_sat.py | 12 +- .../overlapping_intervals_sample_sat.py | 53 +- .../sat/samples/rabbits_and_pheasants_sat.py | 12 +- .../sat/samples/ranking_circuit_sample_sat.py | 61 +- ortools/sat/samples/ranking_sample_sat.py | 80 +- ortools/sat/samples/reified_sample_sat.py | 16 +- ortools/sat/samples/schedule_requests_sat.py | 25 +- .../scheduling_with_calendar_sample_sat.py | 41 +- .../search_for_all_solutions_sample_sat.py | 23 +- ortools/sat/samples/simple_sat_program.py | 16 +- .../samples/solution_hinting_sample_sat.py | 20 +- ...print_intermediate_solutions_sample_sat.py | 27 +- .../solve_with_time_limit_sample_sat.py | 16 +- .../sat/samples/step_function_sample_sat.py | 47 +- .../stop_after_n_solutions_sample_sat.py | 25 +- ortools/util/fp_utils.cc | 7 + ortools/util/python/sorted_interval_list.cc | 46 +- .../util/python/sorted_interval_list_test.py | 72 +- ortools/util/zvector.h | 11 +- 103 files changed, 4603 insertions(+), 3385 deletions(-) mode change 100755 => 100644 examples/python/appointments.py mode change 100755 => 100644 examples/python/maze_escape_sat.py create mode 100644 examples/python/memory_layout_and_infeasibility_sat.py mode change 100755 => 100644 examples/python/no_wait_baking_scheduling_sat.py mode change 100755 => 100644 examples/python/prize_collecting_tsp_sat.py mode change 100755 => 100644 examples/python/prize_collecting_vrp_sat.py mode change 100755 => 100644 examples/python/rcpsp_sat.py mode change 100755 => 100644 examples/python/steel_mill_slab_sat.py mode change 100644 => 100755 examples/python/sudoku_sat.py mode change 100644 => 100755 examples/python/zebra_sat.py mode change 100755 => 100644 ortools/sat/samples/assumptions_sample_sat.py mode change 100755 => 100644 ortools/sat/samples/rabbits_and_pheasants_sat.py diff --git a/examples/python/appointments.py b/examples/python/appointments.py old mode 100755 new mode 100644 index e88571070de..017e8c90099 --- a/examples/python/appointments.py +++ b/examples/python/appointments.py @@ -44,17 +44,19 @@ def __init__(self, variables): self.__variables = variables self.__collect = [] - def on_solution_callback(self): + def on_solution_callback(self) -> None: """Collect a new combination.""" - combination = [self.Value(v) for v in self.__variables] + combination = [self.value(v) for v in self.__variables] self.__collect.append(combination) - def combinations(self): + def combinations(self) -> list[list[int]]: """Returns all collected combinations.""" return self.__collect -def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max): +def EnumerateAllKnapsacksWithRepetition( + item_sizes: list[int], total_size_min: int, total_size_max: int +) -> list[list[int]]: """Enumerate all possible knapsacks with total size in the given range. Args: @@ -68,22 +70,26 @@ def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_m nonnegative integer: the number of times we put item #K in the knapsack. """ model = cp_model.CpModel() - variables = [model.NewIntVar(0, total_size_max // size, "") for size in item_sizes] + variables = [ + model.new_int_var(0, total_size_max // size, "") for size in item_sizes + ] load = sum(variables[i] * size for i, size in enumerate(item_sizes)) - model.AddLinearConstraint(load, total_size_min, total_size_max) + model.add_linear_constraint(load, total_size_min, total_size_max) solver = cp_model.CpSolver() solution_collector = AllSolutionCollector(variables) # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True - # Solve - solver.Solve(model, solution_collector) + # solve + solver.solve(model, solution_collector) return solution_collector.combinations() def AggregateItemCollectionsOptimally( - item_collections, max_num_collections, ideal_item_ratios -): + item_collections: list[list[int]], + max_num_collections: int, + ideal_item_ratios: list[float], +) -> list[int]: """Selects a set (with repetition) of combination of items optimally. Given a set of collections of N possible items (in each collection, an item @@ -173,7 +179,9 @@ def AggregateItemCollectionsOptimally( return [] -def GetOptimalSchedule(demand): +def GetOptimalSchedule( + demand: list[tuple[float, str, int]] +) -> list[tuple[int, list[tuple[int, str]]]]: """Computes the optimal schedule for the installation input. Args: @@ -186,7 +194,9 @@ def GetOptimalSchedule(demand): The same output type as EnumerateAllKnapsacksWithRepetition. """ combinations = EnumerateAllKnapsacksWithRepetition( - [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value + [a[2] + _COMMUTE_TIME.value for a in demand], + _LOAD_MIN.value, + _LOAD_MAX.value, ) print( ( @@ -199,14 +209,14 @@ def GetOptimalSchedule(demand): combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand] ) output = [] - for i in range(len(selection)): - if selection[i] != 0: + for i, s in enumerate(selection): + if s != 0: output.append( ( - selection[i], + s, [ - (combinations[i][t], demand[t][1]) - for t in range(len(demand)) + (combinations[i][t], d[1]) + for t, d in enumerate(demand) if combinations[i][t] != 0 ], ) @@ -252,7 +262,8 @@ def main(_): per_type = installed_per_type[name] if installed != 0: print( - f" {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned" + f" {per_type} ({per_type * 100.0 / installed}%) installations of" + f" type {name} planned" ) else: print(f" {per_type} installations of type {name} planned") diff --git a/examples/python/assignment_with_constraints_sat.py b/examples/python/assignment_with_constraints_sat.py index b0dc5b5db6c..3a805787a12 100644 --- a/examples/python/assignment_with_constraints_sat.py +++ b/examples/python/assignment_with_constraints_sat.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve an assignment problem with combination constraints on workers.""" +"""solve an assignment problem with combination constraints on workers.""" from typing import Sequence from absl import app @@ -20,7 +20,7 @@ def solve_assignment(): - """Solve the assignment problem.""" + """solve the assignment problem.""" # Data. cost = [ [90, 76, 75, 70, 50, 74], @@ -73,44 +73,45 @@ def solve_assignment(): model = cp_model.CpModel() # Variables selected = [ - [model.NewBoolVar("x[%i,%i]" % (i, j)) for j in all_tasks] for i in all_workers + [model.new_bool_var("x[%i,%i]" % (i, j)) for j in all_tasks] + for i in all_workers ] - works = [model.NewBoolVar("works[%i]" % i) for i in all_workers] + works = [model.new_bool_var("works[%i]" % i) for i in all_workers] # Constraints # Link selected and workers. for i in range(num_workers): - model.AddMaxEquality(works[i], selected[i]) + model.add_max_equality(works[i], selected[i]) # Each task is assigned to at least one worker. for j in all_tasks: - model.Add(sum(selected[i][j] for i in all_workers) >= 1) + model.add(sum(selected[i][j] for i in all_workers) >= 1) # Total task size for each worker is at most total_size_max for i in all_workers: - model.Add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max) + model.add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max) # Group constraints. - model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1) - model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2) - model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3) + model.add_allowed_assignments([works[0], works[1], works[2], works[3]], group1) + model.add_allowed_assignments([works[4], works[5], works[6], works[7]], group2) + model.add_allowed_assignments([works[8], works[9], works[10], works[11]], group3) # Objective - model.Minimize( + model.minimize( sum(selected[i][j] * cost[i][j] for j in all_tasks for i in all_workers) ) # Solve and output solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print("Total cost = %i" % solver.ObjectiveValue()) + print("Total cost = %i" % solver.objective_value) print() for i in all_workers: for j in all_tasks: - if solver.BooleanValue(selected[i][j]): + if solver.boolean_value(selected[i][j]): print( "Worker ", i, " assigned to task ", j, " Cost = ", cost[i][j] ) @@ -118,9 +119,9 @@ def solve_assignment(): print() print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/balance_group_sat.py b/examples/python/balance_group_sat.py index b11fb8fe11f..87093264c72 100644 --- a/examples/python/balance_group_sat.py +++ b/examples/python/balance_group_sat.py @@ -41,14 +41,14 @@ def on_solution_callback(self): print("Solution %i" % self.__solution_count) self.__solution_count += 1 - print(" objective value = %i" % self.ObjectiveValue()) + print(" objective value = %i" % self.objective_value) groups = {} sums = {} for g in self.__all_groups: groups[g] = [] sums[g] = 0 for item in self.__all_items: - if self.BooleanValue(self.__item_in_group[(item, g)]): + if self.boolean_value(self.__item_in_group[(item, g)]): groups[g].append(item) sums[g] += self.__values[item] @@ -77,7 +77,7 @@ def main(argv: Sequence[str]) -> None: all_items = range(num_items) all_colors = range(num_colors) - # Values for each items. + # values for each items. values = [1 + i + (i * i // 200) for i in all_items] # Color for each item (simple modulo). colors = [i % num_colors for i in all_items] @@ -108,26 +108,26 @@ def main(argv: Sequence[str]) -> None: item_in_group = {} for i in all_items: for g in all_groups: - item_in_group[(i, g)] = model.NewBoolVar("item %d in group %d" % (i, g)) + item_in_group[(i, g)] = model.new_bool_var("item %d in group %d" % (i, g)) # Each group must have the same size. for g in all_groups: - model.Add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group) + model.add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group) # One item must belong to exactly one group. for i in all_items: - model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1) + model.add(sum(item_in_group[(i, g)] for g in all_groups) == 1) # The deviation of the sum of each items in a group against the average. - e = model.NewIntVar(0, 550, "epsilon") + e = model.new_int_var(0, 550, "epsilon") # Constrain the sum of values in one group around the average sum per group. for g in all_groups: - model.Add( + model.add( sum(item_in_group[(i, g)] * values[i] for i in all_items) <= average_sum_per_group + e ) - model.Add( + model.add( sum(item_in_group[(i, g)] * values[i] for i in all_items) >= average_sum_per_group - e ) @@ -136,24 +136,24 @@ def main(argv: Sequence[str]) -> None: color_in_group = {} for g in all_groups: for c in all_colors: - color_in_group[(c, g)] = model.NewBoolVar( + color_in_group[(c, g)] = model.new_bool_var( "color %d is in group %d" % (c, g) ) # Item is in a group implies its color is in that group. for i in all_items: for g in all_groups: - model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)]) + model.add_implication(item_in_group[(i, g)], color_in_group[(colors[i], g)]) # If a color is in a group, it must contains at least # min_items_of_same_color_per_group items from that color. for c in all_colors: for g in all_groups: literal = color_in_group[(c, g)] - model.Add( + model.add( sum(item_in_group[(i, g)] for i in items_per_color[c]) >= min_items_of_same_color_per_group - ).OnlyEnforceIf(literal) + ).only_enforce_if(literal) # Compute the maximum number of colors in a group. max_color = num_items_per_group // min_items_of_same_color_per_group @@ -161,10 +161,10 @@ def main(argv: Sequence[str]) -> None: # Redundant constraint, it helps with solving time. if max_color < num_colors: for g in all_groups: - model.Add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color) + model.add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color) - # Minimize epsilon - model.Minimize(e) + # minimize epsilon + model.minimize(e) solver = cp_model.CpSolver() # solver.parameters.log_search_progress = True @@ -172,14 +172,14 @@ def main(argv: Sequence[str]) -> None: solution_printer = SolutionPrinter( values, colors, all_groups, all_items, item_in_group ) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) if status == cp_model.OPTIMAL: - print("Optimal epsilon: %i" % solver.ObjectiveValue()) + print("Optimal epsilon: %i" % solver.objective_value) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) else: print("No solution found") diff --git a/examples/python/bus_driver_scheduling_sat.py b/examples/python/bus_driver_scheduling_sat.py index 18d5713f97a..a1972edc1f1 100644 --- a/examples/python/bus_driver_scheduling_sat.py +++ b/examples/python/bus_driver_scheduling_sat.py @@ -1708,7 +1708,7 @@ ] # yapf:disable -def bus_driver_scheduling(minimize_drivers, max_num_drivers): +def bus_driver_scheduling(minimize_drivers: bool, max_num_drivers: int) -> int: """Optimize the bus driver scheduling problem. This model has two modes. @@ -1806,14 +1806,14 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): for d in range(num_drivers): start_times.append( - model.NewIntVar(min_start_time - setup_time, max_end_time, "start_%i" % d) + model.new_int_var(min_start_time - setup_time, max_end_time, "start_%i" % d) ) end_times.append( - model.NewIntVar(min_start_time, max_end_time + cleanup_time, "end_%i" % d) + model.new_int_var(min_start_time, max_end_time + cleanup_time, "end_%i" % d) ) - driving_times.append(model.NewIntVar(0, max_driving_time, "driving_%i" % d)) + driving_times.append(model.new_int_var(0, max_driving_time, "driving_%i" % d)) working_times.append( - model.NewIntVar(0, max_working_time, "working_times_%i" % d) + model.new_int_var(0, max_working_time, "working_times_%i" % d) ) incoming_literals = collections.defaultdict(list) @@ -1824,13 +1824,13 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # Create all the shift variables before iterating on the transitions # between these shifts. for s in range(num_shifts): - total_driving[d, s] = model.NewIntVar( + total_driving[d, s] = model.new_int_var( 0, max_driving_time, "dr_%i_%i" % (d, s) ) - no_break_driving[d, s] = model.NewIntVar( + no_break_driving[d, s] = model.new_int_var( 0, max_driving_time_without_pauses, "mdr_%i_%i" % (d, s) ) - performed[d, s] = model.NewBoolVar("performed_%i_%i" % (d, s)) + performed[d, s] = model.new_bool_var("performed_%i_%i" % (d, s)) for s in range(num_shifts): shift = shifts[s] @@ -1839,42 +1839,48 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): # Arc from source to shift. # - set the start time of the driver # - increase driving time and driving time since break - source_lit = model.NewBoolVar("%i from source to %i" % (d, s)) + source_lit = model.new_bool_var("%i from source to %i" % (d, s)) outgoing_source_literals.append(source_lit) incoming_literals[s].append(source_lit) shared_incoming_literals[s].append(source_lit) - model.Add(start_times[d] == shift[3] - setup_time).OnlyEnforceIf(source_lit) - model.Add(total_driving[d, s] == duration).OnlyEnforceIf(source_lit) - model.Add(no_break_driving[d, s] == duration).OnlyEnforceIf(source_lit) + model.add(start_times[d] == shift[3] - setup_time).only_enforce_if( + source_lit + ) + model.add(total_driving[d, s] == duration).only_enforce_if(source_lit) + model.add(no_break_driving[d, s] == duration).only_enforce_if(source_lit) starting_shifts[d, s] = source_lit # Arc from shift to sink # - set the end time of the driver # - set the driving times of the driver - sink_lit = model.NewBoolVar("%i from %i to sink" % (d, s)) + sink_lit = model.new_bool_var("%i from %i to sink" % (d, s)) outgoing_literals[s].append(sink_lit) shared_outgoing_literals[s].append(sink_lit) incoming_sink_literals.append(sink_lit) - model.Add(end_times[d] == shift[4] + cleanup_time).OnlyEnforceIf(sink_lit) - model.Add(driving_times[d] == total_driving[d, s]).OnlyEnforceIf(sink_lit) + model.add(end_times[d] == shift[4] + cleanup_time).only_enforce_if(sink_lit) + model.add(driving_times[d] == total_driving[d, s]).only_enforce_if(sink_lit) # Node not performed # - set both driving times to 0 # - add a looping arc on the node - model.Add(total_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not()) - model.Add(no_break_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not()) - incoming_literals[s].append(performed[d, s].Not()) - outgoing_literals[s].append(performed[d, s].Not()) - # Not adding to the shared lists, because, globally, each node will have - # one incoming literal, and one outgoing literal. + model.add(total_driving[d, s] == 0).only_enforce_if( + performed[d, s].negated() + ) + model.add(no_break_driving[d, s] == 0).only_enforce_if( + performed[d, s].negated() + ) + incoming_literals[s].append(performed[d, s].negated()) + outgoing_literals[s].append(performed[d, s].negated()) + # negated adding to the shared lists, because, globally, each node will + # have one incoming literal, and one outgoing literal. # Node performed: # - add upper bound on start_time # - add lower bound on end_times - model.Add(start_times[d] <= shift[3] - setup_time).OnlyEnforceIf( + model.add(start_times[d] <= shift[3] - setup_time).only_enforce_if( performed[d, s] ) - model.Add(end_times[d] >= shift[4] + cleanup_time).OnlyEnforceIf( + model.add(end_times[d] >= shift[4] + cleanup_time).only_enforce_if( performed[d, s] ) @@ -1883,22 +1889,22 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): delay = other[3] - shift[4] if delay < min_delay_between_shifts: continue - lit = model.NewBoolVar("%i from %i to %i" % (d, s, o)) + lit = model.new_bool_var("%i from %i to %i" % (d, s, o)) # Increase driving time - model.Add( + model.add( total_driving[d, o] == total_driving[d, s] + other[5] - ).OnlyEnforceIf(lit) + ).only_enforce_if(lit) # Increase no_break_driving or reset it to 0 depending on the delay if delay >= min_pause_after_4h: - model.Add(no_break_driving[d, o] == other[5]).OnlyEnforceIf(lit) + model.add(no_break_driving[d, o] == other[5]).only_enforce_if(lit) else: - model.Add( + model.add( no_break_driving[d, o] == no_break_driving[d, s] + other[5] - ).OnlyEnforceIf(lit) + ).only_enforce_if(lit) - # Add arc + # add arc outgoing_literals[s].append(lit) shared_outgoing_literals[s].append(lit) incoming_literals[o].append(lit) @@ -1908,68 +1914,72 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): delay_literals.append(lit) delay_weights.append(delay) - model.Add(working_times[d] == end_times[d] - start_times[d]) + model.add(working_times[d] == end_times[d] - start_times[d]) if minimize_drivers: # Driver is not working. - working = model.NewBoolVar("working_%i" % d) - model.Add(start_times[d] == min_start_time).OnlyEnforceIf(working.Not()) - model.Add(end_times[d] == min_start_time).OnlyEnforceIf(working.Not()) - model.Add(driving_times[d] == 0).OnlyEnforceIf(working.Not()) + working = model.new_bool_var("working_%i" % d) + model.add(start_times[d] == min_start_time).only_enforce_if( + working.negated() + ) + model.add(end_times[d] == min_start_time).only_enforce_if(working.negated()) + model.add(driving_times[d] == 0).only_enforce_if(working.negated()) working_drivers.append(working) - outgoing_source_literals.append(working.Not()) - incoming_sink_literals.append(working.Not()) + outgoing_source_literals.append(working.negated()) + incoming_sink_literals.append(working.negated()) # Conditional working time constraints - model.Add(working_times[d] >= min_working_time).OnlyEnforceIf(working) - model.Add(working_times[d] == 0).OnlyEnforceIf(working.Not()) + model.add(working_times[d] >= min_working_time).only_enforce_if(working) + model.add(working_times[d] == 0).only_enforce_if(working.negated()) else: # Working time constraints - model.Add(working_times[d] >= min_working_time) + model.add(working_times[d] >= min_working_time) # Create circuit constraint. - model.AddExactlyOne(outgoing_source_literals) + model.add_exactly_one(outgoing_source_literals) for s in range(num_shifts): - model.AddExactlyOne(outgoing_literals[s]) - model.AddExactlyOne(incoming_literals[s]) - model.AddExactlyOne(incoming_sink_literals) + model.add_exactly_one(outgoing_literals[s]) + model.add_exactly_one(incoming_literals[s]) + model.add_exactly_one(incoming_sink_literals) # Each shift is covered. for s in range(num_shifts): - model.AddExactlyOne(performed[d, s] for d in range(num_drivers)) + model.add_exactly_one(performed[d, s] for d in range(num_drivers)) # Globally, each node has one incoming and one outgoing literal - model.AddExactlyOne(shared_incoming_literals[s]) - model.AddExactlyOne(shared_outgoing_literals[s]) + model.add_exactly_one(shared_incoming_literals[s]) + model.add_exactly_one(shared_outgoing_literals[s]) # Symmetry breaking # The first 3 shifts must be performed by 3 different drivers. # Let's assign them to the first 3 drivers in sequence - model.Add(starting_shifts[0, 0] == 1) - model.Add(starting_shifts[1, 1] == 1) - model.Add(starting_shifts[2, 2] == 1) + model.add(starting_shifts[0, 0] == 1) + model.add(starting_shifts[1, 1] == 1) + model.add(starting_shifts[2, 2] == 1) if minimize_drivers: # Push non working drivers to the end for d in range(num_drivers - 1): - model.AddImplication(working_drivers[d].Not(), working_drivers[d + 1].Not()) + model.add_implication( + working_drivers[d].negated(), working_drivers[d + 1].negated() + ) # Redundant constraints: sum of driving times = sum of shift driving times - model.Add(cp_model.LinearExpr.Sum(driving_times) == total_driving_time) + model.add(cp_model.LinearExpr.sum(driving_times) == total_driving_time) if not minimize_drivers: - model.Add( - cp_model.LinearExpr.Sum(working_times) + model.add( + cp_model.LinearExpr.sum(working_times) == total_driving_time + num_drivers * (setup_time + cleanup_time) - + cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights) + + cp_model.LinearExpr.weighted_sum(delay_literals, delay_weights) ) if minimize_drivers: - # Minimize the number of working drivers - model.Minimize(cp_model.LinearExpr.Sum(working_drivers)) + # minimize the number of working drivers + model.minimize(cp_model.LinearExpr.sum(working_drivers)) else: - # Minimize the sum of delays between tasks, which in turns minimize the + # minimize the sum of delays between tasks, which in turns minimize the # sum of working times as the total driving time is fixed - model.Minimize(cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights)) + model.minimize(cp_model.LinearExpr.weighted_sum(delay_literals, delay_weights)) if not minimize_drivers and _OUTPUT_PROTO.value: print("Writing proto to %s" % _OUTPUT_PROTO.value) @@ -1981,41 +1991,41 @@ def bus_driver_scheduling(minimize_drivers, max_num_drivers): if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) if status != cp_model.OPTIMAL and status != cp_model.FEASIBLE: return -1 # Display solution if minimize_drivers: - max_num_drivers = int(solver.ObjectiveValue()) + max_num_drivers = int(solver.objective_value) print("minimal number of drivers =", max_num_drivers) return max_num_drivers for d in range(num_drivers): print("Driver %i: " % (d + 1)) - print(" total driving time =", solver.Value(driving_times[d])) + print(" total driving time =", solver.value(driving_times[d])) print( " working time =", - solver.Value(working_times[d]) + setup_time + cleanup_time, + solver.value(working_times[d]) + setup_time + cleanup_time, ) first = True for s in range(num_shifts): shift = shifts[s] - if not solver.BooleanValue(performed[d, s]): + if not solver.boolean_value(performed[d, s]): continue # Hack to detect if the waiting time between the last shift and # this one exceeds 30 minutes. For this, we look at the # no_break_driving which was reinitialized in that case. - if solver.Value(no_break_driving[d, s]) == shift[5] and not first: + if solver.value(no_break_driving[d, s]) == shift[5] and not first: print(" **break**") print(" shift ", shift[0], ":", shift[1], "-", shift[2]) first = False - return int(solver.ObjectiveValue()) + return int(solver.objective_value) def main(_): diff --git a/examples/python/chemical_balance_sat.py b/examples/python/chemical_balance_sat.py index 6585e7d12ce..d3604c6bfdb 100644 --- a/examples/python/chemical_balance_sat.py +++ b/examples/python/chemical_balance_sat.py @@ -70,37 +70,40 @@ def chemical_balance(): for s in all_sets ] - set_vars = [model.NewIntVar(0, max_set[s], f"set_{s}") for s in all_sets] + set_vars = [model.new_int_var(0, max_set[s], f"set_{s}") for s in all_sets] - epsilon = model.NewIntVar(0, 10000000, "epsilon") + epsilon = model.new_int_var(0, 10000000, "epsilon") for p in all_products: - model.Add( + model.add( sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets) <= int(max_quantities[p][1] * 10000) ) - model.Add( + model.add( sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets) >= int(max_quantities[p][1] * 10000) - epsilon ) - model.Minimize(epsilon) + model.minimize(epsilon) # Creates a solver and solves. solver = cp_model.CpSolver() - status = solver.Solve(model) - print(f"Status = {solver.StatusName(status)}") + status = solver.solve(model) + print(f"Status = {solver.status_name(status)}") # The objective value of the solution. - print(f"Optimal objective value = {solver.ObjectiveValue() / 10000.0}") + print(f"Optimal objective value = {solver.objective_value / 10000.0}") for s in all_sets: - print(f" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}", end=" ") + print( + f" {chemical_set[s][0]} = {solver.value(set_vars[s]) / 1000.0}", + end=" ", + ) print() for p in all_products: name = max_quantities[p][0] max_quantity = max_quantities[p][1] quantity = sum( - solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1] + solver.value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1] for s in all_sets ) print(f"{name}: {quantity} out of {max_quantity}") diff --git a/examples/python/clustering_sat.py b/examples/python/clustering_sat.py index e4acc1809f4..9cfb60cf0d0 100644 --- a/examples/python/clustering_sat.py +++ b/examples/python/clustering_sat.py @@ -83,14 +83,14 @@ def clustering_sat(): obj_coeffs = [] for n1 in range(num_nodes - 1): for n2 in range(n1 + 1, num_nodes): - same = model.NewBoolVar("neighbors_%i_%i" % (n1, n2)) + same = model.new_bool_var("neighbors_%i_%i" % (n1, n2)) neighbors[n1, n2] = same obj_vars.append(same) obj_coeffs.append(distance_matrix[n1][n2] + distance_matrix[n2][n1]) # Number of neighborss: for n in range(num_nodes): - model.Add( + model.add( sum(neighbors[m, n] for m in range(n)) + sum(neighbors[n, m] for m in range(n + 1, num_nodes)) == group_size - 1 @@ -100,23 +100,23 @@ def clustering_sat(): for n1 in range(num_nodes - 2): for n2 in range(n1 + 1, num_nodes - 1): for n3 in range(n2 + 1, num_nodes): - model.Add( + model.add( neighbors[n1, n3] + neighbors[n2, n3] + neighbors[n1, n2] != 2 ) # Redundant constraints on total sum of neighborss. - model.Add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2) + model.add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2) # Minimize weighted sum of arcs. - model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.num_search_workers = 8 - status = solver.Solve(model) - print(solver.ResponseStats()) + status = solver.solve(model) + print(solver.response_stats()) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: visited = set() @@ -126,7 +126,7 @@ def clustering_sat(): visited.add(n) output = str(n) for o in range(n + 1, num_nodes): - if solver.BooleanValue(neighbors[n, o]): + if solver.boolean_value(neighbors[n, o]): visited.add(o) output += " " + str(o) print("Group", g, ":", output) diff --git a/examples/python/cover_rectangle_sat.py b/examples/python/cover_rectangle_sat.py index dc4dcd640ed..2beb09ce0a4 100644 --- a/examples/python/cover_rectangle_sat.py +++ b/examples/python/cover_rectangle_sat.py @@ -19,7 +19,7 @@ from ortools.sat.python import cp_model -def cover_rectangle(num_squares): +def cover_rectangle(num_squares: int) -> bool: """Try to fill the rectangle with a given number of squares.""" size_x = 60 size_y = 50 @@ -35,17 +35,17 @@ def cover_rectangle(num_squares): # Creates intervals for the NoOverlap2D and size variables. for i in range(num_squares): - size = model.NewIntVar(1, size_y, "size_%i" % i) - start_x = model.NewIntVar(0, size_x, "sx_%i" % i) - end_x = model.NewIntVar(0, size_x, "ex_%i" % i) - start_y = model.NewIntVar(0, size_y, "sy_%i" % i) - end_y = model.NewIntVar(0, size_y, "ey_%i" % i) + size = model.new_int_var(1, size_y, "size_%i" % i) + start_x = model.new_int_var(0, size_x, "sx_%i" % i) + end_x = model.new_int_var(0, size_x, "ex_%i" % i) + start_y = model.new_int_var(0, size_y, "sy_%i" % i) + end_y = model.new_int_var(0, size_y, "ey_%i" % i) - interval_x = model.NewIntervalVar(start_x, size, end_x, "ix_%i" % i) - interval_y = model.NewIntervalVar(start_y, size, end_y, "iy_%i" % i) + interval_x = model.new_interval_var(start_x, size, end_x, "ix_%i" % i) + interval_y = model.new_interval_var(start_y, size, end_y, "iy_%i" % i) - area = model.NewIntVar(1, size_y * size_y, "area_%i" % i) - model.AddMultiplicationEquality(area, [size, size]) + area = model.new_int_var(1, size_y * size_y, "area_%i" % i) + model.add_multiplication_equality(area, [size, size]) areas.append(area) x_intervals.append(interval_x) @@ -55,47 +55,46 @@ def cover_rectangle(num_squares): y_starts.append(start_y) # Main constraint. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) # Redundant constraints. - model.AddCumulative(x_intervals, sizes, size_y) - model.AddCumulative(y_intervals, sizes, size_x) + model.add_cumulative(x_intervals, sizes, size_y) + model.add_cumulative(y_intervals, sizes, size_x) # Forces the rectangle to be exactly covered. - model.Add(sum(areas) == size_x * size_y) + model.add(sum(areas) == size_x * size_y) # Symmetry breaking 1: sizes are ordered. for i in range(num_squares - 1): - model.Add(sizes[i] <= sizes[i + 1]) + model.add(sizes[i] <= sizes[i + 1]) # Define same to be true iff sizes[i] == sizes[i + 1] - same = model.NewBoolVar("") - model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same) - model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not()) + same = model.new_bool_var("") + model.add(sizes[i] == sizes[i + 1]).only_enforce_if(same) + model.add(sizes[i] < sizes[i + 1]).only_enforce_if(same.negated()) # Tie break with starts. - model.Add(x_starts[i] <= x_starts[i + 1]).OnlyEnforceIf(same) + model.add(x_starts[i] <= x_starts[i + 1]).only_enforce_if(same) # Symmetry breaking 2: first square in one quadrant. - model.Add(x_starts[0] < (size_x + 1) // 2) - model.Add(y_starts[0] < (size_y + 1) // 2) + model.add(x_starts[0] < (size_x + 1) // 2) + model.add(y_starts[0] < (size_y + 1) // 2) # Creates a solver and solves. solver = cp_model.CpSolver() - solver.parameters.num_workers = 16 - # solver.parameters.log_search_progress = True + solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 10.0 - status = solver.Solve(model) - print("%s found in %0.2fs" % (solver.StatusName(status), solver.WallTime())) + status = solver.solve(model) + print("%s found in %0.2fs" % (solver.status_name(status), solver.wall_time)) # Prints solution. solution_found = status == cp_model.OPTIMAL or status == cp_model.FEASIBLE if solution_found: display = [[" " for _ in range(size_x)] for _ in range(size_y)] for i in range(num_squares): - sol_x = solver.Value(x_starts[i]) - sol_y = solver.Value(y_starts[i]) - sol_s = solver.Value(sizes[i]) + sol_x = solver.value(x_starts[i]) + sol_y = solver.value(y_starts[i]) + sol_s = solver.value(sizes[i]) char = format(i, "01x") for j in range(sol_s): for k in range(sol_s): diff --git a/examples/python/cryptarithm_sat.py b/examples/python/cryptarithm_sat.py index d96b5c6ad37..c519d990e0f 100644 --- a/examples/python/cryptarithm_sat.py +++ b/examples/python/cryptarithm_sat.py @@ -20,58 +20,58 @@ def send_more_money(): - """Solve the cryptarithmic puzzle SEND+MORE=MONEY.""" + """solve the cryptarithmic puzzle SEND+MORE=MONEY.""" model = cp_model.CpModel() # Create variables. # Since s is a leading digit, it can't be 0. - s = model.NewIntVar(1, 9, "s") - e = model.NewIntVar(0, 9, "e") - n = model.NewIntVar(0, 9, "n") - d = model.NewIntVar(0, 9, "d") + s = model.new_int_var(1, 9, "s") + e = model.new_int_var(0, 9, "e") + n = model.new_int_var(0, 9, "n") + d = model.new_int_var(0, 9, "d") # Since m is a leading digit, it can't be 0. - m = model.NewIntVar(1, 9, "m") - o = model.NewIntVar(0, 9, "o") - r = model.NewIntVar(0, 9, "r") - y = model.NewIntVar(0, 9, "y") + m = model.new_int_var(1, 9, "m") + o = model.new_int_var(0, 9, "o") + r = model.new_int_var(0, 9, "r") + y = model.new_int_var(0, 9, "y") # Create carry variables. c0 is true if the first column of addends carries # a 1, c2 is true if the second column carries a 1, and so on. - c0 = model.NewBoolVar("c0") - c1 = model.NewBoolVar("c1") - c2 = model.NewBoolVar("c2") - c3 = model.NewBoolVar("c3") + c0 = model.new_bool_var("c0") + c1 = model.new_bool_var("c1") + c2 = model.new_bool_var("c2") + c3 = model.new_bool_var("c3") # Force all letters to take on different values. - model.AddAllDifferent(s, e, n, d, m, o, r, y) + model.add_all_different(s, e, n, d, m, o, r, y) # Column 0: - model.Add(c0 == m) + model.add(c0 == m) # Column 1: - model.Add(c1 + s + m == o + 10 * c0) + model.add(c1 + s + m == o + 10 * c0) # Column 2: - model.Add(c2 + e + o == n + 10 * c1) + model.add(c2 + e + o == n + 10 * c1) # Column 3: - model.Add(c3 + n + r == e + 10 * c2) + model.add(c3 + n + r == e + 10 * c2) # Column 4: - model.Add(d + e == y + 10 * c3) + model.add(d + e == y + 10 * c3) - # Solve model. + # solve model. solver = cp_model.CpSolver() - if solver.Solve(model) == cp_model.OPTIMAL: + if solver.solve(model) == cp_model.OPTIMAL: print("Optimal solution found!") - print("s:", solver.Value(s)) - print("e:", solver.Value(e)) - print("n:", solver.Value(n)) - print("d:", solver.Value(d)) - print("m:", solver.Value(m)) - print("o:", solver.Value(o)) - print("r:", solver.Value(r)) - print("y:", solver.Value(y)) + print("s:", solver.value(s)) + print("e:", solver.value(e)) + print("n:", solver.value(n)) + print("d:", solver.value(d)) + print("m:", solver.value(m)) + print("o:", solver.value(o)) + print("r:", solver.value(r)) + print("y:", solver.value(y)) def main(_): diff --git a/examples/python/flexible_job_shop_sat.py b/examples/python/flexible_job_shop_sat.py index 9eea9d24f41..f216ae9b243 100644 --- a/examples/python/flexible_job_shop_sat.py +++ b/examples/python/flexible_job_shop_sat.py @@ -41,13 +41,13 @@ def on_solution_callback(self): """Called at each new solution.""" print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 def flexible_jobshop(): - """Solve a small flexible jobshop problem.""" + """solve a small flexible jobshop problem.""" # Data part. jobs = [ # task = (processing_time, machine_id) [ # Job 0 @@ -113,12 +113,12 @@ def flexible_jobshop(): # Create main interval for the task. suffix_name = "_j%i_t%i" % (job_id, task_id) - start = model.NewIntVar(0, horizon, "start" + suffix_name) - duration = model.NewIntVar( + start = model.new_int_var(0, horizon, "start" + suffix_name) + duration = model.new_int_var( min_duration, max_duration, "duration" + suffix_name ) - end = model.NewIntVar(0, horizon, "end" + suffix_name) - interval = model.NewIntervalVar( + end = model.new_int_var(0, horizon, "end" + suffix_name) + interval = model.new_interval_var( start, duration, end, "interval" + suffix_name ) @@ -127,7 +127,7 @@ def flexible_jobshop(): # Add precedence with previous task in the same job. if previous_end is not None: - model.Add(start >= previous_end) + model.add(start >= previous_end) previous_end = end # Create alternative intervals. @@ -135,19 +135,19 @@ def flexible_jobshop(): l_presences = [] for alt_id in all_alternatives: alt_suffix = "_j%i_t%i_a%i" % (job_id, task_id, alt_id) - l_presence = model.NewBoolVar("presence" + alt_suffix) - l_start = model.NewIntVar(0, horizon, "start" + alt_suffix) + l_presence = model.new_bool_var("presence" + alt_suffix) + l_start = model.new_int_var(0, horizon, "start" + alt_suffix) l_duration = task[alt_id][0] - l_end = model.NewIntVar(0, horizon, "end" + alt_suffix) - l_interval = model.NewOptionalIntervalVar( + l_end = model.new_int_var(0, horizon, "end" + alt_suffix) + l_interval = model.new_optional_interval_var( l_start, l_duration, l_end, l_presence, "interval" + alt_suffix ) l_presences.append(l_presence) # Link the primary/global variables with the local ones. - model.Add(start == l_start).OnlyEnforceIf(l_presence) - model.Add(duration == l_duration).OnlyEnforceIf(l_presence) - model.Add(end == l_end).OnlyEnforceIf(l_presence) + model.add(start == l_start).only_enforce_if(l_presence) + model.add(duration == l_duration).only_enforce_if(l_presence) + model.add(end == l_end).only_enforce_if(l_presence) # Add the local interval to the right machine. intervals_per_resources[task[alt_id][1]].append(l_interval) @@ -156,10 +156,10 @@ def flexible_jobshop(): presences[(job_id, task_id, alt_id)] = l_presence # Select exactly one presence variable. - model.AddExactlyOne(l_presences) + model.add_exactly_one(l_presences) else: intervals_per_resources[task[0][1]].append(interval) - presences[(job_id, task_id, 0)] = model.NewConstant(1) + presences[(job_id, task_id, 0)] = model.new_constant(1) job_ends.append(previous_end) @@ -167,28 +167,28 @@ def flexible_jobshop(): for machine_id in all_machines: intervals = intervals_per_resources[machine_id] if len(intervals) > 1: - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Makespan objective - makespan = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(makespan, job_ends) - model.Minimize(makespan) + makespan = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(makespan, job_ends) + model.minimize(makespan) # Solve model. solver = cp_model.CpSolver() solution_printer = SolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Print final solution. for job_id in all_jobs: print("Job %i:" % job_id) for task_id in range(len(jobs[job_id])): - start_value = solver.Value(starts[(job_id, task_id)]) + start_value = solver.value(starts[(job_id, task_id)]) machine = -1 duration = -1 selected = -1 for alt_id in range(len(jobs[job_id][task_id])): - if solver.Value(presences[(job_id, task_id, alt_id)]): + if solver.value(presences[(job_id, task_id, alt_id)]): duration = jobs[job_id][task_id][alt_id][0] machine = jobs[job_id][task_id][alt_id][1] selected = alt_id @@ -197,12 +197,12 @@ def flexible_jobshop(): % (job_id, task_id, start_value, selected, machine, duration) ) - print("Solve status: %s" % solver.StatusName(status)) - print("Optimal objective value: %i" % solver.ObjectiveValue()) + print("solve status: %s" % solver.status_name(status)) + print("Optimal objective value: %i" % solver.objective_value) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) flexible_jobshop() diff --git a/examples/python/gate_scheduling_sat.py b/examples/python/gate_scheduling_sat.py index 00aebdeebd6..67790f42dcc 100644 --- a/examples/python/gate_scheduling_sat.py +++ b/examples/python/gate_scheduling_sat.py @@ -67,66 +67,70 @@ def main(_): for i in all_jobs: # Create main interval. - start = model.NewIntVar(0, horizon, "start_%i" % i) + start = model.new_int_var(0, horizon, "start_%i" % i) duration = jobs[i][0] - end = model.NewIntVar(0, horizon, "end_%i" % i) - interval = model.NewIntervalVar(start, duration, end, "interval_%i" % i) + end = model.new_int_var(0, horizon, "end_%i" % i) + interval = model.new_interval_var(start, duration, end, "interval_%i" % i) starts.append(start) intervals.append(interval) ends.append(end) demands.append(jobs[i][1]) # Create an optional copy of interval to be executed on machine 0. - performed_on_m0 = model.NewBoolVar("perform_%i_on_m0" % i) + performed_on_m0 = model.new_bool_var("perform_%i_on_m0" % i) performed.append(performed_on_m0) - start0 = model.NewIntVar(0, horizon, "start_%i_on_m0" % i) - end0 = model.NewIntVar(0, horizon, "end_%i_on_m0" % i) - interval0 = model.NewOptionalIntervalVar( + start0 = model.new_int_var(0, horizon, "start_%i_on_m0" % i) + end0 = model.new_int_var(0, horizon, "end_%i_on_m0" % i) + interval0 = model.new_optional_interval_var( start0, duration, end0, performed_on_m0, "interval_%i_on_m0" % i ) intervals0.append(interval0) # Create an optional copy of interval to be executed on machine 1. - start1 = model.NewIntVar(0, horizon, "start_%i_on_m1" % i) - end1 = model.NewIntVar(0, horizon, "end_%i_on_m1" % i) - interval1 = model.NewOptionalIntervalVar( - start1, duration, end1, performed_on_m0.Not(), "interval_%i_on_m1" % i + start1 = model.new_int_var(0, horizon, "start_%i_on_m1" % i) + end1 = model.new_int_var(0, horizon, "end_%i_on_m1" % i) + interval1 = model.new_optional_interval_var( + start1, + duration, + end1, + performed_on_m0.negated(), + "interval_%i_on_m1" % i, ) intervals1.append(interval1) # We only propagate the constraint if the tasks is performed on the machine. - model.Add(start0 == start).OnlyEnforceIf(performed_on_m0) - model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not()) + model.add(start0 == start).only_enforce_if(performed_on_m0) + model.add(start1 == start).only_enforce_if(performed_on_m0.negated()) # Width constraint (modeled as a cumulative) - model.AddCumulative(intervals, demands, max_width) + model.add_cumulative(intervals, demands, max_width) # Choose which machine to perform the jobs on. - model.AddNoOverlap(intervals0) - model.AddNoOverlap(intervals1) + model.add_no_overlap(intervals0) + model.add_no_overlap(intervals1) # Objective variable. - makespan = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(makespan, ends) - model.Minimize(makespan) + makespan = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(makespan, ends) + model.minimize(makespan) # Symmetry breaking. - model.Add(performed[0] == 0) + model.add(performed[0] == 0) # Solve model. solver = cp_model.CpSolver() - solver.Solve(model) + solver.solve(model) # Output solution. if visualization.RunFromIPython(): - output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0) - output.AddTitle("Makespan = %i" % solver.ObjectiveValue()) + output = visualization.SvgWrapper(solver.objective_value, max_width, 40.0) + output.AddTitle("Makespan = %i" % solver.objective_value) color_manager = visualization.ColorManager() color_manager.SeedRandomColor(0) for i in all_jobs: - performed_machine = 1 - solver.Value(performed[i]) - start = solver.Value(starts[i]) + performed_machine = 1 - solver.value(performed[i]) + start = solver.value(starts[i]) d_x = jobs[i][0] d_y = jobs[i][1] s_y = performed_machine * (max_width - d_y) @@ -139,17 +143,17 @@ def main(_): output.Display() else: print("Solution") - print(" - makespan = %i" % solver.ObjectiveValue()) + print(" - makespan = %i" % solver.objective_value) for i in all_jobs: - performed_machine = 1 - solver.Value(performed[i]) - start = solver.Value(starts[i]) + performed_machine = 1 - solver.value(performed[i]) + start = solver.value(starts[i]) print( " - Job %i starts at %i on machine %i" % (i, start, performed_machine) ) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) if __name__ == "__main__": diff --git a/examples/python/golomb_sat.py b/examples/python/golomb_sat.py index c23a11a2b8f..561cf44fdd9 100644 --- a/examples/python/golomb_sat.py +++ b/examples/python/golomb_sat.py @@ -38,7 +38,7 @@ ) -def solve_golomb_ruler(order, params): +def solve_golomb_ruler(order: int, params: str): """Solve the Golomb ruler problem.""" # Create the model. model = cp_model.CpModel() @@ -46,26 +46,26 @@ def solve_golomb_ruler(order, params): var_max = order * order all_vars = list(range(0, order)) - marks = [model.NewIntVar(0, var_max, f"marks_{i}") for i in all_vars] + marks = [model.new_int_var(0, var_max, f"marks_{i}") for i in all_vars] - model.Add(marks[0] == 0) + model.add(marks[0] == 0) for i in range(order - 2): - model.Add(marks[i + 1] > marks[i]) + model.add(marks[i + 1] > marks[i]) diffs = [] for i in range(order - 1): for j in range(i + 1, order): - diff = model.NewIntVar(0, var_max, f"diff [{j},{i}]") - model.Add(diff == marks[j] - marks[i]) + diff = model.new_int_var(0, var_max, f"diff [{j},{i}]") + model.add(diff == marks[j] - marks[i]) diffs.append(diff) - model.AddAllDifferent(diffs) + model.add_all_different(diffs) # symmetry breaking if order > 2: - model.Add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0]) + model.add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0]) # Objective - model.Minimize(marks[order - 1]) + model.minimize(marks[order - 1]) # Solve the model. solver = cp_model.CpSolver() @@ -73,21 +73,21 @@ def solve_golomb_ruler(order, params): text_format.Parse(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() print(f"Golomb ruler(order={order})") - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Print solution. - print(f"status: {solver.StatusName(status)}") + print(f"status: {solver.status_name(status)}") if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): for idx, var in enumerate(marks): - print(f"mark[{idx}]: {solver.Value(var)}") - intervals = [solver.Value(diff) for diff in diffs] + print(f"mark[{idx}]: {solver.value(var)}") + intervals = [solver.value(diff) for diff in diffs] intervals.sort() print(f"intervals: {intervals}") print("Statistics:") - print(f"- conflicts: {solver.NumConflicts()}") - print(f"- branches : {solver.NumBranches()}") - print(f"- wall time: {solver.WallTime()}s\n") + print(f"- conflicts: {solver.num_conflicts}") + print(f"- branches : {solver.num_branches}") + print(f"- wall time: {solver.wall_time}s\n") def main(argv: Sequence[str]) -> None: diff --git a/examples/python/hidato_sat.py b/examples/python/hidato_sat.py index 02fb4a9d9c7..eb84aa6cc6d 100755 --- a/examples/python/hidato_sat.py +++ b/examples/python/hidato_sat.py @@ -14,12 +14,13 @@ """Solves the Hidato problem with the CP-SAT solver.""" +from typing import Union from absl import app from ortools.sat.colab import visualization from ortools.sat.python import cp_model -def build_pairs(rows, cols): +def build_pairs(rows: int, cols: int) -> list[tuple[int, int]]: """Build closeness pairs for consecutive numbers. Build set of allowed pairs such that two consecutive numbers touch @@ -48,7 +49,7 @@ def build_pairs(rows, cols): return result -def print_solution(positions, rows, cols): +def print_solution(positions: list[int], rows: int, cols: int): """Print a current solution.""" # Create empty board. board = [] @@ -63,7 +64,7 @@ def print_solution(positions, rows, cols): print_matrix(board) -def print_matrix(game): +def print_matrix(game: list[list[int]]) -> None: """Pretty print of a matrix.""" rows = len(game) cols = len(game[0]) @@ -77,7 +78,7 @@ def print_matrix(game): print(line) -def build_puzzle(problem): +def build_puzzle(problem: int) -> Union[None, list[list[int]]]: """Build the problem from its index.""" # # models, a 0 indicates an open cell which number is not yet known. @@ -146,8 +147,8 @@ def build_puzzle(problem): return puzzle -def solve_hidato(puzzle, index): - """Solve the given hidato table.""" +def solve_hidato(puzzle: list[list[int]], index: int): + """solve the given hidato table.""" # Create the model. model = cp_model.CpModel() @@ -161,58 +162,58 @@ def solve_hidato(puzzle, index): print_matrix(puzzle) # - # declare variables + # Declare variables. # - positions = [model.NewIntVar(0, r * c - 1, "p[%i]" % i) for i in range(r * c)] + positions = [model.new_int_var(0, r * c - 1, "p[%i]" % i) for i in range(r * c)] # - # constraints + # Constraints. # - model.AddAllDifferent(positions) + model.add_all_different(positions) # - # Fill in the clues + # Fill in the clues. # for i in range(r): for j in range(c): if puzzle[i][j] > 0: - model.Add(positions[puzzle[i][j] - 1] == i * c + j) + model.add(positions[puzzle[i][j] - 1] == i * c + j) # Consecutive numbers much touch each other in the grid. # We use an allowed assignment constraint to model it. close_tuples = build_pairs(r, c) for k in range(0, r * c - 1): - model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples) + model.add_allowed_assignments([positions[k], positions[k + 1]], close_tuples) # - # solution and search + # Solution and search. # solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: if visualization.RunFromIPython(): output = visualization.SvgWrapper(10, r, 40.0) for i, var in enumerate(positions): - val = solver.Value(var) + val = solver.value(var) x = val % c y = val // c color = "white" if puzzle[y][x] == 0 else "lightgreen" output.AddRectangle(x, r - y - 1, 1, 1, color, "black", str(i + 1)) - output.AddTitle("Puzzle %i solved in %f s" % (index, solver.WallTime())) + output.AddTitle("Puzzle %i solved in %f s" % (index, solver.wall_time)) output.Display() else: print_solution( - [solver.Value(x) for x in positions], + [solver.value(x) for x in positions], r, c, ) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(_): diff --git a/examples/python/jobshop_ft06_distance_sat.py b/examples/python/jobshop_ft06_distance_sat.py index a0edaa02bd4..dbea74de66e 100755 --- a/examples/python/jobshop_ft06_distance_sat.py +++ b/examples/python/jobshop_ft06_distance_sat.py @@ -31,7 +31,7 @@ from ortools.sat.python import cp_model -def distance_between_jobs(x, y): +def distance_between_jobs(x: int, y: int) -> int: """Returns the distance between tasks of job x and tasks of job y.""" return abs(x - y) @@ -73,10 +73,10 @@ def jobshop_ft06_distance(): all_tasks = {} for i in all_jobs: for j in all_machines: - start_var = model.NewIntVar(0, horizon, "start_%i_%i" % (i, j)) + start_var = model.new_int_var(0, horizon, "start_%i_%i" % (i, j)) duration = durations[i][j] - end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j)) - interval_var = model.NewIntervalVar( + end_var = model.new_int_var(0, horizon, "end_%i_%i" % (i, j)) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval_%i_%i" % (i, j) ) all_tasks[(i, j)] = task_type( @@ -96,51 +96,51 @@ def jobshop_ft06_distance(): job_indices.append(j) job_starts.append(all_tasks[(j, k)].start) job_ends.append(all_tasks[(j, k)].end) - model.AddNoOverlap(job_intervals) + model.add_no_overlap(job_intervals) arcs = [] for j1 in range(len(job_intervals)): # Initial arc from the dummy node (0) to a task. - start_lit = model.NewBoolVar("%i is first job" % j1) + start_lit = model.new_bool_var("%i is first job" % j1) arcs.append((0, j1 + 1, start_lit)) # Final arc from an arc to the dummy node. - arcs.append((j1 + 1, 0, model.NewBoolVar("%i is last job" % j1))) + arcs.append((j1 + 1, 0, model.new_bool_var("%i is last job" % j1))) for j2 in range(len(job_intervals)): if j1 == j2: continue - lit = model.NewBoolVar("%i follows %i" % (j2, j1)) + lit = model.new_bool_var("%i follows %i" % (j2, j1)) arcs.append((j1 + 1, j2 + 1, lit)) # We add the reified precedence to link the literal with the # times of the two tasks. min_distance = distance_between_jobs(j1, j2) - model.Add(job_starts[j2] >= job_ends[j1] + min_distance).OnlyEnforceIf( - lit - ) + model.add( + job_starts[j2] >= job_ends[j1] + min_distance + ).only_enforce_if(lit) - model.AddCircuit(arcs) + model.add_circuit(arcs) # Precedences inside a job. for i in all_jobs: for j in range(0, machines_count - 1): - model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) + model.add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs] ) - model.Minimize(obj_var) + model.minimize(obj_var) # Solve model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # Output solution. if status == cp_model.OPTIMAL: - print("Optimal makespan: %i" % solver.ObjectiveValue()) + print("Optimal makespan: %i" % solver.objective_value) jobshop_ft06_distance() diff --git a/examples/python/jobshop_ft06_sat.py b/examples/python/jobshop_ft06_sat.py index 7de58ff9340..24bc31ca267 100755 --- a/examples/python/jobshop_ft06_sat.py +++ b/examples/python/jobshop_ft06_sat.py @@ -29,7 +29,7 @@ from ortools.sat.python import cp_model -def jobshop_ft06(): +def jobshop_ft06() -> None: """Solves the ft06 jobshop.""" # Creates the solver. model = cp_model.CpModel() @@ -66,10 +66,10 @@ def jobshop_ft06(): all_tasks = {} for i in all_jobs: for j in all_machines: - start_var = model.NewIntVar(0, horizon, "start_%i_%i" % (i, j)) + start_var = model.new_int_var(0, horizon, "start_%i_%i" % (i, j)) duration = durations[i][j] - end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j)) - interval_var = model.NewIntervalVar( + end_var = model.new_int_var(0, horizon, "end_%i_%i" % (i, j)) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval_%i_%i" % (i, j) ) all_tasks[(i, j)] = task_type( @@ -85,35 +85,35 @@ def jobshop_ft06(): if machines[j][k] == i: machines_jobs.append(all_tasks[(j, k)].interval) machine_to_jobs[i] = machines_jobs - model.AddNoOverlap(machines_jobs) + model.add_no_overlap(machines_jobs) # Precedences inside a job. for i in all_jobs: for j in range(0, machines_count - 1): - model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) + model.add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs] ) - model.Minimize(obj_var) + model.minimize(obj_var) - # Solve model. + # Solve the model. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) - # Output solution. + # Output the solution. if status == cp_model.OPTIMAL: if visualization.RunFromIPython(): starts = [ - [solver.Value(all_tasks[(i, j)][0]) for j in all_machines] + [solver.value(all_tasks[(i, j)][0]) for j in all_machines] for i in all_jobs ] visualization.DisplayJobshop(starts, durations, machines, "FT06") else: - print("Optimal makespan: %i" % solver.ObjectiveValue()) + print("Optimal makespan: %i" % solver.objective_value) jobshop_ft06() diff --git a/examples/python/jobshop_with_maintenance_sat.py b/examples/python/jobshop_with_maintenance_sat.py index 953ad9380d6..d9a7e76a89d 100644 --- a/examples/python/jobshop_with_maintenance_sat.py +++ b/examples/python/jobshop_with_maintenance_sat.py @@ -31,7 +31,7 @@ def on_solution_callback(self): """Called at each new solution.""" print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 @@ -65,13 +65,14 @@ def jobshop_with_maintenance(): machine_to_intervals = collections.defaultdict(list) for job_id, job in enumerate(jobs_data): - for task_id, task in enumerate(job): + for entry in enumerate(job): + task_id, task = entry machine = task[0] duration = task[1] suffix = "_%i_%i" % (job_id, task_id) - start_var = model.NewIntVar(0, horizon, "start" + suffix) - end_var = model.NewIntVar(0, horizon, "end" + suffix) - interval_var = model.NewIntervalVar( + start_var = model.new_int_var(0, horizon, "start" + suffix) + end_var = model.new_int_var(0, horizon, "end" + suffix) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval" + suffix ) all_tasks[job_id, task_id] = task_type( @@ -80,31 +81,31 @@ def jobshop_with_maintenance(): machine_to_intervals[machine].append(interval_var) # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}). - machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, "weekend_0")) + machine_to_intervals[0].append(model.new_interval_var(4, 4, 8, "weekend_0")) # Create and add disjunctive constraints. for machine in all_machines: - model.AddNoOverlap(machine_to_intervals[machine]) + model.add_no_overlap(machine_to_intervals[machine]) # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): - model.Add( + model.add( all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end ) # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)], ) - model.Minimize(obj_var) + model.minimize(obj_var) # Solve model. solver = cp_model.CpSolver() solution_printer = SolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Output solution. if status == cp_model.OPTIMAL: @@ -115,7 +116,7 @@ def jobshop_with_maintenance(): machine = task[0] assigned_jobs[machine].append( assigned_task_type( - start=solver.Value(all_tasks[job_id, task_id].start), + start=solver.value(all_tasks[job_id, task_id].start), job=job_id, index=task_id, duration=task[1], @@ -132,13 +133,13 @@ def jobshop_with_maintenance(): for assigned_task in assigned_jobs[machine]: name = "job_%i_%i" % (assigned_task.job, assigned_task.index) - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line_tasks += "%-10s" % name start = assigned_task.start duration = assigned_task.duration sol_tmp = "[%i,%i]" % (start, start + duration) - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line += "%-10s" % sol_tmp sol_line += "\n" @@ -147,12 +148,12 @@ def jobshop_with_maintenance(): output += sol_line # Finally print the solution found. - print("Optimal Schedule Length: %i" % solver.ObjectiveValue()) + print("Optimal Schedule Length: %i" % solver.objective_value) print(output) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/knapsack_2d_sat.py b/examples/python/knapsack_2d_sat.py index 3972a772671..3400f349659 100644 --- a/examples/python/knapsack_2d_sat.py +++ b/examples/python/knapsack_2d_sat.py @@ -29,6 +29,7 @@ from ortools.sat.python import cp_model + _OUTPUT_PROTO = flags.DEFINE_string( "output_proto", "", "Output file to write the cp_model proto to." ) @@ -42,7 +43,7 @@ ) -def build_data(): +def build_data() -> tuple[pd.Series, int, int]: """Build the data frame.""" data = """ item width height available value color @@ -70,8 +71,8 @@ def build_data(): return (data, max_height, max_width) -def solve_with_duplicate_items(data, max_height, max_width): - """Solve the problem by building 2 items (rotated or not) for each item.""" +def solve_with_duplicate_items(data: pd.Series, max_height: int, max_width: int): + """solve the problem by building 2 items (rotated or not) for each item.""" # Derived data (expanded to individual items). data_widths = data["width"].to_numpy() data_heights = data["height"].to_numpy() @@ -105,41 +106,47 @@ def solve_with_duplicate_items(data, max_height, max_width): for i in range(num_items): ## Is the item used? - is_used.append(model.NewBoolVar(f"is_used{i}")) + is_used.append(model.new_bool_var(f"is_used{i}")) ## Item coordinates. - x_starts.append(model.NewIntVar(0, max_width, f"x_start{i}")) - x_ends.append(model.NewIntVar(0, max_width, f"x_end{i}")) - y_starts.append(model.NewIntVar(0, max_height, f"y_start{i}")) - y_ends.append(model.NewIntVar(0, max_height, f"y_end{i}")) + x_starts.append(model.new_int_var(0, max_width, f"x_start{i}")) + x_ends.append(model.new_int_var(0, max_width, f"x_end{i}")) + y_starts.append(model.new_int_var(0, max_height, f"y_start{i}")) + y_ends.append(model.new_int_var(0, max_height, f"y_end{i}")) ## Interval variables. x_intervals.append( - model.NewIntervalVar( - x_starts[i], item_widths[i] * is_used[i], x_ends[i], f"x_interval{i}" + model.new_interval_var( + x_starts[i], + item_widths[i] * is_used[i], + x_ends[i], + f"x_interval{i}", ) ) y_intervals.append( - model.NewIntervalVar( - y_starts[i], item_heights[i] * is_used[i], y_ends[i], f"y_interval{i}" + model.new_interval_var( + y_starts[i], + item_heights[i] * is_used[i], + y_ends[i], + f"y_interval{i}", ) ) # Unused boxes are fixed at (0.0). - model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) - model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) + model.add(x_starts[i] == 0).only_enforce_if(is_used[i].negated()) + model.add(y_starts[i] == 0).only_enforce_if(is_used[i].negated()) # Constraints. ## Only one of non-rotated/rotated pair can be used. for i in range(num_data_items): - model.Add(is_used[i] + is_used[i + num_data_items] <= 1) + model.add(is_used[i] + is_used[i + num_data_items] <= 1) ## 2D no overlap. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) ## Objective. - model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values)) + model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values)) # Output proto to file. if _OUTPUT_PROTO.value: @@ -152,27 +159,29 @@ def solve_with_duplicate_items(data, max_height, max_width): if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) # Report solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])} + used = {i for i in range(num_items) if solver.boolean_value(is_used[i])} data = pd.DataFrame( { - "x_start": [solver.Value(x_starts[i]) for i in used], - "y_start": [solver.Value(y_starts[i]) for i in used], + "x_start": [solver.value(x_starts[i]) for i in used], + "y_start": [solver.value(y_starts[i]) for i in used], "item_width": [item_widths[i] for i in used], "item_height": [item_heights[i] for i in used], - "x_end": [solver.Value(x_ends[i]) for i in used], - "y_end": [solver.Value(y_ends[i]) for i in used], + "x_end": [solver.value(x_ends[i]) for i in used], + "y_end": [solver.value(y_ends[i]) for i in used], "item_value": [item_values[i] for i in used], } ) print(data) -def solve_with_duplicate_optional_items(data, max_height, max_width): - """Solve the problem by building 2 optional items (rotated or not) for each item.""" +def solve_with_duplicate_optional_items( + data: pd.Series, max_height: int, max_width: int +): + """solve the problem by building 2 optional items (rotated or not) for each item.""" # Derived data (expanded to individual items). data_widths = data["width"].to_numpy() data_heights = data["height"].to_numpy() @@ -204,42 +213,42 @@ def solve_with_duplicate_optional_items(data, max_height, max_width): for i in range(num_items): ## Is the item used? - is_used.append(model.NewBoolVar(f"is_used{i}")) + is_used.append(model.new_bool_var(f"is_used{i}")) ## Item coordinates. x_starts.append( - model.NewIntVar(0, max_width - int(item_widths[i]), f"x_start{i}") + model.new_int_var(0, max_width - int(item_widths[i]), f"x_start{i}") ) y_starts.append( - model.NewIntVar(0, max_height - int(item_heights[i]), f"y_start{i}") + model.new_int_var(0, max_height - int(item_heights[i]), f"y_start{i}") ) ## Interval variables. x_intervals.append( - model.NewOptionalFixedSizeIntervalVar( + model.new_optional_fixed_size_interval_var( x_starts[i], item_widths[i], is_used[i], f"x_interval{i}" ) ) y_intervals.append( - model.NewOptionalFixedSizeIntervalVar( + model.new_optional_fixed_size_interval_var( y_starts[i], item_heights[i], is_used[i], f"y_interval{i}" ) ) # Unused boxes are fixed at (0.0). - model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) - model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not()) + model.add(x_starts[i] == 0).only_enforce_if(is_used[i].negated()) + model.add(y_starts[i] == 0).only_enforce_if(is_used[i].negated()) # Constraints. ## Only one of non-rotated/rotated pair can be used. for i in range(num_data_items): - model.Add(is_used[i] + is_used[i + num_data_items] <= 1) + model.add(is_used[i] + is_used[i + num_data_items] <= 1) ## 2D no overlap. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) ## Objective. - model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values)) + model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values)) # Output proto to file. if _OUTPUT_PROTO.value: @@ -247,32 +256,32 @@ def solve_with_duplicate_optional_items(data, max_height, max_width): with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) # Report solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])} + used = {i for i in range(num_items) if solver.boolean_value(is_used[i])} data = pd.DataFrame( { - "x_start": [solver.Value(x_starts[i]) for i in used], - "y_start": [solver.Value(y_starts[i]) for i in used], + "x_start": [solver.value(x_starts[i]) for i in used], + "y_start": [solver.value(y_starts[i]) for i in used], "item_width": [item_widths[i] for i in used], "item_height": [item_heights[i] for i in used], - "x_end": [solver.Value(x_starts[i]) + item_widths[i] for i in used], - "y_end": [solver.Value(y_starts[i]) + item_heights[i] for i in used], + "x_end": [solver.value(x_starts[i]) + item_widths[i] for i in used], + "y_end": [solver.value(y_starts[i]) + item_heights[i] for i in used], "item_value": [item_values[i] for i in used], } ) print(data) -def solve_with_rotations(data, max_height, max_width): - """Solve the problem by rotating items.""" +def solve_with_rotations(data: pd.Series, max_height: int, max_width: int): + """solve the problem by rotating items.""" # Derived data (expanded to individual items). data_widths = data["width"].to_numpy() data_heights = data["height"].to_numpy() @@ -301,25 +310,29 @@ def solve_with_rotations(data, max_height, max_width): for i in range(num_items): sizes = [0, int(item_widths[i]), int(item_heights[i])] # X coordinates. - x_starts.append(model.NewIntVar(0, max_width, f"x_start{i}")) + x_starts.append(model.new_int_var(0, max_width, f"x_start{i}")) x_sizes.append( - model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"x_size{i}") + model.new_int_var_from_domain( + cp_model.Domain.FromValues(sizes), f"x_size{i}" + ) ) - x_ends.append(model.NewIntVar(0, max_width, f"x_end{i}")) + x_ends.append(model.new_int_var(0, max_width, f"x_end{i}")) # Y coordinates. - y_starts.append(model.NewIntVar(0, max_height, f"y_start{i}")) + y_starts.append(model.new_int_var(0, max_height, f"y_start{i}")) y_sizes.append( - model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"y_size{i}") + model.new_int_var_from_domain( + cp_model.Domain.FromValues(sizes), f"y_size{i}" + ) ) - y_ends.append(model.NewIntVar(0, max_height, f"y_end{i}")) + y_ends.append(model.new_int_var(0, max_height, f"y_end{i}")) ## Interval variables x_intervals.append( - model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i], f"x_interval{i}") + model.new_interval_var(x_starts[i], x_sizes[i], x_ends[i], f"x_interval{i}") ) y_intervals.append( - model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i], f"y_interval{i}") + model.new_interval_var(y_starts[i], y_sizes[i], y_ends[i], f"y_interval{i}") ) # is_used[i] == True if and only if item i is selected. @@ -329,34 +342,34 @@ def solve_with_rotations(data, max_height, max_width): ## for each item, decide is unselected, no_rotation, rotated. for i in range(num_items): - not_selected = model.NewBoolVar(f"not_selected_{i}") - no_rotation = model.NewBoolVar(f"no_rotation_{i}") - rotated = model.NewBoolVar(f"rotated_{i}") + not_selected = model.new_bool_var(f"not_selected_{i}") + no_rotation = model.new_bool_var(f"no_rotation_{i}") + rotated = model.new_bool_var(f"rotated_{i}") ### Exactly one state must be chosen. - model.AddExactlyOne(not_selected, no_rotation, rotated) + model.add_exactly_one(not_selected, no_rotation, rotated) ### Define height and width according to the state. dim1 = item_widths[i] dim2 = item_heights[i] # Unused boxes are fixed at (0.0). - model.Add(x_sizes[i] == 0).OnlyEnforceIf(not_selected) - model.Add(y_sizes[i] == 0).OnlyEnforceIf(not_selected) - model.Add(x_starts[i] == 0).OnlyEnforceIf(not_selected) - model.Add(y_starts[i] == 0).OnlyEnforceIf(not_selected) + model.add(x_sizes[i] == 0).only_enforce_if(not_selected) + model.add(y_sizes[i] == 0).only_enforce_if(not_selected) + model.add(x_starts[i] == 0).only_enforce_if(not_selected) + model.add(y_starts[i] == 0).only_enforce_if(not_selected) # Sizes are fixed by the rotation. - model.Add(x_sizes[i] == dim1).OnlyEnforceIf(no_rotation) - model.Add(y_sizes[i] == dim2).OnlyEnforceIf(no_rotation) - model.Add(x_sizes[i] == dim2).OnlyEnforceIf(rotated) - model.Add(y_sizes[i] == dim1).OnlyEnforceIf(rotated) + model.add(x_sizes[i] == dim1).only_enforce_if(no_rotation) + model.add(y_sizes[i] == dim2).only_enforce_if(no_rotation) + model.add(x_sizes[i] == dim2).only_enforce_if(rotated) + model.add(y_sizes[i] == dim1).only_enforce_if(rotated) - is_used.append(not_selected.Not()) + is_used.append(not_selected.negated()) ## 2D no overlap. - model.AddNoOverlap2D(x_intervals, y_intervals) + model.add_no_overlap_2d(x_intervals, y_intervals) # Objective. - model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values)) + model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values)) # Output proto to file. if _OUTPUT_PROTO.value: @@ -364,24 +377,24 @@ def solve_with_rotations(data, max_height, max_width): with open(_OUTPUT_PROTO.value, "w") as text_file: text_file.write(str(model)) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) - status = solver.Solve(model) + status = solver.solve(model) # Report solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])} + used = {i for i in range(num_items) if solver.boolean_value(is_used[i])} data = pd.DataFrame( { - "x_start": [solver.Value(x_starts[i]) for i in used], - "y_start": [solver.Value(y_starts[i]) for i in used], - "item_width": [solver.Value(x_sizes[i]) for i in used], - "item_height": [solver.Value(y_sizes[i]) for i in used], - "x_end": [solver.Value(x_ends[i]) for i in used], - "y_end": [solver.Value(y_ends[i]) for i in used], + "x_start": [solver.value(x_starts[i]) for i in used], + "y_start": [solver.value(y_starts[i]) for i in used], + "item_width": [solver.value(x_sizes[i]) for i in used], + "item_height": [solver.value(y_sizes[i]) for i in used], + "x_end": [solver.value(x_ends[i]) for i in used], + "y_end": [solver.value(y_ends[i]) for i in used], "item_value": [item_values[i] for i in used], } ) @@ -389,7 +402,7 @@ def solve_with_rotations(data, max_height, max_width): def main(_): - """Solve the problem with all models.""" + """solve the problem with all models.""" data, max_height, max_width = build_data() if _MODEL.value == "duplicate": solve_with_duplicate_items(data, max_height, max_width) diff --git a/examples/python/line_balancing_sat.py b/examples/python/line_balancing_sat.py index b2999ebfee1..47bd9506708 100644 --- a/examples/python/line_balancing_sat.py +++ b/examples/python/line_balancing_sat.py @@ -184,7 +184,7 @@ def solve_model_greedily(model): def solve_boolean_model(model, hint): - """Solve the given model.""" + """solve the given model.""" print("Solving using the Boolean model") # Model data @@ -207,73 +207,73 @@ def solve_boolean_model(model, hint): # Create the variables for t in all_tasks: for p in all_pods: - assign[t, p] = model.NewBoolVar(f"assign_{t}_{p}") - possible[t, p] = model.NewBoolVar(f"possible_{t}_{p}") + assign[t, p] = model.new_bool_var(f"assign_{t}_{p}") + possible[t, p] = model.new_bool_var(f"possible_{t}_{p}") # active[p] indicates if pod p is active. - active = [model.NewBoolVar(f"active_{p}") for p in all_pods] + active = [model.new_bool_var(f"active_{p}") for p in all_pods] # Each task is done on exactly one pod. for t in all_tasks: - model.AddExactlyOne([assign[t, p] for p in all_pods]) + model.add_exactly_one([assign[t, p] for p in all_pods]) # Total tasks assigned to one pod cannot exceed cycle time. for p in all_pods: - model.Add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time) + model.add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time) # Maintain the possible variables: # possible at pod p -> possible at any pod after p for t in all_tasks: for p in range(num_pods - 1): - model.AddImplication(possible[t, p], possible[t, p + 1]) + model.add_implication(possible[t, p], possible[t, p + 1]) # Link possible and active variables. for t in all_tasks: for p in all_pods: - model.AddImplication(assign[t, p], possible[t, p]) + model.add_implication(assign[t, p], possible[t, p]) if p > 1: - model.AddImplication(assign[t, p], possible[t, p - 1].Not()) + model.add_implication(assign[t, p], possible[t, p - 1].negated()) # Precedences. for before, after in precedences: for p in range(1, num_pods): - model.AddImplication(assign[before, p], possible[after, p - 1].Not()) + model.add_implication(assign[before, p], possible[after, p - 1].negated()) # Link active variables with the assign one. for p in all_pods: all_assign_vars = [assign[t, p] for t in all_tasks] for a in all_assign_vars: - model.AddImplication(a, active[p]) - model.AddBoolOr(all_assign_vars + [active[p].Not()]) + model.add_implication(a, active[p]) + model.add_bool_or(all_assign_vars + [active[p].negated()]) # Force pods to be contiguous. This is critical to get good lower bounds # on the objective, even if it makes feasibility harder. for p in range(1, num_pods): - model.AddImplication(active[p - 1].Not(), active[p].Not()) + model.add_implication(active[p - 1].negated(), active[p].negated()) for t in all_tasks: - model.AddImplication(active[p].Not(), possible[t, p - 1]) + model.add_implication(active[p].negated(), possible[t, p - 1]) # Objective. - model.Minimize(sum(active)) + model.minimize(sum(active)) - # Add search hinting from the greedy solution. + # add search hinting from the greedy solution. for t in all_tasks: - model.AddHint(assign[t, hint[t]], 1) + model.add_hint(assign[t, hint[t]], 1) if _OUTPUT_PROTO.value: print(f"Writing proto to {_OUTPUT_PROTO.value}") - model.ExportToFile(_OUTPUT_PROTO.value) + model.export_to_file(_OUTPUT_PROTO.value) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.Solve(model) + solver.solve(model) def solve_scheduling_model(model, hint): - """Solve the given model using a cumutive model.""" + """solve the given model using a cumutive model.""" print("Solving using the scheduling model") # Model data @@ -290,47 +290,49 @@ def solve_scheduling_model(model, hint): # pod[t] indicates on which pod the task is performed. pods = {} for t in all_tasks: - pods[t] = model.NewIntVar(0, num_pods - 1, f"pod_{t}") + pods[t] = model.new_int_var(0, num_pods - 1, f"pod_{t}") # Create the variables intervals = [] demands = [] for t in all_tasks: - interval = model.NewFixedSizeIntervalVar(pods[t], 1, "") + interval = model.new_fixed_size_interval_var(pods[t], 1, "") intervals.append(interval) demands.append(durations[t]) - # Add terminating interval as the objective. - obj_var = model.NewIntVar(1, num_pods, "obj_var") - obj_size = model.NewIntVar(1, num_pods, "obj_duration") - obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1, "obj_interval") + # add terminating interval as the objective. + obj_var = model.new_int_var(1, num_pods, "obj_var") + obj_size = model.new_int_var(1, num_pods, "obj_duration") + obj_interval = model.new_interval_var( + obj_var, obj_size, num_pods + 1, "obj_interval" + ) intervals.append(obj_interval) demands.append(cycle_time) # Cumulative constraint. - model.AddCumulative(intervals, demands, cycle_time) + model.add_cumulative(intervals, demands, cycle_time) # Precedences. for before, after in precedences: - model.Add(pods[after] >= pods[before]) + model.add(pods[after] >= pods[before]) # Objective. - model.Minimize(obj_var) + model.minimize(obj_var) - # Add search hinting from the greedy solution. + # add search hinting from the greedy solution. for t in all_tasks: - model.AddHint(pods[t], hint[t]) + model.add_hint(pods[t], hint[t]) if _OUTPUT_PROTO.value: print(f"Writing proto to{_OUTPUT_PROTO.value}") - model.ExportToFile(_OUTPUT_PROTO.value) + model.export_to_file(_OUTPUT_PROTO.value) - # Solve model. + # solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.Solve(model) + solver.solve(model) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/maze_escape_sat.py b/examples/python/maze_escape_sat.py old mode 100755 new mode 100644 index cbd123bd70d..e01948b9c6d --- a/examples/python/maze_escape_sat.py +++ b/examples/python/maze_escape_sat.py @@ -50,8 +50,8 @@ def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, before_rank = position_to_rank[(x, y, z)] after_index = index_map[(x + dx, y + dy, z + dz)] after_rank = position_to_rank[(x + dx, y + dy, z + dz)] - move_literal = model.NewBoolVar("") - model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal) + move_literal = model.new_bool_var("") + model.add(after_rank == before_rank + 1).only_enforce_if(move_literal) arcs.append((before_index, after_index, move_literal)) @@ -79,13 +79,13 @@ def escape_the_maze(params, output_proto): position_to_rank = {} for coord in reverse_map: - position_to_rank[coord] = model.NewIntVar(0, counter - 1, f"rank_{coord}") + position_to_rank[coord] = model.new_int_var(0, counter - 1, f"rank_{coord}") # Path constraints. - model.Add(position_to_rank[start] == 0) - model.Add(position_to_rank[end] == counter - 1) + model.add(position_to_rank[start] == 0) + model.add(position_to_rank[end] == counter - 1) for i in range(len(boxes) - 1): - model.Add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]]) + model.add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]]) # Circuit constraint: visit all blocks exactly once, and maintains the rank # of each block. @@ -116,18 +116,18 @@ def escape_the_maze(params, output_proto): arcs.append((index_map[end], index_map[start], True)) # Adds the circuit (hamiltonian path) constraint. - model.AddCircuit(arcs) + model.add_circuit(arcs) # Exports the model if required. if output_proto: - model.ExportToFile(output_proto) + model.export_to_file(output_proto) # Solve model. solver = cp_model.CpSolver() if params: text_format.Parse(params, solver.parameters) solver.parameters.log_search_progress = True - result = solver.Solve(model) + result = solver.solve(model) # Prints solution. if result == cp_model.OPTIMAL: @@ -136,7 +136,7 @@ def escape_the_maze(params, output_proto): for y in range(size): for z in range(size): position = (x, y, z) - rank = solver.Value(position_to_rank[position]) + rank = solver.value(position_to_rank[position]) msg = f"({x}, {y}, {z})" if position == start: msg += " [start]" diff --git a/examples/python/memory_layout_and_infeasibility_sat.py b/examples/python/memory_layout_and_infeasibility_sat.py new file mode 100644 index 00000000000..51b970f7259 --- /dev/null +++ b/examples/python/memory_layout_and_infeasibility_sat.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Copyright 2010-2022 Google LLC +# 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. + +"""Solves the memory allocation problem, and returns a minimal set of demands to explain infeasibility.""" + +from collections.abc import Sequence +from typing import List + +from absl import app +from absl import flags + +from google.protobuf import text_format +from ortools.sat.python import cp_model + + +_OUTPUT_PROTO = flags.DEFINE_string( + "output_proto", "", "Output file to write the cp_model proto to." +) +_PARAMS = flags.DEFINE_string( + "params", "num_workers:1,linearization_level:2", "Sat solver parameters." +) + + +# Input of the problem. +DEMANDS = [ + [1578, 1583, 43008, 1], + [1588, 1589, 11264, 1], + [1590, 1595, 43008, 1], + [1583, 1588, 47872, 1], + [1589, 1590, 22848, 1], + [1586, 1590, 22848, 1], + [1591, 1594, 43008, 1], +] +CAPACITY = 98304 + + +def solve_hard_model(output_proto: str, params: str) -> bool: + """Solves the hard assignment model.""" + print("Solving the hard assignment model") + model = cp_model.CpModel() + + x_intervals: List[cp_model.IntervalVar] = [] + y_starts: List[cp_model.IntVar] = [] + y_intervals: List[cp_model.IntervalVar] = [] + + for start, end, demand, unused_alignment in DEMANDS: + x_interval = model.new_fixed_size_interval_var(start, end - start + 1, "") + y_start = model.new_int_var(0, CAPACITY - demand, "") + y_interval = model.new_fixed_size_interval_var(y_start, demand, "") + + x_intervals.append(x_interval) + y_starts.append(y_start) + y_intervals.append(y_interval) + + model.add_no_overlap_2d(x_intervals, y_intervals) + + if output_proto: + model.export_to_file(output_proto) + + solver = cp_model.CpSolver() + if params: + text_format.Parse(params, solver.parameters) + status = solver.solve(model) + print(solver.response_stats()) + + if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: + for index, start in enumerate(y_starts): + print(f"task {index} buffer starts at {solver.value(start)}") + + return status != cp_model.INFEASIBLE + + +def solve_soft_model_with_assumptions() -> None: + """Solves the soft model using assumptions.""" + print("Solving the soft model using assumptions") + + model = cp_model.CpModel() + + presences: List[cp_model.IntVar] = [] + x_intervals: List[cp_model.IntervalVar] = [] + y_starts: List[cp_model.IntVar] = [] + y_intervals: List[cp_model.IntervalVar] = [] + + for start, end, demand, unused_alignment in DEMANDS: + presence = model.new_bool_var("") + x_interval = model.new_optional_fixed_size_interval_var( + start, end - start + 1, presence, "" + ) + y_start = model.new_int_var(0, CAPACITY - demand, "") + y_interval = model.new_optional_fixed_size_interval_var( + y_start, demand, presence, "" + ) + + presences.append(presence) + x_intervals.append(x_interval) + y_starts.append(y_start) + y_intervals.append(y_interval) + + model.add_no_overlap_2d(x_intervals, y_intervals) + model.add_assumptions(presences) + + solver = cp_model.CpSolver() + status = solver.solve(model) + print(solver.response_stats()) + if status == cp_model.INFEASIBLE: + # The list actually contains the indices of the variables sufficient to + # explain infeasibility. + infeasible_variable_indices = solver.sufficient_assumptions_for_infeasibility() + infeasible_variable_indices_set = set(infeasible_variable_indices) + + for index, presence in enumerate(presences): + if presence.index in infeasible_variable_indices_set: + print(f"using task {index} is sufficient to explain infeasibility") + + +def solve_soft_model_with_maximization(params: str) -> None: + """Solves the soft model using maximization.""" + print("Solving the soft model using minimization") + + model = cp_model.CpModel() + + presences: List[cp_model.IntVar] = [] + x_intervals: List[cp_model.IntervalVar] = [] + y_starts: List[cp_model.IntVar] = [] + y_intervals: List[cp_model.IntervalVar] = [] + + for start, end, demand, unused_alignment in DEMANDS: + presence = model.new_bool_var("") + x_interval = model.new_optional_fixed_size_interval_var( + start, end - start + 1, presence, "" + ) + y_start = model.new_int_var(0, CAPACITY - demand, "") + y_interval = model.new_optional_fixed_size_interval_var( + y_start, demand, presence, "" + ) + + presences.append(presence) + x_intervals.append(x_interval) + y_starts.append(y_start) + y_intervals.append(y_interval) + + model.add_no_overlap_2d(x_intervals, y_intervals) + + model.maximize(sum(presences)) + + solver = cp_model.CpSolver() + if params: + text_format.Parse(params, solver.parameters) + status = solver.solve(model) + print(solver.response_stats()) + if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: + for index, presence in enumerate(presences): + if not solver.boolean_value(presence): + print(f"task {index} does not fit") + else: + print(f"task {index} buffer starts at {solver.value(y_starts[index])}") + + +def main(argv: Sequence[str]) -> None: + if len(argv) > 1: + raise app.UsageError("Too many command-line arguments.") + if not solve_hard_model(_OUTPUT_PROTO.value, _PARAMS.value): + solve_soft_model_with_assumptions() + solve_soft_model_with_maximization(_PARAMS.value) + + +if __name__ == "__main__": + app.run(main) diff --git a/examples/python/no_wait_baking_scheduling_sat.py b/examples/python/no_wait_baking_scheduling_sat.py old mode 100755 new mode 100644 index 4cd2ab09717..1c970b1aad1 --- a/examples/python/no_wait_baking_scheduling_sat.py +++ b/examples/python/no_wait_baking_scheduling_sat.py @@ -232,68 +232,68 @@ def solve_with_cp_sat(recipes, resources, orders): start = None if previous_end is None: - start = model.NewIntVar(start_work, horizon, f"start{suffix}") + start = model.new_int_var(start_work, horizon, f"start{suffix}") orders_sequence_of_events[order_id].append( (start, f"start{suffix}") ) else: start = previous_end - size = model.NewIntVar( + size = model.new_int_var( task.min_duration, task.max_duration, f"size{suffix}" ) end = None if task == recipe.tasks[-1]: # The order must end after the due_date. Ideally, exactly at the # due_date. - tardiness = model.NewIntVar(0, horizon - due_date, f"end{suffix}") + tardiness = model.new_int_var(0, horizon - due_date, f"end{suffix}") end = tardiness + due_date # Store the end_var for the objective. tardiness_vars.append(tardiness) else: - end = model.NewIntVar(start_work, horizon, f"end{suffix}") + end = model.new_int_var(start_work, horizon, f"end{suffix}") orders_sequence_of_events[order_id].append((end, f"end{suffix}")) previous_end = end # Per resource copy. presence_literals = [] for resource in resource_list_by_skill_name[skill_name]: - presence = model.NewBoolVar(f"presence{suffix}_{resource.name}") - copy = model.NewOptionalIntervalVar( + presence = model.new_bool_var(f"presence{suffix}_{resource.name}") + copy = model.new_optional_interval_var( start, size, end, presence, f"interval{suffix}_{resource.name}" ) interval_list_by_resource_name[resource.name].append(copy) presence_literals.append(presence) # Only one copy will be performed. - model.AddExactlyOne(presence_literals) + model.add_exactly_one(presence_literals) # Create resource constraints. for resource in resources: intervals = interval_list_by_resource_name[resource.name] if resource.capacity == 1: - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) else: - model.AddCumulative(intervals, [1] * len(intervals), resource.capacity) + model.add_cumulative(intervals, [1] * len(intervals), resource.capacity) # The objective is to minimize the sum of the tardiness values of each jobs. # The tardiness is difference between the end time of an order and its # due date. - model.Minimize(sum(tardiness_vars)) + model.minimize(sum(tardiness_vars)) # Solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for order_id in sorted_orders: print(f"{order_id}:") for time_expr, event_id in orders_sequence_of_events[order_id]: - time = solver.Value(time_expr) + time = solver.value(time_expr) print(f" {event_id} at {time // 60}:{time % 60:02}") diff --git a/examples/python/nqueens_sat.py b/examples/python/nqueens_sat.py index fb93eebb9d2..a2c32d87c28 100644 --- a/examples/python/nqueens_sat.py +++ b/examples/python/nqueens_sat.py @@ -26,16 +26,17 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, queens): + def __init__(self, queens: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__queens = queens self.__solution_count = 0 self.__start_time = time.time() - def SolutionCount(self): + @property + def solution_count(self) -> int: return self.__solution_count - def on_solution_callback(self): + def on_solution_callback(self) -> None: current_time = time.time() print( "Solution %i, time = %f s" @@ -46,7 +47,7 @@ def on_solution_callback(self): all_queens = range(len(self.__queens)) for i in all_queens: for j in all_queens: - if self.Value(self.__queens[j]) == i: + if self.value(self.__queens[j]) == i: # There is a queen in column j, row i. print("Q", end=" ") else: @@ -63,41 +64,43 @@ def main(_): ### Creates the variables. # The array index is the column, and the value is the row. - queens = [model.NewIntVar(0, board_size - 1, "x%i" % i) for i in range(board_size)] + queens = [ + model.new_int_var(0, board_size - 1, "x%i" % i) for i in range(board_size) + ] ### Creates the constraints. # All columns must be different because the indices of queens are all # different, so we just add the all different constraint on the rows. - model.AddAllDifferent(queens) + model.add_all_different(queens) # No two queens can be on the same diagonal. diag1 = [] diag2 = [] for i in range(board_size): - q1 = model.NewIntVar(0, 2 * board_size, "diag1_%i" % i) - q2 = model.NewIntVar(-board_size, board_size, "diag2_%i" % i) + q1 = model.new_int_var(0, 2 * board_size, "diag1_%i" % i) + q2 = model.new_int_var(-board_size, board_size, "diag2_%i" % i) diag1.append(q1) diag2.append(q2) - model.Add(q1 == queens[i] + i) - model.Add(q2 == queens[i] - i) - model.AddAllDifferent(diag1) - model.AddAllDifferent(diag2) + model.add(q1 == queens[i] + i) + model.add(q2 == queens[i] - i) + model.add_all_different(diag1) + model.add_all_different(diag2) ### Solve model. solver = cp_model.CpSolver() solution_printer = NQueenSolutionPrinter(queens) # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True - # Solve. - solver.Solve(model, solution_printer) + # solve. + solver.solve(model, solution_printer) print() print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) - print(" - solutions found : %i" % solution_printer.SolutionCount()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) + print(" - solutions found : %i" % solution_printer.solution_count) if __name__ == "__main__": diff --git a/examples/python/prize_collecting_tsp_sat.py b/examples/python/prize_collecting_tsp_sat.py old mode 100755 new mode 100644 index a82e4657411..bdb363f3b26 --- a/examples/python/prize_collecting_tsp_sat.py +++ b/examples/python/prize_collecting_tsp_sat.py @@ -71,14 +71,19 @@ # Create a console solution printer. -def print_solution(solver, visited_nodes, used_arcs, num_nodes): +def print_solution( + solver: cp_model.CpSolver, + visited_nodes: list[cp_model.IntVar], + used_arcs: dict[tuple[int, int], cp_model.IntVar], + num_nodes: int, +) -> None: """Prints solution on console.""" # Display dropped nodes. dropped_nodes = "Dropped nodes:" for i in range(num_nodes): if i == 0: continue - if not solver.BooleanValue(visited_nodes[i]): + if not solver.boolean_value(visited_nodes[i]): dropped_nodes += f" {i}({VISIT_VALUES[i]})" print(dropped_nodes) # Display routes @@ -94,7 +99,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes): for node in range(num_nodes): if node == current_node: continue - if solver.BooleanValue(used_arcs[current_node, node]): + if solver.boolean_value(used_arcs[current_node, node]): route_distance += DISTANCE_MATRIX[current_node][node] current_node = node if current_node == 0: @@ -102,7 +107,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes): break plan_output += f" {current_node}\n" plan_output += f"Distance of the route: {route_distance}m\n" - plan_output += f"Value collected: {value_collected}/{sum(VISIT_VALUES)}\n" + plan_output += f"value collected: {value_collected}/{sum(VISIT_VALUES)}\n" print(plan_output) @@ -123,8 +128,8 @@ def prize_collecting_tsp(): # Create the circuit constraint. arcs = [] for i in all_nodes: - is_visited = model.NewBoolVar(f"{i} is visited") - arcs.append((i, i, is_visited.Not())) + is_visited = model.new_bool_var(f"{i} is visited") + arcs.append((i, i, is_visited.negated())) obj_vars.append(is_visited) obj_coeffs.append(VISIT_VALUES[i]) @@ -132,22 +137,22 @@ def prize_collecting_tsp(): for j in all_nodes: if i == j: - used_arcs[i, j] = is_visited.Not() + used_arcs[i, j] = is_visited.negated() continue - arc_is_used = model.NewBoolVar(f"{j} follows {i}") + arc_is_used = model.new_bool_var(f"{j} follows {i}") arcs.append((i, j, arc_is_used)) obj_vars.append(arc_is_used) obj_coeffs.append(-DISTANCE_MATRIX[i][j]) used_arcs[i, j] = arc_is_used - model.AddCircuit(arcs) + model.add_circuit(arcs) # Node 0 must be visited. - model.Add(visited_nodes[0] == 1) + model.add(visited_nodes[0] == 1) # limit the route distance - model.Add( + model.add( sum( used_arcs[i, j] * DISTANCE_MATRIX[i][j] for i in all_nodes @@ -157,7 +162,7 @@ def prize_collecting_tsp(): ) # Maximize visited node values minus the travelled distance. - model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() @@ -166,7 +171,7 @@ def prize_collecting_tsp(): solver.parameters.num_search_workers = 8 solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: print_solution(solver, visited_nodes, used_arcs, num_nodes) diff --git a/examples/python/prize_collecting_vrp_sat.py b/examples/python/prize_collecting_vrp_sat.py old mode 100755 new mode 100644 index 73873232500..d0aee475ff3 --- a/examples/python/prize_collecting_vrp_sat.py +++ b/examples/python/prize_collecting_vrp_sat.py @@ -71,7 +71,13 @@ # Create a console solution printer. -def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): +def print_solution( + solver: cp_model.CpSolver, + visited_nodes: dict[int, list[cp_model.IntVar]], + used_arcs: dict[int, dict[tuple[int, int], cp_model.IntVar]], + num_nodes: int, + num_vehicles: int, +) -> None: """Prints solution on console.""" # Display dropped nodes. dropped_nodes = "Dropped nodes:" @@ -79,7 +85,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): if node == 0: continue is_visited = sum( - [solver.BooleanValue(visited_nodes[v][node]) for v in range(num_vehicles)] + [solver.boolean_value(visited_nodes[v][node]) for v in range(num_vehicles)] ) if not is_visited: dropped_nodes += f" {node}({VISIT_VALUES[node]})" @@ -100,7 +106,7 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): for node in range(num_nodes): if node == current_node: continue - if solver.BooleanValue(used_arcs[v][current_node, node]): + if solver.boolean_value(used_arcs[v][current_node, node]): route_distance += DISTANCE_MATRIX[current_node][node] current_node = node if current_node == 0: @@ -108,12 +114,12 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles): break plan_output += f" {current_node}\n" plan_output += f"Distance of the route: {route_distance}m\n" - plan_output += f"Value collected: {value_collected}\n" + plan_output += f"value collected: {value_collected}\n" print(plan_output) total_distance += route_distance total_value_collected += value_collected print(f"Total Distance: {total_distance}m") - print(f"Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}") + print(f"Total value collected: {total_value_collected}/{sum(VISIT_VALUES)}") def prize_collecting_vrp(): @@ -137,8 +143,8 @@ def prize_collecting_vrp(): used_arcs[v] = {} arcs = [] for i in all_nodes: - is_visited = model.NewBoolVar(f"{i} is visited") - arcs.append((i, i, is_visited.Not())) + is_visited = model.new_bool_var(f"{i} is visited") + arcs.append((i, i, is_visited.negated())) obj_vars.append(is_visited) obj_coeffs.append(VISIT_VALUES[i]) @@ -146,22 +152,22 @@ def prize_collecting_vrp(): for j in all_nodes: if i == j: - used_arcs[v][i, j] = is_visited.Not() + used_arcs[v][i, j] = is_visited.negated() continue - arc_is_used = model.NewBoolVar(f"{j} follows {i}") + arc_is_used = model.new_bool_var(f"{j} follows {i}") arcs.append((i, j, arc_is_used)) obj_vars.append(arc_is_used) obj_coeffs.append(-DISTANCE_MATRIX[i][j]) used_arcs[v][i, j] = arc_is_used - model.AddCircuit(arcs) + model.add_circuit(arcs) # Node 0 must be visited. - model.Add(visited_nodes[v][0] == 1) + model.add(visited_nodes[v][0] == 1) # limit the route distance - model.Add( + model.add( sum( used_arcs[v][i, j] * DISTANCE_MATRIX[i][j] for i in all_nodes @@ -172,10 +178,10 @@ def prize_collecting_vrp(): # Each node is visited at most once for node in range(1, num_nodes): - model.AddAtMostOne([visited_nodes[v][node] for v in range(num_vehicles)]) + model.add_at_most_one([visited_nodes[v][node] for v in range(num_vehicles)]) # Maximize visited node values minus the travelled distance. - model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() @@ -183,7 +189,7 @@ def prize_collecting_vrp(): solver.parameters.max_time_in_seconds = 15.0 solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles) diff --git a/examples/python/proto_solve.py b/examples/python/proto_solve.py index 4fb965cbbd3..523d2f2c202 100644 --- a/examples/python/proto_solve.py +++ b/examples/python/proto_solve.py @@ -20,7 +20,7 @@ def main(_): # Create solver. solver = model_builder.ModelSolver(_SOLVER.value) - if not solver: + if not solver.solver_is_supported(): print(f'Cannot create solver with name \'{_SOLVER.value}\'') return @@ -36,4 +36,4 @@ def main(_): if __name__ == '__main__': - app.run(main) \ No newline at end of file + app.run(main) diff --git a/examples/python/qubo_sat.py b/examples/python/qubo_sat.py index 3d7364f837a..9af9e02c2ab 100644 --- a/examples/python/qubo_sat.py +++ b/examples/python/qubo_sat.py @@ -653,14 +653,14 @@ def solve_qubo(): - """Solve the Qubo problem.""" + """solve the Qubo problem.""" - # Constraint programming engine + # Build the model. model = cp_model.CpModel() num_vars = len(RAW_DATA) all_vars = range(num_vars) - variables = [model.NewBoolVar("x_%i" % i) for i in all_vars] + variables = [model.new_bool_var("x_%i" % i) for i in all_vars] obj_vars = [] obj_coeffs = [] @@ -672,10 +672,10 @@ def solve_qubo(): if coeff == 0.0: continue x_j = variables[j] - var = model.NewBoolVar("") - model.AddBoolOr([x_i.Not(), x_j.Not(), var]) - model.AddImplication(var, x_i) - model.AddImplication(var, x_j) + var = model.new_bool_var("") + model.add_bool_or([x_i.negated(), x_j.negated(), var]) + model.add_implication(var, x_i) + model.add_implication(var, x_j) obj_vars.append(var) obj_coeffs.append(coeff) @@ -685,14 +685,14 @@ def solve_qubo(): obj_vars.append(variables[i]) obj_coeffs.append(self_coeff) - model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) ### Solve model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 16 solver.parameters.log_search_progress = True solver.parameters.max_time_in_seconds = 30 - solver.Solve(model) + solver.solve(model) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/rcpsp_sat.py b/examples/python/rcpsp_sat.py old mode 100755 new mode 100644 index 1cd34e69328..e698ebd6092 --- a/examples/python/rcpsp_sat.py +++ b/examples/python/rcpsp_sat.py @@ -19,10 +19,10 @@ Data use in flags: http://www.om-db.wi.tum.de/psplib/data.html - """ import collections +from typing import Optional from absl import app from absl import flags @@ -46,7 +46,7 @@ _ADD_REDUNDANT_ENERGETIC_CONSTRAINTS = flags.DEFINE_bool( "add_redundant_energetic_constraints", False, - "Add redundant energetic constraints on the pairs of tasks extracted from" + "add redundant energetic constraints on the pairs of tasks extracted from" + " precedence graph.", ) _DELAY_TIME_LIMIT = flags.DEFINE_float( @@ -63,7 +63,7 @@ ) -def PrintProblemStatistics(problem): +def print_problem_statistics(problem: rcpsp_pb2.RcpspProblem): """Display various statistics on the problem.""" # Determine problem type. @@ -108,7 +108,9 @@ def PrintProblemStatistics(problem): print(f" - {tasks_with_delay} tasks with successor delays") -def AnalyseDependencyGraph(problem): +def analyse_dependency_graph( + problem: rcpsp_pb2.RcpspProblem, +) -> tuple[list[tuple[int, int, list[int]]], dict[int, list[int]]]: """Analyses the dependency graph to improve the model. Args: @@ -144,7 +146,7 @@ def AnalyseDependencyGraph(problem): # Search for pair of tasks, containing at least two parallel branch between # them in the precedence graph. num_candidates = 0 - result = [] + result: list[tuple[int, int, list[int]]] = [] for source, start_outs in outs.items(): if len(start_outs) <= 1: # Starting with the unique successor of source will be as good. @@ -177,27 +179,27 @@ def AnalyseDependencyGraph(problem): result.append((source, sink, common)) # Sort entries lexicographically by (len(common), source, sink) - def Price(entry): + def price(entry): return num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] + entry[1] - result.sort(key=Price) + result.sort(key=price) print(f" - created {len(result)} pairs of nodes to examine", flush=True) return result, after -def SolveRcpsp( - problem, - proto_file, - params, - active_tasks, - source, - sink, - intervals_of_tasks, - delays, - in_main_solve=False, - initial_solution=None, - lower_bound=0, -): +def solve_rcpsp( + problem: rcpsp_pb2.RcpspProblem, + proto_file: str, + params: str, + active_tasks: set[int], + source: int, + sink: int, + intervals_of_tasks: list[tuple[int, int, list[int]]], + delays: dict[tuple[int, int], tuple[int, int]], + in_main_solve: bool = False, + initial_solution: Optional[rcpsp_pb2.RcpspAssignment] = None, + lower_bound: int = 0, +) -> tuple[int, int, Optional[rcpsp_pb2.RcpspAssignment]]: """Parse and solve a given RCPSP problem in proto format. The model will only look at the tasks {source} + {sink} + active_tasks, and @@ -224,7 +226,7 @@ def SolveRcpsp( """ # Create the model. model = cp_model.CpModel() - model.SetName(problem.name) + model.name = problem.name num_resources = len(problem.resources) @@ -269,16 +271,16 @@ def SolveRcpsp( num_recipes = len(task.recipes) all_recipes = range(num_recipes) - start_var = model.NewIntVar(0, horizon, f"start_of_task_{t}") - end_var = model.NewIntVar(0, horizon, f"end_of_task_{t}") + start_var = model.new_int_var(0, horizon, f"start_of_task_{t}") + end_var = model.new_int_var(0, horizon, f"end_of_task_{t}") literals = [] if num_recipes > 1: # Create one literal per recipe. - literals = [model.NewBoolVar(f"is_present_{t}_{r}") for r in all_recipes] + literals = [model.new_bool_var(f"is_present_{t}_{r}") for r in all_recipes] # Exactly one recipe must be performed. - model.AddExactlyOne(literals) + model.add_exactly_one(literals) else: literals = [1] @@ -293,19 +295,19 @@ def SolveRcpsp( demand_matrix[(resource, recipe_index)] = demand # Create the duration variable from the accumulated durations. - duration_var = model.NewIntVarFromDomain( - cp_model.Domain.FromValues(task_to_recipe_durations[t]), + duration_var = model.new_int_var_from_domain( + cp_model.Domain.from_values(task_to_recipe_durations[t]), f"duration_of_task_{t}", ) # Link the recipe literals and the duration_var. for r in range(num_recipes): - model.Add(duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf( + model.add(duration_var == task_to_recipe_durations[t][r]).only_enforce_if( literals[r] ) # Create the interval of the task. - task_interval = model.NewIntervalVar( + task_interval = model.new_interval_var( start_var, duration_var, end_var, f"task_interval_{t}" ) @@ -320,14 +322,14 @@ def SolveRcpsp( for res in all_resources: demands = [demand_matrix[(res, recipe)] for recipe in all_recipes] task_resource_to_fixed_demands[(t, res)] = demands - demand_var = model.NewIntVarFromDomain( - cp_model.Domain.FromValues(demands), f"demand_{t}_{res}" + demand_var = model.new_int_var_from_domain( + cp_model.Domain.from_values(demands), f"demand_{t}_{res}" ) task_to_resource_demands[t].append(demand_var) # Link the recipe literals and the demand_var. for r in all_recipes: - model.Add(demand_var == demand_matrix[(res, r)]).OnlyEnforceIf( + model.add(demand_var == demand_matrix[(res, r)]).only_enforce_if( literals[r] ) @@ -348,10 +350,13 @@ def SolveRcpsp( ) # Create makespan variable - makespan = model.NewIntVar(lower_bound, horizon, "makespan") - makespan_size = model.NewIntVar(1, horizon, "interval_makespan_size") - interval_makespan = model.NewIntervalVar( - makespan, makespan_size, model.NewConstant(horizon + 1), "interval_makespan" + makespan = model.new_int_var(lower_bound, horizon, "makespan") + makespan_size = model.new_int_var(1, horizon, "interval_makespan_size") + interval_makespan = model.new_interval_var( + makespan, + makespan_size, + model.new_constant(horizon + 1), + "interval_makespan", ) # Add precedences. @@ -370,21 +375,21 @@ def SolveRcpsp( p1 = task_to_presence_literals[task_id][m1] if next_id == sink: delay = delay_matrix.recipe_delays[m1].min_delays[0] - model.Add(s1 + delay <= makespan).OnlyEnforceIf(p1) + model.add(s1 + delay <= makespan).only_enforce_if(p1) else: for m2 in range(num_next_modes): delay = delay_matrix.recipe_delays[m1].min_delays[m2] s2 = task_starts[next_id] p2 = task_to_presence_literals[next_id][m2] - model.Add(s1 + delay <= s2).OnlyEnforceIf([p1, p2]) + model.add(s1 + delay <= s2).only_enforce_if([p1, p2]) else: # Normal dependencies (task ends before the start of successors). for t in all_active_tasks: for n in problem.tasks[t].successors: if n == sink: - model.Add(task_ends[t] <= makespan) + model.add(task_ends[t] <= makespan) elif n in active_tasks: - model.Add(task_ends[t] <= task_starts[n]) + model.add(task_ends[t] <= task_starts[n]) # Containers for resource investment problems. capacities = [] # Capacity variables for all resources. @@ -404,8 +409,8 @@ def SolveRcpsp( demands = [task_to_resource_demands[t][res] for t in all_active_tasks] if problem.is_resource_investment: - capacity = model.NewIntVar(0, c, f"capacity_of_{res}") - model.AddCumulative(intervals, demands, capacity) + capacity = model.new_int_var(0, c, f"capacity_of_{res}") + model.add_cumulative(intervals, demands, capacity) capacities.append(capacity) max_cost += c * resource.unit_cost else: # Standard renewable resource. @@ -413,7 +418,7 @@ def SolveRcpsp( intervals.append(interval_makespan) demands.append(c) - model.AddCumulative(intervals, demands, c) + model.add_cumulative(intervals, demands, c) else: # Non empty non renewable resource. (single mode only) if problem.is_consumer_producer: reservoir_starts = [] @@ -424,15 +429,15 @@ def SolveRcpsp( reservoir_demands.append( task_resource_to_fixed_demands[(t, res)][0] ) - model.AddReservoirConstraint( + model.add_reservoir_constraint( reservoir_starts, reservoir_demands, resource.min_capacity, resource.max_capacity, ) else: # No producer-consumer. We just sum the demands. - model.Add( - cp_model.LinearExpr.Sum( + model.add( + cp_model.LinearExpr.sum( [task_to_resource_demands[t][res] for t in all_active_tasks] ) <= c @@ -440,8 +445,8 @@ def SolveRcpsp( # Objective. if problem.is_resource_investment: - objective = model.NewIntVar(0, max_cost, "capacity_costs") - model.Add( + objective = model.new_int_var(0, max_cost, "capacity_costs") + model.add( objective == sum( problem.resources[i].unit_cost * capacities[i] @@ -451,17 +456,17 @@ def SolveRcpsp( else: objective = makespan - model.Minimize(objective) + model.minimize(objective) # Add min delay constraints. if delays is not None: for (local_start, local_end), (min_delay, _) in delays.items(): if local_start == source and local_end in active_tasks: - model.Add(task_starts[local_end] >= min_delay) + model.add(task_starts[local_end] >= min_delay) elif local_start in active_tasks and local_end == sink: - model.Add(makespan >= task_ends[local_start] + min_delay) + model.add(makespan >= task_ends[local_start] + min_delay) elif local_start in active_tasks and local_end in active_tasks: - model.Add(task_starts[local_end] >= task_ends[local_start] + min_delay) + model.add(task_starts[local_end] >= task_ends[local_start] + min_delay) problem_is_single_mode = True for t in all_active_tasks: @@ -504,7 +509,7 @@ def SolveRcpsp( if sum_of_max_energies <= c * min_delay: ignored_constraits += 1 continue - model.Add( + model.add( c * (task_starts[local_end] - task_ends[local_start]) >= sum(task_resource_to_energy[(t, res)] for t in common) ) @@ -518,15 +523,15 @@ def SolveRcpsp( # Add solution hint. if initial_solution: for t in all_active_tasks: - model.AddHint(task_starts[t], initial_solution.start_of_task[t]) + model.add_hint(task_starts[t], initial_solution.start_of_task[t]) if len(task_to_presence_literals[t]) > 1: selected = initial_solution.selected_recipe_of_task[t] - model.AddHint(task_to_presence_literals[t][selected], 1) + model.add_hint(task_to_presence_literals[t][selected], 1) # Write model to file. if proto_file: print(f"Writing proto to{proto_file}") - model.ExportToFile(proto_file) + model.export_to_file(proto_file) # Solve model. solver = cp_model.CpSolver() @@ -548,28 +553,35 @@ def SolveRcpsp( if in_main_solve: solver.parameters.log_search_progress = True # - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: assignment = rcpsp_pb2.RcpspAssignment() for t, _ in enumerate(problem.tasks): if t in task_starts: - assignment.start_of_task.append(solver.Value(task_starts[t])) + assignment.start_of_task.append(solver.value(task_starts[t])) for r, recipe_literal in enumerate(task_to_presence_literals[t]): - if solver.BooleanValue(recipe_literal): + if solver.boolean_value(recipe_literal): assignment.selected_recipe_of_task.append(r) break else: # t is not an active task. assignment.start_of_task.append(0) assignment.selected_recipe_of_task.append(0) return ( - int(solver.BestObjectiveBound()), - int(solver.ObjectiveValue()), + int(solver.best_objective_bound), + int(solver.objective_value), assignment, ) return -1, -1, None -def ComputeDelaysBetweenNodes(problem, task_intervals): +def compute_delays_between_nodes( + problem: rcpsp_pb2.RcpspProblem, + task_intervals: list[tuple[int, int, list[int]]], +) -> tuple[ + dict[tuple[int, int], tuple[int, int]], + Optional[rcpsp_pb2.RcpspAssignment], + bool, +]: """Computes the min delays between all pairs of tasks in 'task_intervals'. Args: @@ -594,11 +606,11 @@ def ComputeDelaysBetweenNodes(problem, task_intervals): num_delays_not_found = 0 optimal_found = True for start_task, end_task, active_tasks in task_intervals: - min_delay, feasible_delay, assignment = SolveRcpsp( + min_delay, feasible_delay, assignment = solve_rcpsp( problem, "", f"num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}", - active_tasks, + set(active_tasks), start_task, end_task, [], @@ -623,7 +635,13 @@ def ComputeDelaysBetweenNodes(problem, task_intervals): return delays, complete_problem_assignment, optimal_found -def AcceptNewCandidate(problem, after, demand_map, current, candidate): +def accept_new_candidate( + problem: rcpsp_pb2.RcpspProblem, + after: dict[int, list[int]], + demand_map: dict[tuple[int, int], int], + current: list[int], + candidate: int, +) -> bool: """Check if candidate is compatible with the tasks in current.""" for c in current: if candidate in after[c] or c in after[candidate]: @@ -643,7 +661,11 @@ def AcceptNewCandidate(problem, after, demand_map, current, candidate): return True -def ComputePreemptiveLowerBound(problem, after, lower_bound): +def compute_preemptive_lower_bound( + problem: rcpsp_pb2.RcpspProblem, + after: dict[int, list[int]], + lower_bound: int, +) -> int: """Computes a preemtive lower bound for the makespan statically. For this, it breaks all intervals into a set of intervals of size one. @@ -696,7 +718,7 @@ def ComputePreemptiveLowerBound(problem, after, lower_bound): new_combinations = [[t]] for c in all_combinations: - if AcceptNewCandidate(problem, after, demand_map, c, t): + if accept_new_candidate(problem, after, demand_map, c, t): new_combinations.append(c + [t]) all_combinations.extend(new_combinations) @@ -705,14 +727,14 @@ def ComputePreemptiveLowerBound(problem, after, lower_bound): if len(all_combinations) > 5000000: return lower_bound # Abort if too large. - # Solve the selection model. + # solve the selection model. # TODO(user): a few possible improvements: # 1/ use "dominating" columns, i.e. if you can add a task to a column, then # do not use that column. # 2/ Merge all task with exactly same demands into one. model = cp_model.CpModel() - model.SetName(f"lower_bound_{problem.name}") + model.name = f"lower_bound_{problem.name}" vars_per_task = collections.defaultdict(list) all_vars = [] @@ -720,29 +742,29 @@ def ComputePreemptiveLowerBound(problem, after, lower_bound): min_duration = max_duration for t in c: min_duration = min(min_duration, duration_map[t]) - count = model.NewIntVar(0, min_duration, f"count_{c}") + count = model.new_int_var(0, min_duration, f"count_{c}") all_vars.append(count) for t in c: vars_per_task[t].append(count) # Each task must be performed. for t in all_active_tasks: - model.Add(sum(vars_per_task[t]) >= duration_map[t]) + model.add(sum(vars_per_task[t]) >= duration_map[t]) # Objective - objective_var = model.NewIntVar(lower_bound, sum_of_demands, "objective_var") - model.Add(objective_var == sum(all_vars)) + objective_var = model.new_int_var(lower_bound, sum_of_demands, "objective_var") + model.add(objective_var == sum(all_vars)) - model.Minimize(objective_var) + model.minimize(objective_var) - # Solve model. + # solve model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 16 solver.parameters.max_time_in_seconds = _PREEMPTIVE_LB_TIME_LIMIT.value - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: status_str = "optimal" if status == cp_model.OPTIMAL else "" - lower_bound = max(lower_bound, int(solver.BestObjectiveBound())) + lower_bound = max(lower_bound, int(solver.best_objective_bound)) print(f" - {status_str} static lower bound = {lower_bound}", flush=True) return lower_bound @@ -753,10 +775,10 @@ def main(_): rcpsp_parser.parse_file(_INPUT.value) problem = rcpsp_parser.problem() - PrintProblemStatistics(problem) + print_problem_statistics(problem) - intervals_of_tasks, after = AnalyseDependencyGraph(problem) - delays, initial_solution, optimal_found = ComputeDelaysBetweenNodes( + intervals_of_tasks, after = analyse_dependency_graph(problem) + delays, initial_solution, optimal_found = compute_delays_between_nodes( problem, intervals_of_tasks ) @@ -764,9 +786,9 @@ def main(_): key = (0, last_task) lower_bound = delays[key][0] if key in delays else 0 if not optimal_found and _PREEMPTIVE_LB_TIME_LIMIT.value > 0.0: - lower_bound = ComputePreemptiveLowerBound(problem, after, lower_bound) + lower_bound = compute_preemptive_lower_bound(problem, after, lower_bound) - SolveRcpsp( + solve_rcpsp( problem=problem, proto_file=_OUTPUT_PROTO.value, params=_PARAMS.value, diff --git a/examples/python/shift_scheduling_sat.py b/examples/python/shift_scheduling_sat.py index 96b36c80e68..c681f46ceef 100644 --- a/examples/python/shift_scheduling_sat.py +++ b/examples/python/shift_scheduling_sat.py @@ -17,8 +17,8 @@ from absl import app from absl import flags -from ortools.sat.python import cp_model from google.protobuf import text_format +from ortools.sat.python import cp_model _OUTPUT_PROTO = flags.DEFINE_string( "output_proto", "", "Output file to write the cp_model proto to." @@ -28,7 +28,9 @@ ) -def negated_bounded_span(works, start, length): +def negated_bounded_span( + works: list[cp_model.BoolVarT], start: int, length: int +) -> list[cp_model.BoolVarT]: """Filters an isolated sub-sequence of variables assined to True. Extract the span of Boolean variables [start, start + length), negate them, @@ -46,20 +48,28 @@ def negated_bounded_span(works, start, length): or by the start or end of works. """ sequence = [] - # Left border (start of works, or works[start - 1]) + # left border (start of works, or works[start - 1]) if start > 0: sequence.append(works[start - 1]) for i in range(length): - sequence.append(works[start + i].Not()) - # Right border (end of works or works[start + length]) + sequence.append(works[start + i].negated()) + # right border (end of works or works[start + length]) if start + length < len(works): sequence.append(works[start + length]) return sequence def add_soft_sequence_constraint( - model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix -): + model: cp_model.CpModel, + works: list[cp_model.BoolVarT], + hard_min: int, + soft_min: int, + min_cost: int, + soft_max: int, + hard_max: int, + max_cost: int, + prefix: str, +) -> tuple[list[cp_model.BoolVarT], list[int]]: """Sequence constraint on true variables with soft and hard bounds. This constraint look at every maximal contiguous sequence of variables @@ -93,7 +103,7 @@ def add_soft_sequence_constraint( # Forbid sequences that are too short. for length in range(1, hard_min): for start in range(len(works) - length + 1): - model.AddBoolOr(negated_bounded_span(works, start, length)) + model.add_bool_or(negated_bounded_span(works, start, length)) # Penalize sequences that are below the soft limit. if min_cost > 0: @@ -101,9 +111,9 @@ def add_soft_sequence_constraint( for start in range(len(works) - length + 1): span = negated_bounded_span(works, start, length) name = ": under_span(start=%i, length=%i)" % (start, length) - lit = model.NewBoolVar(prefix + name) + lit = model.new_bool_var(prefix + name) span.append(lit) - model.AddBoolOr(span) + model.add_bool_or(span) cost_literals.append(lit) # We filter exactly the sequence with a short length. # The penalty is proportional to the delta with soft_min. @@ -115,23 +125,33 @@ def add_soft_sequence_constraint( for start in range(len(works) - length + 1): span = negated_bounded_span(works, start, length) name = ": over_span(start=%i, length=%i)" % (start, length) - lit = model.NewBoolVar(prefix + name) + lit = model.new_bool_var(prefix + name) span.append(lit) - model.AddBoolOr(span) + model.add_bool_or(span) cost_literals.append(lit) # Cost paid is max_cost * excess length. cost_coefficients.append(max_cost * (length - soft_max)) # Just forbid any sequence of true variables with length hard_max + 1 for start in range(len(works) - hard_max): - model.AddBoolOr([works[i].Not() for i in range(start, start + hard_max + 1)]) + model.add_bool_or( + [works[i].negated() for i in range(start, start + hard_max + 1)] + ) return cost_literals, cost_coefficients def add_soft_sum_constraint( - model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix -): - """Sum constraint with soft and hard bounds. + model: cp_model.CpModel, + works: list[cp_model.BoolVarT], + hard_min: int, + soft_min: int, + min_cost: int, + soft_max: int, + hard_max: int, + max_cost: int, + prefix: str, +) -> tuple[list[cp_model.IntVar], list[int]]: + """sum constraint with soft and hard bounds. This constraint counts the variables assigned to true from works. If forbids sum < hard_min or > hard_max. @@ -160,33 +180,33 @@ def add_soft_sum_constraint( """ cost_variables = [] cost_coefficients = [] - sum_var = model.NewIntVar(hard_min, hard_max, "") + sum_var = model.new_int_var(hard_min, hard_max, "") # This adds the hard constraints on the sum. - model.Add(sum_var == sum(works)) + model.add(sum_var == sum(works)) # Penalize sums below the soft_min target. if soft_min > hard_min and min_cost > 0: - delta = model.NewIntVar(-len(works), len(works), "") - model.Add(delta == soft_min - sum_var) + delta = model.new_int_var(-len(works), len(works), "") + model.add(delta == soft_min - sum_var) # TODO(user): Compare efficiency with only excess >= soft_min - sum_var. - excess = model.NewIntVar(0, 7, prefix + ": under_sum") - model.AddMaxEquality(excess, [delta, 0]) + excess = model.new_int_var(0, 7, prefix + ": under_sum") + model.add_max_equality(excess, [delta, 0]) cost_variables.append(excess) cost_coefficients.append(min_cost) # Penalize sums above the soft_max target. if soft_max < hard_max and max_cost > 0: - delta = model.NewIntVar(-7, 7, "") - model.Add(delta == sum_var - soft_max) - excess = model.NewIntVar(0, 7, prefix + ": over_sum") - model.AddMaxEquality(excess, [delta, 0]) + delta = model.new_int_var(-7, 7, "") + model.add(delta == sum_var - soft_max) + excess = model.new_int_var(0, 7, prefix + ": over_sum") + model.add_max_equality(excess, [delta, 0]) cost_variables.append(excess) cost_coefficients.append(max_cost) return cost_variables, cost_coefficients -def solve_shift_scheduling(params, output_proto): +def solve_shift_scheduling(params: str, output_proto: str): """Solves the shift scheduling problem.""" # Data num_employees = 8 @@ -281,22 +301,22 @@ def solve_shift_scheduling(params, output_proto): for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): - work[e, s, d] = model.NewBoolVar("work%i_%i_%i" % (e, s, d)) + work[e, s, d] = model.new_bool_var("work%i_%i_%i" % (e, s, d)) # Linear terms of the objective in a minimization context. - obj_int_vars = [] - obj_int_coeffs = [] - obj_bool_vars = [] - obj_bool_coeffs = [] + obj_int_vars: list[cp_model.IntVar] = [] + obj_int_coeffs: list[int] = [] + obj_bool_vars: list[cp_model.BoolVarT] = [] + obj_bool_coeffs: list[int] = [] # Exactly one shift per day. for e in range(num_employees): for d in range(num_days): - model.AddExactlyOne(work[e, s, d] for s in range(num_shifts)) + model.add_exactly_one(work[e, s, d] for s in range(num_shifts)) # Fixed assignments. for e, s, d in fixed_assignments: - model.Add(work[e, s, d] == 1) + model.add(work[e, s, d] == 1) # Employee requests for e, s, d, w in requests: @@ -348,17 +368,17 @@ def solve_shift_scheduling(params, output_proto): for e in range(num_employees): for d in range(num_days - 1): transition = [ - work[e, previous_shift, d].Not(), - work[e, next_shift, d + 1].Not(), + work[e, previous_shift, d].negated(), + work[e, next_shift, d + 1].negated(), ] if cost == 0: - model.AddBoolOr(transition) + model.add_bool_or(transition) else: - trans_var = model.NewBoolVar( + trans_var = model.new_bool_var( "transition (employee=%i, day=%i)" % (e, d) ) transition.append(trans_var) - model.AddBoolOr(transition) + model.add_bool_or(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) @@ -369,18 +389,18 @@ def solve_shift_scheduling(params, output_proto): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] - worked = model.NewIntVar(min_demand, num_employees, "") - model.Add(worked == sum(works)) + worked = model.new_int_var(min_demand, num_employees, "") + model.add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = "excess_demand(shift=%i, week=%i, day=%i)" % (s, w, d) - excess = model.NewIntVar(0, num_employees - min_demand, name) - model.Add(excess == worked - min_demand) + excess = model.new_int_var(0, num_employees - min_demand, name) + model.add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Objective - model.Minimize( + model.minimize( sum(obj_bool_vars[i] * obj_bool_coeffs[i] for i in range(len(obj_bool_vars))) + sum(obj_int_vars[i] * obj_int_coeffs[i] for i in range(len(obj_int_vars))) ) @@ -395,7 +415,7 @@ def solve_shift_scheduling(params, output_proto): if params: text_format.Parse(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: @@ -408,32 +428,32 @@ def solve_shift_scheduling(params, output_proto): schedule = "" for d in range(num_days): for s in range(num_shifts): - if solver.BooleanValue(work[e, s, d]): + if solver.boolean_value(work[e, s, d]): schedule += shifts[s] + " " print("worker %i: %s" % (e, schedule)) print() print("Penalties:") for i, var in enumerate(obj_bool_vars): - if solver.BooleanValue(var): + if solver.boolean_value(var): penalty = obj_bool_coeffs[i] if penalty > 0: - print(" %s violated, penalty=%i" % (var.Name(), penalty)) + print(f" {var.name} violated, penalty={penalty}") else: - print(" %s fulfilled, gain=%i" % (var.Name(), -penalty)) + print(f" {var.name} fulfilled, gain={-penalty}") for i, var in enumerate(obj_int_vars): - if solver.Value(var) > 0: + if solver.value(var) > 0: print( " %s violated by %i, linear penalty=%i" - % (var.Name(), solver.Value(var), obj_int_coeffs[i]) + % (var.name, solver.value(var), obj_int_coeffs[i]) ) print() print("Statistics") - print(" - status : %s" % solver.StatusName(status)) - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - status : %s" % solver.status_name(status)) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) def main(_): diff --git a/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py b/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py index 2381de7831f..fa496da34e6 100644 --- a/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py +++ b/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat.py @@ -48,7 +48,7 @@ def on_solution_callback(self): """Called after each new solution found.""" print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 @@ -433,34 +433,34 @@ def single_machine_scheduling(): % (job_id, release_date, duration, due_date) ) name_suffix = "_%i" % job_id - start = model.NewIntVar(release_date, due_date, "s" + name_suffix) - end = model.NewIntVar(release_date, due_date, "e" + name_suffix) - interval = model.NewIntervalVar(start, duration, end, "i" + name_suffix) + start = model.new_int_var(release_date, due_date, "s" + name_suffix) + end = model.new_int_var(release_date, due_date, "e" + name_suffix) + interval = model.new_interval_var(start, duration, end, "i" + name_suffix) starts.append(start) ends.append(end) intervals.append(interval) # No overlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # ---------------------------------------------------------------------------- # Transition times using a circuit constraint. arcs = [] for i in all_jobs: # Initial arc from the dummy node (0) to a task. - start_lit = model.NewBoolVar("") + start_lit = model.new_bool_var("") arcs.append((0, i + 1, start_lit)) # If this task is the first, set to minimum starting time. min_start_time = max(release_dates[i], setup_times[0][i]) - model.Add(starts[i] == min_start_time).OnlyEnforceIf(start_lit) + model.add(starts[i] == min_start_time).only_enforce_if(start_lit) # Final arc from an arc to the dummy node. - arcs.append((i + 1, 0, model.NewBoolVar(""))) + arcs.append((i + 1, 0, model.new_bool_var(""))) for j in all_jobs: if i == j: continue - lit = model.NewBoolVar("%i follows %i" % (j, i)) + lit = model.new_bool_var("%i follows %i" % (j, i)) arcs.append((i + 1, j + 1, lit)) # We add the reified precedence to link the literal with the times of the @@ -468,27 +468,27 @@ def single_machine_scheduling(): # If release_dates[j] == 0, we can strenghten this precedence into an # equality as we are minimizing the makespan. if release_dates[j] == 0: - model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf( + model.add(starts[j] == ends[i] + setup_times[i + 1][j]).only_enforce_if( lit ) else: - model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf( + model.add(starts[j] >= ends[i] + setup_times[i + 1][j]).only_enforce_if( lit ) - model.AddCircuit(arcs) + model.add_circuit(arcs) # ---------------------------------------------------------------------------- # Precedences. for before, after in precedences: print("job %i is after job %i" % (after, before)) - model.Add(ends[before] <= starts[after]) + model.add(ends[before] <= starts[after]) # ---------------------------------------------------------------------------- # Objective. - makespan = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(makespan, ends) - model.Minimize(makespan) + makespan = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(makespan, ends) + model.minimize(makespan) # ---------------------------------------------------------------------------- # Write problem to file. @@ -503,11 +503,11 @@ def single_machine_scheduling(): if parameters: text_format.Parse(parameters, solver.parameters) solution_printer = SolutionPrinter() - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) for job_id in all_jobs: print( "job %i starts at %i end ends at %i" - % (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id])) + % (job_id, solver.value(starts[job_id]), solver.value(ends[job_id])) ) diff --git a/examples/python/spread_robots_sat.py b/examples/python/spread_robots_sat.py index fb5011b3169..d85fc1afa80 100644 --- a/examples/python/spread_robots_sat.py +++ b/examples/python/spread_robots_sat.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Maximize the minimum of pairwise distances between n robots in a square space.""" +"""maximize the minimum of pairwise distances between n robots in a square space.""" import math from typing import Sequence @@ -38,8 +38,8 @@ def spread_robots(num_robots: int, room_size: int, params: str): model = cp_model.CpModel() # Create the list of coordinates (x, y) for each robot. - x = [model.NewIntVar(1, room_size, f"x_{i}") for i in range(num_robots)] - y = [model.NewIntVar(1, room_size, f"y_{i}") for i in range(num_robots)] + x = [model.new_int_var(1, room_size, f"x_{i}") for i in range(num_robots)] + y = [model.new_int_var(1, room_size, f"y_{i}") for i in range(num_robots)] # The specification of the problem is to maximize the minimum euclidian # distance between any two robots. Unfortunately, the euclidian distance @@ -58,7 +58,7 @@ def spread_robots(num_robots: int, room_size: int, params: str): # forall i: # scaled_min_square_distance <= scaling * (x_diff_sq[i] + y_diff_sq[i]) scaling = 1000 - scaled_min_square_distance = model.NewIntVar( + scaled_min_square_distance = model.new_int_var( 0, 2 * scaling * room_size**2, "scaled_min_square_distance" ) @@ -67,45 +67,45 @@ def spread_robots(num_robots: int, room_size: int, params: str): for i in range(num_robots - 1): for j in range(i + 1, num_robots): # Compute the distance on each dimension between robot i and robot j. - x_diff = model.NewIntVar(-room_size, room_size, f"x_diff{i}") - y_diff = model.NewIntVar(-room_size, room_size, f"y_diff{i}") - model.Add(x_diff == x[i] - x[j]) - model.Add(y_diff == y[i] - y[j]) + x_diff = model.new_int_var(-room_size, room_size, f"x_diff{i}") + y_diff = model.new_int_var(-room_size, room_size, f"y_diff{i}") + model.add(x_diff == x[i] - x[j]) + model.add(y_diff == y[i] - y[j]) # Compute the square of the previous differences. - x_diff_sq = model.NewIntVar(0, room_size**2, f"x_diff_sq{i}") - y_diff_sq = model.NewIntVar(0, room_size**2, f"y_diff_sq{i}") - model.AddMultiplicationEquality(x_diff_sq, x_diff, x_diff) - model.AddMultiplicationEquality(y_diff_sq, y_diff, y_diff) + x_diff_sq = model.new_int_var(0, room_size**2, f"x_diff_sq{i}") + y_diff_sq = model.new_int_var(0, room_size**2, f"y_diff_sq{i}") + model.add_multiplication_equality(x_diff_sq, x_diff, x_diff) + model.add_multiplication_equality(y_diff_sq, y_diff, y_diff) # We just need to be <= to the scaled square distance as we are # maximizing the min distance, which is equivalent as maximizing the min # square distance. - model.Add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq)) + model.add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq)) # Naive symmetry breaking. for i in range(1, num_robots): - model.Add(x[0] <= x[i]) - model.Add(y[0] <= y[i]) + model.add(x[0] <= x[i]) + model.add(y[0] <= y[i]) # Objective - model.Maximize(scaled_min_square_distance) + model.maximize(scaled_min_square_distance) # Creates a solver and solves the model. solver = cp_model.CpSolver() if params: text_format.Parse(params, solver.parameters) solver.parameters.log_search_progress = True - status = solver.Solve(model) + status = solver.solve(model) # Prints the solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print( f"Spread {num_robots} with a min pairwise distance of" - f" {math.sqrt(solver.ObjectiveValue() / scaling)}" + f" {math.sqrt(solver.objective_value / scaling)}" ) for i in range(num_robots): - print(f"robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}") + print(f"robot {i}: x={solver.value(x[i])} y={solver.value(y[i])}") else: print("No solution found.") diff --git a/examples/python/steel_mill_slab_sat.py b/examples/python/steel_mill_slab_sat.py old mode 100755 new mode 100644 index f119ff3c262..667f0217541 --- a/examples/python/steel_mill_slab_sat.py +++ b/examples/python/steel_mill_slab_sat.py @@ -131,22 +131,22 @@ def __init__(self, orders, assign, load, loss): def on_solution_callback(self): """Called on each new solution.""" current_time = time.time() - objective = sum(self.Value(l) for l in self.__loss) + objective = sum(self.value(l) for l in self.__loss) print( "Solution %i, time = %f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, objective) ) self.__solution_count += 1 orders_in_slab = [ - [o for o in self.__all_orders if self.Value(self.__assign[o][s])] + [o for o in self.__all_orders if self.value(self.__assign[o][s])] for s in self.__all_slabs ] for s in self.__all_slabs: if orders_in_slab[s]: line = " - slab %i, load = %i, loss = %i, orders = [" % ( s, - self.Value(self.__load[s]), - self.Value(self.__loss[s]), + self.value(self.__load[s]), + self.value(self.__loss[s]), ) for o in orders_in_slab[s]: line += "#%i(w%i, c%i) " % ( @@ -193,44 +193,48 @@ def steel_mill_slab(problem, break_symmetries): # Create the model and the decision variables. model = cp_model.CpModel() assign = [ - [model.NewBoolVar("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] + [model.new_bool_var("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] for o in all_orders ] - loads = [model.NewIntVar(0, max_capacity, "load_of_slab_%i" % s) for s in all_slabs] + loads = [ + model.new_int_var(0, max_capacity, "load_of_slab_%i" % s) for s in all_slabs + ] color_is_in_slab = [ - [model.NewBoolVar("color_%i_in_slab_%i" % (c + 1, s)) for c in all_colors] + [model.new_bool_var("color_%i_in_slab_%i" % (c + 1, s)) for c in all_colors] for s in all_slabs ] # Compute load of all slabs. for s in all_slabs: - model.Add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s]) + model.add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s]) # Orders are assigned to one slab. for o in all_orders: - model.AddExactlyOne(assign[o]) + model.add_exactly_one(assign[o]) # Redundant constraint (sum of loads == sum of widths). - model.Add(sum(loads) == sum(widths)) + model.add(sum(loads) == sum(widths)) # Link present_colors and assign. for c in all_colors: for s in all_slabs: for o in orders_per_color[c]: - model.AddImplication(assign[o][s], color_is_in_slab[s][c]) - model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not()) + model.add_implication(assign[o][s], color_is_in_slab[s][c]) + model.add_implication( + color_is_in_slab[s][c].negated(), assign[o][s].negated() + ) # At most two colors per slab. for s in all_slabs: - model.Add(sum(color_is_in_slab[s]) <= 2) + model.add(sum(color_is_in_slab[s]) <= 2) # Project previous constraint on unique_color_orders for s in all_slabs: - model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2) + model.add(sum(assign[o][s] for o in unique_color_orders) <= 2) # Symmetry breaking. for s in range(num_slabs - 1): - model.Add(loads[s] >= loads[s + 1]) + model.add(loads[s] >= loads[s + 1]) # Collect equivalent orders. width_to_unique_color_order = {} @@ -271,38 +275,38 @@ def steel_mill_slab(problem, break_symmetries): positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: - positions[p[0]] = model.NewIntVar( + positions[p[0]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[0] ) - model.AddMapDomain(positions[p[0]], assign[p[0]]) + model.add_map_domain(positions[p[0]], assign[p[0]]) if p[1] not in positions: - positions[p[1]] = model.NewIntVar( + positions[p[1]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[1] ) - model.AddMapDomain(positions[p[1]], assign[p[1]]) + model.add_map_domain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. - model.Add(positions[p[0]] <= positions[p[1]]) + model.add(positions[p[0]] <= positions[p[1]]) # Objective. - obj = model.NewIntVar(0, num_slabs * max_loss, "obj") - losses = [model.NewIntVar(0, max_loss, "loss_%i" % s) for s in all_slabs] + obj = model.new_int_var(0, num_slabs * max_loss, "obj") + losses = [model.new_int_var(0, max_loss, "loss_%i" % s) for s in all_slabs] for s in all_slabs: - model.AddElement(loads[s], loss_array, losses[s]) - model.Add(obj == sum(losses)) - model.Minimize(obj) + model.add_element(loads[s], loss_array, losses[s]) + model.add(obj == sum(losses)) + model.minimize(obj) ### Solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) objective_printer = cp_model.ObjectiveSolutionPrinter() - status = solver.Solve(model, objective_printer) + status = solver.solve(model, objective_printer) ### Output the solution. if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): print( "Loss = %i, time = %f s, %i conflicts" - % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + % (solver.objective_value, solver.wall_time, solver.num_conflicts) ) else: print("No solution") @@ -381,11 +385,11 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): # Create the model and the decision variables. model = cp_model.CpModel() assign = [ - [model.NewBoolVar("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] + [model.new_bool_var("assign_%i_to_slab_%i" % (o, s)) for s in all_slabs] for o in all_orders ] - loads = [model.NewIntVar(0, max_capacity, "load_%i" % s) for s in all_slabs] - losses = [model.NewIntVar(0, max_loss, "loss_%i" % s) for s in all_slabs] + loads = [model.new_int_var(0, max_capacity, "load_%i" % s) for s in all_slabs] + losses = [model.new_int_var(0, max_loss, "loss_%i" % s) for s in all_slabs] unsorted_valid_slabs = collect_valid_slabs_dp( capacities, colors, widths, loss_array @@ -394,20 +398,20 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2]) for s in all_slabs: - model.AddAllowedAssignments( + model.add_allowed_assignments( [assign[o][s] for o in all_orders] + [losses[s], loads[s]], valid_slabs ) # Orders are assigned to one slab. for o in all_orders: - model.AddExactlyOne(assign[o]) + model.add_exactly_one(assign[o]) # Redundant constraint (sum of loads == sum of widths). - model.Add(sum(loads) == sum(widths)) + model.add(sum(loads) == sum(widths)) # Symmetry breaking. for s in range(num_slabs - 1): - model.Add(loads[s] >= loads[s + 1]) + model.add(loads[s] >= loads[s + 1]) # Collect equivalent orders. if break_symmetries: @@ -453,20 +457,20 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: - positions[p[0]] = model.NewIntVar( + positions[p[0]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[0] ) - model.AddMapDomain(positions[p[0]], assign[p[0]]) + model.add_map_domain(positions[p[0]], assign[p[0]]) if p[1] not in positions: - positions[p[1]] = model.NewIntVar( + positions[p[1]] = model.new_int_var( 0, num_slabs - 1, "position_of_slab_%i" % p[1] ) - model.AddMapDomain(positions[p[1]], assign[p[1]]) + model.add_map_domain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. - model.Add(positions[p[0]] <= positions[p[1]]) + model.add(positions[p[0]] <= positions[p[1]]) # Objective. - model.Minimize(sum(losses)) + model.minimize(sum(losses)) print("Model created") @@ -476,13 +480,13 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries): text_format.Parse(_PARAMS.value, solver.parameters) solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) ### Output the solution. if status == cp_model.OPTIMAL: print( "Loss = %i, time = %.2f s, %i conflicts" - % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + % (solver.objective_value, solver.wall_time, solver.num_conflicts) ) else: print("No solution") @@ -522,21 +526,21 @@ def steel_mill_slab_with_column_generation(problem): # create model and decision variables. model = cp_model.CpModel() - selected = [model.NewBoolVar("selected_%i" % i) for i in all_valid_slabs] + selected = [model.new_bool_var("selected_%i" % i) for i in all_valid_slabs] for order_id in all_orders: - model.Add( + model.add( sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id]) == 1 ) # Redundant constraint (sum of loads == sum of widths). - model.Add( + model.add( sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths) ) # Objective. - model.Minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs)) + model.minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs)) print("Model created") @@ -545,13 +549,13 @@ def steel_mill_slab_with_column_generation(problem): if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) ### Output the solution. if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): print( "Loss = %i, time = %.2f s, %i conflicts" - % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()) + % (solver.objective_value, solver.wall_time, solver.num_conflicts) ) else: print("No solution") diff --git a/examples/python/sudoku_sat.py b/examples/python/sudoku_sat.py old mode 100644 new mode 100755 index bf4b391ca96..732ee74a97a --- a/examples/python/sudoku_sat.py +++ b/examples/python/sudoku_sat.py @@ -42,15 +42,15 @@ def solve_sudoku(): grid = {} for i in line: for j in line: - grid[(i, j)] = model.NewIntVar(1, line_size, "grid %i %i" % (i, j)) + grid[(i, j)] = model.new_int_var(1, line_size, "grid %i %i" % (i, j)) # AllDifferent on rows. for i in line: - model.AddAllDifferent(grid[(i, j)] for j in line) + model.add_all_different(grid[(i, j)] for j in line) # AllDifferent on columns. for j in line: - model.AddAllDifferent(grid[(i, j)] for i in line) + model.add_all_different(grid[(i, j)] for i in line) # AllDifferent on cells. for i in cell: @@ -60,20 +60,20 @@ def solve_sudoku(): for dj in cell: one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)]) - model.AddAllDifferent(one_cell) + model.add_all_different(one_cell) # Initial values. for i in line: for j in line: if initial_grid[i][j]: - model.Add(grid[(i, j)] == initial_grid[i][j]) + model.add(grid[(i, j)] == initial_grid[i][j]) - # Solve and print out the solution. + # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: for i in line: - print([int(solver.Value(grid[(i, j)])) for j in line]) + print([int(solver.value(grid[(i, j)])) for j in line]) solve_sudoku() diff --git a/examples/python/task_allocation_sat.py b/examples/python/task_allocation_sat.py index 0027f533bee..04009582aac 100644 --- a/examples/python/task_allocation_sat.py +++ b/examples/python/task_allocation_sat.py @@ -246,12 +246,12 @@ def task_allocation_sat(): assign = {} for task in all_tasks: for slot in all_slots: - assign[(task, slot)] = model.NewBoolVar("x[%i][%i]" % (task, slot)) - count = model.NewIntVar(0, nslots, "count") - slot_used = [model.NewBoolVar("slot_used[%i]" % s) for s in all_slots] + assign[(task, slot)] = model.new_bool_var("x[%i][%i]" % (task, slot)) + count = model.new_int_var(0, nslots, "count") + slot_used = [model.new_bool_var("slot_used[%i]" % s) for s in all_slots] for task in all_tasks: - model.Add( + model.add( sum( assign[(task, slot)] for slot in all_slots if available[task][slot] == 1 ) @@ -259,38 +259,40 @@ def task_allocation_sat(): ) for slot in all_slots: - model.Add( + model.add( sum( assign[(task, slot)] for task in all_tasks if available[task][slot] == 1 ) <= capacity ) - model.AddBoolOr( + model.add_bool_or( [assign[(task, slot)] for task in all_tasks if available[task][slot] == 1] - ).OnlyEnforceIf(slot_used[slot]) + ).only_enforce_if(slot_used[slot]) for task in all_tasks: if available[task][slot] == 1: - model.AddImplication(slot_used[slot].Not(), assign[(task, slot)].Not()) + model.add_implication( + slot_used[slot].negated(), assign[(task, slot)].negated() + ) else: - model.Add(assign[(task, slot)] == 0) + model.add(assign[(task, slot)] == 0) - model.Add(count == sum(slot_used)) + model.add(count == sum(slot_used)) # Redundant constraint. This instance is easier if we add this constraint. - # model.Add(count >= (nslots + capacity - 1) // capacity) + # model.add(count >= (nslots + capacity - 1) // capacity) - model.Minimize(count) + model.minimize(count) # Create a solver and solve the problem. solver = cp_model.CpSolver() # Uses the portfolion of heuristics. solver.parameters.log_search_progress = True solver.parameters.num_search_workers = 16 - status = solver.Solve(model) + status = solver.solve(model) print("Statistics") - print(" - status =", solver.StatusName(status)) - print(" - optimal solution =", solver.ObjectiveValue()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - status =", solver.status_name(status)) + print(" - optimal solution =", solver.objective_value) + print(" - wall time : %f s" % solver.wall_time) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/tasks_and_workers_assignment_sat.py b/examples/python/tasks_and_workers_assignment_sat.py index 0960f59c5c7..83f2d62b320 100644 --- a/examples/python/tasks_and_workers_assignment_sat.py +++ b/examples/python/tasks_and_workers_assignment_sat.py @@ -29,13 +29,13 @@ def __init__(self): def on_solution_callback(self): print( "Solution %i, time = %f s, objective = %i" - % (self.__solution_count, self.WallTime(), self.ObjectiveValue()) + % (self.__solution_count, self.wall_time, self.objective_value) ) self.__solution_count += 1 def tasks_and_workers_assignment_sat(): - """Solve the assignment problem.""" + """solve the assignment problem.""" model = cp_model.CpModel() # CP-SAT solver is integer only. @@ -53,71 +53,71 @@ def tasks_and_workers_assignment_sat(): x = {} for i in all_workers: for j in all_groups: - x[i, j] = model.NewBoolVar("x[%i,%i]" % (i, j)) + x[i, j] = model.new_bool_var("x[%i,%i]" % (i, j)) ## y_kj is 1 if task k is assigned to group j y = {} for k in all_tasks: for j in all_groups: - y[k, j] = model.NewBoolVar("x[%i,%i]" % (k, j)) + y[k, j] = model.new_bool_var("x[%i,%i]" % (k, j)) # Constraints # Each task k is assigned to a group and only one. for k in all_tasks: - model.Add(sum(y[k, j] for j in all_groups) == 1) + model.add(sum(y[k, j] for j in all_groups) == 1) # Each worker i is assigned to a group and only one. for i in all_workers: - model.Add(sum(x[i, j] for j in all_groups) == 1) + model.add(sum(x[i, j] for j in all_groups) == 1) - # cost per group + # Cost per group sum_of_costs = sum(task_cost) averages = [] num_workers_in_group = [] scaled_sum_of_costs_in_group = [] scaling = 1000 # We introduce scaling to deal with floating point average. for j in all_groups: - n = model.NewIntVar(1, num_workers, "num_workers_in_group_%i" % j) - model.Add(n == sum(x[i, j] for i in all_workers)) - c = model.NewIntVar(0, sum_of_costs * scaling, "sum_of_costs_of_group_%i" % j) - model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks)) - a = model.NewIntVar(0, sum_of_costs * scaling, "average_cost_of_group_%i" % j) - model.AddDivisionEquality(a, c, n) + n = model.new_int_var(1, num_workers, "num_workers_in_group_%i" % j) + model.add(n == sum(x[i, j] for i in all_workers)) + c = model.new_int_var(0, sum_of_costs * scaling, "sum_of_costs_of_group_%i" % j) + model.add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks)) + a = model.new_int_var(0, sum_of_costs * scaling, "average_cost_of_group_%i" % j) + model.add_division_equality(a, c, n) averages.append(a) num_workers_in_group.append(n) scaled_sum_of_costs_in_group.append(c) # All workers are assigned. - model.Add(sum(num_workers_in_group) == num_workers) + model.add(sum(num_workers_in_group) == num_workers) # Objective. - obj = model.NewIntVar(0, sum_of_costs * scaling, "obj") - model.AddMaxEquality(obj, averages) - model.Minimize(obj) + obj = model.new_int_var(0, sum_of_costs * scaling, "obj") + model.add_max_equality(obj, averages) + model.minimize(obj) # Solve and print out the solution. solver = cp_model.CpSolver() solver.parameters.max_time_in_seconds = 60 * 60 * 2 objective_printer = ObjectivePrinter() - status = solver.Solve(model, objective_printer) - print(solver.ResponseStats()) + status = solver.solve(model, objective_printer) + print(solver.response_stats()) if status == cp_model.OPTIMAL: for j in all_groups: print("Group %i" % j) for i in all_workers: - if solver.BooleanValue(x[i, j]): + if solver.boolean_value(x[i, j]): print(" - worker %i" % i) for k in all_tasks: - if solver.BooleanValue(y[k, j]): + if solver.boolean_value(y[k, j]): print(" - task %i with cost %i" % (k, task_cost[k])) print( " - sum_of_costs = %i" - % (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling) + % (solver.value(scaled_sum_of_costs_in_group[j]) // scaling) ) - print(" - average cost = %f" % (solver.Value(averages[j]) * 1.0 / scaling)) + print(" - average cost = %f" % (solver.value(averages[j]) * 1.0 / scaling)) tasks_and_workers_assignment_sat() diff --git a/examples/python/tsp_sat.py b/examples/python/tsp_sat.py index f0ef4f916e1..02b149377fd 100644 --- a/examples/python/tsp_sat.py +++ b/examples/python/tsp_sat.py @@ -82,17 +82,17 @@ def main(): if i == j: continue - lit = model.NewBoolVar("%i follows %i" % (j, i)) + lit = model.new_bool_var("%i follows %i" % (j, i)) arcs.append((i, j, lit)) arc_literals[i, j] = lit obj_vars.append(lit) obj_coeffs.append(DISTANCE_MATRIX[i][j]) - model.AddCircuit(arcs) + model.add_circuit(arcs) # Minimize weighted sum of arcs. Because this s - model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) + model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars)))) # Solve and print out the solution. solver = cp_model.CpSolver() @@ -100,8 +100,8 @@ def main(): # To benefit from the linearization of the circuit constraint. solver.parameters.linearization_level = 2 - solver.Solve(model) - print(solver.ResponseStats()) + solver.solve(model) + print(solver.response_stats()) current_node = 0 str_route = "%i" % current_node @@ -111,7 +111,7 @@ def main(): for i in all_nodes: if i == current_node: continue - if solver.BooleanValue(arc_literals[current_node, i]): + if solver.boolean_value(arc_literals[current_node, i]): str_route += " -> %i" % i route_distance += DISTANCE_MATRIX[current_node][i] current_node = i diff --git a/examples/python/vendor_scheduling_sat.py b/examples/python/vendor_scheduling_sat.py index ac65975e476..60957851ef9 100644 --- a/examples/python/vendor_scheduling_sat.py +++ b/examples/python/vendor_scheduling_sat.py @@ -48,13 +48,13 @@ def on_solution_callback(self): for i in range(self.__num_vendors): print( " - vendor %i: " % i, - self.__possible_schedules[self.Value(self.__selected_schedules[i])], + self.__possible_schedules[self.value(self.__selected_schedules[i])], ) print() for j in range(self.__num_hours): print(" - # workers on day%2i: " % j, end=" ") - print(self.Value(self.__hours_stat[j]), end=" ") + print(self.value(self.__hours_stat[j]), end=" ") print() print() @@ -101,38 +101,40 @@ def vendor_scheduling_sat(): all_hours = range(num_hours) # - # declare variables + # Declare variables # x = {} for v in all_vendors: tmp = [] for h in all_hours: - x[v, h] = model.NewIntVar(0, num_work_types, "x[%i,%i]" % (v, h)) + x[v, h] = model.new_int_var(0, num_work_types, "x[%i,%i]" % (v, h)) tmp.append(x[v, h]) - selected_schedule = model.NewIntVar(0, num_possible_schedules - 1, "s[%i]" % v) - hours = model.NewIntVar(0, num_hours, "h[%i]" % v) + selected_schedule = model.new_int_var( + 0, num_possible_schedules - 1, "s[%i]" % v + ) + hours = model.new_int_var(0, num_hours, "h[%i]" % v) selected_schedules.append(selected_schedule) vendors_stat.append(hours) tmp.append(selected_schedule) tmp.append(hours) - model.AddAllowedAssignments(tmp, possible_schedules) + model.add_allowed_assignments(tmp, possible_schedules) # # Statistics and constraints for each hour # for h in all_hours: - workers = model.NewIntVar(0, 1000, "workers[%i]" % h) - model.Add(workers == sum(x[v, h] for v in all_vendors)) + workers = model.new_int_var(0, 1000, "workers[%i]" % h) + model.add(workers == sum(x[v, h] for v in all_vendors)) hours_stat.append(workers) - model.Add(workers * max_traffic_per_vendor >= traffic[h]) + model.add(workers * max_traffic_per_vendor >= traffic[h]) # # Redundant constraint: sort selected_schedules # for v in range(num_vendors - 1): - model.Add(selected_schedules[v] <= selected_schedules[v + 1]) + model.add(selected_schedules[v] <= selected_schedules[v + 1]) # Solve model. solver = cp_model.CpSolver() @@ -145,13 +147,13 @@ def vendor_scheduling_sat(): hours_stat, min_vendors, ) - status = solver.Solve(model, solution_printer) - print("Status = %s" % solver.StatusName(status)) + status = solver.solve(model, solution_printer) + print("Status = %s" % solver.status_name(status)) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) print(" - number of solutions found: %i" % solution_printer.solution_count()) diff --git a/examples/python/wedding_optimal_chart_sat.py b/examples/python/wedding_optimal_chart_sat.py index a9c0cf8521d..e23803a0642 100644 --- a/examples/python/wedding_optimal_chart_sat.py +++ b/examples/python/wedding_optimal_chart_sat.py @@ -56,7 +56,7 @@ def __init__(self, seats, names, num_tables, num_guests): def on_solution_callback(self): current_time = time.time() - objective = self.ObjectiveValue() + objective = self.objective_value print( "Solution %i, time = %f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, objective) @@ -66,10 +66,10 @@ def on_solution_callback(self): for t in range(self.__num_tables): print("Table %d: " % t) for g in range(self.__num_guests): - if self.Value(self.__seats[(t, g)]): + if self.value(self.__seats[(t, g)]): print(" " + self.__names[g]) - def num_solutions(self): + def num_solutions(self) -> int: return self.__solution_count @@ -148,12 +148,12 @@ def solve_with_discrete_model(): seats = {} for t in all_tables: for g in all_guests: - seats[(t, g)] = model.NewBoolVar("guest %i seats on table %i" % (g, t)) + seats[(t, g)] = model.new_bool_var("guest %i seats on table %i" % (g, t)) colocated = {} for g1 in range(num_guests - 1): for g2 in range(g1 + 1, num_guests): - colocated[(g1, g2)] = model.NewBoolVar( + colocated[(g1, g2)] = model.new_bool_var( "guest %i seats with guest %i" % (g1, g2) ) @@ -161,12 +161,12 @@ def solve_with_discrete_model(): for g1 in range(num_guests - 1): for g2 in range(g1 + 1, num_guests): for t in all_tables: - same_table[(g1, g2, t)] = model.NewBoolVar( + same_table[(g1, g2, t)] = model.new_bool_var( "guest %i seats with guest %i on table %i" % (g1, g2, t) ) # Objective - model.Maximize( + model.maximize( sum( connections[g1][g2] * colocated[g1, g2] for g1 in range(num_guests - 1) @@ -181,35 +181,35 @@ def solve_with_discrete_model(): # Everybody seats at one table. for g in all_guests: - model.Add(sum(seats[(t, g)] for t in all_tables) == 1) + model.add(sum(seats[(t, g)] for t in all_tables) == 1) # Tables have a max capacity. for t in all_tables: - model.Add(sum(seats[(t, g)] for g in all_guests) <= table_capacity) + model.add(sum(seats[(t, g)] for g in all_guests) <= table_capacity) # Link colocated with seats for g1 in range(num_guests - 1): for g2 in range(g1 + 1, num_guests): for t in all_tables: # Link same_table and seats. - model.AddBoolOr( + model.add_bool_or( [ - seats[(t, g1)].Not(), - seats[(t, g2)].Not(), + seats[(t, g1)].negated(), + seats[(t, g2)].negated(), same_table[(g1, g2, t)], ] ) - model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)]) - model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)]) + model.add_implication(same_table[(g1, g2, t)], seats[(t, g1)]) + model.add_implication(same_table[(g1, g2, t)], seats[(t, g2)]) # Link colocated and same_table. - model.Add( + model.add( sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(g1, g2)] ) # Min known neighbors rule. for g in all_guests: - model.Add( + model.add( sum( same_table[(g, g2, t)] for g2 in range(g + 1, num_guests) @@ -226,17 +226,17 @@ def solve_with_discrete_model(): ) # Symmetry breaking. First guest seats on the first table. - model.Add(seats[(0, 0)] == 1) + model.add(seats[(0, 0)] == 1) ### Solve model. solver = cp_model.CpSolver() solution_printer = WeddingChartPrinter(seats, names, num_tables, num_guests) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) print("Statistics") - print(" - conflicts : %i" % solver.NumConflicts()) - print(" - branches : %i" % solver.NumBranches()) - print(" - wall time : %f s" % solver.WallTime()) + print(" - conflicts : %i" % solver.num_conflicts) + print(" - branches : %i" % solver.num_branches) + print(" - wall time : %f s" % solver.wall_time) print(" - num solutions: %i" % solution_printer.num_solutions()) diff --git a/examples/python/weighted_latency_problem_sat.py b/examples/python/weighted_latency_problem_sat.py index 3db149a3855..8acb20d911e 100644 --- a/examples/python/weighted_latency_problem_sat.py +++ b/examples/python/weighted_latency_problem_sat.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a random Weighted Latency problem with the CP-SAT solver.""" +"""solve a random Weighted Latency problem with the CP-SAT solver.""" import random from typing import Sequence @@ -61,10 +61,12 @@ def solve_with_cp_sat(x, y, profits): # because of the manhattan distance, the sum of distances is bounded by this. horizon = _GRID_SIZE.value * 2 * _NUM_NODES.value - times = [model.NewIntVar(0, horizon, f"x_{i}") for i in range(_NUM_NODES.value + 1)] + times = [ + model.new_int_var(0, horizon, f"x_{i}") for i in range(_NUM_NODES.value + 1) + ] # Node 0 is the start node. - model.Add(times[0] == 0) + model.add(times[0] == 0) # Create the circuit constraint. arcs = [] @@ -74,29 +76,29 @@ def solve_with_cp_sat(x, y, profits): continue # We use a manhattan distance between nodes. distance = abs(x[i] - x[j]) + abs(y[i] - y[j]) - lit = model.NewBoolVar(f"{i}_to_{j}") + lit = model.new_bool_var(f"{i}_to_{j}") arcs.append((i, j, lit)) - # Add transitions between nodes. + # add transitions between nodes. if i == 0: # Initial transition - model.Add(times[j] == distance).OnlyEnforceIf(lit) + model.add(times[j] == distance).only_enforce_if(lit) elif j != 0: # We do not care for the last transition. - model.Add(times[j] == times[i] + distance).OnlyEnforceIf(lit) - model.AddCircuit(arcs) + model.add(times[j] == times[i] + distance).only_enforce_if(lit) + model.add_circuit(arcs) - model.Minimize(cp_model.LinearExpr.WeightedSum(times, profits)) + model.minimize(cp_model.LinearExpr.weighted_sum(times, profits)) if _PROTO_FILE.value: - model.ExportToFile(_PROTO_FILE.value) + model.export_to_file(_PROTO_FILE.value) # Solve model. solver = cp_model.CpSolver() if _PARAMS.value: text_format.Parse(_PARAMS.value, solver.parameters) solver.parameters.log_search_progress = True - solver.Solve(model) + solver.solve(model) def main(argv: Sequence[str]) -> None: diff --git a/examples/python/zebra_sat.py b/examples/python/zebra_sat.py old mode 100644 new mode 100755 index b0e4ba01e87..24a5afceb04 --- a/examples/python/zebra_sat.py +++ b/examples/python/zebra_sat.py @@ -44,77 +44,77 @@ def solve_zebra(): # Create the model. model = cp_model.CpModel() - red = model.NewIntVar(1, 5, "red") - green = model.NewIntVar(1, 5, "green") - yellow = model.NewIntVar(1, 5, "yellow") - blue = model.NewIntVar(1, 5, "blue") - ivory = model.NewIntVar(1, 5, "ivory") - - englishman = model.NewIntVar(1, 5, "englishman") - spaniard = model.NewIntVar(1, 5, "spaniard") - japanese = model.NewIntVar(1, 5, "japanese") - ukrainian = model.NewIntVar(1, 5, "ukrainian") - norwegian = model.NewIntVar(1, 5, "norwegian") - - dog = model.NewIntVar(1, 5, "dog") - snails = model.NewIntVar(1, 5, "snails") - fox = model.NewIntVar(1, 5, "fox") - zebra = model.NewIntVar(1, 5, "zebra") - horse = model.NewIntVar(1, 5, "horse") - - tea = model.NewIntVar(1, 5, "tea") - coffee = model.NewIntVar(1, 5, "coffee") - water = model.NewIntVar(1, 5, "water") - milk = model.NewIntVar(1, 5, "milk") - fruit_juice = model.NewIntVar(1, 5, "fruit juice") - - old_gold = model.NewIntVar(1, 5, "old gold") - kools = model.NewIntVar(1, 5, "kools") - chesterfields = model.NewIntVar(1, 5, "chesterfields") - lucky_strike = model.NewIntVar(1, 5, "lucky strike") - parliaments = model.NewIntVar(1, 5, "parliaments") - - model.AddAllDifferent(red, green, yellow, blue, ivory) - model.AddAllDifferent(englishman, spaniard, japanese, ukrainian, norwegian) - model.AddAllDifferent(dog, snails, fox, zebra, horse) - model.AddAllDifferent(tea, coffee, water, milk, fruit_juice) - model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold) - - model.Add(englishman == red) - model.Add(spaniard == dog) - model.Add(coffee == green) - model.Add(ukrainian == tea) - model.Add(green == ivory + 1) - model.Add(old_gold == snails) - model.Add(kools == yellow) - model.Add(milk == 3) - model.Add(norwegian == 1) - - diff_fox_chesterfields = model.NewIntVar(-4, 4, "diff_fox_chesterfields") - model.Add(diff_fox_chesterfields == fox - chesterfields) - model.AddAbsEquality(1, diff_fox_chesterfields) - - diff_horse_kools = model.NewIntVar(-4, 4, "diff_horse_kools") - model.Add(diff_horse_kools == horse - kools) - model.AddAbsEquality(1, diff_horse_kools) - - model.Add(lucky_strike == fruit_juice) - model.Add(japanese == parliaments) - - diff_norwegian_blue = model.NewIntVar(-4, 4, "diff_norwegian_blue") - model.Add(diff_norwegian_blue == norwegian - blue) - model.AddAbsEquality(1, diff_norwegian_blue) + red = model.new_int_var(1, 5, "red") + green = model.new_int_var(1, 5, "green") + yellow = model.new_int_var(1, 5, "yellow") + blue = model.new_int_var(1, 5, "blue") + ivory = model.new_int_var(1, 5, "ivory") + + englishman = model.new_int_var(1, 5, "englishman") + spaniard = model.new_int_var(1, 5, "spaniard") + japanese = model.new_int_var(1, 5, "japanese") + ukrainian = model.new_int_var(1, 5, "ukrainian") + norwegian = model.new_int_var(1, 5, "norwegian") + + dog = model.new_int_var(1, 5, "dog") + snails = model.new_int_var(1, 5, "snails") + fox = model.new_int_var(1, 5, "fox") + zebra = model.new_int_var(1, 5, "zebra") + horse = model.new_int_var(1, 5, "horse") + + tea = model.new_int_var(1, 5, "tea") + coffee = model.new_int_var(1, 5, "coffee") + water = model.new_int_var(1, 5, "water") + milk = model.new_int_var(1, 5, "milk") + fruit_juice = model.new_int_var(1, 5, "fruit juice") + + old_gold = model.new_int_var(1, 5, "old gold") + kools = model.new_int_var(1, 5, "kools") + chesterfields = model.new_int_var(1, 5, "chesterfields") + lucky_strike = model.new_int_var(1, 5, "lucky strike") + parliaments = model.new_int_var(1, 5, "parliaments") + + model.add_all_different(red, green, yellow, blue, ivory) + model.add_all_different(englishman, spaniard, japanese, ukrainian, norwegian) + model.add_all_different(dog, snails, fox, zebra, horse) + model.add_all_different(tea, coffee, water, milk, fruit_juice) + model.add_all_different(parliaments, kools, chesterfields, lucky_strike, old_gold) + + model.add(englishman == red) + model.add(spaniard == dog) + model.add(coffee == green) + model.add(ukrainian == tea) + model.add(green == ivory + 1) + model.add(old_gold == snails) + model.add(kools == yellow) + model.add(milk == 3) + model.add(norwegian == 1) + + diff_fox_chesterfields = model.new_int_var(-4, 4, "diff_fox_chesterfields") + model.add(diff_fox_chesterfields == fox - chesterfields) + model.add_abs_equality(1, diff_fox_chesterfields) + + diff_horse_kools = model.new_int_var(-4, 4, "diff_horse_kools") + model.add(diff_horse_kools == horse - kools) + model.add_abs_equality(1, diff_horse_kools) + + model.add(lucky_strike == fruit_juice) + model.add(japanese == parliaments) + + diff_norwegian_blue = model.new_int_var(-4, 4, "diff_norwegian_blue") + model.add(diff_norwegian_blue == norwegian - blue) + model.add_abs_equality(1, diff_norwegian_blue) # Solve and print out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: people = [englishman, spaniard, japanese, ukrainian, norwegian] - water_drinker = [p for p in people if solver.Value(p) == solver.Value(water)][0] - zebra_owner = [p for p in people if solver.Value(p) == solver.Value(zebra)][0] - print("The", water_drinker.Name(), "drinks water.") - print("The", zebra_owner.Name(), "owns the zebra.") + water_drinker = [p for p in people if solver.value(p) == solver.value(water)][0] + zebra_owner = [p for p in people if solver.value(p) == solver.value(zebra)][0] + print("The", water_drinker.name, "drinks water.") + print("The", zebra_owner.name, "owns the zebra.") else: print("No solutions to the zebra problem, this is unusual!") diff --git a/ortools/sat/BUILD.bazel b/ortools/sat/BUILD.bazel index daef72cf5e5..633b0bcab46 100644 --- a/ortools/sat/BUILD.bazel +++ b/ortools/sat/BUILD.bazel @@ -12,15 +12,43 @@ # limitations under the License. # Home of CP/SAT solver (which includes SAT, max-SAT and PB problems). -# -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") -load("@rules_java//java:defs.bzl", "java_proto_library") load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_java//java:defs.bzl", "java_proto_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") load("@rules_python//python:proto.bzl", "py_proto_library") -package( - default_visibility = ["//visibility:public"], +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "cp_model", + srcs = ["cp_model.cc"], + hdrs = ["cp_model.h"], + deps = [ + ":cp_model_cc_proto", + ":cp_model_solver", + ":cp_model_utils", + ":model", + ":sat_parameters_cc_proto", + "//ortools/util:sorted_interval_list", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "model", + hdrs = ["model.h"], + deps = [ + "//ortools/base", + "//ortools/base:typeid", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + ], ) proto_library( @@ -43,16 +71,6 @@ java_proto_library( deps = [":sat_parameters_proto"], ) -proto_library( - name = "boolean_problem_proto", - srcs = ["boolean_problem.proto"], -) - -cc_proto_library( - name = "boolean_problem_cc_proto", - deps = [":boolean_problem_proto"], -) - proto_library( name = "cp_model_proto", srcs = ["cp_model.proto"], @@ -70,55 +88,27 @@ py_proto_library( java_proto_library( name = "cp_model_java_proto", - visibility = ["//visibility:public"], deps = [":cp_model_proto"], ) -cc_library( - name = "cp_model", - srcs = ["cp_model.cc"], - hdrs = ["cp_model.h"], - visibility = ["//visibility:public"], - deps = [ - ":cp_model_cc_proto", - ":cp_model_solver", - ":cp_model_utils", - ":model", - ":sat_parameters_cc_proto", - "//ortools/util:sorted_interval_list", - "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:span", - ], -) - -cc_library( - name = "model", - hdrs = ["model.h"], - visibility = ["//visibility:public"], - deps = [ - "//ortools/base", - "//ortools/base:typeid", - "@com_google_absl//absl/container:flat_hash_map", - ], -) - cc_library( name = "cp_model_utils", srcs = ["cp_model_utils.cc"], hdrs = ["cp_model_utils.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", - "//ortools/base", "//ortools/base:file", "//ortools/base:hash", "//ortools/base:stl_util", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + "@com_google_protobuf//:protobuf", ], ) @@ -126,20 +116,39 @@ cc_library( name = "synchronization", srcs = ["synchronization.cc"], hdrs = ["synchronization.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", ":integer", ":model", ":sat_base", + ":sat_parameters_cc_proto", + ":sat_solver", + ":util", "//ortools/base", + "//ortools/base:status_macros", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/base:timer", "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", ], ) @@ -147,20 +156,22 @@ cc_library( name = "cp_model_checker", srcs = ["cp_model_checker.cc"], hdrs = ["cp_model_checker.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", ":sat_parameters_cc_proto", "//ortools/base", - "//ortools/base:hash", + "//ortools/base:types", "//ortools/port:proto_utils", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -168,21 +179,19 @@ cc_library( name = "constraint_violation", srcs = ["constraint_violation.cc"], hdrs = ["constraint_violation.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", ":sat_parameters_cc_proto", ":util", - "//ortools/base", - "//ortools/base:hash", + "//ortools/base:stl_util", "//ortools/graph:strongly_connected_components", - "//ortools/port:proto_utils", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", - "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/strings", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -191,7 +200,6 @@ cc_library( name = "feasibility_jump", srcs = ["feasibility_jump.cc"], hdrs = ["feasibility_jump.h"], - visibility = ["//visibility:public"], deps = [ ":constraint_violation", ":cp_model_cc_proto", @@ -199,6 +207,7 @@ cc_library( ":cp_model_utils", ":integer", ":linear_model", + ":restart", ":sat_parameters_cc_proto", ":stat_tables", ":subsolver", @@ -206,11 +215,15 @@ cc_library( ":util", "//ortools/algorithms:binary_search", "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/functional:function_ref", "@com_google_absl//absl/log", - "@com_google_absl//absl/random", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", ], ) @@ -219,7 +232,6 @@ cc_library( name = "linear_model", srcs = ["linear_model.cc"], hdrs = ["linear_model.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", @@ -235,11 +247,10 @@ cc_library( name = "parameters_validation", srcs = ["parameters_validation.cc"], hdrs = ["parameters_validation.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_search", ":sat_parameters_cc_proto", - "//ortools/base", + "@com_google_absl//absl/strings", ], ) @@ -254,22 +265,18 @@ cc_library( ":integer", ":integer_search", ":model", + ":sat_base", + ":sat_parameters_cc_proto", ":util", "//ortools/base", - "//ortools/base:cleanup", + "//ortools/base:types", + "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/strings:str_format", - ], -) - -cc_library( - name = "cp_model_objective", - srcs = ["cp_model_objective.cc"], - hdrs = ["cp_model_objective.h"], - deps = [ - ":cp_model_cc_proto", - ":cp_model_utils", - "//ortools/base", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", ], ) @@ -277,7 +284,6 @@ cc_library( name = "cp_model_solver", srcs = ["cp_model_solver.cc"], hdrs = ["cp_model_solver.h"], - visibility = ["//visibility:public"], deps = [ ":circuit", ":clause", @@ -285,6 +291,7 @@ cc_library( ":cp_model_checker", ":cp_model_lns", ":cp_model_loader", + ":cp_model_mapping", ":cp_model_postsolve", ":cp_model_presolve", ":cp_model_search", @@ -295,19 +302,23 @@ cc_library( ":drat_proof_handler", ":feasibility_jump", ":feasibility_pump", + ":implied_bounds", ":integer", ":integer_expr", ":integer_search", ":intervals", ":lb_tree_search", + ":linear_constraint", ":linear_model", ":linear_programming_constraint", ":linear_relaxation", + ":lp_utils", ":max_hs", ":model", ":optimization", ":parameters_validation", ":precedences", + ":presolve_context", ":probing", ":rins", ":sat_base", @@ -318,26 +329,39 @@ cc_library( ":stat_tables", ":subsolver", ":synchronization", + ":util", ":work_assignment", "//ortools/base", - "//ortools/base:file", - "//ortools/base:stl_util", + "//ortools/base:cleanup", + "//ortools/base:status_macros", "//ortools/base:strong_vector", "//ortools/base:threadpool", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/graph:connected_components", + "//ortools/linear_solver:linear_solver_cc_proto", "//ortools/port:proto_utils", "//ortools/util:logging", + "//ortools/util:random_engine", "//ortools/util:sigint", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -345,7 +369,6 @@ cc_library( cc_library( name = "cp_model_mapping", hdrs = ["cp_model_mapping.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_utils", @@ -356,12 +379,12 @@ cc_library( ":sat_base", "//ortools/base", "//ortools/base:strong_vector", - "//ortools/util:logging", - "//ortools/util:sorted_interval_list", + "//ortools/base:types", "//ortools/util:strong_integers", - "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", ], ) @@ -369,7 +392,6 @@ cc_library( name = "cp_model_loader", srcs = ["cp_model_loader.cc"], hdrs = ["cp_model_loader.h"], - visibility = ["//visibility:public"], deps = [ ":all_different", ":circuit", @@ -384,6 +406,7 @@ cc_library( ":integer", ":integer_expr", ":intervals", + ":linear_constraint", ":linear_relaxation", ":model", ":pb_constraint", @@ -394,10 +417,13 @@ cc_library( ":symmetry", ":table", ":timetable", + ":util", + "//ortools/algorithms:sparse_permutation", "//ortools/base", - "//ortools/base:file", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:types", + "//ortools/util:logging", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", @@ -405,10 +431,24 @@ cc_library( "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_protobuf//:protobuf", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) +proto_library( + name = "boolean_problem_proto", + srcs = ["boolean_problem.proto"], +) + +cc_proto_library( + name = "boolean_problem_cc_proto", + deps = [":boolean_problem_proto"], +) + cc_library( name = "presolve_util", srcs = ["presolve_util.cc"], @@ -419,14 +459,23 @@ cc_library( ":util", "//ortools/base", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/random", "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", ], ) @@ -437,65 +486,93 @@ cc_library( deps = [ ":cp_model_cc_proto", ":cp_model_loader", + ":cp_model_mapping", ":cp_model_utils", + ":integer", ":lp_utils", ":model", ":presolve_util", ":sat_parameters_cc_proto", + ":sat_solver", ":util", "//ortools/base", "//ortools/base:mathutil", - "//ortools/base:strong_vector", "//ortools/port:proto_utils", "//ortools/util:affine_relation", "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", - "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) cc_library( name = "cp_model_presolve", - srcs = ["cp_model_presolve.cc"], + srcs = [ + "cp_model_presolve.cc", + ], hdrs = ["cp_model_presolve.h"], deps = [ ":circuit", + ":clause", ":cp_model_cc_proto", ":cp_model_checker", ":cp_model_expand", - ":cp_model_loader", ":cp_model_mapping", - ":cp_model_objective", ":cp_model_symmetries", ":cp_model_utils", ":diffn_util", ":diophantine", ":inclusion", + ":integer", + ":model", ":presolve_context", ":presolve_util", ":probing", ":sat_base", + ":sat_inprocessing", ":sat_parameters_cc_proto", + ":sat_solver", ":simplification", + ":util", ":var_domination", "//ortools/base", - "//ortools/base:hash", "//ortools/base:mathutil", "//ortools/base:stl_util", - "//ortools/port:proto_utils", + "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/graph:strongly_connected_components", + "//ortools/graph:topologicalsorter", "//ortools/util:affine_relation", "//ortools/util:bitset", + "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + "@com_google_protobuf//:protobuf", ], ) @@ -509,7 +586,9 @@ cc_library( ":cp_model_cc_proto", ":cp_model_utils", "//ortools/base", - "@com_google_absl//absl/base:core_headers", + "//ortools/base:types", + "//ortools/util:sorted_interval_list", + "@com_google_absl//absl/log:check", ], ) @@ -519,15 +598,26 @@ cc_library( hdrs = ["cp_model_expand.h"], deps = [ ":cp_model_cc_proto", + ":cp_model_checker", ":cp_model_utils", ":presolve_context", + ":sat_parameters_cc_proto", ":util", "//ortools/base", - "//ortools/base:hash", + "//ortools/base:stl_util", + "//ortools/base:types", + "//ortools/port:proto_utils", + "//ortools/util:logging", "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", ], ) @@ -538,9 +628,12 @@ cc_library( ":model", "//ortools/base", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:strong_integers", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], @@ -549,6 +642,7 @@ cc_library( # Enable a warning to check for floating point to integer conversions. # In GCC-4.8, this was "-Wreal-conversion", but was removed in 4.9 # In Clang, this warning is "-Wfloat-conversion" +W_FLOAT_CONVERSION = "-Wfloat-conversion" cc_library( name = "sat_solver", @@ -569,16 +663,22 @@ cc_library( "//ortools/base", "//ortools/base:hash", "//ortools/base:stl_util", - "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/port:proto_utils", "//ortools/port:sysinfo", + "//ortools/util:bitset", "//ortools/util:logging", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], @@ -596,6 +696,7 @@ cc_library( "//ortools/port:proto_utils", "//ortools/util:bitset", "//ortools/util:running_stat", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], @@ -611,10 +712,21 @@ cc_library( ":integer", ":model", ":sat_base", + ":sat_parameters_cc_proto", ":sat_solver", ":util", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/util:bitset", "//ortools/util:logging", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -624,16 +736,29 @@ cc_library( hdrs = ["sat_inprocessing.h"], deps = [ ":clause", + ":drat_checker", ":model", ":probing", ":sat_base", ":sat_decision", + ":sat_parameters_cc_proto", ":sat_solver", ":util", "//ortools/base", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/util:bitset", + "//ortools/util:integer_pq", + "//ortools/util:logging", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:inlined_vector", - ], + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], ) cc_library( @@ -647,9 +772,12 @@ cc_library( ":sat_parameters_cc_proto", ":util", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:integer_pq", - "//ortools/util:random_engine", + "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -668,14 +796,20 @@ cc_library( "//ortools/base:hash", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/graph:strongly_connected_components", "//ortools/util:bitset", - "//ortools/util:random_engine", "//ortools/util:stats", "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/types:span", ], ) @@ -686,22 +820,25 @@ cc_library( hdrs = ["simplification.h"], deps = [ ":drat_proof_handler", + ":model", ":probing", ":sat_base", ":sat_inprocessing", ":sat_parameters_cc_proto", ":sat_solver", - ":util", "//ortools/algorithms:dynamic_partition", "//ortools/base", "//ortools/base:adjustable_priority_queue", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/graph:strongly_connected_components", "//ortools/util:logging", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -715,14 +852,16 @@ cc_library( ":sat_base", ":sat_parameters_cc_proto", "//ortools/base", - "//ortools/base:hash", - "//ortools/base:murmur", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], @@ -735,10 +874,10 @@ cc_library( deps = [ ":sat_base", "//ortools/algorithms:sparse_permutation", - "//ortools/base", "//ortools/base:strong_vector", "//ortools/util:stats", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -751,6 +890,7 @@ cc_library( "//ortools/algorithms:dynamic_partition", "//ortools/algorithms:sparse_permutation", "//ortools/base", + "@com_google_absl//absl/log:check", ], ) @@ -763,10 +903,21 @@ cc_library( ":cp_model_utils", ":integer", ":presolve_context", + ":presolve_util", + ":util", "//ortools/algorithms:dynamic_partition", "//ortools/base", + "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/util:affine_relation", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -783,9 +934,8 @@ cc_library( "//ortools/base", "//ortools/base:cleanup", "//ortools/base:hash", - "//ortools/base:iterator_adaptors", - "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:types", "//ortools/graph:iterators", "//ortools/util:bitset", "//ortools/util:rev", @@ -797,6 +947,8 @@ cc_library( "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], @@ -807,12 +959,16 @@ cc_library( srcs = ["integer_search.cc"], hdrs = ["integer_search.h"], deps = [ + ":cp_model_cc_proto", ":cp_model_mapping", ":implied_bounds", ":integer", + ":intervals", ":linear_programming_constraint", + ":model", ":probing", ":pseudo_costs", + ":restart", ":rins", ":sat_base", ":sat_decision", @@ -821,8 +977,15 @@ cc_library( ":sat_solver", ":synchronization", ":util", + "//ortools/base", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/types:span", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) @@ -833,12 +996,23 @@ cc_library( deps = [ ":cp_model_mapping", ":integer", + ":integer_expr", ":integer_search", ":linear_programming_constraint", + ":model", ":sat_base", + ":sat_decision", ":sat_parameters_cc_proto", ":sat_solver", ":synchronization", + ":util", + "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) @@ -848,8 +1022,14 @@ cc_library( hdrs = ["pseudo_costs.h"], deps = [ ":integer", - ":sat_decision", + ":model", + ":sat_base", ":sat_parameters_cc_proto", + ":util", + "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -870,8 +1050,15 @@ cc_library( ":sat_solver", "//ortools/base", "//ortools/base:strong_vector", + "//ortools/util:rev", "//ortools/util:sort", "//ortools/util:strong_integers", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -891,37 +1078,19 @@ cc_library( "//ortools/base:cleanup", "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/base:types", + "//ortools/graph", "//ortools/graph:topologicalsorter", "//ortools/util:bitset", "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:inlined_vector", - ], -) - -cc_library( - name = "implied_bounds", - srcs = ["implied_bounds.cc"], - hdrs = ["implied_bounds.h"], - deps = [ - ":integer", - ":linear_constraint", - ":model", - ":sat_base", - ":synchronization", - "//ortools/base", - "//ortools/base:strong_vector", - "//ortools/util:bitset", - "//ortools/util:strong_integers", - "@com_google_absl//absl/container:inlined_vector", - ], -) - -cc_library( - name = "inclusion", - hdrs = ["inclusion.h"], - deps = [ - "//ortools/base", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -937,13 +1106,19 @@ cc_library( ":model", ":precedences", ":sat_base", + ":sat_parameters_cc_proto", ":sat_solver", ":util", "//ortools/base", + "//ortools/base:mathutil", "//ortools/base:stl_util", "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/types:span", ], ) @@ -953,11 +1128,23 @@ cc_library( hdrs = ["linear_propagation.h"], deps = [ ":integer", + ":model", ":sat_base", ":sat_solver", ":synchronization", + "//ortools/base:stl_util", "//ortools/base:strong_vector", + "//ortools/util:bitset", + "//ortools/util:rev", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -972,13 +1159,14 @@ cc_library( ":sat_base", ":sat_solver", "//ortools/base", + "//ortools/base:types", "//ortools/graph:strongly_connected_components", "//ortools/util:sort", - "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -989,7 +1177,7 @@ cc_library( deps = [ ":integer", "//ortools/base", - "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1010,8 +1198,11 @@ cc_library( ":theta_tree", ":timetable", "//ortools/base", - "//ortools/base:iterator_adaptors", + "//ortools/util:sort", "//ortools/util:strong_integers", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", ], ) @@ -1022,12 +1213,12 @@ cc_library( deps = [ ":integer", ":intervals", + ":model", ":sat_base", - ":sat_solver", - "//ortools/base", "//ortools/util:rev", - "//ortools/util:sort", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1036,13 +1227,13 @@ cc_library( srcs = ["timetable_edgefinding.cc"], hdrs = ["timetable_edgefinding.h"], deps = [ - ":implied_bounds", ":integer", ":intervals", + ":model", ":sat_base", - "//ortools/base", - "//ortools/util:sort", + "//ortools/base:iterator_adaptors", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1054,6 +1245,7 @@ cc_library( ":cumulative_energy", ":disjunctive", ":integer", + ":integer_expr", ":intervals", ":linear_constraint", ":model", @@ -1066,6 +1258,8 @@ cc_library( ":timetable_edgefinding", "//ortools/base", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", ], ) @@ -1074,15 +1268,15 @@ cc_library( srcs = ["cumulative_energy.cc"], hdrs = ["cumulative_energy.h"], deps = [ - ":implied_bounds", ":integer", ":intervals", ":model", - ":sat_base", ":theta_tree", + ":util", "//ortools/base", - "//ortools/util:sort", + "//ortools/base:iterator_adaptors", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1102,13 +1296,19 @@ cc_library( "//ortools/algorithms:sparse_permutation", "//ortools/base", "//ortools/base:hash", + "//ortools/base:status_macros", "//ortools/base:strong_vector", + "//ortools/graph", "//ortools/graph:io", "//ortools/graph:util", "//ortools/port:proto_utils", "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], ) @@ -1119,25 +1319,40 @@ cc_library( hdrs = ["linear_relaxation.h"], deps = [ ":circuit", + ":clause", ":cp_model_cc_proto", ":cp_model_mapping", + ":cp_model_utils", ":cuts", ":diffn_cuts", ":implied_bounds", ":integer", ":integer_expr", + ":intervals", ":linear_constraint", - ":linear_programming_constraint", ":model", + ":precedences", ":presolve_util", ":routing_cuts", ":sat_base", ":sat_parameters_cc_proto", + ":sat_solver", ":scheduling_cuts", + ":util", "//ortools/base", - "//ortools/base:iterator_adaptors", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/types:span", ], ) @@ -1148,8 +1363,15 @@ cc_library( deps = [ ":integer", ":model", - "//ortools/base", + ":sat_base", "//ortools/base:mathutil", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:strong_integers", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", ], ) @@ -1158,6 +1380,7 @@ cc_library( srcs = ["linear_programming_constraint.cc"], hdrs = ["linear_programming_constraint.h"], deps = [ + ":cp_model_cc_proto", ":cuts", ":implied_bounds", ":integer", @@ -1165,25 +1388,35 @@ cc_library( ":linear_constraint", ":linear_constraint_manager", ":model", + ":sat_base", + ":sat_parameters_cc_proto", + ":sat_solver", + ":synchronization", ":util", ":zero_half_cuts", "//ortools/algorithms:binary_search", "//ortools/base", + "//ortools/base:mathutil", "//ortools/base:strong_vector", "//ortools/glop:parameters_cc_proto", - "//ortools/glop:preprocessor", "//ortools/glop:revised_simplex", "//ortools/glop:status", - "//ortools/graph:strongly_connected_components", + "//ortools/glop:variables_info", "//ortools/lp_data", "//ortools/lp_data:base", - "//ortools/lp_data:matrix_scaler", + "//ortools/lp_data:lp_data_utils", + "//ortools/lp_data:scattered_vector", + "//ortools/lp_data:sparse_column", + "//ortools/util:bitset", "//ortools/util:rev", "//ortools/util:saturated_arithmetic", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1198,11 +1431,22 @@ cc_library( ":sat_parameters_cc_proto", ":synchronization", ":util", + "//ortools/base", + "//ortools/base:hash", + "//ortools/base:strong_vector", "//ortools/glop:revised_simplex", + "//ortools/glop:variables_info", + "//ortools/lp_data:base", "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", ], ) @@ -1211,18 +1455,28 @@ cc_library( srcs = ["cuts.cc"], hdrs = ["cuts.h"], deps = [ + ":clause", ":implied_bounds", ":integer", ":linear_constraint", ":linear_constraint_manager", ":model", ":sat_base", - ":util", + ":synchronization", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", - "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/numeric:int128", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1235,12 +1489,20 @@ cc_library( ":cuts", ":integer", ":linear_constraint", + ":linear_constraint_manager", ":model", + ":sat_base", + ":util", "//ortools/base", "//ortools/base:cleanup", "//ortools/base:mathutil", + "//ortools/base:strong_vector", "//ortools/graph", "//ortools/graph:max_flow", + "//ortools/util:strong_integers", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1250,6 +1512,7 @@ cc_library( hdrs = ["scheduling_cuts.h"], deps = [ ":cuts", + ":implied_bounds", ":integer", ":intervals", ":linear_constraint", @@ -1259,8 +1522,17 @@ cc_library( ":util", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1271,6 +1543,7 @@ cc_library( deps = [ ":cuts", ":diffn_util", + ":implied_bounds", ":integer", ":intervals", ":linear_constraint", @@ -1280,8 +1553,17 @@ cc_library( ":util", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1295,6 +1577,7 @@ cc_library( "//ortools/base", "//ortools/lp_data:base", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", ], ) @@ -1308,7 +1591,6 @@ cc_library( ":cp_model_cc_proto", ":cp_model_utils", ":integer", - ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", "//ortools/base", @@ -1319,7 +1601,12 @@ cc_library( "//ortools/lp_data", "//ortools/lp_data:base", "//ortools/util:fp_utils", + "//ortools/util:logging", + "//ortools/util:saturated_arithmetic", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -1330,6 +1617,7 @@ cc_library( deps = [ ":boolean_problem", ":boolean_problem_cc_proto", + ":clause", ":cp_model_mapping", ":encoding", ":integer", @@ -1340,15 +1628,24 @@ cc_library( ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", + ":synchronization", ":util", "//ortools/base", - "//ortools/linear_solver:linear_solver_cc_proto", + "//ortools/base:cleanup", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", "//ortools/port:proto_utils", + "//ortools/util:sorted_interval_list", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", ], ) @@ -1358,26 +1655,40 @@ cc_library( hdrs = ["max_hs.h"], deps = [ ":boolean_problem", - ":boolean_problem_cc_proto", + ":cp_model_cc_proto", ":cp_model_mapping", ":cp_model_utils", ":encoding", ":integer", ":integer_expr", ":integer_search", + ":linear_constraint", ":linear_relaxation", ":model", ":optimization", ":pb_constraint", + ":presolve_util", ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", + ":synchronization", ":util", "//ortools/base", + "//ortools/base:cleanup", + "//ortools/base:stl_util", + "//ortools/base:strong_vector", + "//ortools/base:timer", "//ortools/linear_solver:linear_solver_cc_proto", "//ortools/port:proto_utils", "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], @@ -1397,13 +1708,19 @@ cc_library( "//ortools/util:random_engine", "//ortools/util:saturated_arithmetic", "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/log:log_streamer", "@com_google_absl//absl/numeric:int128", "@com_google_absl//absl/random", "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -1438,13 +1755,11 @@ cc_library( ":model", ":sat_base", ":sat_solver", - ":util", - "//ortools/base", - "//ortools/base:stl_util", - "//ortools/util:sorted_interval_list", + "//ortools/base:types", "//ortools/util:strong_integers", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1456,12 +1771,12 @@ cc_library( ":integer", ":model", ":sat_base", - ":sat_solver", "//ortools/base", + "//ortools/base:types", "//ortools/util:rev", - "//ortools/util:sort", "//ortools/util:strong_integers", - "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1472,8 +1787,16 @@ cc_library( deps = [ ":integer", ":intervals", + "//ortools/base", + "//ortools/base:stl_util", "//ortools/graph:connected_components", + "//ortools/util:integer_pq", + "//ortools/util:strong_integers", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", ], ) @@ -1482,20 +1805,23 @@ cc_library( srcs = ["diffn.cc"], hdrs = ["diffn.h"], deps = [ - ":cumulative", + ":cumulative_energy", ":diffn_util", ":disjunctive", ":integer", + ":integer_expr", ":intervals", + ":linear_constraint", ":model", + ":precedences", ":sat_base", - ":sat_solver", - ":theta_tree", - "//ortools/base", - "//ortools/util:rev", - "//ortools/util:sort", + ":sat_parameters_cc_proto", + ":timetable", + "//ortools/util:saturated_arithmetic", "//ortools/util:strong_integers", - "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1509,10 +1835,15 @@ cc_library( ":sat_base", ":sat_solver", "//ortools/base", + "//ortools/base:types", + "//ortools/graph:strongly_connected_components", "//ortools/util:rev", "//ortools/util:strong_integers", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", ], ) @@ -1528,7 +1859,10 @@ cc_library( ":sat_solver", "//ortools/base", "//ortools/base:stl_util", + "//ortools/base:types", "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", ], ) @@ -1536,27 +1870,41 @@ cc_library( name = "cp_model_lns", srcs = ["cp_model_lns.cc"], hdrs = ["cp_model_lns.h"], - visibility = ["//visibility:public"], deps = [ ":cp_model_cc_proto", ":cp_model_mapping", ":cp_model_presolve", ":cp_model_utils", ":integer", + ":linear_constraint_manager", ":linear_programming_constraint", ":model", ":presolve_context", ":rins", + ":sat_parameters_cc_proto", ":subsolver", ":synchronization", + ":util", "//ortools/base", "//ortools/base:stl_util", - "//ortools/base:threadpool", + "//ortools/graph:connected_components", "//ortools/util:adaptative_parameter_value", - "//ortools/util:random_engine", + "//ortools/util:integer_pq", + "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", "//ortools/util:time_limit", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", ], ) @@ -1564,22 +1912,32 @@ cc_library( name = "feasibility_pump", srcs = ["feasibility_pump.cc"], hdrs = ["feasibility_pump.h"], - visibility = ["//visibility:public"], deps = [ - ":cp_model_cc_proto", ":cp_model_mapping", ":integer", ":linear_constraint", + ":model", ":sat_base", + ":sat_parameters_cc_proto", ":sat_solver", ":synchronization", ":util", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/glop:parameters_cc_proto", "//ortools/glop:revised_simplex", + "//ortools/glop:status", "//ortools/lp_data", "//ortools/lp_data:base", "//ortools/lp_data:lp_data_utils", + "//ortools/lp_data:sparse_column", "//ortools/util:saturated_arithmetic", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "//ortools/util:time_limit", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", ], ) @@ -1587,16 +1945,15 @@ cc_library( name = "rins", srcs = ["rins.cc"], hdrs = ["rins.h"], - visibility = ["//visibility:public"], deps = [ - ":cp_model_mapping", ":integer", ":linear_programming_constraint", ":model", ":synchronization", - "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/types:optional", + "//ortools/util:strong_integers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/random:bit_gen_ref", + "@com_google_absl//absl/random:distributions", ], ) @@ -1604,12 +1961,15 @@ cc_library( name = "subsolver", srcs = ["subsolver.cc"], hdrs = ["subsolver.h"], - visibility = ["//visibility:public"], deps = [ "//ortools/base", "//ortools/base:threadpool", + "//ortools/base:timer", + "//ortools/base:types", "//ortools/util:stats", - "//ortools/util:time_limit", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", ], @@ -1624,11 +1984,9 @@ cc_library( ":drat_writer", ":sat_base", "//ortools/base", - "//ortools/base:file", "//ortools/base:strong_vector", "//ortools/util:strong_integers", - "@com_google_absl//absl/hash", - "@com_google_absl//absl/status", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], ) @@ -1637,20 +1995,15 @@ cc_library( name = "drat_checker", srcs = ["drat_checker.cc"], hdrs = ["drat_checker.h"], - # data = [ - # "testdata/drup.cnf", - # "testdata/drup.drat", - # ], deps = [ ":sat_base", "//ortools/base", "//ortools/base:hash", - "//ortools/base:stl_util", "//ortools/base:strong_vector", "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/status", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", @@ -1665,12 +2018,58 @@ cc_library( ":sat_base", "//ortools/base", "//ortools/base:file", + "//ortools/base:status_macros", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", ], ) +cc_binary( + name = "sat_runner", + srcs = [ + "opb_reader.h", + "sat_runner.cc", + ], + deps = [ + ":boolean_problem", + ":boolean_problem_cc_proto", + ":cp_model_cc_proto", + ":cp_model_solver", + ":model", + ":sat_cnf_reader", + ":sat_parameters_cc_proto", + "//ortools/base", + "//ortools/base:file", + "//ortools/base:path", + "//ortools/util:file_util", + "//ortools/util:filelineiter", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/log:flags", + "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", + ], +) + +cc_library( + name = "sat_cnf_reader", + hdrs = ["sat_cnf_reader.h"], + deps = [ + ":boolean_problem_cc_proto", + ":cp_model_cc_proto", + "//ortools/base", + "//ortools/util:filelineiter", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + cc_library( name = "cp_model_symmetries", srcs = ["cp_model_symmetries.cc"], @@ -1679,15 +2078,27 @@ cc_library( ":cp_model_cc_proto", ":cp_model_mapping", ":cp_model_utils", + ":model", ":presolve_context", + ":sat_base", ":sat_parameters_cc_proto", ":sat_solver", ":symmetry_util", + ":util", "//ortools/algorithms:find_graph_symmetries", "//ortools/algorithms:sparse_permutation", + "//ortools/base", "//ortools/base:hash", + "//ortools/graph", + "//ortools/util:affine_relation", "//ortools/util:logging", + "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", "@com_google_protobuf//:protobuf", ], ) @@ -1707,8 +2118,47 @@ cc_library( ":cp_model_utils", ":model", ":sat_parameters_cc_proto", + "//ortools/util:logging", + "//ortools/util:sorted_interval_list", + "//ortools/util:time_limit", + ], +) + +cc_library( + name = "implied_bounds", + srcs = ["implied_bounds.cc"], + hdrs = ["implied_bounds.h"], + deps = [ + "linear_constraint", + ":clause", + ":integer", + ":model", + ":sat_base", + ":sat_parameters_cc_proto", + ":sat_solver", + ":synchronization", "//ortools/base", + "//ortools/base:strong_vector", + "//ortools/base:types", + "//ortools/util:bitset", + "//ortools/util:sorted_interval_list", + "//ortools/util:strong_integers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "inclusion", + hdrs = ["inclusion.h"], + deps = [ + "//ortools/base", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -1718,6 +2168,7 @@ cc_library( hdrs = ["diophantine.h"], deps = [ ":util", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/numeric:int128", "@com_google_absl//absl/types:span", ], @@ -1738,47 +2189,26 @@ cc_library( ":sat_solver", ":synchronization", ":util", + "//ortools/util:strong_integers", "//ortools/util:time_limit", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/log", "@com_google_absl//absl/log:check", - "@com_google_absl//absl/random", "@com_google_absl//absl/random:distributions", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/types:span", ], ) -cc_binary( - name = "sat_runner", - srcs = [ - "opb_reader.h", - "sat_cnf_reader.h", - "sat_runner.cc", - ], +cc_library( + name = "cp_model_objective", + srcs = ["cp_model_objective.cc"], + hdrs = ["cp_model_objective.h"], deps = [ - ":boolean_problem", - ":boolean_problem_cc_proto", ":cp_model_cc_proto", - ":cp_model_solver", - ":drat_proof_handler", - ":lp_utils", - ":optimization", - ":sat_solver", - ":simplification", - ":symmetry", - "//ortools/algorithms:sparse_permutation", - "//ortools/base", - "//ortools/base:file", - "//ortools/base:path", - "//ortools/base:threadpool", - "//ortools/lp_data:mps_reader", - "//ortools/lp_data:proto_utils", - "//ortools/util:filelineiter", - "//ortools/util:sigint", - "//ortools/util:time_limit", - "@com_google_absl//absl/status", - "@com_google_absl//absl/strings", - "@com_google_protobuf//:protobuf", + ":cp_model_utils", + "//ortools/util:sorted_interval_list", + "@com_google_absl//absl/log:check", ], ) diff --git a/ortools/sat/clause.cc b/ortools/sat/clause.cc index b17185b31d4..c6cc9ae7d5f 100644 --- a/ortools/sat/clause.cc +++ b/ortools/sat/clause.cc @@ -1199,7 +1199,6 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { DCHECK(InvariantsAreOk()); // TODO(user): We could just do it directly though. - int num_fixed_during_scc = 0; const int32_t size(implications_.size()); std::vector> scc; double dtime = 0.0; @@ -1213,7 +1212,6 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { for (const Literal l : graph.to_fix_) { if (assignment.LiteralIsFalse(l)) return false; if (assignment.LiteralIsTrue(l)) continue; - ++num_fixed_during_scc; if (!FixLiteral(l)) return false; } } @@ -1252,7 +1250,6 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { const Literal to_fix = all_true ? l : l.Negated(); if (assignment.LiteralIsFalse(to_fix)) return false; if (assignment.LiteralIsTrue(to_fix)) continue; - ++num_fixed_during_scc; if (!FixLiteral(l)) return false; } @@ -1355,9 +1352,9 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) { } time_limit_->AdvanceDeterministicTime(dtime); - if (num_fixed_during_scc > 0) { - RemoveFixedVariables(); - } + const int num_fixed_during_scc = + trail_->Index() - num_processed_fixed_variables_; + RemoveFixedVariables(); DCHECK(InvariantsAreOk()); LOG_IF(INFO, log_info) << "SCC. " << num_equivalences << " redundant equivalent literals. " diff --git a/ortools/sat/cp_model_checker.cc b/ortools/sat/cp_model_checker.cc index 5e602e7b9df..54e7ade2064 100644 --- a/ortools/sat/cp_model_checker.cc +++ b/ortools/sat/cp_model_checker.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -915,7 +916,10 @@ bool PossibleIntegerOverflow(const CpModelProto& model, // In addition to computing the min/max possible sum, we also often compare // it with the constraint bounds, so we do not want max - min to overflow. // We might also create an intermediate variable to represent the sum. - if (sum_min < std::numeric_limits::min() / 2) return true; + // + // Note that it is important to be symmetric here, as we do not want expr to + // pass but not -expr! + if (sum_min < -std::numeric_limits::max() / 2) return true; if (sum_max > std::numeric_limits::max() / 2) return true; return false; } @@ -1376,6 +1380,7 @@ class ConstraintChecker { std::sort(events.begin(), events.end()); + // This works because we will process negative demands first. int64_t current_load = 0; for (const auto& [time, delta] : events) { current_load += delta; diff --git a/ortools/sat/cp_model_expand.cc b/ortools/sat/cp_model_expand.cc index 03e9b356252..7ba5a96064a 100644 --- a/ortools/sat/cp_model_expand.cc +++ b/ortools/sat/cp_model_expand.cc @@ -29,11 +29,13 @@ #include "absl/log/check.h" #include "absl/meta/type_traits.h" #include "absl/strings/str_cat.h" +#include "google/protobuf/message.h" #include "ortools/base/logging.h" #include "ortools/base/stl_util.h" #include "ortools/base/types.h" #include "ortools/port/proto_utils.h" #include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_checker.h" #include "ortools/sat/cp_model_utils.h" #include "ortools/sat/presolve_context.h" #include "ortools/sat/sat_parameters.pb.h" @@ -573,14 +575,34 @@ void ExpandVariableElement(ConstraintProto* ct, PresolveContext* context) { if (var_domain.IsFixed()) { context->AddImplyInDomain(index_lit, target_ref, var_domain); } else { + // We make sure we only use positive ref. + // + // TODO(user): Get rid of this code once we accept affine in element + // constraint. ConstraintProto* const ct = context->working_model->add_constraints(); ct->add_enforcement_literal(index_lit); - ct->mutable_linear()->add_vars(var); - ct->mutable_linear()->add_coeffs(1); - ct->mutable_linear()->add_vars(target_ref); - ct->mutable_linear()->add_coeffs(-1); + if (RefIsPositive(var)) { + ct->mutable_linear()->add_vars(var); + ct->mutable_linear()->add_coeffs(1); + } else { + ct->mutable_linear()->add_vars(NegatedRef(var)); + ct->mutable_linear()->add_coeffs(-1); + } + if (RefIsPositive(target_ref)) { + ct->mutable_linear()->add_vars(target_ref); + ct->mutable_linear()->add_coeffs(-1); + } else { + ct->mutable_linear()->add_vars(NegatedRef(target_ref)); + ct->mutable_linear()->add_coeffs(1); + } ct->mutable_linear()->add_domain(0); ct->mutable_linear()->add_domain(0); + + // Note that this should have been checked at model validation. + DCHECK(!PossibleIntegerOverflow(*context->working_model, + ct->mutable_linear()->vars(), + ct->mutable_linear()->coeffs())) + << google::protobuf::ShortFormat(*ct); } } diff --git a/ortools/sat/cp_model_presolve.cc b/ortools/sat/cp_model_presolve.cc index e54104f56b7..a54f79a6068 100644 --- a/ortools/sat/cp_model_presolve.cc +++ b/ortools/sat/cp_model_presolve.cc @@ -1595,6 +1595,7 @@ bool CpModelPresolver::PresolveIntDiv(ConstraintProto* ct) { bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) { if (context_->ModelIsUnsat()) return false; + // TODO(user): Presolve f(X) = g(X) % fixed_mod. const LinearExpressionProto target = ct->int_mod().target(); const LinearExpressionProto expr = ct->int_mod().exprs(0); const LinearExpressionProto mod = ct->int_mod().exprs(1); @@ -1668,7 +1669,8 @@ bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) { // expr.vars(0) is large, the implied domain is not too complex. if (target.vars().size() == 1 && expr.vars().size() == 1 && context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) && - context_->VariableIsUniqueAndRemovable(target.vars(0))) { + context_->VariableIsUniqueAndRemovable(target.vars(0)) && + target.vars(0) != expr.vars(0)) { const int64_t fixed_mod = context_->FixedValue(mod); std::vector values; const Domain dom = context_->DomainOf(target.vars(0)); @@ -5144,6 +5146,7 @@ bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) { if (!MarkConstraintAsFalse(interval_ct)) { return false; } + context_->UpdateConstraintVariableUsage(interval_index); context_->UpdateRuleStats( "no_overlap: unperform duplicate non zero-sized intervals"); // We can remove the interval from the no_overlap. @@ -6980,11 +6983,16 @@ bool CpModelPresolver::PresolvePureSatPart() { // removing variable from the objective if they can be set to their "low" // objective value, and also removing enforcement literal that can be set to // false and don't appear elsewhere. + int num_in_extra_constraints = 0; std::vector can_be_removed(num_variables, false); for (int i = 0; i < num_variables; ++i) { const int var = new_to_old_index[i]; if (context_->VarToConstraints(var).empty()) { can_be_removed[i] = true; + } else { + // That might correspond to the objective or a variable with an affine + // relation that is still in the model. + ++num_in_extra_constraints; } } @@ -7011,7 +7019,7 @@ bool CpModelPresolver::PresolvePureSatPart() { absl::StrongVector equiv_map; if (!context_->params().debug_postsolve_with_full_solver() && num_ignored_variables == 0 && num_ignored_constraints == 0 && - !context_->working_model->has_objective()) { + num_in_extra_constraints == 0) { // Some problems are formulated in such a way that our SAT heuristics // simply works without conflict. Get them out of the way first because it // is possible that the presolve lose this "lucky" ordering. This is in @@ -9065,7 +9073,7 @@ void CpModelPresolver::DetectDominatedLinearConstraints() { // TODO(user): Also substitute if this appear in the objective? // TODO(user): In some case we only need common_part <= new_var. -void CpModelPresolver::RemoveCommonPart( +bool CpModelPresolver::RemoveCommonPart( const absl::flat_hash_map& common_var_coeff_map, const std::vector>& block) { int new_var; @@ -9138,6 +9146,14 @@ void CpModelPresolver::RemoveCommonPart( new_linear->add_coeffs(-1); new_linear->add_domain(0); new_linear->add_domain(0); + if (PossibleIntegerOverflow(*context_->working_model, new_linear->vars(), + new_linear->coeffs())) { + context_->UpdateRuleStats( + "TODO linear matrix: possible overflow in common part!"); + context_->working_model->mutable_constraints()->RemoveLast(); + return false; + } + context_->UpdateNewConstraintsVariableUsage(); } @@ -9182,6 +9198,7 @@ void CpModelPresolver::RemoveCommonPart( } context_->UpdateConstraintVariableUsage(c); } + return true; } namespace { @@ -9330,10 +9347,10 @@ void CpModelPresolver::FindBigVerticalLinearOverlap() { } // Introduce new_var = common_part and perform the substitution. + if (!RemoveCommonPart(coeff_map, block)) continue; ++num_blocks; nz_reduction += saved_nz; context_->UpdateRuleStats("linear matrix: common vertical rectangle"); - RemoveCommonPart(coeff_map, block); } timer.AddCounter("blocks", num_blocks); @@ -9463,8 +9480,6 @@ void CpModelPresolver::FindBigHorizontalLinearOverlap() { // Introduce a new variable = common_part. // Use it in all linear constraint. if (block.size() > 1) { - context_->UpdateRuleStats("linear matrix: common horizontal rectangle"); - // Try to extend with exact matches that were skipped. const int match_size = var_to_coeff_non_zeros.size(); for (const auto [index, old_match_size] : old_matches) { @@ -9490,13 +9505,15 @@ void CpModelPresolver::FindBigHorizontalLinearOverlap() { // TODO(user): avoid creating the map? this is not visible in profile // though since we only do it when a reduction is performed. - ++num_blocks; absl::flat_hash_map coeff_map; for (const int var : var_to_coeff_non_zeros) { coeff_map[var] = var_to_coeff[var]; } + if (!RemoveCommonPart(coeff_map, block)) continue; + + ++num_blocks; nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size()); - RemoveCommonPart(coeff_map, block); + context_->UpdateRuleStats("linear matrix: common horizontal rectangle"); for (const int i : used_sorted_linear) sorted_linear[i] = -1; } } @@ -11742,7 +11759,8 @@ CpSolverStatus CpModelPresolver::Presolve() { if (context_->params().cp_model_use_sat_presolve()) { if (!time_limit_->LimitReached()) { if (!PresolvePureSatPart()) { - (void)context_->NotifyThatModelIsUnsat("UNSAT during SAT presolve"); + (void)context_->NotifyThatModelIsUnsat( + "Proven Infeasible during SAT presolve"); return InfeasibleStatus(); } } diff --git a/ortools/sat/cp_model_presolve.h b/ortools/sat/cp_model_presolve.h index 6b5538035c9..b0bc119efdb 100644 --- a/ortools/sat/cp_model_presolve.h +++ b/ortools/sat/cp_model_presolve.h @@ -263,7 +263,13 @@ class CpModelPresolver { // Assumes that all [constraint_index, multiple] in block are linear // constraint that contains multiple * common_part and perform the // substitution. - void RemoveCommonPart( + // + // Returns false if the substitution cannot be performed because the equation + // common_part = new_variable is a linear equation with potential overflow. + // + // TODO(user): I would be great to change the overflow precondition so that + // this cannot happen by maybe taking the rhs into account? + bool RemoveCommonPart( const absl::flat_hash_map& common_var_coeff_map, const std::vector>& block); diff --git a/ortools/sat/cp_model_solver.cc b/ortools/sat/cp_model_solver.cc index c44b65eb1f3..dc7c6b64d12 100644 --- a/ortools/sat/cp_model_solver.cc +++ b/ortools/sat/cp_model_solver.cc @@ -182,6 +182,8 @@ ABSL_FLAG( ABSL_FLAG(bool, cp_model_ignore_objective, false, "If true, ignore the objective."); +ABSL_FLAG(bool, cp_model_ignore_hints, false, + "If true, ignore any supplied hints."); ABSL_FLAG(bool, cp_model_fingerprint_model, true, "Fingerprint the model."); namespace operations_research { @@ -3865,6 +3867,9 @@ void TestSolutionHintForFeasibility(const CpModelProto& model_proto, // TODO(user): If the hint specifies all non-fixed variables we could also // do the check. if (model_proto.solution_hint().vars_size() != model_proto.variables_size()) { + SOLVER_LOG(logger, "The solution hint is incomplete: ", + model_proto.solution_hint().vars_size(), " out of ", + model_proto.variables_size(), " variables hinted."); return; } @@ -4134,6 +4139,12 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) { context->working_model->clear_floating_point_objective(); } + if (absl::GetFlag(FLAGS_cp_model_ignore_hints) && + context->working_model->has_solution_hint()) { + SOLVER_LOG(logger, "Ignoring solution hint"); + context->working_model->clear_solution_hint(); + } + // Checks for hints early in case they are forced to be hard constraints. if (params.fix_variables_to_their_hinted_value() && model_proto.has_solution_hint()) { diff --git a/ortools/sat/docs/README.md b/ortools/sat/docs/README.md index 4f15cbbd242..d0b442f43b1 100644 --- a/ortools/sat/docs/README.md +++ b/ortools/sat/docs/README.md @@ -40,21 +40,21 @@ def SimpleSatProgram(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) # Creates a solver and solves the model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") else: print("No solution found.") diff --git a/ortools/sat/docs/boolean_logic.md b/ortools/sat/docs/boolean_logic.md index e581c04be6e..52d43680168 100644 --- a/ortools/sat/docs/boolean_logic.md +++ b/ortools/sat/docs/boolean_logic.md @@ -29,8 +29,8 @@ from ortools.sat.python import cp_model def LiteralSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - not_x = x.Not() + x = model.new_bool_var("x") + not_x = x.negated() print(x) print(not_x) @@ -131,10 +131,10 @@ from ortools.sat.python import cp_model def BoolOrSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") + x = model.new_bool_var("x") + y = model.new_bool_var("y") - model.AddBoolOr([x, y.Not()]) + model.add_bool_or([x, y.negated()]) BoolOrSampleSat() @@ -241,20 +241,20 @@ def ReifiedSampleSat(): """Showcase creating a reified constraint.""" model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - b = model.NewBoolVar("b") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + b = model.new_bool_var("b") # First version using a half-reified bool and. - model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b) + model.add_bool_and(x, y.negated()).only_enforce_if(b) # Second version using implications. - model.AddImplication(b, x) - model.AddImplication(b, y.Not()) + model.add_implication(b, x) + model.add_implication(b, y.negated()) # Third version using bool or. - model.AddBoolOr(b.Not(), x) - model.AddBoolOr(b.Not(), y.Not()) + model.add_bool_or(b.negated(), x) + model.add_bool_or(b.negated(), y.negated()) ReifiedSampleSat() @@ -407,22 +407,22 @@ def BooleanProductSampleSat(): p == x * y, which is the same as p <=> x and y """ model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - p = model.NewBoolVar("p") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + p = model.new_bool_var("p") # x and y implies p, rewrite as not(x and y) or p. - model.AddBoolOr(x.Not(), y.Not(), p) + model.add_bool_or(x.negated(), y.negated(), p) # p implies x and y, expanded into two implications. - model.AddImplication(p, x) - model.AddImplication(p, y) + model.add_implication(p, x) + model.add_implication(p, y) # Create a solver and solve. solver = cp_model.CpSolver() solution_printer = cp_model.VarArraySolutionPrinter([x, y, p]) solver.parameters.enumerate_all_solutions = True - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) BooleanProductSampleSat() diff --git a/ortools/sat/docs/channeling.md b/ortools/sat/docs/channeling.md index 7dcf50f3f2d..3faa138b792 100644 --- a/ortools/sat/docs/channeling.md +++ b/ortools/sat/docs/channeling.md @@ -38,20 +38,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def ChannelingSampleSat(): """Demonstrates how to link integer constraints together.""" @@ -60,24 +55,24 @@ def ChannelingSampleSat(): model = cp_model.CpModel() # Declare our two primary variables. - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") # Declare our intermediate boolean variable. - b = model.NewBoolVar("b") + b = model.new_bool_var("b") # Implement b == (x >= 5). - model.Add(x >= 5).OnlyEnforceIf(b) - model.Add(x < 5).OnlyEnforceIf(b.Not()) + model.add(x >= 5).only_enforce_if(b) + model.add(x < 5).only_enforce_if(b.negated()) # Create our two half-reified constraints. # First, b implies (y == 10 - x). - model.Add(y == 10 - x).OnlyEnforceIf(b) + model.add(y == 10 - x).only_enforce_if(b) # Second, not(b) implies y == 0. - model.Add(y == 0).OnlyEnforceIf(b.Not()) + model.add(y == 0).only_enforce_if(b.negated()) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -89,7 +84,7 @@ def ChannelingSampleSat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, y, b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) ChannelingSampleSat() @@ -364,43 +359,43 @@ def BinpackingProblemSat(): for i in all_items: num_copies = items[i][1] for b in all_bins: - x[(i, b)] = model.NewIntVar(0, num_copies, f"x[{i},{b}]") + x[(i, b)] = model.new_int_var(0, num_copies, f"x[{i},{b}]") # Load variables. - load = [model.NewIntVar(0, bin_capacity, f"load[{b}]") for b in all_bins] + load = [model.new_int_var(0, bin_capacity, f"load[{b}]") for b in all_bins] # Slack variables. - slacks = [model.NewBoolVar(f"slack[{b}]") for b in all_bins] + slacks = [model.new_bool_var(f"slack[{b}]") for b in all_bins] # Links load and x. for b in all_bins: - model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) + model.add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) # Place all items. for i in all_items: - model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1]) + model.add(sum(x[(i, b)] for b in all_bins) == items[i][1]) # Links load and slack through an equivalence relation. safe_capacity = bin_capacity - slack_capacity for b in all_bins: # slack[b] => load[b] <= safe_capacity. - model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b]) + model.add(load[b] <= safe_capacity).only_enforce_if(slacks[b]) # not(slack[b]) => load[b] > safe_capacity. - model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not()) + model.add(load[b] > safe_capacity).only_enforce_if(slacks[b].negated()) # Maximize sum of slacks. - model.Maximize(sum(slacks)) + model.maximize(sum(slacks)) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) - print(f"Solve status: {solver.StatusName(status)}") + status = solver.solve(model) + print(f"solve status: {solver.status_name(status)}") if status == cp_model.OPTIMAL: - print(f"Optimal objective value: {solver.ObjectiveValue()}") + print(f"Optimal objective value: {solver.objective_value}") print("Statistics") - print(f" - conflicts : {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time : {solver.WallTime()}s") + print(f" - conflicts : {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time : {solver.wall_time}s") BinpackingProblemSat() diff --git a/ortools/sat/docs/integer_arithmetic.md b/ortools/sat/docs/integer_arithmetic.md index 11967ff53c4..665587f3d68 100644 --- a/ortools/sat/docs/integer_arithmetic.md +++ b/ortools/sat/docs/integer_arithmetic.md @@ -125,20 +125,20 @@ def RabbitsAndPheasantsSat(): """Solves the rabbits + pheasants problem.""" model = cp_model.CpModel() - r = model.NewIntVar(0, 100, "r") - p = model.NewIntVar(0, 100, "p") + r = model.new_int_var(0, 100, "r") + p = model.new_int_var(0, 100, "p") # 20 heads. - model.Add(r + p == 20) + model.add(r + p == 20) # 56 legs. - model.Add(4 * r + 2 * p == 56) + model.add(4 * r + 2 * p == 56) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"{solver.Value(r)} rabbits and {solver.Value(p)} pheasants") + print(f"{solver.value(r)} rabbits and {solver.value(p)} pheasants") RabbitsAndPheasantsSat() @@ -307,20 +307,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def earliness_tardiness_cost_sample_sat(): """Encode the piecewise linear expression.""" @@ -334,7 +329,7 @@ def earliness_tardiness_cost_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the piecewise linear function. # @@ -343,24 +338,24 @@ def earliness_tardiness_cost_sample_sat(): # ed ld # large_constant = 1000 - expr = model.NewIntVar(0, large_constant, "expr") + expr = model.new_int_var(0, large_constant, "expr") # First segment. - s1 = model.NewIntVar(-large_constant, large_constant, "s1") - model.Add(s1 == earliness_cost * (earliness_date - x)) + s1 = model.new_int_var(-large_constant, large_constant, "s1") + model.add(s1 == earliness_cost * (earliness_date - x)) # Second segment. s2 = 0 # Third segment. - s3 = model.NewIntVar(-large_constant, large_constant, "s3") - model.Add(s3 == lateness_cost * (x - lateness_date)) + s3 = model.new_int_var(-large_constant, large_constant, "s3") + model.add(s3 == lateness_cost * (x - lateness_date)) # Link together expr and x through s1, s2, and s3. - model.AddMaxEquality(expr, [s1, s2, s3]) + model.add_max_equality(expr, [s1, s2, s3]) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -372,7 +367,7 @@ def earliness_tardiness_cost_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) earliness_tardiness_cost_sample_sat() @@ -649,20 +644,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def step_function_sample_sat(): """Encode the step function.""" @@ -671,7 +661,7 @@ def step_function_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the step function # Note it is not defined for x == 2. @@ -682,32 +672,32 @@ def step_function_sample_sat(): # -- --- 0 # 0 ================ 20 # - expr = model.NewIntVar(0, 3, "expr") + expr = model.new_int_var(0, 3, "expr") # expr == 0 on [5, 6] U [8, 10] - b0 = model.NewBoolVar("b0") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)]) - ).OnlyEnforceIf(b0) - model.Add(expr == 0).OnlyEnforceIf(b0) + b0 = model.new_bool_var("b0") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(5, 6), (8, 10)]) + ).only_enforce_if(b0) + model.add(expr == 0).only_enforce_if(b0) # expr == 2 on [0, 1] U [3, 4] U [11, 20] - b2 = model.NewBoolVar("b2") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(0, 1), (3, 4), (11, 20)]) - ).OnlyEnforceIf(b2) - model.Add(expr == 2).OnlyEnforceIf(b2) + b2 = model.new_bool_var("b2") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(0, 1), (3, 4), (11, 20)]) + ).only_enforce_if(b2) + model.add(expr == 2).only_enforce_if(b2) # expr == 3 when x == 7 - b3 = model.NewBoolVar("b3") - model.Add(x == 7).OnlyEnforceIf(b3) - model.Add(expr == 3).OnlyEnforceIf(b3) + b3 = model.new_bool_var("b3") + model.add(x == 7).only_enforce_if(b3) + model.add(expr == 3).only_enforce_if(b3) # At least one bi is true. (we could use an exactly one constraint). - model.AddBoolOr(b0, b2, b3) + model.add_bool_or(b0, b2, b3) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -719,7 +709,7 @@ def step_function_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) step_function_sample_sat() diff --git a/ortools/sat/docs/model.md b/ortools/sat/docs/model.md index 3e213210167..e1ad4eaa912 100644 --- a/ortools/sat/docs/model.md +++ b/ortools/sat/docs/model.md @@ -85,26 +85,26 @@ def SolutionHintingSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # Solution hinting: x <- 1, y <- 2 - model.AddHint(x, 1) - model.AddHint(y, 2) + model.add_hint(x, 1) + model.add_hint(y, 2) # Creates a solver and solves. solver = cp_model.CpSolver() solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolutionHintingSampleSat() @@ -318,34 +318,34 @@ def CloneModelSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # Creates a solver and solves. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print("Optimal value of the original model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the original model: {}".format(solver.objective_value)) - # Clone the model. - copy = model.Clone() + # Clones the model. + copy = model.clone() - copy_x = copy.GetIntVarFromProtoIndex(x.Index()) - copy_y = copy.GetIntVarFromProtoIndex(y.Index()) + copy_x = copy.get_int_var_from_proto_index(x.index) + copy_y = copy.get_int_var_from_proto_index(y.index) - copy.Add(copy_x + copy_y <= 1) + copy.add(copy_x + copy_y <= 1) - status = solver.Solve(copy) + status = solver.solve(copy) if status == cp_model.OPTIMAL: - print("Optimal value of the modified model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the modified model: {}".format(solver.objective_value)) CloneModelSampleSat() diff --git a/ortools/sat/docs/scheduling.md b/ortools/sat/docs/scheduling.md index 776d4df361f..5a5d65c2482 100644 --- a/ortools/sat/docs/scheduling.md +++ b/ortools/sat/docs/scheduling.md @@ -38,22 +38,22 @@ def IntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - interval_var = model.NewIntervalVar(start_var, duration, end_var + 2, "interval") + end_var = model.new_int_var(0, horizon, "end") + interval_var = model.new_interval_var(start_var, duration, end_var + 2, "interval") print(f"interval = {repr(interval_var)}") # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewFixedSizeIntervalVar( + fixed_size_interval_var = model.new_fixed_size_interval_var( start_var, 10, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewFixedSizeIntervalVar(5, 10, "fixed_interval") + fixed_interval = model.new_fixed_size_interval_var(5, 10, "fixed_interval") print(f"fixed_interval = {repr(fixed_interval)}") @@ -207,11 +207,11 @@ def OptionalIntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - presence_var = model.NewBoolVar("presence") - interval_var = model.NewOptionalIntervalVar( + end_var = model.new_int_var(0, horizon, "end") + presence_var = model.new_bool_var("presence") + interval_var = model.new_optional_interval_var( start_var, duration, end_var + 2, presence_var, "interval" ) @@ -219,13 +219,13 @@ def OptionalIntervalSampleSat(): # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewOptionalFixedSizeIntervalVar( + fixed_size_interval_var = model.new_optional_fixed_size_interval_var( start_var, 10, presence_var, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewOptionalFixedSizeIntervalVar( + fixed_interval = model.new_optional_fixed_size_interval_var( 5, 10, presence_var, "fixed_interval" ) print(f"fixed_interval = {repr(fixed_interval)}") @@ -385,45 +385,45 @@ def NoOverlapSampleSat(): horizon = 21 # 3 weeks. # Task 0, duration 2. - start_0 = model.NewIntVar(0, horizon, "start_0") + start_0 = model.new_int_var(0, horizon, "start_0") duration_0 = 2 # Python cp/sat code accepts integer variables or constants. - end_0 = model.NewIntVar(0, horizon, "end_0") - task_0 = model.NewIntervalVar(start_0, duration_0, end_0, "task_0") + end_0 = model.new_int_var(0, horizon, "end_0") + task_0 = model.new_interval_var(start_0, duration_0, end_0, "task_0") # Task 1, duration 4. - start_1 = model.NewIntVar(0, horizon, "start_1") + start_1 = model.new_int_var(0, horizon, "start_1") duration_1 = 4 # Python cp/sat code accepts integer variables or constants. - end_1 = model.NewIntVar(0, horizon, "end_1") - task_1 = model.NewIntervalVar(start_1, duration_1, end_1, "task_1") + end_1 = model.new_int_var(0, horizon, "end_1") + task_1 = model.new_interval_var(start_1, duration_1, end_1, "task_1") # Task 2, duration 3. - start_2 = model.NewIntVar(0, horizon, "start_2") + start_2 = model.new_int_var(0, horizon, "start_2") duration_2 = 3 # Python cp/sat code accepts integer variables or constants. - end_2 = model.NewIntVar(0, horizon, "end_2") - task_2 = model.NewIntervalVar(start_2, duration_2, end_2, "task_2") + end_2 = model.new_int_var(0, horizon, "end_2") + task_2 = model.new_interval_var(start_2, duration_2, end_2, "task_2") # Weekends. - weekend_0 = model.NewIntervalVar(5, 2, 7, "weekend_0") - weekend_1 = model.NewIntervalVar(12, 2, 14, "weekend_1") - weekend_2 = model.NewIntervalVar(19, 2, 21, "weekend_2") + weekend_0 = model.new_interval_var(5, 2, 7, "weekend_0") + weekend_1 = model.new_interval_var(12, 2, 14, "weekend_1") + weekend_2 = model.new_interval_var(19, 2, 21, "weekend_2") # No Overlap constraint. - model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) + model.add_no_overlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) # Makespan objective. - obj = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(obj, [end_0, end_1, end_2]) - model.Minimize(obj) + obj = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(obj, [end_0, end_1, end_2]) + model.minimize(obj) # Solve model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Print out makespan and the start times for all tasks. - print(f"Optimal Schedule Length: {solver.ObjectiveValue()}") - print(f"Task 0 starts at {solver.Value(start_0)}") - print(f"Task 1 starts at {solver.Value(start_1)}") - print(f"Task 2 starts at {solver.Value(start_2)}") + print(f"Optimal Schedule Length: {solver.objective_value}") + print(f"Task 0 starts at {solver.value(start_0)}") + print(f"Task 1 starts at {solver.value(start_1)}") + print(f"Task 2 starts at {solver.value(start_2)}") else: print(f"Solver exited with nonoptimal status: {status}") @@ -659,7 +659,8 @@ the capacity between the actual profile and it max capacity. ```python #!/usr/bin/env python3 -"""Solve a simple scheduling problem with a variable work load.""" +"""Solves a simple scheduling problem with a variable work load.""" + import io import pandas as pd @@ -741,12 +742,12 @@ def main(): horizon: int = 24 * 60 # Variables - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", lower_bounds=0, upper_bounds=horizon, index=tasks_df.index ) - performed = model.NewBoolVarSeries(name="performed", index=tasks_df.index) + performed = model.new_bool_var_series(name="performed", index=tasks_df.index) - intervals = model.NewOptionalFixedSizeIntervalVarSeries( + intervals = model.new_optional_fixed_size_interval_var_series( name="intervals", index=tasks_df.index, starts=starts, @@ -756,7 +757,7 @@ def main(): # Set up the profile. We use fixed (intervals, demands) to fill in the space # between the actual load profile and the max capacity. - time_period_intervals = model.NewFixedSizeIntervalVarSeries( + time_period_intervals = model.new_fixed_size_interval_var_series( name="time_period_intervals", index=capacity_df.index, starts=capacity_df.start_hour * minutes_per_period, @@ -765,7 +766,7 @@ def main(): time_period_heights = max_capacity - capacity_df.capacity # Cumulative constraint. - model.AddCumulative( + model.add_cumulative( intervals.to_list() + time_period_intervals.to_list(), tasks_df.load.to_list() + time_period_heights.to_list(), max_capacity, @@ -774,18 +775,18 @@ def main(): # Objective: maximize the value of performed intervals. # 1 is the max priority. max_priority = max(tasks_df.priority) - model.Maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) + model.maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) # Create the solver and solve the model. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 30.0 - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - start_values = solver.Values(starts) - performed_values = solver.BooleanValues(performed) + start_values = solver.values(starts) + performed_values = solver.boolean_values(performed) for task in tasks_df.index: if performed_values[task]: print(f"task {task} starts at {start_values[task]}") @@ -828,7 +829,12 @@ number of other intervals that precede it. from ortools.sat.python import cp_model -def RankTasks(model, starts, presences, ranks): +def RankTasks( + model: cp_model.CpModel, + starts: list[cp_model.IntVar], + presences: list[cp_model.IntVar], + ranks: list[cp_model.IntVar], +): """This method adds constraints and variables to links tasks and ranks. This method assumes that all starts are disjoint, meaning that all tasks have @@ -852,36 +858,44 @@ def RankTasks(model, starts, presences, ranks): if i == j: precedences[(i, j)] = presences[i] else: - prec = model.NewBoolVar(f"{i} before {j}") + prec = model.new_bool_var(f"{i} before {j}") precedences[(i, j)] = prec - model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec) + model.add(starts[i] < starts[j]).only_enforce_if(prec) # Treats optional intervals. for i in range(num_tasks - 1): for j in range(i + 1, num_tasks): tmp_array = [precedences[(i, j)], precedences[(j, i)]] - if not cp_model.ObjectIsATrueLiteral(presences[i]): - tmp_array.append(presences[i].Not()) + if not cp_model.object_is_a_true_literal(presences[i]): + tmp_array.append(presences[i].negated()) # Makes sure that if i is not performed, all precedences are false. - model.AddImplication(presences[i].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[i].Not(), precedences[(j, i)].Not()) - if not cp_model.ObjectIsATrueLiteral(presences[j]): - tmp_array.append(presences[j].Not()) + model.add_implication( + presences[i].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[i].negated(), precedences[(j, i)].negated() + ) + if not cp_model.object_is_a_true_literal(presences[j]): + tmp_array.append(presences[j].negated()) # Makes sure that if j is not performed, all precedences are false. - model.AddImplication(presences[j].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[j].Not(), precedences[(j, i)].Not()) + model.add_implication( + presences[j].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[j].negated(), precedences[(j, i)].negated() + ) # The following bool_or will enforce that for any two intervals: # i precedes j or j precedes i or at least one interval is not # performed. - model.AddBoolOr(tmp_array) + model.add_bool_or(tmp_array) # Redundant constraint: it propagates early that at most one precedence # is true. - model.AddImplication(precedences[(i, j)], precedences[(j, i)].Not()) - model.AddImplication(precedences[(j, i)], precedences[(i, j)].Not()) + model.add_implication(precedences[(i, j)], precedences[(j, i)].negated()) + model.add_implication(precedences[(j, i)], precedences[(i, j)].negated()) # Links precedences and ranks. for i in all_tasks: - model.Add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) + model.add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) def RankingSampleSat(): @@ -900,15 +914,15 @@ def RankingSampleSat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - end = model.NewIntVar(0, horizon, f"end[{t}]") + end = model.new_int_var(0, horizon, f"end[{t}]") if t < num_tasks // 2: - interval = model.NewIntervalVar(start, duration, end, f"interval[{t}]") + interval = model.new_interval_var(start, duration, end, f"interval[{t}]") presence = True else: - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_interval_var( start, duration, end, presence, f"o_interval[{t}]" ) starts.append(start) @@ -917,45 +931,44 @@ def RankingSampleSat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. RankTasks(model, starts, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(ends[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") @@ -1510,26 +1523,26 @@ def rank_tasks_with_circuit( arcs: List[cp_model.ArcT] = [] for i in all_tasks: # if node i is first. - start_lit = model.NewBoolVar(f"start_{i}") + start_lit = model.new_bool_var(f"start_{i}") arcs.append((0, i + 1, start_lit)) - model.Add(ranks[i] == 0).OnlyEnforceIf(start_lit) + model.add(ranks[i] == 0).only_enforce_if(start_lit) # As there are no other constraints on the problem, we can add this # redundant constraint. - model.Add(starts[i] == 0).OnlyEnforceIf(start_lit) + model.add(starts[i] == 0).only_enforce_if(start_lit) # if node i is last. - end_lit = model.NewBoolVar(f"end_{i}") + end_lit = model.new_bool_var(f"end_{i}") arcs.append((i + 1, 0, end_lit)) for j in all_tasks: if i == j: - arcs.append((i + 1, i + 1, presences[i].Not())) - model.Add(ranks[i] == -1).OnlyEnforceIf(presences[i].Not()) + arcs.append((i + 1, i + 1, presences[i].negated())) + model.add(ranks[i] == -1).only_enforce_if(presences[i].negated()) else: - literal = model.NewBoolVar(f"arc_{i}_to_{j}") + literal = model.new_bool_var(f"arc_{i}_to_{j}") arcs.append((i + 1, j + 1, literal)) - model.Add(ranks[j] == ranks[i] + 1).OnlyEnforceIf(literal) + model.add(ranks[j] == ranks[i] + 1).only_enforce_if(literal) # To perform the transitive reduction from precedences to successors, # we need to tie the starts of the tasks with 'literal'. @@ -1538,17 +1551,19 @@ def rank_tasks_with_circuit( # # Note that we could use this literal to penalize the transition, add an # extra delay to the precedence. - model.Add(starts[j] >= starts[i] + durations[i]).OnlyEnforceIf(literal) + model.add(starts[j] >= starts[i] + durations[i]).only_enforce_if( + literal + ) # Manage the empty circuit - empty = model.NewBoolVar("empty") + empty = model.new_bool_var("empty") arcs.append((0, 0, empty)) for i in all_tasks: - model.AddImplication(empty, presences[i].Not()) + model.add_implication(empty, presences[i].negated()) # Add the circuit constraint. - model.AddCircuit(arcs) + model.add_circuit(arcs) def ranking_sample_sat(): @@ -1567,14 +1582,14 @@ def ranking_sample_sat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalFixedSizeIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_fixed_size_interval_var( start, duration, presence, f"opt_interval[{t}]" ) if t < num_tasks // 2: - model.Add(presence == 1) + model.add(presence == 1) starts.append(start) durations.append(duration) @@ -1582,45 +1597,44 @@ def ranking_sample_sat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. rank_tasks_with_circuit(model, starts, durations, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(starts[t] + durations[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(starts[t] + durations[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") @@ -1660,20 +1674,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def SchedulingWithCalendarSampleSat(): """Interval spanning across a lunch break.""" @@ -1687,25 +1696,27 @@ def SchedulingWithCalendarSampleSat(): # Because the duration is at least 3 hours, work cannot start after 15h. # Because of the break, work cannot start at 13h. - start = model.NewIntVarFromDomain( - cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), "start" + start = model.new_int_var_from_domain( + cp_model.Domain.from_intervals([(8, 12), (14, 15)]), "start" ) - duration = model.NewIntVar(3, 4, "duration") - end = model.NewIntVar(8, 18, "end") - unused_interval = model.NewIntervalVar(start, duration, end, "interval") + duration = model.new_int_var(3, 4, "duration") + end = model.new_int_var(8, 18, "end") + unused_interval = model.new_interval_var(start, duration, end, "interval") # We have 2 states (spanning across lunch or not) - across = model.NewBoolVar("across") - non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15]) - model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf( - across.Not() + across = model.new_bool_var("across") + non_spanning_hours = cp_model.Domain.from_values([8, 9, 10, 14, 15]) + model.add_linear_expression_in_domain(start, non_spanning_hours).only_enforce_if( + across.negated() ) - model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across) - model.Add(duration == 3).OnlyEnforceIf(across.Not()) - model.Add(duration == 4).OnlyEnforceIf(across) + model.add_linear_constraint(start, 11, 12).only_enforce_if(across) + model.add(duration == 3).only_enforce_if(across.negated()) + model.add(duration == 4).only_enforce_if(across) # Search for x values in increasing order. - model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy( + [start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + ) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -1717,7 +1728,7 @@ def SchedulingWithCalendarSampleSat(): # Search and print all solutions. solution_printer = VarArraySolutionPrinter([start, duration, across]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) SchedulingWithCalendarSampleSat() @@ -1768,20 +1779,15 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def OverlappingIntervals(): """Create the overlapping Boolean variables and enumerate all states.""" @@ -1790,45 +1796,47 @@ def OverlappingIntervals(): horizon = 7 # First interval. - start_var_a = model.NewIntVar(0, horizon, "start_a") + start_var_a = model.new_int_var(0, horizon, "start_a") duration_a = 3 - end_var_a = model.NewIntVar(0, horizon, "end_a") - unused_interval_var_a = model.NewIntervalVar( + end_var_a = model.new_int_var(0, horizon, "end_a") + unused_interval_var_a = model.new_interval_var( start_var_a, duration_a, end_var_a, "interval_a" ) # Second interval. - start_var_b = model.NewIntVar(0, horizon, "start_b") + start_var_b = model.new_int_var(0, horizon, "start_b") duration_b = 2 - end_var_b = model.NewIntVar(0, horizon, "end_b") - unused_interval_var_b = model.NewIntervalVar( + end_var_b = model.new_int_var(0, horizon, "end_b") + unused_interval_var_b = model.new_interval_var( start_var_b, duration_b, end_var_b, "interval_b" ) # a_after_b Boolean variable. - a_after_b = model.NewBoolVar("a_after_b") - model.Add(start_var_a >= end_var_b).OnlyEnforceIf(a_after_b) - model.Add(start_var_a < end_var_b).OnlyEnforceIf(a_after_b.Not()) + a_after_b = model.new_bool_var("a_after_b") + model.add(start_var_a >= end_var_b).only_enforce_if(a_after_b) + model.add(start_var_a < end_var_b).only_enforce_if(a_after_b.negated()) # b_after_a Boolean variable. - b_after_a = model.NewBoolVar("b_after_a") - model.Add(start_var_b >= end_var_a).OnlyEnforceIf(b_after_a) - model.Add(start_var_b < end_var_a).OnlyEnforceIf(b_after_a.Not()) + b_after_a = model.new_bool_var("b_after_a") + model.add(start_var_b >= end_var_a).only_enforce_if(b_after_a) + model.add(start_var_b < end_var_a).only_enforce_if(b_after_a.negated()) # Result Boolean variable. - a_overlaps_b = model.NewBoolVar("a_overlaps_b") + a_overlaps_b = model.new_bool_var("a_overlaps_b") # Option a: using only clauses - model.AddBoolOr(a_after_b, b_after_a, a_overlaps_b) - model.AddImplication(a_after_b, a_overlaps_b.Not()) - model.AddImplication(b_after_a, a_overlaps_b.Not()) + model.add_bool_or(a_after_b, b_after_a, a_overlaps_b) + model.add_implication(a_after_b, a_overlaps_b.negated()) + model.add_implication(b_after_a, a_overlaps_b.negated()) # Option b: using an exactly one constraint. - # model.AddExactlyOne(a_after_b, b_after_a, a_overlaps_b) + # model.add_exactly_one(a_after_b, b_after_a, a_overlaps_b) # Search for start values in increasing order for the two intervals. - model.AddDecisionStrategy( - [start_var_a, start_var_b], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + model.add_decision_strategy( + [start_var_a, start_var_b], + cp_model.CHOOSE_FIRST, + cp_model.SELECT_MIN_VALUE, ) # Create a solver and solve with a fixed search. @@ -1841,7 +1849,7 @@ def OverlappingIntervals(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([start_var_a, start_var_b, a_overlaps_b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) OverlappingIntervals() diff --git a/ortools/sat/docs/solver.md b/ortools/sat/docs/solver.md index 019d3621417..abf193e5ef5 100644 --- a/ortools/sat/docs/solver.md +++ b/ortools/sat/docs/solver.md @@ -24,11 +24,11 @@ def SolveWithTimeLimitSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Adds an all-different constraint. - model.Add(x != y) + model.add(x != y) # Creates a solver and solves the model. solver = cp_model.CpSolver() @@ -36,12 +36,12 @@ def SolveWithTimeLimitSampleSat(): # Sets a time limit of 10 seconds. solver.parameters.max_time_in_seconds = 10.0 - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") SolveWithTimeLimitSampleSat() @@ -207,20 +207,21 @@ from ortools.sat.python import cp_model class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: print(f"Solution {self.__solution_count}") - print(f" objective value = {self.ObjectiveValue()}") + print(f" objective value = {self.objective_value}") for v in self.__variables: - print(f" {v}={self.Value(v)}", end=" ") + print(f" {v}={self.value(v)}", end=" ") print() self.__solution_count += 1 - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -231,22 +232,22 @@ def SolveAndPrintIntermediateSolutionsSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Creates the constraints. - model.Add(x != y) + model.add(x != y) - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # Creates a solver and solves. solver = cp_model.CpSolver() solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolveAndPrintIntermediateSolutionsSampleSat() @@ -469,18 +470,19 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -491,12 +493,12 @@ def SearchForAllSolutionsSampleSat(): # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Create the constraints. - model.Add(x != y) + model.add(x != y) # Create a solver and solve. solver = cp_model.CpSolver() @@ -504,10 +506,10 @@ def SearchForAllSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SearchForAllSolutionsSampleSat() @@ -725,22 +727,23 @@ from ortools.sat.python import cp_model class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables, limit): + def __init__(self, variables: list[cp_model.IntVar], limit: int): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 self.__solution_limit = limit - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() if self.__solution_count >= self.__solution_limit: print(f"Stop search after {self.__solution_limit} solutions") - self.StopSearch() + self.stop_search() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -750,9 +753,9 @@ def StopAfterNSolutionsSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Create a solver and solve. solver = cp_model.CpSolver() @@ -760,10 +763,10 @@ def StopAfterNSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") - assert solution_printer.solution_count() == 5 + status = solver.solve(model, solution_printer) + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") + assert solution_printer.solution_count == 5 StopAfterNSolutionsSampleSat() diff --git a/ortools/sat/docs/troubleshooting.md b/ortools/sat/docs/troubleshooting.md index ed921ef8e6e..6ce7339086b 100644 --- a/ortools/sat/docs/troubleshooting.md +++ b/ortools/sat/docs/troubleshooting.md @@ -107,31 +107,31 @@ def main(): model = cp_model.CpModel() # Creates the variables. - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - z = model.NewIntVar(0, 10, "z") - a = model.NewBoolVar("a") - b = model.NewBoolVar("b") - c = model.NewBoolVar("c") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + z = model.new_int_var(0, 10, "z") + a = model.new_bool_var("a") + b = model.new_bool_var("b") + c = model.new_bool_var("c") # Creates the constraints. - model.Add(x > y).OnlyEnforceIf(a) - model.Add(y > z).OnlyEnforceIf(b) - model.Add(z > x).OnlyEnforceIf(c) + model.add(x > y).only_enforce_if(a) + model.add(y > z).only_enforce_if(b) + model.add(z > x).only_enforce_if(c) # Add assumptions - model.AddAssumptions([a, b, c]) + model.add_assumptions([a, b, c]) # Creates a solver and solves. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # Print solution. - print(f"Status = {solver.StatusName(status)}") + print(f"Status = {solver.status_name(status)}") if status == cp_model.INFEASIBLE: print( - "SufficientAssumptionsForInfeasibility = " - f"{solver.SufficientAssumptionsForInfeasibility()}" + "sufficient_assumptions_for_infeasibility = " + f"{solver.sufficient_assumptions_for_infeasibility()}" ) diff --git a/ortools/sat/feasibility_jump.cc b/ortools/sat/feasibility_jump.cc index 73b60ea1f52..f4ede40b812 100644 --- a/ortools/sat/feasibility_jump.cc +++ b/ortools/sat/feasibility_jump.cc @@ -273,6 +273,14 @@ void FeasibilityJumpSolver::ResetCurrentSolution() { } } } + + // Overwrite with the (partial) hint on the first batch. + if (num_batches_ == 0 && linear_model_->model_proto().has_solution_hint()) { + const auto& hint = linear_model_->model_proto().solution_hint(); + for (int i = 0; i < hint.vars().size(); ++i) { + solution[hint.vars(i)] = hint.values(i); + } + } } void FeasibilityJumpSolver::PerturbateCurrentSolution() { @@ -348,7 +356,7 @@ std::function FeasibilityJumpSolver::GenerateTask(int64_t /*task_id*/) { // In incomplete mode, query the starting solution for the shared response // manager. - if (type() == SubSolver::INCOMPLETE) { + if (type() == SubSolver::INCOMPLETE) { // violation_ls. // Choose a base solution for this neighborhood. const SharedSolutionRepository& repo = shared_response_->SolutionsRepository(); @@ -375,7 +383,7 @@ std::function FeasibilityJumpSolver::GenerateTask(int64_t /*task_id*/) { should_recompute_violations = true; reset_weights = true; } - } else { + } else { // feasibility_jump. // Restart? Note that we always "restart" the first time. const double dtime = evaluator_->DeterministicTime(); if (dtime >= dtime_restart_threshold_ && diff --git a/ortools/sat/lp_utils.cc b/ortools/sat/lp_utils.cc index 220ee3eb6aa..2d1a2f3ed08 100644 --- a/ortools/sat/lp_utils.cc +++ b/ortools/sat/lp_utils.cc @@ -816,14 +816,21 @@ double FindBestScalingAndComputeErrors( // error of wanted_absolute_activity_precision and still make sure we will // have no integer overflow. // + // Important: the loop is written in such a way that ComputeScalingErrors() + // is called on the last factor. + // // TODO(user): Make this faster. double x = std::min(scaling_factor, 1.0); for (; x <= scaling_factor; x *= 2) { ComputeScalingErrors(coefficients, lower_bounds, upper_bounds, x, relative_coeff_error, scaled_sum_error); if (*scaled_sum_error < wanted_absolute_activity_precision * x) break; + + // This could happen if we always have enough precision. + if (x == scaling_factor) break; } scaling_factor = x; + DCHECK(std::isfinite(scaling_factor)); // Because we deal with an approximate input, scaling with a power of 2 might // not be the best choice. It is also possible user used rational coeff and @@ -834,6 +841,7 @@ double FindBestScalingAndComputeErrors( // Note that if our current precisions is already above the requested one, // we choose integer scaling if we get a better precision. const double integer_factor = FindFractionalScaling(coefficients, 1e-8); + DCHECK(std::isfinite(integer_factor)); if (integer_factor != 0 && integer_factor < scaling_factor) { double local_relative_coeff_error; double local_scaled_sum_error; @@ -850,6 +858,7 @@ double FindBestScalingAndComputeErrors( } } + DCHECK(std::isfinite(scaling_factor)); return scaling_factor; } diff --git a/ortools/sat/presolve_context.cc b/ortools/sat/presolve_context.cc index 31ba1ca2b73..9d4f677501b 100644 --- a/ortools/sat/presolve_context.cc +++ b/ortools/sat/presolve_context.cc @@ -446,8 +446,9 @@ ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith( } modified_domains.Set(var); if (domains[var].IsEmpty()) { - is_unsat_ = true; - return false; + return NotifyThatModelIsUnsat( + absl::StrCat("var #", ref, " as empty domain after intersecting with ", + domain.ToString())); } // Propagate the domain of the representative right away. @@ -467,8 +468,9 @@ ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith( if (domain.Contains(expr.offset())) { return true; } else { - is_unsat_ = true; - return false; + return NotifyThatModelIsUnsat(absl::StrCat( + expr.ShortDebugString(), " as empty domain after intersecting with ", + domain.ToString())); } } if (expr.vars().size() == 1) { // Affine diff --git a/ortools/sat/presolve_context.h b/ortools/sat/presolve_context.h index 4cebda89e0d..8b80a8ea32b 100644 --- a/ortools/sat/presolve_context.h +++ b/ortools/sat/presolve_context.h @@ -251,7 +251,6 @@ class PresolveContext { absl::string_view message = "") { // TODO(user): Report any explanation for the client in a nicer way? SOLVER_LOG(logger_, "INFEASIBLE: '", message, "'"); - DCHECK(!is_unsat_); is_unsat_ = true; return false; } diff --git a/ortools/sat/python/cp_model.py b/ortools/sat/python/cp_model.py index aa0529aba28..44d97490784 100644 --- a/ortools/sat/python/cp_model.py +++ b/ortools/sat/python/cp_model.py @@ -121,6 +121,7 @@ IntegralT = Union[numbers.Integral, np.integer, int] NumberT = Union[numbers.Integral, np.integer, int, numbers.Number, np.double, float] LiteralT = Union["IntVar", "_NotBooleanVariable", IntegralT, bool] +BoolVarT = Union["IntVar", "_NotBooleanVariable"] VariableT = Union["IntVar", IntegralT] LinearExprT = Union["LinearExpr", "IntVar", IntegralT] ObjLinearExprT = Union["LinearExpr", "IntVar", NumberT] @@ -128,7 +129,7 @@ _IndexOrSeries = Union[pd.Index, pd.Series] -def DisplayBounds(bounds: Sequence[int]) -> str: +def display_bounds(bounds: Sequence[int]) -> str: """Displays a flattened list of intervals.""" out = "" for i in range(0, len(bounds), 2): @@ -141,27 +142,27 @@ def DisplayBounds(bounds: Sequence[int]) -> str: return out -def ShortName(model: cp_model_pb2.CpModelProto, i: int) -> str: +def short_name(model: cp_model_pb2.CpModelProto, i: int) -> str: """Returns a short name of an integer variable, or its negation.""" if i < 0: - return "Not(%s)" % ShortName(model, -i - 1) + return "not(%s)" % short_name(model, -i - 1) v = model.variables[i] if v.name: return v.name elif len(v.domain) == 2 and v.domain[0] == v.domain[1]: return str(v.domain[0]) else: - return "[%s]" % DisplayBounds(v.domain) + return "[%s]" % display_bounds(v.domain) -def ShortExprName( +def short_expr_name( model: cp_model_pb2.CpModelProto, e: cp_model_pb2.LinearExpressionProto ) -> str: """Pretty-print LinearExpressionProto instances.""" if not e.vars: return str(e.offset) if len(e.vars) == 1: - var_name = ShortName(model, e.vars[0]) + var_name = short_name(model, e.vars[0]) coeff = e.coeffs[0] result = "" if coeff == 1: @@ -191,14 +192,14 @@ class LinearExpr: * You can define linear constraints as in: ``` - model.Add(x + 2 * y <= 5) - model.Add(sum(array_of_vars) == 5) + model.add(x + 2 * y <= 5) + model.add(sum(array_of_vars) == 5) ``` * In CP-SAT, the objective is a linear expression: ``` - model.Minimize(x + 2 * y + z) + model.minimize(x + 2 * y + z) ``` * For large arrays, using the LinearExpr class is faster that using the python @@ -206,13 +207,13 @@ class LinearExpr: linear expressions or coefficients as follows: ``` - model.Minimize(cp_model.LinearExpr.Sum(expressions)) - model.Add(cp_model.LinearExpr.WeightedSum(expressions, coefficients) >= 0) + model.minimize(cp_model.LinearExpr.sum(expressions)) + model.add(cp_model.LinearExpr.weighted_sum(expressions, coefficients) >= 0) ``` """ @classmethod - def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: + def sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: """Creates the expression sum(expressions).""" if len(expressions) == 1: return expressions[0] @@ -220,7 +221,7 @@ def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: @overload @classmethod - def WeightedSum( + def weighted_sum( cls, expressions: Sequence[LinearExprT], coefficients: Sequence[IntegralT], @@ -229,7 +230,7 @@ def WeightedSum( @overload @classmethod - def WeightedSum( + def weighted_sum( cls, expressions: Sequence[ObjLinearExprT], coefficients: Sequence[NumberT], @@ -237,9 +238,9 @@ def WeightedSum( ... @classmethod - def WeightedSum(cls, expressions, coefficients): + def weighted_sum(cls, expressions, coefficients): """Creates the expression sum(expressions[i] * coefficients[i]).""" - if LinearExpr.IsEmptyOrAllNull(coefficients): + if LinearExpr.is_empty_or_all_null(coefficients): return 0 elif len(expressions) == 1: return expressions[0] * coefficients[0] @@ -248,7 +249,7 @@ def WeightedSum(cls, expressions, coefficients): @overload @classmethod - def Term( + def term( cls, expressions: LinearExprT, coefficients: IntegralT, @@ -257,7 +258,7 @@ def Term( @overload @classmethod - def Term( + def term( cls, expressions: ObjLinearExprT, coefficients: NumberT, @@ -265,7 +266,7 @@ def Term( ... @classmethod - def Term(cls, expression, coefficient): + def term(cls, expression, coefficient): """Creates `expression * coefficient`.""" if cmh.is_zero(coefficient): return 0 @@ -273,14 +274,14 @@ def Term(cls, expression, coefficient): return expression * coefficient @classmethod - def IsEmptyOrAllNull(cls, coefficients: Sequence[NumberT]) -> bool: + def is_empty_or_all_null(cls, coefficients: Sequence[NumberT]) -> bool: for c in coefficients: if not cmh.is_zero(c): return False return True @classmethod - def RebuildFromLinearExpressionProto( + def rebuild_from_linear_expression_proto( cls, model: cp_model_pb2.CpModelProto, proto: cp_model_pb2.LinearExpressionProto, @@ -306,7 +307,7 @@ def RebuildFromLinearExpressionProto( else: return _WeightedSum(variables, coeffs, offset) - def GetIntegerVarValueMap(self) -> Tuple[Dict[VariableT, IntegralT], int]: + def get_integer_var_value_map(self) -> Tuple[Dict[VariableT, IntegralT], int]: """Scans the expression, and returns (var_coef_map, constant).""" coeffs = collections.defaultdict(int) constant = 0 @@ -316,29 +317,31 @@ def GetIntegerVarValueMap(self) -> Tuple[Dict[VariableT, IntegralT], int]: if cmh.is_integral(expr): constant += coeff * int(expr) elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, IntVar): coeffs[expr] += coeff elif isinstance(expr, _NotBooleanVariable): constant += coeff - coeffs[expr.Not()] -= coeff + coeffs[expr.negated()] -= coeff else: raise TypeError("Unrecognized linear expression: " + str(expr)) return coeffs, constant - def GetFloatVarValueMap(self) -> Tuple[Dict[VariableT, float], float, bool]: + def get_float_var_value_map( + self, + ) -> Tuple[Dict[VariableT, float], float, bool]: """Scans the expression. Returns (var_coef_map, constant, is_integer).""" coeffs = {} constant = 0 @@ -350,18 +353,18 @@ def GetFloatVarValueMap(self) -> Tuple[Dict[VariableT, float], float, bool]: elif cmh.is_a_number(expr): constant += coeff * float(expr) elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - constant += expr.Constant() * coeff + constant += expr.constant() * coeff elif isinstance(expr, IntVar): if expr in coeffs: coeffs[expr] += coeff @@ -369,10 +372,10 @@ def GetFloatVarValueMap(self) -> Tuple[Dict[VariableT, float], float, bool]: coeffs[expr] = coeff elif isinstance(expr, _NotBooleanVariable): constant += coeff - if expr.Not() in coeffs: - coeffs[expr.Not()] -= coeff + if expr.negated() in coeffs: + coeffs[expr.negated()] -= coeff else: - coeffs[expr.Not()] = -coeff + coeffs[expr.negated()] = -coeff else: raise TypeError("Unrecognized linear expression: " + str(expr)) is_integer = cmh.is_integral(constant) @@ -389,7 +392,7 @@ def __hash__(self): def __abs__(self): raise NotImplementedError( "calling abs() on a linear expression is not supported, " - "please use CpModel.AddAbsEquality" + "please use CpModel.add_abs_equality" ) def __add__(self, arg): @@ -429,25 +432,25 @@ def __rmul__(self, arg): def __div__(self, _): raise NotImplementedError( "calling / on a linear expression is not supported, " - "please use CpModel.AddDivisionEquality" + "please use CpModel.add_division_equality" ) def __truediv__(self, _): raise NotImplementedError( "calling // on a linear expression is not supported, " - "please use CpModel.AddDivisionEquality" + "please use CpModel.add_division_equality" ) def __mod__(self, _): raise NotImplementedError( "calling %% on a linear expression is not supported, " - "please use CpModel.AddModuloEquality" + "please use CpModel.add_modulo_equality" ) def __pow__(self, _): raise NotImplementedError( "calling ** on a linear expression is not supported, " - "please use CpModel.AddMultiplicationEquality" + "please use CpModel.add_multiplication_equality" ) def __lshift__(self, _): @@ -463,19 +466,19 @@ def __rshift__(self, _): def __and__(self, _): raise NotImplementedError( "calling and on a linear expression is not supported, " - "please use CpModel.AddBoolAnd" + "please use CpModel.add_bool_and" ) def __or__(self, _): raise NotImplementedError( "calling or on a linear expression is not supported, " - "please use CpModel.AddBoolOr" + "please use CpModel.add_bool_or" ) def __xor__(self, _): raise NotImplementedError( "calling xor on a linear expression is not supported, " - "please use CpModel.AddBoolXor" + "please use CpModel.add_bool_xor" ) def __neg__(self): @@ -543,6 +546,61 @@ def __ne__(self, arg): else: return BoundedLinearExpression(self - arg, [INT_MIN, -1, 1, INT_MAX]) + # Compatibility with pre PEP8 + # pylint: disable=invalid-name + @classmethod + def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT: + """Creates the expression sum(expressions).""" + return cls.sum(expressions) + + @overload + @classmethod + def WeightedSum( + cls, + expressions: Sequence[LinearExprT], + coefficients: Sequence[IntegralT], + ) -> LinearExprT: + ... + + @overload + @classmethod + def WeightedSum( + cls, + expressions: Sequence[ObjLinearExprT], + coefficients: Sequence[NumberT], + ) -> ObjLinearExprT: + ... + + @classmethod + def WeightedSum(cls, expressions, coefficients): + """Creates the expression sum(expressions[i] * coefficients[i]).""" + return cls.weighted_sum(expressions, coefficients) + + @overload + @classmethod + def Term( + cls, + expressions: LinearExprT, + coefficients: IntegralT, + ) -> LinearExprT: + ... + + @overload + @classmethod + def Term( + cls, + expressions: ObjLinearExprT, + coefficients: NumberT, + ) -> ObjLinearExprT: + ... + + @classmethod + def Term(cls, expression, coefficient): + """Creates `expression * coefficient`.""" + return cls.term(expression, coefficient) + + # pylint: enable=invalid-name + class _Sum(LinearExpr): """Represents the sum of two LinearExprs.""" @@ -550,21 +608,21 @@ class _Sum(LinearExpr): def __init__(self, left, right): for x in [left, right]: if not cmh.is_a_number(x) and not isinstance(x, LinearExpr): - raise TypeError("Not an linear expression: " + str(x)) + raise TypeError("not an linear expression: " + str(x)) self.__left = left self.__right = right - def Left(self): + def left(self): return self.__left - def Right(self): + def right(self): return self.__right def __str__(self): return f"({self.__left} + {self.__right})" def __repr__(self): - return f"Sum({repr(self.__left)}, {repr(self.__right)})" + return f"sum({self.__left!r}, {self.__right!r})" class _ProductCst(LinearExpr): @@ -573,8 +631,8 @@ class _ProductCst(LinearExpr): def __init__(self, expr, coeff): coeff = cmh.assert_is_a_number(coeff) if isinstance(expr, _ProductCst): - self.__expr = expr.Expression() - self.__coef = expr.Coefficient() * coeff + self.__expr = expr.expression() + self.__coef = expr.coefficient() * coeff else: self.__expr = expr self.__coef = coeff @@ -586,12 +644,12 @@ def __str__(self): return "(" + str(self.__coef) + " * " + str(self.__expr) + ")" def __repr__(self): - return "ProductCst(" + repr(self.__expr) + ", " + repr(self.__coef) + ")" + return f"ProductCst({self.__expr!r}, {self.__coef!r})" - def Coefficient(self): + def coefficient(self): return self.__coef - def Expression(self): + def expression(self): return self.__expr @@ -610,7 +668,7 @@ def __init__(self, expressions, constant=0): elif isinstance(x, LinearExpr): self.__expressions.append(x) else: - raise TypeError("Not an linear expression: " + str(x)) + raise TypeError("not an linear expression: " + str(x)) def __str__(self): constant_terms = (self.__constant,) if self.__constant != 0 else () @@ -625,10 +683,10 @@ def __repr__(self): exprs_str = ", ".join(map(repr, self.__expressions)) return f"SumArray({exprs_str}, {self.__constant})" - def Expressions(self): + def expressions(self): return self.__expressions - def Constant(self): + def constant(self): return self.__constant @@ -641,7 +699,7 @@ def __init__(self, expressions, coefficients, constant=0): self.__constant = constant if len(expressions) != len(coefficients): raise TypeError( - "In the LinearExpr.WeightedSum method, the expression array and the " + "In the LinearExpr.weighted_sum method, the expression array and the " " coefficient array must have the same length." ) for e, c in zip(expressions, coefficients): @@ -655,7 +713,7 @@ def __init__(self, expressions, coefficients, constant=0): self.__expressions.append(e) self.__coefficients.append(c) else: - raise TypeError("Not an linear expression: " + str(e)) + raise TypeError("not an linear expression: " + str(e)) def __str__(self): output = None @@ -683,17 +741,18 @@ def __str__(self): return output def __repr__(self): - exprs_str = ", ".join(map(repr, self.__expressions)) - coeffs_str = ", ".join(map(repr, self.__coefficients)) - return f"WeightedSum([{exprs_str}], [{coeffs_str}], {self.__constant})" + return ( + f"weighted_sum({self.__expressions!r}, {self.__coefficients!r}," + f" {self.__constant})" + ) - def Expressions(self): + def expressions(self): return self.__expressions - def Coefficients(self): + def coefficients(self): return self.__coefficients - def Constant(self): + def constant(self): return self.__constant @@ -717,7 +776,7 @@ def __init__( domain: Union[int, Domain], name: Optional[str], ): - """See CpModel.NewIntVar below.""" + """See CpModel.new_int_var below.""" self.__negation: Optional[_NotBooleanVariable] = None # Python do not support multiple __init__ methods. # This method is only called from the CpModel class. @@ -732,22 +791,24 @@ def __init__( else: self.__index: int = len(model.variables) self.__var: cp_model_pb2.IntegerVariableProto = model.variables.add() - self.__var.domain.extend(cast(Domain, domain).FlattenedIntervals()) + self.__var.domain.extend(cast(Domain, domain).flattened_intervals()) self.__var.name = name - def Index(self) -> int: + @property + def index(self) -> int: """Returns the index of the variable in the model.""" return self.__index - def Proto(self) -> cp_model_pb2.IntegerVariableProto: + @property + def proto(self) -> cp_model_pb2.IntegerVariableProto: """Returns the variable protobuf.""" return self.__var - def IsEqualTo(self, other: ...) -> bool: + def is_equal_to(self, other: ...) -> bool: """Returns true if self == other in the python sense.""" if not isinstance(other, IntVar): return False - return self.Index() == other.Index() + return self.index == other.index def __str__(self) -> str: if not self.__var.name: @@ -762,29 +823,47 @@ def __str__(self) -> str: return self.__var.name def __repr__(self) -> str: - return "%s(%s)" % (self.__var.name, DisplayBounds(self.__var.domain)) + return "%s(%s)" % (self.__var.name, display_bounds(self.__var.domain)) - def Name(self) -> str: + @property + def name(self) -> str: if not self.__var or not self.__var.name: return "" return self.__var.name - def Not(self) -> "_NotBooleanVariable": + def negated(self) -> "_NotBooleanVariable": """Returns the negation of a Boolean variable. This method implements the logical negation of a Boolean variable. It is only valid if the variable has a Boolean domain (0 or 1). - Note that this method is nilpotent: `x.Not().Not() == x`. + Note that this method is nilpotent: `x.negated().negated() == x`. """ for bound in self.__var.domain: if bound < 0 or bound > 1: - raise TypeError("Cannot call Not on a non boolean variable: %s" % self) + raise TypeError( + f"cannot call negated on a non boolean variable: {self}" + ) if self.__negation is None: self.__negation = _NotBooleanVariable(self) return self.__negation + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + Not = negated + + def Name(self) -> str: + return self.name + + def Proto(self) -> cp_model_pb2.IntegerVariableProto: + return self.proto + + def Index(self) -> int: + return self.index + + # pylint: enable=invalid-name + class _NotBooleanVariable(LinearExpr): """Negation of a boolean variable.""" @@ -792,13 +871,18 @@ class _NotBooleanVariable(LinearExpr): def __init__(self, boolvar: IntVar): self.__boolvar: IntVar = boolvar - def Index(self) -> int: - return -self.__boolvar.Index() - 1 + @property + def index(self) -> int: + return -self.__boolvar.index - 1 - def Not(self) -> IntVar: + def negated(self) -> IntVar: return self.__boolvar def __str__(self) -> str: + return self.name + + @property + def name(self) -> str: return "not(%s)" % str(self.__boolvar) def __bool__(self) -> bool: @@ -806,14 +890,24 @@ def __bool__(self) -> bool: "Evaluating a literal as a Boolean value is not implemented." ) + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + def Not(self) -> "IntVar": + return self.negated() + + def Index(self) -> int: + return self.index + + # pylint: enable=invalid-name + class BoundedLinearExpression: """Represents a linear constraint: `lb <= linear expression <= ub`. The only use of this class is to be added to the CpModel through - `CpModel.Add(expression)`, as in: + `CpModel.add(expression)`, as in: - model.Add(x + 2 * y -1 >= z) + model.add(x + 2 * y -1 >= z) """ def __init__(self, expr: LinearExprT, bounds: Sequence[int]): @@ -842,17 +936,17 @@ def __str__(self): ): return str(self.__expr) + " != " + str(self.__bounds[1] + 1) else: - return str(self.__expr) + " in [" + DisplayBounds(self.__bounds) + "]" + return str(self.__expr) + " in [" + display_bounds(self.__bounds) + "]" - def Expression(self) -> LinearExprT: + def expression(self) -> LinearExprT: return self.__expr - def Bounds(self) -> Sequence[int]: + def bounds(self) -> Sequence[int]: return self.__bounds def __bool__(self) -> bool: if isinstance(self.__expr, LinearExpr): - coeffs_map, constant = self.__expr.GetIntegerVarValueMap() + coeffs_map, constant = self.__expr.get_integer_var_value_map() all_coeffs = set(coeffs_map.values()) same_var = set([0]) eq_bounds = [0, 0] @@ -882,37 +976,37 @@ def __bool__(self) -> bool: class Constraint: """Base class for constraints. - Constraints are built by the CpModel through the Add methods. + Constraints are built by the CpModel through the add methods. Once created by the CpModel class, they are automatically added to the model. The purpose of this class is to allow specification of enforcement literals for this constraint. - b = model.NewBoolVar('b') - x = model.NewIntVar(0, 10, 'x') - y = model.NewIntVar(0, 10, 'y') + b = model.new_bool_var('b') + x = model.new_int_var(0, 10, 'x') + y = model.new_int_var(0, 10, 'y') - model.Add(x + 2 * y == 5).OnlyEnforceIf(b.Not()) + model.add(x + 2 * y == 5).only_enforce_if(b.negated()) """ def __init__( self, cp_model: "CpModel", ): - self.__index: int = len(cp_model.Proto().constraints) + self.__index: int = len(cp_model.proto.constraints) self.__cp_model: "CpModel" = cp_model self.__constraint: cp_model_pb2.ConstraintProto = ( - cp_model.Proto().constraints.add() + cp_model.proto.constraints.add() ) @overload - def OnlyEnforceIf(self, boolvar: Iterable[LiteralT]) -> "Constraint": + def only_enforce_if(self, boolvar: Iterable[LiteralT]) -> "Constraint": ... @overload - def OnlyEnforceIf(self, *boolvar: LiteralT) -> "Constraint": + def only_enforce_if(self, *boolvar: LiteralT) -> "Constraint": ... - def OnlyEnforceIf(self, *boolvar) -> "Constraint": + def only_enforce_if(self, *boolvar) -> "Constraint": """Adds an enforcement literal to the constraint. This method adds one or more literals (that is, a boolean variable or its @@ -929,7 +1023,7 @@ def OnlyEnforceIf(self, *boolvar) -> "Constraint": Returns: self. """ - for lit in ExpandGeneratorOrTuple(boolvar): + for lit in expand_generator_or_tuple(boolvar): if (isinstance(lit, bool) and lit) or (cmh.is_integral(lit) and lit == 1): # Always true. Do nothing. pass @@ -937,15 +1031,15 @@ def OnlyEnforceIf(self, *boolvar) -> "Constraint": cmh.is_integral(lit) and lit == 0 ): self.__constraint.enforcement_literal.append( - self.__cp_model.NewConstant(0).Index() + self.__cp_model.new_constant(0).index ) else: self.__constraint.enforcement_literal.append( - cast(Union[IntVar, _NotBooleanVariable], lit).Index() + cast(Union[IntVar, _NotBooleanVariable], lit).index ) return self - def WithName(self, name: str) -> "Constraint": + def with_name(self, name: str) -> "Constraint": """Sets the name of the constraint.""" if name: self.__constraint.name = name @@ -953,20 +1047,39 @@ def WithName(self, name: str) -> "Constraint": self.__constraint.ClearField("name") return self - def Name(self) -> str: + @property + def name(self) -> str: """Returns the name of the constraint.""" if not self.__constraint or not self.__constraint.name: return "" return self.__constraint.name - def Index(self) -> int: + @property + def index(self) -> int: """Returns the index of the constraint in the model.""" return self.__index - def Proto(self) -> cp_model_pb2.ConstraintProto: + @property + def proto(self) -> cp_model_pb2.ConstraintProto: """Returns the constraint protobuf.""" return self.__constraint + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + OnlyEnforceIf = only_enforce_if + WithName = with_name + + def Name(self) -> str: + return self.name + + def Index(self) -> int: + return self.index + + def Proto(self) -> cp_model_pb2.ConstraintProto: + return self.proto + + # pylint: enable=invalid-name + class IntervalVar: """Represents an Interval variable. @@ -1017,11 +1130,13 @@ def __init__( if name: self.__ct.name = name - def Index(self) -> int: + @property + def index(self) -> int: """Returns the index of the interval constraint in the model.""" return self.__index - def Proto(self) -> cp_model_pb2.IntervalConstraintProto: + @property + def proto(self) -> cp_model_pb2.IntervalConstraintProto: """Returns the interval protobuf.""" return self.__ct.interval @@ -1033,60 +1148,78 @@ def __repr__(self): if self.__ct.enforcement_literal: return "%s(start = %s, size = %s, end = %s, is_present = %s)" % ( self.__ct.name, - ShortExprName(self.__model, interval.start), - ShortExprName(self.__model, interval.size), - ShortExprName(self.__model, interval.end), - ShortName(self.__model, self.__ct.enforcement_literal[0]), + short_expr_name(self.__model, interval.start), + short_expr_name(self.__model, interval.size), + short_expr_name(self.__model, interval.end), + short_name(self.__model, self.__ct.enforcement_literal[0]), ) else: return "%s(start = %s, size = %s, end = %s)" % ( self.__ct.name, - ShortExprName(self.__model, interval.start), - ShortExprName(self.__model, interval.size), - ShortExprName(self.__model, interval.end), + short_expr_name(self.__model, interval.start), + short_expr_name(self.__model, interval.size), + short_expr_name(self.__model, interval.end), ) - def Name(self) -> str: + @property + def name(self) -> str: if not self.__ct or not self.__ct.name: return "" return self.__ct.name - def StartExpr(self) -> LinearExprT: - return LinearExpr.RebuildFromLinearExpressionProto( + def start_expr(self) -> LinearExprT: + return LinearExpr.rebuild_from_linear_expression_proto( self.__model, self.__ct.interval.start ) - def SizeExpr(self) -> LinearExprT: - return LinearExpr.RebuildFromLinearExpressionProto( + def size_expr(self) -> LinearExprT: + return LinearExpr.rebuild_from_linear_expression_proto( self.__model, self.__ct.interval.size ) - def EndExpr(self) -> LinearExprT: - return LinearExpr.RebuildFromLinearExpressionProto( + def end_expr(self) -> LinearExprT: + return LinearExpr.rebuild_from_linear_expression_proto( self.__model, self.__ct.interval.end ) + # Pre PEP8 compatibility. + # pylint: disable=invalid-name + def Name(self) -> str: + return self.name -def ObjectIsATrueLiteral(literal: LiteralT) -> bool: + def Index(self) -> int: + return self.index + + def Proto(self) -> cp_model_pb2.IntervalConstraintProto: + return self.proto + + StartExpr = start_expr + SizeExpr = size_expr + EndExpr = end_expr + + # pylint: enable=invalid-name + + +def object_is_a_true_literal(literal: LiteralT) -> bool: """Checks if literal is either True, or a Boolean literals fixed to True.""" if isinstance(literal, IntVar): - proto = literal.Proto() + proto = literal.proto return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 if isinstance(literal, _NotBooleanVariable): - proto = literal.Not().Proto() + proto = literal.negated().proto return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 if cmh.is_integral(literal): return int(literal) == 1 return False -def ObjectIsAFalseLiteral(literal: LiteralT) -> bool: +def object_is_a_false_literal(literal: LiteralT) -> bool: """Checks if literal is either False, or a Boolean literals fixed to False.""" if isinstance(literal, IntVar): - proto = literal.Proto() + proto = literal.proto return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 if isinstance(literal, _NotBooleanVariable): - proto = literal.Not().Proto() + proto = literal.negated().proto return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 if cmh.is_integral(literal): return int(literal) == 0 @@ -1099,7 +1232,7 @@ class CpModel: Methods beginning with: * ```New``` create integer, boolean, or interval variables. - * ```Add``` create new constraints and add them to the model. + * ```add``` create new constraints and add them to the model. """ def __init__(self): @@ -1107,19 +1240,21 @@ def __init__(self): self.__constant_map = {} # Naming. - def Name(self) -> str: + @property + def name(self) -> str: """Returns the name of the model.""" if not self.__model or not self.__model.name: return "" return self.__model.name - def SetName(self, name: str): + @name.setter + def name(self, name: str): """Sets the name of the model.""" self.__model.name = name # Integer variable. - def NewIntVar(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: + def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: """Create an integer variable with domain [lb, ub]. The CP-SAT solver is limited to integer variables. If you have fractional @@ -1137,12 +1272,12 @@ def NewIntVar(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: return IntVar(self.__model, Domain(lb, ub), name) - def NewIntVarFromDomain(self, domain: Domain, name: str) -> IntVar: + def new_int_var_from_domain(self, domain: Domain, name: str) -> IntVar: """Create an integer variable from a domain. A domain is a set of integers specified by a collection of intervals. - For example, `model.NewIntVarFromDomain(cp_model. - Domain.FromIntervals([[1, 2], [4, 6]]), 'x')` + For example, `model.new_int_var_from_domain(cp_model. + Domain.from_intervals([[1, 2], [4, 6]]), 'x')` Args: domain: An instance of the Domain class. @@ -1153,15 +1288,15 @@ def NewIntVarFromDomain(self, domain: Domain, name: str) -> IntVar: """ return IntVar(self.__model, domain, name) - def NewBoolVar(self, name: str) -> IntVar: + def new_bool_var(self, name: str) -> IntVar: """Creates a 0-1 variable with the given name.""" return IntVar(self.__model, Domain(0, 1), name) - def NewConstant(self, value: IntegralT) -> IntVar: + def new_constant(self, value: IntegralT) -> IntVar: """Declares a constant integer.""" - return IntVar(self.__model, self.GetOrMakeIndexFromConstant(value), None) + return IntVar(self.__model, self.get_or_make_index_from_constant(value), None) - def NewIntVarSeries( + def new_int_var_series( self, name: str, index: pd.Index, @@ -1204,8 +1339,12 @@ def NewIntVarSeries( f" upper_bound={upper_bounds} for variable set={name}" ) - lower_bounds = _ConvertToIntegralSeriesAndValidateIndex(lower_bounds, index) - upper_bounds = _ConvertToIntegralSeriesAndValidateIndex(upper_bounds, index) + lower_bounds = _convert_to_integral_series_and_validate_index( + lower_bounds, index + ) + upper_bounds = _convert_to_integral_series_and_validate_index( + upper_bounds, index + ) return pd.Series( index=index, data=[ @@ -1219,7 +1358,7 @@ def NewIntVarSeries( ], ) - def NewBoolVarSeries( + def new_bool_var_series( self, name: str, index: pd.Index, @@ -1237,53 +1376,53 @@ def NewBoolVarSeries( TypeError: if the `index` is invalid (e.g. a `DataFrame`). ValueError: if the `name` is not a valid identifier or already exists. """ - return self.NewIntVarSeries( + return self.new_int_var_series( name=name, index=index, lower_bounds=0, upper_bounds=1 ) # Linear constraints. - def AddLinearConstraint( + def add_linear_constraint( self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT ) -> Constraint: """Adds the constraint: `lb <= linear_expr <= ub`.""" - return self.AddLinearExpressionInDomain(linear_expr, Domain(lb, ub)) + return self.add_linear_expression_in_domain(linear_expr, Domain(lb, ub)) - def AddLinearExpressionInDomain( + def add_linear_expression_in_domain( self, linear_expr: LinearExprT, domain: Domain ) -> Constraint: """Adds the constraint: `linear_expr` in `domain`.""" if isinstance(linear_expr, LinearExpr): ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - coeffs_map, constant = linear_expr.GetIntegerVarValueMap() + model_ct = self.__model.constraints[ct.index] + coeffs_map, constant = linear_expr.get_integer_var_value_map() for t in coeffs_map.items(): if not isinstance(t[0], IntVar): raise TypeError("Wrong argument" + str(t)) c = cmh.assert_is_int64(t[1]) - model_ct.linear.vars.append(t[0].Index()) + model_ct.linear.vars.append(t[0].index) model_ct.linear.coeffs.append(c) model_ct.linear.domain.extend( [ cmh.capped_subtraction(x, constant) - for x in domain.FlattenedIntervals() + for x in domain.flattened_intervals() ] ) return ct elif cmh.is_integral(linear_expr): - if not domain.Contains(int(linear_expr)): - return self.AddBoolOr([]) # Evaluate to false. + if not domain.contains(int(linear_expr)): + return self.add_bool_or([]) # Evaluate to false. else: - return self.AddBoolAnd([]) # Evaluate to true. + return self.add_bool_and([]) # Evaluate to true. raise TypeError( - "Not supported: CpModel.AddLinearExpressionInDomain(" + "not supported: CpModel.add_linear_expression_in_domain(" + str(linear_expr) + " " + str(domain) + ")" ) - def Add(self, ct: Union[BoundedLinearExpression, bool]) -> Constraint: + def add(self, ct: Union[BoundedLinearExpression, bool]) -> Constraint: """Adds a `BoundedLinearExpression` to the model. Args: @@ -1293,26 +1432,26 @@ def Add(self, ct: Union[BoundedLinearExpression, bool]) -> Constraint: An instance of the `Constraint` class. """ if isinstance(ct, BoundedLinearExpression): - return self.AddLinearExpressionInDomain( - ct.Expression(), Domain.FromFlatIntervals(ct.Bounds()) + return self.add_linear_expression_in_domain( + ct.expression(), Domain.from_flat_intervals(ct.bounds()) ) elif ct and isinstance(ct, bool): - return self.AddBoolOr([True]) + return self.add_bool_or([True]) elif not ct and isinstance(ct, bool): - return self.AddBoolOr([]) # Evaluate to false. - raise TypeError("Not supported: CpModel.Add(" + str(ct) + ")") + return self.add_bool_or([]) # Evaluate to false. + raise TypeError("not supported: CpModel.add(" + str(ct) + ")") # General Integer Constraints. @overload - def AddAllDifferent(self, expressions: Iterable[LinearExprT]) -> Constraint: + def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ... @overload - def AddAllDifferent(self, *expressions: LinearExprT) -> Constraint: + def add_all_different(self, *expressions: LinearExprT) -> Constraint: ... - def AddAllDifferent(self, *expressions): + def add_all_different(self, *expressions): """Adds AllDifferent(expressions). This constraint forces all expressions to have different values. @@ -1324,14 +1463,14 @@ def AddAllDifferent(self, *expressions): An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - expanded = ExpandGeneratorOrTuple(expressions) + model_ct = self.__model.constraints[ct.index] + expanded = expand_generator_or_tuple(expressions) model_ct.all_diff.exprs.extend( - [self.ParseLinearExpression(x) for x in expanded] + self.parse_linear_expression(x) for x in expanded ) return ct - def AddElement( + def add_element( self, index: VariableT, variables: Sequence[VariableT], target: VariableT ) -> Constraint: """Adds the element constraint: `variables[index] == target`. @@ -1346,19 +1485,19 @@ def AddElement( """ if not variables: - raise ValueError("AddElement expects a non-empty variables array") + raise ValueError("add_element expects a non-empty variables array") if cmh.is_integral(index): - return self.Add(list(variables)[int(index)] == target) + return self.add(list(variables)[int(index)] == target) ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.element.index = self.GetOrMakeIndex(index) - model_ct.element.vars.extend([self.GetOrMakeIndex(x) for x in variables]) - model_ct.element.target = self.GetOrMakeIndex(target) + model_ct = self.__model.constraints[ct.index] + model_ct.element.index = self.get_or_make_index(index) + model_ct.element.vars.extend([self.get_or_make_index(x) for x in variables]) + model_ct.element.target = self.get_or_make_index(target) return ct - def AddCircuit(self, arcs: Sequence[ArcT]) -> Constraint: + def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint: """Adds Circuit(arcs). Adds a circuit constraint from a sparse list of arcs that encode the graph. @@ -1381,19 +1520,19 @@ def AddCircuit(self, arcs: Sequence[ArcT]) -> Constraint: ValueError: If the list of arcs is empty. """ if not arcs: - raise ValueError("AddCircuit expects a non-empty array of arcs") + raise ValueError("add_circuit expects a non-empty array of arcs") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] for arc in arcs: tail = cmh.assert_is_int32(arc[0]) head = cmh.assert_is_int32(arc[1]) - lit = self.GetOrMakeBooleanIndex(arc[2]) + lit = self.get_or_make_boolean_index(arc[2]) model_ct.circuit.tails.append(tail) model_ct.circuit.heads.append(head) model_ct.circuit.literals.append(lit) return ct - def AddMultipleCircuit(self, arcs: Sequence[ArcT]) -> Constraint: + def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint: """Adds a multiple circuit constraint, aka the 'VRP' constraint. The direct graph where arc #i (from tails[i] to head[i]) is present iff @@ -1418,19 +1557,19 @@ def AddMultipleCircuit(self, arcs: Sequence[ArcT]) -> Constraint: ValueError: If the list of arcs is empty. """ if not arcs: - raise ValueError("AddMultipleCircuit expects a non-empty array of arcs") + raise ValueError("add_multiple_circuit expects a non-empty array of arcs") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] for arc in arcs: tail = cmh.assert_is_int32(arc[0]) head = cmh.assert_is_int32(arc[1]) - lit = self.GetOrMakeBooleanIndex(arc[2]) + lit = self.get_or_make_boolean_index(arc[2]) model_ct.routes.tails.append(tail) model_ct.routes.heads.append(head) model_ct.routes.literals.append(lit) return ct - def AddAllowedAssignments( + def add_allowed_assignments( self, variables: Sequence[VariableT], tuples_list: Iterable[Sequence[IntegralT]], @@ -1458,12 +1597,12 @@ def AddAllowedAssignments( if not variables: raise ValueError( - "AddAllowedAssignments expects a non-empty variables array" + "add_allowed_assignments expects a non-empty variables array" ) ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.table.vars.extend([self.GetOrMakeIndex(x) for x in variables]) + model_ct = self.__model.constraints[ct.index] + model_ct.table.vars.extend([self.get_or_make_index(x) for x in variables]) arity = len(variables) for t in tuples_list: if len(t) != arity: @@ -1474,12 +1613,12 @@ def AddAllowedAssignments( model_ct.table.values.extend(ar) return ct - def AddForbiddenAssignments( + def add_forbidden_assignments( self, variables: Sequence[VariableT], tuples_list: Iterable[Sequence[IntegralT]], ) -> Constraint: - """Adds AddForbiddenAssignments(variables, [tuples_list]). + """Adds add_forbidden_assignments(variables, [tuples_list]). A ForbiddenAssignments constraint is a constraint on an array of variables where the list of impossible combinations is provided in the tuples list. @@ -1501,15 +1640,15 @@ def AddForbiddenAssignments( if not variables: raise ValueError( - "AddForbiddenAssignments expects a non-empty variables array" + "add_forbidden_assignments expects a non-empty variables array" ) index = len(self.__model.constraints) - ct = self.AddAllowedAssignments(variables, tuples_list) + ct = self.add_allowed_assignments(variables, tuples_list) self.__model.constraints[index].table.negated = True return ct - def AddAutomaton( + def add_automaton( self, transition_variables: Sequence[VariableT], starting_state: IntegralT, @@ -1558,18 +1697,18 @@ def AddAutomaton( if not transition_variables: raise ValueError( - "AddAutomaton expects a non-empty transition_variables array" + "add_automaton expects a non-empty transition_variables array" ) if not final_states: - raise ValueError("AddAutomaton expects some final states") + raise ValueError("add_automaton expects some final states") if not transition_triples: - raise ValueError("AddAutomaton expects some transition triples") + raise ValueError("add_automaton expects some transition triples") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.automaton.vars.extend( - [self.GetOrMakeIndex(x) for x in transition_variables] + [self.get_or_make_index(x) for x in transition_variables] ) starting_state = cmh.assert_is_int64(starting_state) model_ct.automaton.starting_state = starting_state @@ -1587,7 +1726,7 @@ def AddAutomaton( model_ct.automaton.transition_head.append(head) return ct - def AddInverse( + def add_inverse( self, variables: Sequence[VariableT], inverse_variables: Sequence[VariableT], @@ -1617,14 +1756,14 @@ def AddInverse( " inverse_variables must have the same length." ) ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.inverse.f_direct.extend([self.GetOrMakeIndex(x) for x in variables]) + model_ct = self.__model.constraints[ct.index] + model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables]) model_ct.inverse.f_inverse.extend( - [self.GetOrMakeIndex(x) for x in inverse_variables] + [self.get_or_make_index(x) for x in inverse_variables] ) return ct - def AddReservoirConstraint( + def add_reservoir_constraint( self, times: Iterable[LinearExprT], level_changes: Iterable[LinearExprT], @@ -1676,18 +1815,18 @@ def AddReservoirConstraint( raise ValueError("Reservoir constraint must have a min_level <= 0") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.reservoir.time_exprs.extend( - [self.ParseLinearExpression(x) for x in times] + [self.parse_linear_expression(x) for x in times] ) model_ct.reservoir.level_changes.extend( - [self.ParseLinearExpression(x) for x in level_changes] + [self.parse_linear_expression(x) for x in level_changes] ) model_ct.reservoir.min_level = min_level model_ct.reservoir.max_level = max_level return ct - def AddReservoirConstraintWithActive( + def add_reservoir_constraint_with_active( self, times: Iterable[LinearExprT], level_changes: Iterable[LinearExprT], @@ -1749,28 +1888,28 @@ def AddReservoirConstraintWithActive( raise ValueError("Reservoir constraint must have a min_level <= 0") ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.reservoir.time_exprs.extend( - [self.ParseLinearExpression(x) for x in times] + [self.parse_linear_expression(x) for x in times] ) model_ct.reservoir.level_changes.extend( - [self.ParseLinearExpression(x) for x in level_changes] + [self.parse_linear_expression(x) for x in level_changes] ) model_ct.reservoir.active_literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in actives] + [self.get_or_make_boolean_index(x) for x in actives] ) model_ct.reservoir.min_level = min_level model_ct.reservoir.max_level = max_level return ct - def AddMapDomain( + def add_map_domain( self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0 ): """Adds `var == i + offset <=> bool_var_array[i] == true for all i`.""" for i, bool_var in enumerate(bool_var_array): - b_index = bool_var.Index() - var_index = var.Index() + b_index = bool_var.index + var_index = var.index model_ct = self.__model.constraints.add() model_ct.linear.vars.append(var_index) model_ct.linear.coeffs.append(1) @@ -1786,107 +1925,119 @@ def AddMapDomain( if offset + i + 1 <= INT_MAX: model_ct.linear.domain.extend([offset + i + 1, INT_MAX]) - def AddImplication(self, a: LiteralT, b: LiteralT) -> Constraint: + def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint: """Adds `a => b` (`a` implies `b`).""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.bool_or.literals.append(self.GetOrMakeBooleanIndex(b)) - model_ct.enforcement_literal.append(self.GetOrMakeBooleanIndex(a)) + model_ct = self.__model.constraints[ct.index] + model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b)) + model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a)) return ct @overload - def AddBoolOr(self, literals: Iterable[LiteralT]) -> Constraint: + def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddBoolOr(self, *literals: LiteralT) -> Constraint: + def add_bool_or(self, *literals: LiteralT) -> Constraint: ... - def AddBoolOr(self, *literals): - """Adds `Or(literals) == true`: Sum(literals) >= 1.""" + def add_bool_or(self, *literals): + """Adds `Or(literals) == true`: sum(literals) >= 1.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.bool_or.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddAtLeastOne(self, literals: Iterable[LiteralT]) -> Constraint: + def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddAtLeastOne(self, *literals: LiteralT) -> Constraint: + def add_at_least_one(self, *literals: LiteralT) -> Constraint: ... - def AddAtLeastOne(self, *literals): - """Same as `AddBoolOr`: `Sum(literals) >= 1`.""" - return self.AddBoolOr(*literals) + def add_at_least_one(self, *literals): + """Same as `add_bool_or`: `sum(literals) >= 1`.""" + return self.add_bool_or(*literals) @overload - def AddAtMostOne(self, literals: Iterable[LiteralT]) -> Constraint: + def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddAtMostOne(self, *literals: LiteralT) -> Constraint: + def add_at_most_one(self, *literals: LiteralT) -> Constraint: ... - def AddAtMostOne(self, *literals): - """Adds `AtMostOne(literals)`: `Sum(literals) <= 1`.""" + def add_at_most_one(self, *literals): + """Adds `AtMostOne(literals)`: `sum(literals) <= 1`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.at_most_one.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddExactlyOne(self, literals: Iterable[LiteralT]) -> Constraint: + def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddExactlyOne(self, *literals: LiteralT) -> Constraint: + def add_exactly_one(self, *literals: LiteralT) -> Constraint: ... - def AddExactlyOne(self, *literals): - """Adds `ExactlyOne(literals)`: `Sum(literals) == 1`.""" + def add_exactly_one(self, *literals): + """Adds `ExactlyOne(literals)`: `sum(literals) == 1`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.exactly_one.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddBoolAnd(self, literals: Iterable[LiteralT]) -> Constraint: + def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddBoolAnd(self, *literals: LiteralT) -> Constraint: + def add_bool_and(self, *literals: LiteralT) -> Constraint: ... - def AddBoolAnd(self, *literals): + def add_bool_and(self, *literals): """Adds `And(literals) == true`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.bool_and.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct @overload - def AddBoolXOr(self, literals: Iterable[LiteralT]) -> Constraint: + def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ... @overload - def AddBoolXOr(self, *literals: LiteralT) -> Constraint: + def add_bool_xor(self, *literals: LiteralT) -> Constraint: ... - def AddBoolXOr(self, *literals): + def add_bool_xor(self, *literals): """Adds `XOr(literals) == true`. - In contrast to AddBoolOr and AddBoolAnd, it does not support - .OnlyEnforceIf(). + In contrast to add_bool_or and add_bool_and, it does not support + .only_enforce_if(). Args: *literals: the list of literals in the constraint. @@ -1895,85 +2046,88 @@ def AddBoolXOr(self, *literals): An `Constraint` object. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.bool_xor.literals.extend( - [self.GetOrMakeBooleanIndex(x) for x in ExpandGeneratorOrTuple(literals)] + [ + self.get_or_make_boolean_index(x) + for x in expand_generator_or_tuple(literals) + ] ) return ct - def AddMinEquality( + def add_min_equality( self, target: LinearExprT, exprs: Iterable[LinearExprT] ) -> Constraint: """Adds `target == Min(exprs)`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.lin_max.exprs.extend( - [self.ParseLinearExpression(x, True) for x in exprs] + [self.parse_linear_expression(x, True) for x in exprs] ) - model_ct.lin_max.target.CopyFrom(self.ParseLinearExpression(target, True)) + model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True)) return ct - def AddMaxEquality( + def add_max_equality( self, target: LinearExprT, exprs: Iterable[LinearExprT] ) -> Constraint: """Adds `target == Max(exprs)`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.lin_max.exprs.extend([self.ParseLinearExpression(x) for x in exprs]) - model_ct.lin_max.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs]) + model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddDivisionEquality( + def add_division_equality( self, target: LinearExprT, num: LinearExprT, denom: LinearExprT ) -> Constraint: """Adds `target == num // denom` (integer division rounded towards 0).""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.int_div.exprs.append(self.ParseLinearExpression(num)) - model_ct.int_div.exprs.append(self.ParseLinearExpression(denom)) - model_ct.int_div.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.int_div.exprs.append(self.parse_linear_expression(num)) + model_ct.int_div.exprs.append(self.parse_linear_expression(denom)) + model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddAbsEquality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: + def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: """Adds `target == Abs(var)`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.lin_max.exprs.append(self.ParseLinearExpression(expr)) - model_ct.lin_max.exprs.append(self.ParseLinearExpression(expr, True)) - model_ct.lin_max.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.lin_max.exprs.append(self.parse_linear_expression(expr)) + model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True)) + model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddModuloEquality( + def add_modulo_equality( self, target: LinearExprT, var: LinearExprT, mod: LinearExprT ) -> Constraint: """Adds `target = var % mod`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] - model_ct.int_mod.exprs.append(self.ParseLinearExpression(var)) - model_ct.int_mod.exprs.append(self.ParseLinearExpression(mod)) - model_ct.int_mod.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct = self.__model.constraints[ct.index] + model_ct.int_mod.exprs.append(self.parse_linear_expression(var)) + model_ct.int_mod.exprs.append(self.parse_linear_expression(mod)) + model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target)) return ct - def AddMultiplicationEquality( + def add_multiplication_equality( self, target: LinearExprT, *expressions: Union[Iterable[LinearExprT], LinearExprT], ) -> Constraint: """Adds `target == expressions[0] * .. * expressions[n]`.""" ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.int_prod.exprs.extend( [ - self.ParseLinearExpression(expr) - for expr in ExpandGeneratorOrTuple(expressions) + self.parse_linear_expression(expr) + for expr in expand_generator_or_tuple(expressions) ] ) - model_ct.int_prod.target.CopyFrom(self.ParseLinearExpression(target)) + model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target)) return ct # Scheduling support - def NewIntervalVar( + def new_interval_var( self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str ) -> IntervalVar: """Creates an interval variable from start, size, and end. @@ -1995,22 +2149,26 @@ def NewIntervalVar( An `IntervalVar` object. """ - self.Add(start + size == end) + self.add(start + size == end) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(end) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(end) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) if len(size_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: size must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: size must be affine or constant." + ) if len(end_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: end must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: end must be affine or constant." + ) return IntervalVar(self.__model, start_expr, size_expr, end_expr, None, name) - def NewIntervalVarSeries( + def new_interval_var_series( self, name: str, index: pd.Index, @@ -2047,13 +2205,13 @@ def NewIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToLinearExprSeriesAndValidateIndex(sizes, index) - ends = _ConvertToLinearExprSeriesAndValidateIndex(ends, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index) + ends = _convert_to_linear_expr_series_and_validate_index(ends, index) interval_array = [] for i in index: interval_array.append( - self.NewIntervalVar( + self.new_interval_var( start=starts[i], size=sizes[i], end=ends[i], @@ -2062,7 +2220,7 @@ def NewIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def NewFixedSizeIntervalVar( + def new_fixed_size_interval_var( self, start: LinearExprT, size: IntegralT, name: str ) -> IntervalVar: """Creates an interval variable from start, and a fixed size. @@ -2080,16 +2238,16 @@ def NewFixedSizeIntervalVar( An `IntervalVar` object. """ size = cmh.assert_is_int64(size) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(start + size) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(start + size) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) return IntervalVar(self.__model, start_expr, size_expr, end_expr, None, name) - def NewFixedSizeIntervalVarSeries( + def new_fixed_size_interval_var_series( self, name: str, index: pd.Index, @@ -2122,12 +2280,12 @@ def NewFixedSizeIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToIntegralSeriesAndValidateIndex(sizes, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_integral_series_and_validate_index(sizes, index) interval_array = [] for i in index: interval_array.append( - self.NewFixedSizeIntervalVar( + self.new_fixed_size_interval_var( start=starts[i], size=sizes[i], name=f"{name}[{i}]", @@ -2135,7 +2293,7 @@ def NewFixedSizeIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def NewOptionalIntervalVar( + def new_optional_interval_var( self, start: LinearExprT, size: LinearExprT, @@ -2167,27 +2325,31 @@ def NewOptionalIntervalVar( An `IntervalVar` object. """ - # Add the linear constraint. - self.Add(start + size == end).OnlyEnforceIf(is_present) + # add the linear constraint. + self.add(start + size == end).only_enforce_if(is_present) # Creates the IntervalConstraintProto object. - is_present_index = self.GetOrMakeBooleanIndex(is_present) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(end) + is_present_index = self.get_or_make_boolean_index(is_present) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(end) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) if len(size_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: size must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: size must be affine or constant." + ) if len(end_expr.vars) > 1: - raise TypeError("cp_model.NewIntervalVar: end must be affine or constant.") + raise TypeError( + "cp_model.new_interval_var: end must be affine or constant." + ) return IntervalVar( self.__model, start_expr, size_expr, end_expr, is_present_index, name ) - def NewOptionalIntervalVarSeries( + def new_optional_interval_var_series( self, name: str, index: pd.Index, @@ -2228,15 +2390,15 @@ def NewOptionalIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToLinearExprSeriesAndValidateIndex(sizes, index) - ends = _ConvertToLinearExprSeriesAndValidateIndex(ends, index) - are_present = _ConvertToLiteralSeriesAndValidateIndex(are_present, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index) + ends = _convert_to_linear_expr_series_and_validate_index(ends, index) + are_present = _convert_to_literal_series_and_validate_index(are_present, index) interval_array = [] for i in index: interval_array.append( - self.NewOptionalIntervalVar( + self.new_optional_interval_var( start=starts[i], size=sizes[i], end=ends[i], @@ -2246,7 +2408,7 @@ def NewOptionalIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def NewOptionalFixedSizeIntervalVar( + def new_optional_fixed_size_interval_var( self, start: LinearExprT, size: IntegralT, @@ -2270,14 +2432,14 @@ def NewOptionalFixedSizeIntervalVar( An `IntervalVar` object. """ size = cmh.assert_is_int64(size) - start_expr = self.ParseLinearExpression(start) - size_expr = self.ParseLinearExpression(size) - end_expr = self.ParseLinearExpression(start + size) + start_expr = self.parse_linear_expression(start) + size_expr = self.parse_linear_expression(size) + end_expr = self.parse_linear_expression(start + size) if len(start_expr.vars) > 1: raise TypeError( - "cp_model.NewIntervalVar: start must be affine or constant." + "cp_model.new_interval_var: start must be affine or constant." ) - is_present_index = self.GetOrMakeBooleanIndex(is_present) + is_present_index = self.get_or_make_boolean_index(is_present) return IntervalVar( self.__model, start_expr, @@ -2287,7 +2449,7 @@ def NewOptionalFixedSizeIntervalVar( name, ) - def NewOptionalFixedSizeIntervalVarSeries( + def new_optional_fixed_size_interval_var_series( self, name: str, index: pd.Index, @@ -2324,13 +2486,13 @@ def NewOptionalFixedSizeIntervalVarSeries( if not name.isidentifier(): raise ValueError("name={} is not a valid identifier".format(name)) - starts = _ConvertToLinearExprSeriesAndValidateIndex(starts, index) - sizes = _ConvertToIntegralSeriesAndValidateIndex(sizes, index) - are_present = _ConvertToLiteralSeriesAndValidateIndex(are_present, index) + starts = _convert_to_linear_expr_series_and_validate_index(starts, index) + sizes = _convert_to_integral_series_and_validate_index(sizes, index) + are_present = _convert_to_literal_series_and_validate_index(are_present, index) interval_array = [] for i in index: interval_array.append( - self.NewOptionalFixedSizeIntervalVar( + self.new_optional_fixed_size_interval_var( start=starts[i], size=sizes[i], is_present=are_present[i], @@ -2339,7 +2501,7 @@ def NewOptionalFixedSizeIntervalVarSeries( ) return pd.Series(index=index, data=interval_array) - def AddNoOverlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint: + def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint: """Adds NoOverlap(interval_vars). A NoOverlap constraint ensures that all present intervals do not overlap @@ -2352,13 +2514,13 @@ def AddNoOverlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint: An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.no_overlap.intervals.extend( - [self.GetIntervalIndex(x) for x in interval_vars] + [self.get_interval_index(x) for x in interval_vars] ) return ct - def AddNoOverlap2D( + def add_no_overlap_2d( self, x_intervals: Iterable[IntervalVar], y_intervals: Iterable[IntervalVar], @@ -2380,16 +2542,16 @@ def AddNoOverlap2D( An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.no_overlap_2d.x_intervals.extend( - [self.GetIntervalIndex(x) for x in x_intervals] + [self.get_interval_index(x) for x in x_intervals] ) model_ct.no_overlap_2d.y_intervals.extend( - [self.GetIntervalIndex(x) for x in y_intervals] + [self.get_interval_index(x) for x in y_intervals] ) return ct - def AddCumulative( + def add_cumulative( self, intervals: Iterable[IntervalVar], demands: Iterable[LinearExprT], @@ -2415,59 +2577,63 @@ def AddCumulative( An instance of the `Constraint` class. """ ct = Constraint(self) - model_ct = self.__model.constraints[ct.Index()] + model_ct = self.__model.constraints[ct.index] model_ct.cumulative.intervals.extend( - [self.GetIntervalIndex(x) for x in intervals] + [self.get_interval_index(x) for x in intervals] ) for d in demands: - model_ct.cumulative.demands.append(self.ParseLinearExpression(d)) - model_ct.cumulative.capacity.CopyFrom(self.ParseLinearExpression(capacity)) + model_ct.cumulative.demands.append(self.parse_linear_expression(d)) + model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity)) return ct # Support for model cloning. - def Clone(self) -> "CpModel": + def clone(self) -> "CpModel": """Reset the model, and creates a new one from a CpModelProto instance.""" clone = CpModel() - clone.Proto().CopyFrom(self.Proto()) - clone.RebuildConstantMap() + clone.proto.CopyFrom(self.proto) + clone.rebuild_constant_map() return clone - def RebuildConstantMap(self): + def rebuild_constant_map(self): """Internal method used during model cloning.""" for i, var in enumerate(self.__model.variables): if len(var.domain) == 2 and var.domain[0] == var.domain[1]: self.__constant_map[var.domain[0]] = i - def GetBoolVarFromProtoIndex(self, index: int) -> IntVar: + def get_bool_var_from_proto_index(self, index: int) -> IntVar: """Returns an already created Boolean variable from its index.""" if index < 0 or index >= len(self.__model.variables): - raise ValueError(f"GetBoolVarFromProtoIndex: out of bound index {index}") + raise ValueError( + f"get_bool_var_from_proto_index: out of bound index {index}" + ) var = self.__model.variables[index] if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1: raise ValueError( - f"GetBoolVarFromProtoIndex: index {index} does not reference" + f"get_bool_var_from_proto_index: index {index} does not reference" + " a Boolean variable" ) return IntVar(self.__model, index, None) - def GetIntVarFromProtoIndex(self, index: int) -> IntVar: + def get_int_var_from_proto_index(self, index: int) -> IntVar: """Returns an already created integer variable from its index.""" if index < 0 or index >= len(self.__model.variables): - raise ValueError(f"GetIntVarFromProtoIndex: out of bound index {index}") + raise ValueError( + f"get_int_var_from_proto_index: out of bound index {index}" + ) return IntVar(self.__model, index, None) - def GetIntervalVarFromProtoIndex(self, index: int) -> IntervalVar: + def get_interval_var_from_proto_index(self, index: int) -> IntervalVar: """Returns an already created interval variable from its index.""" if index < 0 or index >= len(self.__model.constraints): raise ValueError( - f"GetIntervalVarFromProtoIndex: out of bound index {index}" + f"get_interval_var_from_proto_index: out of bound index {index}" ) ct = self.__model.constraints[index] if not ct.HasField("interval"): raise ValueError( - f"GetIntervalVarFromProtoIndex: index {index} does not reference an" - + " interval variable" + f"get_interval_var_from_proto_index: index {index} does not" + " reference an" + " interval variable" ) return IntervalVar(self.__model, index, None, None, None, None) @@ -2477,51 +2643,50 @@ def GetIntervalVarFromProtoIndex(self, index: int) -> IntervalVar: def __str__(self): return str(self.__model) - def Proto(self) -> cp_model_pb2.CpModelProto: + @property + def proto(self) -> cp_model_pb2.CpModelProto: """Returns the underlying CpModelProto.""" return self.__model - def Negated(self, index: int) -> int: + def negated(self, index: int) -> int: return -index - 1 - def GetOrMakeIndex(self, arg: VariableT) -> int: + def get_or_make_index(self, arg: VariableT) -> int: """Returns the index of a variable, its negation, or a number.""" if isinstance(arg, IntVar): - return arg.Index() + return arg.index elif ( isinstance(arg, _ProductCst) - and isinstance(arg.Expression(), IntVar) - and arg.Coefficient() == -1 + and isinstance(arg.expression(), IntVar) + and arg.coefficient() == -1 ): - return -arg.Expression().Index() - 1 + return -arg.expression().index - 1 elif cmh.is_integral(arg): arg = cmh.assert_is_int64(arg) - return self.GetOrMakeIndexFromConstant(arg) + return self.get_or_make_index_from_constant(arg) else: - raise TypeError("NotSupported: model.GetOrMakeIndex(" + str(arg) + ")") + raise TypeError("NotSupported: model.get_or_make_index(" + str(arg) + ")") - def GetOrMakeBooleanIndex(self, arg: LiteralT) -> int: + def get_or_make_boolean_index(self, arg: LiteralT) -> int: """Returns an index from a boolean expression.""" if isinstance(arg, IntVar): - self.AssertIsBooleanVariable(arg) - return arg.Index() + self.assert_is_boolean_variable(arg) + return arg.index elif isinstance(arg, _NotBooleanVariable): - self.AssertIsBooleanVariable(arg.Not()) - return arg.Index() + self.assert_is_boolean_variable(arg.negated()) + return arg.index elif cmh.is_integral(arg): cmh.assert_is_boolean(arg) - return self.GetOrMakeIndexFromConstant(int(arg)) + return self.get_or_make_index_from_constant(int(arg)) else: - raise TypeError( - "NotSupported: model.GetOrMakeBooleanIndex(" + str(arg) + ")" - ) + raise TypeError(f"not supported: model.get_or_make_boolean_index({arg})") - def GetIntervalIndex(self, arg: IntervalVar) -> int: + def get_interval_index(self, arg: IntervalVar) -> int: if not isinstance(arg, IntervalVar): - raise TypeError("NotSupported: model.GetIntervalIndex(%s)" % arg) - return arg.Index() + raise TypeError("NotSupported: model.get_interval_index(%s)" % arg) + return arg.index - def GetOrMakeIndexFromConstant(self, value: IntegralT) -> int: + def get_or_make_index_from_constant(self, value: IntegralT) -> int: if value in self.__constant_map: return self.__constant_map[value] index = len(self.__model.variables) @@ -2529,13 +2694,15 @@ def GetOrMakeIndexFromConstant(self, value: IntegralT) -> int: self.__constant_map[value] = index return index - def VarIndexToVarProto(self, var_index: int) -> cp_model_pb2.IntegerVariableProto: + def var_index_to_var_proto( + self, var_index: int + ) -> cp_model_pb2.IntegerVariableProto: if var_index >= 0: return self.__model.variables[var_index] else: return self.__model.variables[-var_index - 1] - def ParseLinearExpression( + def parse_linear_expression( self, linear_expr: LinearExprT, negate: bool = False ) -> cp_model_pb2.LinearExpressionProto: """Returns a LinearExpressionProto built from a LinearExpr instance.""" @@ -2548,34 +2715,34 @@ def ParseLinearExpression( return result if isinstance(linear_expr, IntVar): - result.vars.append(self.GetOrMakeIndex(linear_expr)) + result.vars.append(self.get_or_make_index(linear_expr)) result.coeffs.append(mult) return result - coeffs_map, constant = cast(LinearExpr, linear_expr).GetIntegerVarValueMap() + coeffs_map, constant = cast(LinearExpr, linear_expr).get_integer_var_value_map() result.offset = constant * mult for t in coeffs_map.items(): if not isinstance(t[0], IntVar): raise TypeError("Wrong argument" + str(t)) c = cmh.assert_is_int64(t[1]) - result.vars.append(t[0].Index()) + result.vars.append(t[0].index) result.coeffs.append(c * mult) return result - def _SetObjective(self, obj: ObjLinearExprT, minimize: bool): + def _set_objective(self, obj: ObjLinearExprT, minimize: bool): """Sets the objective of the model.""" - self.ClearObjective() + self.clear_objective() if isinstance(obj, IntVar): self.__model.objective.coeffs.append(1) self.__model.objective.offset = 0 if minimize: - self.__model.objective.vars.append(obj.Index()) + self.__model.objective.vars.append(obj.index) self.__model.objective.scaling_factor = 1 else: - self.__model.objective.vars.append(self.Negated(obj.Index())) + self.__model.objective.vars.append(self.negated(obj.index)) self.__model.objective.scaling_factor = -1 elif isinstance(obj, LinearExpr): - coeffs_map, constant, is_integer = obj.GetFloatVarValueMap() + coeffs_map, constant, is_integer = obj.get_float_var_value_map() if is_integer: if minimize: self.__model.objective.scaling_factor = 1 @@ -2586,39 +2753,39 @@ def _SetObjective(self, obj: ObjLinearExprT, minimize: bool): for v, c in coeffs_map.items(): self.__model.objective.coeffs.append(c) if minimize: - self.__model.objective.vars.append(v.Index()) + self.__model.objective.vars.append(v.index) else: - self.__model.objective.vars.append(self.Negated(v.Index())) + self.__model.objective.vars.append(self.negated(v.index)) else: self.__model.floating_point_objective.maximize = not minimize self.__model.floating_point_objective.offset = constant for v, c in coeffs_map.items(): self.__model.floating_point_objective.coeffs.append(c) - self.__model.floating_point_objective.vars.append(v.Index()) + self.__model.floating_point_objective.vars.append(v.index) elif cmh.is_integral(obj): self.__model.objective.offset = int(obj) self.__model.objective.scaling_factor = 1 else: raise TypeError("TypeError: " + str(obj) + " is not a valid objective") - def Minimize(self, obj: ObjLinearExprT): + def minimize(self, obj: ObjLinearExprT): """Sets the objective of the model to minimize(obj).""" - self._SetObjective(obj, minimize=True) + self._set_objective(obj, minimize=True) - def Maximize(self, obj: ObjLinearExprT): + def maximize(self, obj: ObjLinearExprT): """Sets the objective of the model to maximize(obj).""" - self._SetObjective(obj, minimize=False) + self._set_objective(obj, minimize=False) - def HasObjective(self) -> bool: + def has_objective(self) -> bool: return self.__model.HasField("objective") or self.__model.HasField( "floating_point_objective" ) - def ClearObjective(self): + def clear_objective(self): self.__model.ClearField("objective") self.__model.ClearField("floating_point_objective") - def AddDecisionStrategy( + def add_decision_strategy( self, variables: Sequence[IntVar], var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy, @@ -2632,24 +2799,24 @@ def AddDecisionStrategy( domain_strategy: heuristic to reduce the domain of the selected variable. Currently, this is advanced code: the union of all strategies added to the model must be complete, i.e. instantiates all variables. Otherwise, - Solve() will fail. + solve() will fail. """ strategy = self.__model.search_strategy.add() for v in variables: - strategy.variables.append(v.Index()) + strategy.variables.append(v.index) strategy.variable_selection_strategy = var_strategy strategy.domain_reduction_strategy = domain_strategy - def ModelStats(self) -> str: + def model_stats(self) -> str: """Returns a string containing some model statistics.""" - return swig_helper.CpSatHelper.ModelStats(self.__model) + return swig_helper.CpSatHelper.model_stats(self.__model) - def Validate(self) -> str: + def validate(self) -> str: """Returns a string indicating that the model is invalid.""" - return swig_helper.CpSatHelper.ValidateModel(self.__model) + return swig_helper.CpSatHelper.validate_model(self.__model) - def ExportToFile(self, file: str) -> bool: + def export_to_file(self, file: str) -> bool: """Write the model as a protocol buffer to 'file'. Args: @@ -2662,52 +2829,126 @@ def ExportToFile(self, file: str) -> bool: """ return swig_helper.CpSatHelper.WriteModelToFile(self.__model, file) - def AssertIsBooleanVariable(self, x: LiteralT) -> None: - if isinstance(x, IntVar): - var = self.__model.variables[x.Index()] - if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1: - raise TypeError("TypeError: " + str(x) + " is not a boolean variable") - elif not isinstance(x, _NotBooleanVariable): - raise TypeError("TypeError: " + str(x) + " is not a boolean variable") - - def AddHint(self, var: IntVar, value: int) -> None: + def add_hint(self, var: IntVar, value: int) -> None: """Adds 'var == value' as a hint to the solver.""" - self.__model.solution_hint.vars.append(self.GetOrMakeIndex(var)) + self.__model.solution_hint.vars.append(self.get_or_make_index(var)) self.__model.solution_hint.values.append(value) - def ClearHints(self): - """Remove any solution hint from the model.""" + def clear_hints(self): + """Removes any solution hint from the model.""" self.__model.ClearField("solution_hint") - def AddAssumption(self, lit: LiteralT) -> None: - """Add the literal 'lit' to the model as assumptions.""" - self.__model.assumptions.append(self.GetOrMakeBooleanIndex(lit)) + def add_assumption(self, lit: LiteralT) -> None: + """Adds the literal to the model as assumptions.""" + self.__model.assumptions.append(self.get_or_make_boolean_index(lit)) - def AddAssumptions(self, literals: Iterable[LiteralT]) -> None: - """Add the literals to the model as assumptions.""" + def add_assumptions(self, literals: Iterable[LiteralT]) -> None: + """Adds the literals to the model as assumptions.""" for lit in literals: - self.AddAssumption(lit) + self.add_assumption(lit) - def ClearAssumptions(self) -> None: - """Remove all assumptions from the model.""" + def clear_assumptions(self) -> None: + """Removes all assumptions from the model.""" self.__model.ClearField("assumptions") + # Helpers. + def assert_is_boolean_variable(self, x: LiteralT) -> None: + if isinstance(x, IntVar): + var = self.__model.variables[x.index] + if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1: + raise TypeError("TypeError: " + str(x) + " is not a boolean variable") + elif not isinstance(x, _NotBooleanVariable): + raise TypeError("TypeError: " + str(x) + " is not a boolean variable") + + # Compatibility with pre PEP8 + # pylint: disable=invalid-name + + def Name(self) -> str: + return self.name + + def SetName(self, name: str) -> None: + self.name = name + + def Proto(self) -> cp_model_pb2.CpModelProto: + return self.proto + + NewIntVar = new_int_var + NewIntVarFromDomain = new_int_var_from_domain + NewBoolVar = new_bool_var + NewConstant = new_constant + NewIntVarSeries = new_int_var_series + NewBoolVarSeries = new_bool_var_series + AddLinearConstraint = add_linear_constraint + AddLinearExpressionInDomain = add_linear_expression_in_domain + Add = add + AddAllDifferent = add_all_different + AddElement = add_element + AddCircuit = add_circuit + AddMultipleCircuit = add_multiple_circuit + AddAllowedAssignments = add_allowed_assignments + AddForbiddenAssignments = add_forbidden_assignments + AddAutomaton = add_automaton + AddInverse = add_inverse + AddReservoirConstraint = add_reservoir_constraint + AddImplication = add_implication + AddBoolOr = add_bool_or + AddAtLeastOne = add_at_least_one + AddAtMostOne = add_at_most_one + AddExactlyOne = add_exactly_one + AddBoolAnd = add_bool_and + AddBoolXOr = add_bool_xor + AddMinEquality = add_min_equality + AddMaxEquality = add_max_equality + AddDivisionEquality = add_division_equality + AddAbsEquality = add_abs_equality + AddModuloEquality = add_modulo_equality + AddMultiplicationEquality = add_multiplication_equality + NewIntervalVar = new_interval_var + NewIntervalVarSeries = new_interval_var_series + NewFixedSizedIntervalVar = new_fixed_size_interval_var + NewOptionalIntervalVar = new_optional_interval_var + NewOptionalIntervalVarSeries = new_optional_interval_var_series + NewOptionalFixedSizedIntervalVar = new_optional_fixed_size_interval_var + NewOptionalFixedSizedIntervalVarSeries = new_optional_fixed_size_interval_var_series + AddNoOverlap = add_no_overlap + AddNoOverlap2D = add_no_overlap_2d + AddCumulative = add_cumulative + Clone = clone + GetBoolVarFromProtoIndex = get_bool_var_from_proto_index + GetIntVarFromProtoIndex = get_int_var_from_proto_index + GetIntervalVarFromProtoIndex = get_interval_var_from_proto_index + Minimize = minimize + Maximize = maximize + HasObjective = has_objective + ClearObjective = clear_objective + AddDecisionStrategy = add_decision_strategy + ModelStats = model_stats + Validate = validate + ExportToFile = export_to_file + AddHint = add_hint + ClearHints = clear_hints + AddAssumption = add_assumption + AddAssumptions = add_assumptions + ClearAssumptions = clear_assumptions + + # pylint: enable=invalid-name + @overload -def ExpandGeneratorOrTuple( +def expand_generator_or_tuple( args: Union[Tuple[LiteralT, ...], Iterable[LiteralT]] ) -> Union[Iterable[LiteralT], LiteralT]: ... @overload -def ExpandGeneratorOrTuple( +def expand_generator_or_tuple( args: Union[Tuple[LinearExprT, ...], Iterable[LinearExprT]] ) -> Union[Iterable[LinearExprT], LinearExprT]: ... -def ExpandGeneratorOrTuple(args): +def expand_generator_or_tuple(args): if hasattr(args, "__len__"): # Tuple if len(args) != 1: return args @@ -2717,7 +2958,7 @@ def ExpandGeneratorOrTuple(args): return args[0] -def EvaluateLinearExpr( +def evaluate_linear_expr( expression: LinearExprT, solution: cp_model_pb2.CpSolverResponse ) -> int: """Evaluate a linear expression against a solution.""" @@ -2733,36 +2974,36 @@ def EvaluateLinearExpr( if cmh.is_integral(expr): value += int(expr) * coeff elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, IntVar): - value += coeff * solution.solution[expr.Index()] + value += coeff * solution.solution[expr.index] elif isinstance(expr, _NotBooleanVariable): - value += coeff * (1 - solution.solution[expr.Not().Index()]) + value += coeff * (1 - solution.solution[expr.negated().index]) else: raise TypeError(f"Cannot interpret {expr} as a linear expression.") return value -def EvaluateBooleanExpression( +def evaluate_boolean_expression( literal: LiteralT, solution: cp_model_pb2.CpSolverResponse ) -> bool: """Evaluate a boolean expression against a solution.""" if cmh.is_integral(literal): return bool(literal) elif isinstance(literal, IntVar) or isinstance(literal, _NotBooleanVariable): - index: int = cast(Union[IntVar, _NotBooleanVariable], literal).Index() + index: int = cast(Union[IntVar, _NotBooleanVariable], literal).index if index >= 0: return bool(solution.solution[index]) else: @@ -2775,10 +3016,10 @@ class CpSolver: """Main solver class. The purpose of this class is to search for a solution to the model provided - to the Solve() method. + to the solve() method. - Once Solve() is called, this class allows inspecting the solution found - with the Value() and BooleanValue() methods, as well as general statistics + Once solve() is called, this class allows inspecting the solution found + with the value() and boolean_value() methods, as well as general statistics about the solve procedure. """ @@ -2791,7 +3032,7 @@ def __init__(self): self.__solve_wrapper: Optional[swig_helper.SolveWrapper] = None self.__lock: threading.Lock = threading.Lock() - def Solve( + def solve( self, model: CpModel, solution_callback: Optional["CpSolverSolutionCallback"] = None, @@ -2800,93 +3041,34 @@ def Solve( with self.__lock: self.__solve_wrapper = swig_helper.SolveWrapper() - self.__solve_wrapper.SetParameters(self.parameters) + self.__solve_wrapper.set_parameters(self.parameters) if solution_callback is not None: - self.__solve_wrapper.AddSolutionCallback(solution_callback) + self.__solve_wrapper.add_solution_callback(solution_callback) if self.log_callback is not None: - self.__solve_wrapper.AddLogCallback(self.log_callback) + self.__solve_wrapper.add_log_callback(self.log_callback) - self.__solution = self.__solve_wrapper.Solve(model.Proto()) + self.__solution = self.__solve_wrapper.solve(model.proto) if solution_callback is not None: - self.__solve_wrapper.ClearSolutionCallback(solution_callback) + self.__solve_wrapper.clear_solution_callback(solution_callback) with self.__lock: self.__solve_wrapper = None return self.__solution.status - def SolveWithSolutionCallback( - self, model: CpModel, callback: "CpSolverSolutionCallback" - ) -> cp_model_pb2.CpSolverStatus: - """DEPRECATED Use Solve() with the callback argument.""" - warnings.warn( - "SolveWithSolutionCallback is deprecated; use Solve() with" - + "the callback argument.", - DeprecationWarning, - ) - return self.Solve(model, callback) - - def SearchForAllSolutions( - self, model: CpModel, callback: "CpSolverSolutionCallback" - ) -> cp_model_pb2.CpSolverStatus: - """DEPRECATED Use Solve() with the right parameter. - - Search for all solutions of a satisfiability problem. - - This method searches for all feasible solutions of a given model. - Then it feeds the solution to the callback. - - Note that the model cannot contain an objective. - - Args: - model: The model to solve. - callback: The callback that will be called at each solution. - - Returns: - The status of the solve: - - * *FEASIBLE* if some solutions have been found - * *INFEASIBLE* if the solver has proved there are no solution - * *OPTIMAL* if all solutions have been found - """ - warnings.warn( - "SearchForAllSolutions is deprecated; use Solve() with" - + "enumerate_all_solutions = True.", - DeprecationWarning, - ) - if model.HasObjective(): - raise TypeError( - "Search for all solutions is only defined on satisfiability problems" - ) - # Store old parameter. - enumerate_all = self.parameters.enumerate_all_solutions - self.parameters.enumerate_all_solutions = True - - self.Solve(model, callback) - - # Restore parameter. - self.parameters.enumerate_all_solutions = enumerate_all - return self.__solution.status - - def StopSearch(self) -> None: + def stop_search(self) -> None: """Stops the current search asynchronously.""" with self.__lock: if self.__solve_wrapper: - self.__solve_wrapper.StopSearch() + self.__solve_wrapper.stop_search() - def _Solution(self) -> cp_model_pb2.CpSolverResponse: - """Checks Solve() has been called, and returns the solution.""" - if self.__solution is None: - raise RuntimeError("Solve() has not been called.") - return self.__solution - - def Value(self, expression: LinearExprT) -> int: + def value(self, expression: LinearExprT) -> int: """Returns the value of a linear expression after solve.""" - return EvaluateLinearExpr(expression, self._Solution()) + return evaluate_linear_expr(expression, self._solution) - def Values(self, variables: _IndexOrSeries) -> pd.Series: + def values(self, variables: _IndexOrSeries) -> pd.Series: """Returns the values of the input variables. If `variables` is a `pd.Index`, then the output will be indexed by the @@ -2901,17 +3083,17 @@ def Values(self, variables: _IndexOrSeries) -> pd.Series: Returns: pd.Series: The values of all variables in the set. """ - solution = self._Solution() - return _AttributeSeries( - func=lambda v: solution.solution[v.Index()], + solution = self._solution + return _attribute_series( + func=lambda v: solution.solution[v.index], values=variables, ) - def BooleanValue(self, literal: LiteralT) -> bool: + def boolean_value(self, literal: LiteralT) -> bool: """Returns the boolean value of a literal after solve.""" - return EvaluateBooleanExpression(literal, self._Solution()) + return evaluate_boolean_expression(literal, self._solution) - def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: + def boolean_values(self, variables: _IndexOrSeries) -> pd.Series: """Returns the values of the input variables. If `variables` is a `pd.Index`, then the output will be indexed by the @@ -2926,68 +3108,200 @@ def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: Returns: pd.Series: The values of all variables in the set. """ - solution = self._Solution() - return _AttributeSeries( - func=lambda literal: EvaluateBooleanExpression(literal, solution), + solution = self._solution + return _attribute_series( + func=lambda literal: evaluate_boolean_expression(literal, solution), values=variables, ) - def ObjectiveValue(self) -> float: + @property + def objective_value(self) -> float: """Returns the value of the objective after solve.""" - return self._Solution().objective_value + return self._solution.objective_value - def BestObjectiveBound(self) -> float: + @property + def best_objective_bound(self) -> float: """Returns the best lower (upper) bound found when min(max)imizing.""" - return self._Solution().best_objective_bound - - def StatusName(self, status: ... = None) -> str: - """Returns the name of the status returned by Solve().""" - if status is None: - status = self._Solution().status - return cp_model_pb2.CpSolverStatus.Name(status) + return self._solution.best_objective_bound - def NumBooleans(self) -> int: + @property + def num_booleans(self) -> int: """Returns the number of boolean variables managed by the SAT solver.""" - return self._Solution().num_booleans + return self._solution.num_booleans - def NumConflicts(self) -> int: + @property + def num_conflicts(self) -> int: """Returns the number of conflicts since the creation of the solver.""" - return self._Solution().num_conflicts + return self._solution.num_conflicts - def NumBranches(self) -> int: + @property + def num_branches(self) -> int: """Returns the number of search branches explored by the solver.""" - return self._Solution().num_branches + return self._solution.num_branches - def WallTime(self) -> float: + @property + def wall_time(self) -> float: """Returns the wall time in seconds since the creation of the solver.""" - return self._Solution().wall_time + return self._solution.wall_time - def UserTime(self) -> float: + @property + def user_time(self) -> float: """Returns the user time in seconds since the creation of the solver.""" - return self._Solution().user_time - - def ResponseStats(self) -> str: - """Returns some statistics on the solution found as a string.""" - return swig_helper.CpSatHelper.SolverResponseStats(self._Solution()) + return self._solution.user_time - def ResponseProto(self) -> cp_model_pb2.CpSolverResponse: + @property + def response_proto(self) -> cp_model_pb2.CpSolverResponse: """Returns the response object.""" - return self._Solution() + return self._solution - def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: + def response_stats(self) -> str: + """Returns some statistics on the solution found as a string.""" + return swig_helper.CpSatHelper.solver_response_stats(self._solution) + + def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]: """Returns the indices of the infeasible assumptions.""" - return self._Solution().sufficient_assumptions_for_infeasibility + return self._solution.sufficient_assumptions_for_infeasibility - def SolutionInfo(self) -> str: + def status_name(self, status: ... = None) -> str: + """Returns the name of the status returned by solve().""" + if status is None: + status = self._solution.status + return cp_model_pb2.CpSolverStatus.Name(status) + + def solution_info(self) -> str: """Returns some information on the solve process. Returns some information on how the solution was found, or the reason why the model or the parameters are invalid. Raises: - RuntimeError: if Solve() has not been called. + RuntimeError: if solve() has not been called. + """ + return self._solution.solution_info + + @property + def _solution(self) -> cp_model_pb2.CpSolverResponse: + """Checks solve() has been called, and returns the solution.""" + if self.__solution is None: + raise RuntimeError("solve() has not been called.") + return self.__solution + + # Compatibility with pre PEP8 + # pylint: disable=invalid-name + + def BestObjectiveBound(self) -> float: + return self.best_objective_bound + + def BooleanValue(self, literal: LiteralT) -> bool: + return self.boolean_value(literal) + + def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: + return self.boolean_values(variables) + + def NumBooleans(self) -> int: + return self.num_booleans + + def NumConflicts(self) -> int: + return self.num_conflicts + + def NumBranches(self) -> int: + return self.num_branches + + def ObjectiveValue(self) -> float: + return self.objective_value + + def ResponseProto(self) -> cp_model_pb2.CpSolverResponse: + return self.response_proto + + def ResponseStats(self) -> str: + return self.response_stats() + + def Solve( + self, + model: CpModel, + solution_callback: Optional["CpSolverSolutionCallback"] = None, + ) -> cp_model_pb2.CpSolverStatus: + return self.solve(model, solution_callback) + + def SolutionInfo(self) -> str: + return self.solution_info() + + def StatusName(self, status: ... = None) -> str: + return self.status_name(status) + + def StopSearch(self) -> None: + self.stop_search() + + def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: + return self.sufficient_assumptions_for_infeasibility() + + def UserTime(self) -> float: + return self.user_time + + def Value(self, expression: LinearExprT) -> int: + return self.value(expression) + + def Values(self, variables: _IndexOrSeries) -> pd.Series: + return self.values(variables) + + def WallTime(self) -> float: + return self.wall_time + + def SolveWithSolutionCallback( + self, model: CpModel, callback: "CpSolverSolutionCallback" + ) -> cp_model_pb2.CpSolverStatus: + """DEPRECATED Use solve() with the callback argument.""" + warnings.warn( + "solve_with_solution_callback is deprecated; use solve() with" + + "the callback argument.", + DeprecationWarning, + ) + return self.solve(model, callback) + + def SearchForAllSolutions( + self, model: CpModel, callback: "CpSolverSolutionCallback" + ) -> cp_model_pb2.CpSolverStatus: + """DEPRECATED Use solve() with the right parameter. + + Search for all solutions of a satisfiability problem. + + This method searches for all feasible solutions of a given model. + Then it feeds the solution to the callback. + + Note that the model cannot contain an objective. + + Args: + model: The model to solve. + callback: The callback that will be called at each solution. + + Returns: + The status of the solve: + + * *FEASIBLE* if some solutions have been found + * *INFEASIBLE* if the solver has proved there are no solution + * *OPTIMAL* if all solutions have been found """ - return self._Solution().solution_info + warnings.warn( + "search_for_all_solutions is deprecated; use solve() with" + + "enumerate_all_solutions = True.", + DeprecationWarning, + ) + if model.has_objective(): + raise TypeError( + "Search for all solutions is only defined on satisfiability problems" + ) + # Store old parameter. + enumerate_all = self.parameters.enumerate_all_solutions + self.parameters.enumerate_all_solutions = True + + self.solve(model, callback) + + # Restore parameter. + self.parameters.enumerate_all_solutions = enumerate_all + return self.__solution.status + + +# pylint: enable=invalid-name class CpSolverSolutionCallback(swig_helper.SolutionCallback): @@ -2996,19 +3310,9 @@ class CpSolverSolutionCallback(swig_helper.SolutionCallback): This class implements a callback that will be called at each new solution found during search. - The method OnSolutionCallback() will be called by the solver, and must be - implemented. The current solution can be queried using the BooleanValue() - and Value() methods. - - It inherits the following methods from its base class: - - * `ObjectiveValue(self)` - * `BestObjectiveBound(self)` - * `NumBooleans(self)` - * `NumConflicts(self)` - * `NumBranches(self)` - * `WallTime(self)` - * `UserTime(self)` + The method on_solution_callback() will be called by the solver, and must be + implemented. The current solution can be queried using the boolean_value() + and value() methods. These methods returns the same information as their counterpart in the `CpSolver` class. @@ -3021,7 +3325,7 @@ def OnSolutionCallback(self) -> None: """Proxy for the same method in snake case.""" self.on_solution_callback() - def BooleanValue(self, lit: LiteralT) -> bool: + def boolean_value(self, lit: LiteralT) -> bool: """Returns the boolean value of a boolean literal. Args: @@ -3033,17 +3337,17 @@ def BooleanValue(self, lit: LiteralT) -> bool: Raises: RuntimeError: if `lit` is not a boolean variable or its negation. """ - if not self.HasResponse(): - raise RuntimeError("Solve() has not been called.") + if not self.has_response(): + raise RuntimeError("solve() has not been called.") if cmh.is_integral(lit): return bool(lit) elif isinstance(lit, IntVar) or isinstance(lit, _NotBooleanVariable): return self.SolutionBooleanValue( - cast(Union[IntVar, _NotBooleanVariable], lit).Index() + cast(Union[IntVar, _NotBooleanVariable], lit).index ) raise TypeError(f"Cannot interpret {lit} as a boolean expression.") - def Value(self, expression: LinearExprT) -> int: + def value(self, expression: LinearExprT) -> int: """Evaluates an linear expression in the current solution. Args: @@ -3056,8 +3360,8 @@ def Value(self, expression: LinearExprT) -> int: Raises: RuntimeError: if 'expression' is not a LinearExpr. """ - if not self.HasResponse(): - raise RuntimeError("Solve() has not been called.") + if not self.has_response(): + raise RuntimeError("solve() has not been called.") value = 0 to_process = [(expression, 1)] @@ -3066,32 +3370,118 @@ def Value(self, expression: LinearExprT) -> int: if cmh.is_integral(expr): value += int(expr) * coeff elif isinstance(expr, _ProductCst): - to_process.append((expr.Expression(), coeff * expr.Coefficient())) + to_process.append((expr.expression(), coeff * expr.coefficient())) elif isinstance(expr, _Sum): - to_process.append((expr.Left(), coeff)) - to_process.append((expr.Right(), coeff)) + to_process.append((expr.left(), coeff)) + to_process.append((expr.right(), coeff)) elif isinstance(expr, _SumArray): - for e in expr.Expressions(): + for e in expr.expressions(): to_process.append((e, coeff)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, _WeightedSum): - for e, c in zip(expr.Expressions(), expr.Coefficients()): + for e, c in zip(expr.expressions(), expr.coefficients()): to_process.append((e, coeff * c)) - value += expr.Constant() * coeff + value += expr.constant() * coeff elif isinstance(expr, IntVar): - value += coeff * self.SolutionIntegerValue(expr.Index()) + value += coeff * self.SolutionIntegerValue(expr.index) elif isinstance(expr, _NotBooleanVariable): - value += coeff * (1 - self.SolutionIntegerValue(expr.Not().Index())) + value += coeff * (1 - self.SolutionIntegerValue(expr.negated().index)) else: raise TypeError( - f"Cannot interpret {expression} as a linear expression." + f"cannot interpret {expression} as a linear expression." ) return value - def Response(self) -> cp_model_pb2.CpSolverResponse: - """Returns the current solution response.""" - return swig_helper.SolutionCallback.Response(self) + def has_response(self) -> bool: + return self.HasResponse() + + def stop_search(self) -> None: + """Stops the current search asynchronously.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + self.StopSearch() + + @property + def objective_value(self) -> float: + """Returns the value of the objective after solve.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.ObjectiveValue() + + @property + def best_objective_bound(self) -> float: + """Returns the best lower (upper) bound found when min(max)imizing.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.BestObjectiveBound() + + @property + def num_booleans(self) -> int: + """Returns the number of boolean variables managed by the SAT solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumBooleans() + + @property + def num_conflicts(self) -> int: + """Returns the number of conflicts since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumConflicts() + + @property + def num_branches(self) -> int: + """Returns the number of search branches explored by the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumBranches() + + @property + def num_integer_propagations(self) -> int: + """Returns the number of integer propagations done by the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumIntegerPropagations() + + @property + def num_boolean_propagations(self) -> int: + """Returns the number of Boolean propagations done by the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.NumBooleanPropagations() + + @property + def deterministic_time(self) -> float: + """Returns the determistic time in seconds since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.DeterministicTime() + + @property + def wall_time(self) -> float: + """Returns the wall time in seconds since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.WallTime() + + @property + def user_time(self) -> float: + """Returns the user time in seconds since the creation of the solver.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.UserTime() + + @property + def response_proto(self) -> cp_model_pb2.CpSolverResponse: + """Returns the response object.""" + if not self.has_response(): + raise RuntimeError("solve() has not been called.") + return self.Response() + + # PeP8 compatibility + Value = value + BooleanValue = boolean_value class ObjectiveSolutionPrinter(CpSolverSolutionCallback): @@ -3105,7 +3495,7 @@ def __init__(self): def on_solution_callback(self) -> None: """Called on each new solution.""" current_time = time.time() - obj = self.ObjectiveValue() + obj = self.objective_value print( "Solution %i, time = %0.2f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, obj) @@ -3129,16 +3519,17 @@ def __init__(self, variables): def on_solution_callback(self) -> None: """Called on each new solution.""" current_time = time.time() - obj = self.ObjectiveValue() + obj = self.objective_value print( "Solution %i, time = %0.2f s, objective = %i" % (self.__solution_count, current_time - self.__start_time, obj) ) for v in self.__variables: - print(" %s = %i" % (v, self.Value(v)), end=" ") + print(" %s = %i" % (v, self.value(v)), end=" ") print() self.__solution_count += 1 + @property def solution_count(self) -> int: """Returns the number of solutions found.""" return self.__solution_count @@ -3161,23 +3552,24 @@ def on_solution_callback(self) -> None: % (self.__solution_count, current_time - self.__start_time) ) for v in self.__variables: - print(" %s = %i" % (v, self.Value(v)), end=" ") + print(" %s = %i" % (v, self.value(v)), end=" ") print() self.__solution_count += 1 + @property def solution_count(self) -> int: """Returns the number of solutions found.""" return self.__solution_count -def _GetIndex(obj: _IndexOrSeries) -> pd.Index: +def _get_index(obj: _IndexOrSeries) -> pd.Index: """Returns the indices of `obj` as a `pd.Index`.""" if isinstance(obj, pd.Series): return obj.index return obj -def _AttributeSeries( +def _attribute_series( *, func: Callable[[IntVar], IntegralT], values: _IndexOrSeries, @@ -3193,11 +3585,11 @@ def _AttributeSeries( """ return pd.Series( data=[func(v) for v in values], - index=_GetIndex(values), + index=_get_index(values), ) -def _ConvertToIntegralSeriesAndValidateIndex( +def _convert_to_integral_series_and_validate_index( value_or_series: Union[IntegralT, pd.Series], index: pd.Index ) -> pd.Series: """Returns a pd.Series of the given index with the corresponding values. @@ -3225,7 +3617,7 @@ def _ConvertToIntegralSeriesAndValidateIndex( return result -def _ConvertToLinearExprSeriesAndValidateIndex( +def _convert_to_linear_expr_series_and_validate_index( value_or_series: Union[LinearExprT, pd.Series], index: pd.Index ) -> pd.Series: """Returns a pd.Series of the given index with the corresponding values. @@ -3253,7 +3645,7 @@ def _ConvertToLinearExprSeriesAndValidateIndex( return result -def _ConvertToLiteralSeriesAndValidateIndex( +def _convert_to_literal_series_and_validate_index( value_or_series: Union[LiteralT, pd.Series], index: pd.Index ) -> pd.Series: """Returns a pd.Series of the given index with the corresponding values. diff --git a/ortools/sat/python/cp_model_test.py b/ortools/sat/python/cp_model_test.py index b317b3b1fe8..3a6370dcf3a 100644 --- a/ortools/sat/python/cp_model_test.py +++ b/ortools/sat/python/cp_model_test.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for ortools.sat.python.cp_model.""" + from absl.testing import absltest import pandas as pd @@ -26,10 +27,11 @@ def __init__(self): cp_model.CpSolverSolutionCallback.__init__(self) self.__solution_count = 0 - def OnSolutionCallback(self): + def on_solution_callback(self): self.__solution_count += 1 - def SolutionCount(self): + @property + def solution_count(self): return self.__solution_count @@ -41,10 +43,11 @@ def __init__(self, variables): self.__sum = 0 self.__vars = variables - def OnSolutionCallback(self): - self.__sum = sum(self.Value(x) for x in self.__vars) + def on_solution_callback(self): + self.__sum = sum(self.value(x) for x in self.__vars) - def Sum(self): + @property + def sum(self): return self.__sum @@ -55,10 +58,11 @@ def __init__(self): cp_model.CpSolverSolutionCallback.__init__(self) self.__obj = 0 - def OnSolutionCallback(self): - self.__obj = self.ObjectiveValue() + def on_solution_callback(self): + self.__obj = self.objective_value - def Obj(self): + @property + def obj(self): return self.__obj @@ -68,11 +72,12 @@ class LogToString: def __init__(self): self.__log = "" - def NewMessage(self, message: str): + def new_message(self, message: str): self.__log += message self.__log += "\n" - def Log(self): + @property + def log(self): return self.__log @@ -80,60 +85,64 @@ class CpModelTest(absltest.TestCase): def testCreateIntegerVariable(self): print("testCreateIntegerVariable") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") self.assertEqual("x", str(x)) self.assertEqual("x(-10..10)", repr(x)) - y = model.NewIntVarFromDomain(cp_model.Domain.FromIntervals([[2, 4], [7]]), "y") + y = model.new_int_var_from_domain( + cp_model.Domain.from_intervals([[2, 4], [7]]), "y" + ) self.assertEqual("y", str(y)) self.assertEqual("y(2..4, 7)", repr(y)) - z = model.NewIntVarFromDomain(cp_model.Domain.FromValues([2, 3, 4, 7]), "z") + z = model.new_int_var_from_domain( + cp_model.Domain.from_values([2, 3, 4, 7]), "z" + ) self.assertEqual("z", str(z)) self.assertEqual("z(2..4, 7)", repr(z)) - t = model.NewIntVarFromDomain( - cp_model.Domain.FromFlatIntervals([2, 4, 7, 7]), "t" + t = model.new_int_var_from_domain( + cp_model.Domain.from_flat_intervals([2, 4, 7, 7]), "t" ) self.assertEqual("t", str(t)) self.assertEqual("t(2..4, 7)", repr(t)) - cst = model.NewConstant(5) + cst = model.new_constant(5) self.assertEqual("5", str(cst)) def testNegation(self): print("testNegation") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - b = model.NewBoolVar("b") - nb = b.Not() - self.assertEqual(b.Not(), nb) - self.assertEqual(b.Not().Not(), b) - self.assertEqual(nb.Index(), -b.Index() - 1) - self.assertRaises(TypeError, x.Not) + x = model.new_int_var(-10, 10, "x") + b = model.new_bool_var("b") + nb = b.negated() + self.assertEqual(b.negated(), nb) + self.assertEqual(b.negated().negated(), b) + self.assertEqual(nb.index, -b.index - 1) + self.assertRaises(TypeError, x.negated) def testEqualityOverload(self): print("testEqualityOverload") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(0, 5, "y") + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(0, 5, "y") self.assertEqual(x, x) self.assertNotEqual(x, y) def testLinear(self): print("testLinear") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - model.Minimize(y) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + model.minimize(y) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(10, solver.Value(x)) - self.assertEqual(-5, solver.Value(y)) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(10, solver.value(x)) + self.assertEqual(-5, solver.value(y)) def testLinearNonEqual(self): print("testLinearNonEqual") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(-x + y != 3).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(-x + y != 3).proto self.assertLen(ct.linear.domain, 4) self.assertEqual(cp_model.INT_MIN, ct.linear.domain[0]) self.assertEqual(2, ct.linear.domain[1]) @@ -143,8 +152,8 @@ def testLinearNonEqual(self): def testEq(self): print("testEq") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x == 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x == 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -154,8 +163,8 @@ def testEq(self): def testGe(self): print("testGe") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x >= 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x >= 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -165,8 +174,8 @@ def testGe(self): def testGt(self): print("testGt") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x > 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x > 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -176,8 +185,8 @@ def testGt(self): def testLe(self): print("testLe") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x <= 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x <= 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -187,8 +196,8 @@ def testLe(self): def testLt(self): print("testLt") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - ct = model.Add(x < 2).Proto() + x = model.new_int_var(-10, 10, "x") + ct = model.add(x < 2).proto self.assertLen(ct.linear.vars, 1) self.assertLen(ct.linear.coeffs, 1) self.assertLen(ct.linear.domain, 2) @@ -198,9 +207,9 @@ def testLt(self): def testEqVar(self): print("testEqVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x == y + 2).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x == y + 2).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -212,9 +221,9 @@ def testEqVar(self): def testGeVar(self): print("testGeVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x >= 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x >= 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -227,9 +236,9 @@ def testGeVar(self): def testGtVar(self): print("testGeVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x > 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x > 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -242,9 +251,9 @@ def testGtVar(self): def testLeVar(self): print("testLeVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x <= 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x <= 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -257,9 +266,9 @@ def testLeVar(self): def testLtVar(self): print("testLtVar") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x < 1 - y).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x < 1 - y).proto self.assertLen(ct.linear.vars, 2) self.assertEqual(1, ct.linear.vars[0] + ct.linear.vars[1]) self.assertLen(ct.linear.coeffs, 2) @@ -272,41 +281,41 @@ def testLtVar(self): def testSimplification1(self): print("testSimplification1") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = (x * 2) * 2 - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testSimplification2(self): print("testSimplification2") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = 2 * (x * 2) - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testSimplification3(self): print("testSimplification3") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = (2 * x) * 2 - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testSimplification4(self): print("testSimplification4") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") + x = model.new_int_var(-10, 10, "x") prod = 2 * (2 * x) - self.assertEqual(x, prod.Expression()) - self.assertEqual(4, prod.Coefficient()) + self.assertEqual(x, prod.expression()) + self.assertEqual(4, prod.coefficient()) def testLinearNonEqualWithConstant(self): print("testLinearNonEqualWithConstant") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.Add(x + y + 5 != 3).Proto() + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add(x + y + 5 != 3).proto self.assertLen(ct.linear.domain, 4) # Checks that saturated arithmetics worked. self.assertEqual(cp_model.INT_MIN, ct.linear.domain[0]) @@ -317,254 +326,254 @@ def testLinearNonEqualWithConstant(self): def testLinearWithEnforcement(self): print("testLinearWithEnforcement") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - b = model.NewBoolVar("b") - model.AddLinearConstraint(x + 2 * y, 0, 10).OnlyEnforceIf(b.Not()) - model.Minimize(y) - self.assertLen(model.Proto().constraints, 1) - self.assertEqual(-3, model.Proto().constraints[0].enforcement_literal[0]) - c = model.NewBoolVar("c") - model.AddLinearConstraint(x + 4 * y, 0, 10).OnlyEnforceIf([b, c]) - self.assertLen(model.Proto().constraints, 2) - self.assertEqual(2, model.Proto().constraints[1].enforcement_literal[0]) - self.assertEqual(3, model.Proto().constraints[1].enforcement_literal[1]) - model.AddLinearConstraint(x + 5 * y, 0, 10).OnlyEnforceIf(c.Not(), b) - self.assertLen(model.Proto().constraints, 3) - self.assertEqual(-4, model.Proto().constraints[2].enforcement_literal[0]) - self.assertEqual(2, model.Proto().constraints[2].enforcement_literal[1]) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + b = model.new_bool_var("b") + model.add_linear_constraint(x + 2 * y, 0, 10).only_enforce_if(b.negated()) + model.minimize(y) + self.assertLen(model.proto.constraints, 1) + self.assertEqual(-3, model.proto.constraints[0].enforcement_literal[0]) + c = model.new_bool_var("c") + model.add_linear_constraint(x + 4 * y, 0, 10).only_enforce_if([b, c]) + self.assertLen(model.proto.constraints, 2) + self.assertEqual(2, model.proto.constraints[1].enforcement_literal[0]) + self.assertEqual(3, model.proto.constraints[1].enforcement_literal[1]) + model.add_linear_constraint(x + 5 * y, 0, 10).only_enforce_if(c.negated(), b) + self.assertLen(model.proto.constraints, 3) + self.assertEqual(-4, model.proto.constraints[2].enforcement_literal[0]) + self.assertEqual(2, model.proto.constraints[2].enforcement_literal[1]) def testConstraintWithName(self): print("testConstraintWithName") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - ct = model.AddLinearConstraint(x + 2 * y, 0, 10).WithName("test_constraint") - self.assertEqual("test_constraint", ct.Name()) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + ct = model.add_linear_constraint(x + 2 * y, 0, 10).with_name("test_constraint") + self.assertEqual("test_constraint", ct.name) def testNaturalApiMinimize(self): print("testNaturalApiMinimize") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.Add(x * 2 - 1 * y == 1) - model.Minimize(x * 1 - 2 * y + 3) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add(x * 2 - 1 * y == 1) + model.minimize(x * 1 - 2 * y + 3) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(5, solver.Value(x)) - self.assertEqual(15, solver.Value(x * 3)) - self.assertEqual(6, solver.Value(1 + x)) - self.assertEqual(-10.0, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(5, solver.value(x)) + self.assertEqual(15, solver.value(x * 3)) + self.assertEqual(6, solver.value(1 + x)) + self.assertEqual(-10.0, solver.objective_value) def testNaturalApiMaximizeFloat(self): print("testNaturalApiMaximizeFloat") model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewIntVar(0, 10, "y") - model.Maximize(x.Not() * 3.5 + x.Not() - y + 2 * y + 1.6) + x = model.new_bool_var("x") + y = model.new_int_var(0, 10, "y") + model.maximize(x.negated() * 3.5 + x.negated() - y + 2 * y + 1.6) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertFalse(solver.BooleanValue(x)) - self.assertTrue(solver.BooleanValue(x.Not())) - self.assertEqual(-10, solver.Value(-y)) - self.assertEqual(16.1, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertFalse(solver.boolean_value(x)) + self.assertTrue(solver.boolean_value(x.negated())) + self.assertEqual(-10, solver.value(-y)) + self.assertEqual(16.1, solver.objective_value) def testNaturalApiMaximizeComplex(self): print("testNaturalApiMaximizeFloat") model = cp_model.CpModel() - x1 = model.NewBoolVar("x1") - x2 = model.NewBoolVar("x1") - x3 = model.NewBoolVar("x1") - x4 = model.NewBoolVar("x1") - model.Maximize( - cp_model.LinearExpr.Sum([x1, x2]) - + cp_model.LinearExpr.WeightedSum([x3, x4.Not()], [2, 4]) + x1 = model.new_bool_var("x1") + x2 = model.new_bool_var("x1") + x3 = model.new_bool_var("x1") + x4 = model.new_bool_var("x1") + model.maximize( + cp_model.LinearExpr.sum([x1, x2]) + + cp_model.LinearExpr.weighted_sum([x3, x4.negated()], [2, 4]) ) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(5, solver.Value(3 + 2 * x1)) - self.assertEqual(3, solver.Value(x1 + x2 + x3)) - self.assertEqual(1, solver.Value(cp_model.LinearExpr.Sum([x1, x2, x3, 0, -2]))) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(5, solver.value(3 + 2 * x1)) + self.assertEqual(3, solver.value(x1 + x2 + x3)) + self.assertEqual(1, solver.value(cp_model.LinearExpr.sum([x1, x2, x3, 0, -2]))) self.assertEqual( 7, - solver.Value( - cp_model.LinearExpr.WeightedSum([x1, x2, x4, 3], [2, 2, 2, 1]) + solver.value( + cp_model.LinearExpr.weighted_sum([x1, x2, x4, 3], [2, 2, 2, 1]) ), ) - self.assertEqual(5, solver.Value(5 * x4.Not())) - self.assertEqual(8, solver.ObjectiveValue()) + self.assertEqual(5, solver.value(5 * x4.negated())) + self.assertEqual(8, solver.objective_value) def testNaturalApiMaximize(self): print("testNaturalApiMaximize") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.Add(2 * x - y == 1) - model.Maximize(x - 2 * y + 3) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add(2 * x - y == 1) + model.maximize(x - 2 * y + 3) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(-4, solver.Value(x)) - self.assertEqual(-9, solver.Value(y)) - self.assertEqual(17, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(-4, solver.value(x)) + self.assertEqual(-9, solver.value(y)) + self.assertEqual(17, solver.objective_value) def testMinimizeConstant(self): print("testMinimizeConstant") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(x >= -1) - model.Minimize(10) + x = model.new_int_var(-10, 10, "x") + model.add(x >= -1) + model.minimize(10) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(10, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(10, solver.objective_value) def testMaximizeConstant(self): print("testMinimizeConstant") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(x >= -1) - model.Maximize(5) + x = model.new_int_var(-10, 10, "x") + model.add(x >= -1) + model.maximize(5) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(5, solver.ObjectiveValue()) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(5, solver.objective_value) def testAddTrue(self): print("testAddTrue") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(3 >= -1) - model.Minimize(x) + x = model.new_int_var(-10, 10, "x") + model.add(3 >= -1) + model.minimize(x) solver = cp_model.CpSolver() - self.assertEqual("OPTIMAL", solver.StatusName(solver.Solve(model))) - self.assertEqual(-10, solver.Value(x)) + self.assertEqual("OPTIMAL", solver.status_name(solver.solve(model))) + self.assertEqual(-10, solver.value(x)) def testAddFalse(self): print("testAddFalse") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - model.Add(3 <= -1) - model.Minimize(x) + x = model.new_int_var(-10, 10, "x") + model.add(3 <= -1) + model.minimize(x) solver = cp_model.CpSolver() - self.assertEqual("INFEASIBLE", solver.StatusName(solver.Solve(model))) + self.assertEqual("INFEASIBLE", solver.status_name(solver.solve(model))) def testSum(self): print("testSum") model = cp_model.CpModel() - x = [model.NewIntVar(0, 2, "x%i" % i) for i in range(100)] - model.Add(sum(x) <= 1) - model.Maximize(x[99]) + x = [model.new_int_var(0, 2, "x%i" % i) for i in range(100)] + model.add(sum(x) <= 1) + model.maximize(x[99]) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(1.0, solver.ObjectiveValue()) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(1.0, solver.objective_value) for i in range(100): - self.assertEqual(solver.Value(x[i]), 1 if i == 99 else 0) + self.assertEqual(solver.value(x[i]), 1 if i == 99 else 0) def testSumWithApi(self): print("testSumWithApi") model = cp_model.CpModel() - x = [model.NewIntVar(0, 2, "x%i" % i) for i in range(100)] - model.Add(cp_model.LinearExpr.Sum(x) <= 1) - model.Maximize(x[99]) + x = [model.new_int_var(0, 2, "x%i" % i) for i in range(100)] + model.add(cp_model.LinearExpr.sum(x) <= 1) + model.maximize(x[99]) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(1.0, solver.ObjectiveValue()) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(1.0, solver.objective_value) for i in range(100): - self.assertEqual(solver.Value(x[i]), 1 if i == 99 else 0) + self.assertEqual(solver.value(x[i]), 1 if i == 99 else 0) def testWeightedSum(self): print("testWeightedSum") model = cp_model.CpModel() - x = [model.NewIntVar(0, 2, "x%i" % i) for i in range(100)] + x = [model.new_int_var(0, 2, "x%i" % i) for i in range(100)] c = [2] * 100 - model.Add(cp_model.LinearExpr.WeightedSum(x, c) <= 3) - model.Maximize(x[99]) + model.add(cp_model.LinearExpr.weighted_sum(x, c) <= 3) + model.maximize(x[99]) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(1.0, solver.ObjectiveValue()) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(1.0, solver.objective_value) for i in range(100): - self.assertEqual(solver.Value(x[i]), 1 if i == 99 else 0) + self.assertEqual(solver.value(x[i]), 1 if i == 99 else 0) def testAllDifferent(self): print("testAllDifferent") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAllDifferent(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].all_diff.exprs, 5) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_all_different(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].all_diff.exprs, 5) def testAllDifferentGen(self): print("testAllDifferentGen") model = cp_model.CpModel() - model.AddAllDifferent(model.NewIntVar(0, 4, "x%i" % i) for i in range(5)) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].all_diff.exprs, 5) + model.add_all_different(model.new_int_var(0, 4, "x%i" % i) for i in range(5)) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].all_diff.exprs, 5) def testAllDifferentList(self): print("testAllDifferentList") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAllDifferent(x[0], x[1], x[2], x[3], x[4]) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].all_diff.exprs, 5) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_all_different(x[0], x[1], x[2], x[3], x[4]) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].all_diff.exprs, 5) def testElement(self): print("testElement") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddElement(x[0], [x[1], 2, 4, x[2]], x[4]) - self.assertLen(model.Proto().variables, 7) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].element.vars, 4) - self.assertEqual(0, model.Proto().constraints[0].element.index) - self.assertEqual(4, model.Proto().constraints[0].element.target) - self.assertRaises(ValueError, model.AddElement, x[0], [], x[4]) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_element(x[0], [x[1], 2, 4, x[2]], x[4]) + self.assertLen(model.proto.variables, 7) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].element.vars, 4) + self.assertEqual(0, model.proto.constraints[0].element.index) + self.assertEqual(4, model.proto.constraints[0].element.target) + self.assertRaises(ValueError, model.add_element, x[0], [], x[4]) def testCircuit(self): print("testCircuit") model = cp_model.CpModel() - x = [model.NewBoolVar(f"x{i}") for i in range(5)] - model.AddCircuit((i, i + 1, x[i]) for i in range(5)) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].circuit.heads, 5) - self.assertLen(model.Proto().constraints[0].circuit.tails, 5) - self.assertLen(model.Proto().constraints[0].circuit.literals, 5) - self.assertRaises(ValueError, model.AddCircuit, []) + x = [model.new_bool_var(f"x{i}") for i in range(5)] + model.add_circuit((i, i + 1, x[i]) for i in range(5)) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].circuit.heads, 5) + self.assertLen(model.proto.constraints[0].circuit.tails, 5) + self.assertLen(model.proto.constraints[0].circuit.literals, 5) + self.assertRaises(ValueError, model.add_circuit, []) def testMultipleCircuit(self): print("testMultipleCircuit") model = cp_model.CpModel() - x = [model.NewBoolVar(f"x{i}") for i in range(5)] - model.AddMultipleCircuit((i, i + 1, x[i]) for i in range(5)) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].routes.heads, 5) - self.assertLen(model.Proto().constraints[0].routes.tails, 5) - self.assertLen(model.Proto().constraints[0].routes.literals, 5) - self.assertRaises(ValueError, model.AddMultipleCircuit, []) + x = [model.new_bool_var(f"x{i}") for i in range(5)] + model.add_multiple_circuit((i, i + 1, x[i]) for i in range(5)) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].routes.heads, 5) + self.assertLen(model.proto.constraints[0].routes.tails, 5) + self.assertLen(model.proto.constraints[0].routes.literals, 5) + self.assertRaises(ValueError, model.add_multiple_circuit, []) def testAllowedAssignments(self): print("testAllowedAssignments") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAllowedAssignments( + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_allowed_assignments( x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0, 0)] ) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].table.vars, 5) - self.assertLen(model.Proto().constraints[0].table.values, 15) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].table.vars, 5) + self.assertLen(model.proto.constraints[0].table.values, 15) self.assertRaises( TypeError, - model.AddAllowedAssignments, + model.add_allowed_assignments, x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) self.assertRaises( ValueError, - model.AddAllowedAssignments, + model.add_allowed_assignments, [], [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) @@ -572,24 +581,24 @@ def testAllowedAssignments(self): def testForbiddenAssignments(self): print("testForbiddenAssignments") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddForbiddenAssignments( + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_forbidden_assignments( x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0, 0)] ) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].table.vars, 5) - self.assertLen(model.Proto().constraints[0].table.values, 15) - self.assertTrue(model.Proto().constraints[0].table.negated) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].table.vars, 5) + self.assertLen(model.proto.constraints[0].table.values, 15) + self.assertTrue(model.proto.constraints[0].table.negated) self.assertRaises( TypeError, - model.AddForbiddenAssignments, + model.add_forbidden_assignments, x, [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) self.assertRaises( ValueError, - model.AddForbiddenAssignments, + model.add_forbidden_assignments, [], [(0, 1, 2, 3, 4), (4, 3, 2, 1, 1), (0, 0, 0, 0)], ) @@ -597,19 +606,19 @@ def testForbiddenAssignments(self): def testAutomaton(self): print("testAutomaton") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - model.AddAutomaton(x, 0, [2, 3], [(0, 0, 0), (0, 1, 1), (1, 2, 2), (2, 3, 3)]) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].automaton.vars, 5) - self.assertLen(model.Proto().constraints[0].automaton.transition_tail, 4) - self.assertLen(model.Proto().constraints[0].automaton.transition_head, 4) - self.assertLen(model.Proto().constraints[0].automaton.transition_label, 4) - self.assertLen(model.Proto().constraints[0].automaton.final_states, 2) - self.assertEqual(0, model.Proto().constraints[0].automaton.starting_state) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + model.add_automaton(x, 0, [2, 3], [(0, 0, 0), (0, 1, 1), (1, 2, 2), (2, 3, 3)]) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].automaton.vars, 5) + self.assertLen(model.proto.constraints[0].automaton.transition_tail, 4) + self.assertLen(model.proto.constraints[0].automaton.transition_head, 4) + self.assertLen(model.proto.constraints[0].automaton.transition_label, 4) + self.assertLen(model.proto.constraints[0].automaton.final_states, 2) + self.assertEqual(0, model.proto.constraints[0].automaton.starting_state) self.assertRaises( TypeError, - model.AddAutomaton, + model.add_automaton, x, 0, [2, 3], @@ -617,93 +626,98 @@ def testAutomaton(self): ) self.assertRaises( ValueError, - model.AddAutomaton, + model.add_automaton, [], 0, [2, 3], [(0, 0, 0), (0, 1, 1), (2, 3, 3)], ) self.assertRaises( - ValueError, model.AddAutomaton, x, 0, [], [(0, 0, 0), (0, 1, 1), (2, 3, 3)] + ValueError, + model.add_automaton, + x, + 0, + [], + [(0, 0, 0), (0, 1, 1), (2, 3, 3)], ) - self.assertRaises(ValueError, model.AddAutomaton, x, 0, [2, 3], []) + self.assertRaises(ValueError, model.add_automaton, x, 0, [2, 3], []) def testInverse(self): print("testInverse") model = cp_model.CpModel() - x = [model.NewIntVar(0, 4, "x%i" % i) for i in range(5)] - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddInverse(x, y) - self.assertLen(model.Proto().variables, 10) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].inverse.f_direct, 5) - self.assertLen(model.Proto().constraints[0].inverse.f_inverse, 5) + x = [model.new_int_var(0, 4, "x%i" % i) for i in range(5)] + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_inverse(x, y) + self.assertLen(model.proto.variables, 10) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].inverse.f_direct, 5) + self.assertLen(model.proto.constraints[0].inverse.f_inverse, 5) def testMaxEquality(self): print("testMaxEquality") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMaxEquality(x, y) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_max_equality(x, y) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEquality(self): print("testMinEquality") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, y) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, y) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityList(self): print("testMinEqualityList") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, [y[0], y[2], y[1], y[3]]) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 4) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, [y[0], y[2], y[1], y[3]]) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 4) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityTuple(self): print("testMinEqualityTuple") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, (y[0], y[2], y[1], y[3])) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 4) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, (y[0], y[2], y[1], y[3])) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 4) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityGenerator(self): print("testMinEqualityGenerator") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMinEquality(x, (z for z in y)) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].lin_max.target.vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.target.coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_min_equality(x, (z for z in y)) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].lin_max.target.vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.target.coeffs[0]) def testMinEqualityWithConstant(self): print("testMinEqualityWithConstant") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 4, "y") - model.AddMinEquality(x, [y, 3]) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - lin_max = model.Proto().constraints[0].lin_max + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 4, "y") + model.add_min_equality(x, [y, 3]) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + lin_max = model.proto.constraints[0].lin_max self.assertLen(lin_max.exprs, 2) self.assertLen(lin_max.exprs[0].vars, 1) self.assertEqual(1, lin_max.exprs[0].vars[0]) @@ -715,16 +729,16 @@ def testMinEqualityWithConstant(self): def testAbs(self): print("testAbs") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(-5, 5, "y") - model.AddAbsEquality(x, y) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].lin_max.exprs, 2) - self.assertEqual(1, model.Proto().constraints[0].lin_max.exprs[0].vars[0]) - self.assertEqual(1, model.Proto().constraints[0].lin_max.exprs[0].coeffs[0]) - self.assertEqual(1, model.Proto().constraints[0].lin_max.exprs[1].vars[0]) - self.assertEqual(-1, model.Proto().constraints[0].lin_max.exprs[1].coeffs[0]) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(-5, 5, "y") + model.add_abs_equality(x, y) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].lin_max.exprs, 2) + self.assertEqual(1, model.proto.constraints[0].lin_max.exprs[0].vars[0]) + self.assertEqual(1, model.proto.constraints[0].lin_max.exprs[0].coeffs[0]) + self.assertEqual(1, model.proto.constraints[0].lin_max.exprs[1].vars[0]) + self.assertEqual(-1, model.proto.constraints[0].lin_max.exprs[1].coeffs[0]) passed = False error_msg = None try: @@ -734,7 +748,7 @@ def testAbs(self): passed = True self.assertEqual( "calling abs() on a linear expression is not supported, " - "please use CpModel.AddAbsEquality", + "please use CpModel.add_abs_equality", error_msg, ) self.assertTrue(passed) @@ -742,16 +756,16 @@ def testAbs(self): def testDivision(self): print("testDivision") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 50, "y") - model.AddDivisionEquality(x, y, 6) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].int_div.exprs, 2) - self.assertEqual(model.Proto().constraints[0].int_div.exprs[0].vars[0], 1) - self.assertEqual(model.Proto().constraints[0].int_div.exprs[0].coeffs[0], 1) - self.assertEmpty(model.Proto().constraints[0].int_div.exprs[1].vars) - self.assertEqual(model.Proto().constraints[0].int_div.exprs[1].offset, 6) + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 50, "y") + model.add_division_equality(x, y, 6) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].int_div.exprs, 2) + self.assertEqual(model.proto.constraints[0].int_div.exprs[0].vars[0], 1) + self.assertEqual(model.proto.constraints[0].int_div.exprs[0].coeffs[0], 1) + self.assertEmpty(model.proto.constraints[0].int_div.exprs[1].vars) + self.assertEqual(model.proto.constraints[0].int_div.exprs[1].offset, 6) passed = False error_msg = None try: @@ -761,7 +775,7 @@ def testDivision(self): passed = True self.assertEqual( "calling // on a linear expression is not supported, " - "please use CpModel.AddDivisionEquality", + "please use CpModel.add_division_equality", error_msg, ) self.assertTrue(passed) @@ -769,16 +783,16 @@ def testDivision(self): def testModulo(self): print("testModulo") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 50, "y") - model.AddModuloEquality(x, y, 6) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].int_mod.exprs, 2) - self.assertEqual(model.Proto().constraints[0].int_mod.exprs[0].vars[0], 1) - self.assertEqual(model.Proto().constraints[0].int_mod.exprs[0].coeffs[0], 1) - self.assertEmpty(model.Proto().constraints[0].int_mod.exprs[1].vars) - self.assertEqual(model.Proto().constraints[0].int_mod.exprs[1].offset, 6) + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 50, "y") + model.add_modulo_equality(x, y, 6) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].int_mod.exprs, 2) + self.assertEqual(model.proto.constraints[0].int_mod.exprs[0].vars[0], 1) + self.assertEqual(model.proto.constraints[0].int_mod.exprs[0].coeffs[0], 1) + self.assertEmpty(model.proto.constraints[0].int_mod.exprs[1].vars) + self.assertEqual(model.proto.constraints[0].int_mod.exprs[1].offset, 6) passed = False error_msg = None try: @@ -788,7 +802,7 @@ def testModulo(self): passed = True self.assertEqual( "calling %% on a linear expression is not supported, " - "please use CpModel.AddModuloEquality", + "please use CpModel.add_modulo_equality", error_msg, ) self.assertTrue(passed) @@ -796,223 +810,225 @@ def testModulo(self): def testMultiplicationEquality(self): print("testMultiplicationEquality") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = [model.NewIntVar(0, 4, "y%i" % i) for i in range(5)] - model.AddMultiplicationEquality(x, y) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].int_prod.exprs, 5) - self.assertEqual(0, model.Proto().constraints[0].int_prod.target.vars[0]) + x = model.new_int_var(0, 4, "x") + y = [model.new_int_var(0, 4, "y%i" % i) for i in range(5)] + model.add_multiplication_equality(x, y) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].int_prod.exprs, 5) + self.assertEqual(0, model.proto.constraints[0].int_prod.target.vars[0]) def testImplication(self): print("testImplication") model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - model.AddImplication(x, y) - self.assertLen(model.Proto().variables, 2) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 1) - self.assertLen(model.Proto().constraints[0].enforcement_literal, 1) - self.assertEqual(x.Index(), model.Proto().constraints[0].enforcement_literal[0]) - self.assertEqual(y.Index(), model.Proto().constraints[0].bool_or.literals[0]) + x = model.new_bool_var("x") + y = model.new_bool_var("y") + model.add_implication(x, y) + self.assertLen(model.proto.variables, 2) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_or.literals, 1) + self.assertLen(model.proto.constraints[0].enforcement_literal, 1) + self.assertEqual(x.index, model.proto.constraints[0].enforcement_literal[0]) + self.assertEqual(y.index, model.proto.constraints[0].bool_or.literals[0]) def testBoolOr(self): print("testBoolOr") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolOr(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 5) - model.AddBoolOr([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddBoolOr, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddBoolOr, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_or(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_or.literals, 5) + model.add_bool_or([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_bool_or, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_bool_or, [y, False]) def testBoolOrListOrGet(self): print("testBoolOrListOrGet") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolOr(x) - model.AddBoolOr(True, x[0], x[2]) - model.AddBoolOr(False, x[0]) - model.AddBoolOr(x[i] for i in [0, 2, 3, 4]) - self.assertLen(model.Proto().variables, 7) - self.assertLen(model.Proto().constraints, 4) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 5) - self.assertLen(model.Proto().constraints[1].bool_or.literals, 3) - self.assertLen(model.Proto().constraints[2].bool_or.literals, 2) - self.assertLen(model.Proto().constraints[3].bool_or.literals, 4) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_or(x) + model.add_bool_or(True, x[0], x[2]) + model.add_bool_or(False, x[0]) + model.add_bool_or(x[i] for i in [0, 2, 3, 4]) + self.assertLen(model.proto.variables, 7) + self.assertLen(model.proto.constraints, 4) + self.assertLen(model.proto.constraints[0].bool_or.literals, 5) + self.assertLen(model.proto.constraints[1].bool_or.literals, 3) + self.assertLen(model.proto.constraints[2].bool_or.literals, 2) + self.assertLen(model.proto.constraints[3].bool_or.literals, 4) def testAtLeastOne(self): print("testAtLeastOne") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddAtLeastOne(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_or.literals, 5) - model.AddAtLeastOne([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddAtLeastOne, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddAtLeastOne, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_at_least_one(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_or.literals, 5) + model.add_at_least_one([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_at_least_one, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_at_least_one, [y, False]) def testAtMostOne(self): print("testAtMostOne") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddAtMostOne(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].at_most_one.literals, 5) - model.AddAtMostOne([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddAtMostOne, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddAtMostOne, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_at_most_one(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].at_most_one.literals, 5) + model.add_at_most_one([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_at_most_one, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_at_most_one, [y, False]) def testExactlyOne(self): print("testExactlyOne") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddExactlyOne(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].exactly_one.literals, 5) - model.AddExactlyOne([x[0], x[1], False]) - self.assertLen(model.Proto().variables, 6) - self.assertRaises(TypeError, model.AddExactlyOne, [x[2], 2]) - y = model.NewIntVar(0, 4, "y") - self.assertRaises(TypeError, model.AddExactlyOne, [y, False]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_exactly_one(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].exactly_one.literals, 5) + model.add_exactly_one([x[0], x[1], False]) + self.assertLen(model.proto.variables, 6) + self.assertRaises(TypeError, model.add_exactly_one, [x[2], 2]) + y = model.new_int_var(0, 4, "y") + self.assertRaises(TypeError, model.add_exactly_one, [y, False]) def testBoolAnd(self): print("testBoolAnd") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolAnd(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_and.literals, 5) - model.AddBoolAnd([x[1], x[2].Not(), True]) - self.assertEqual(1, model.Proto().constraints[1].bool_and.literals[0]) - self.assertEqual(-3, model.Proto().constraints[1].bool_and.literals[1]) - self.assertEqual(5, model.Proto().constraints[1].bool_and.literals[2]) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_and(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_and.literals, 5) + model.add_bool_and([x[1], x[2].negated(), True]) + self.assertEqual(1, model.proto.constraints[1].bool_and.literals[0]) + self.assertEqual(-3, model.proto.constraints[1].bool_and.literals[1]) + self.assertEqual(5, model.proto.constraints[1].bool_and.literals[2]) def testBoolXOr(self): print("testBoolXOr") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - model.AddBoolXOr(x) - self.assertLen(model.Proto().variables, 5) - self.assertLen(model.Proto().constraints, 1) - self.assertLen(model.Proto().constraints[0].bool_xor.literals, 5) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + model.add_bool_xor(x) + self.assertLen(model.proto.variables, 5) + self.assertLen(model.proto.constraints, 1) + self.assertLen(model.proto.constraints[0].bool_xor.literals, 5) def testMapDomain(self): print("testMapDomain") model = cp_model.CpModel() - x = [model.NewBoolVar("x%i" % i) for i in range(5)] - y = model.NewIntVar(0, 10, "y") - model.AddMapDomain(y, x, 2) - self.assertLen(model.Proto().variables, 6) - self.assertLen(model.Proto().constraints, 10) + x = [model.new_bool_var("x%i" % i) for i in range(5)] + y = model.new_int_var(0, 10, "y") + model.add_map_domain(y, x, 2) + self.assertLen(model.proto.variables, 6) + self.assertLen(model.proto.constraints, 10) def testInterval(self): print("testInterval") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - i = model.NewIntervalVar(x, 3, y, "i") - self.assertEqual(1, i.Index()) - - j = model.NewFixedSizeIntervalVar(x, 2, "j") - self.assertEqual(2, j.Index()) - start_expr = j.StartExpr() - size_expr = j.SizeExpr() - end_expr = j.EndExpr() - self.assertEqual(x.Index(), start_expr.Index()) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + i = model.new_interval_var(x, 3, y, "i") + self.assertEqual(1, i.index) + + j = model.new_fixed_size_interval_var(x, 2, "j") + self.assertEqual(2, j.index) + start_expr = j.start_expr() + size_expr = j.size_expr() + end_expr = j.end_expr() + self.assertEqual(x.index, start_expr.index) self.assertEqual(size_expr, 2) self.assertEqual(str(end_expr), "(x + 2)") def testOptionalInterval(self): print("testOptionalInterval") model = cp_model.CpModel() - b = model.NewBoolVar("b") - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - i = model.NewOptionalIntervalVar(x, 3, y, b, "i") - j = model.NewOptionalIntervalVar(x, y, 10, b, "j") - k = model.NewOptionalIntervalVar(x, -y, 10, b, "k") - l = model.NewOptionalIntervalVar(x, 10, -y, b, "l") - self.assertEqual(1, i.Index()) - self.assertEqual(3, j.Index()) - self.assertEqual(5, k.Index()) - self.assertEqual(7, l.Index()) - self.assertRaises(TypeError, model.NewOptionalIntervalVar, 1, 2, 3, x, "x") - self.assertRaises(TypeError, model.NewOptionalIntervalVar, b + x, 2, 3, b, "x") + b = model.new_bool_var("b") + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + i = model.new_optional_interval_var(x, 3, y, b, "i") + j = model.new_optional_interval_var(x, y, 10, b, "j") + k = model.new_optional_interval_var(x, -y, 10, b, "k") + l = model.new_optional_interval_var(x, 10, -y, b, "l") + self.assertEqual(1, i.index) + self.assertEqual(3, j.index) + self.assertEqual(5, k.index) + self.assertEqual(7, l.index) + self.assertRaises(TypeError, model.new_optional_interval_var, 1, 2, 3, x, "x") + self.assertRaises( + TypeError, model.new_optional_interval_var, b + x, 2, 3, b, "x" + ) self.assertRaises( - AttributeError, model.NewOptionalIntervalVar, 1, 2, 3, b + 1, "x" + AttributeError, model.new_optional_interval_var, 1, 2, 3, b + 1, "x" ) def testNoOverlap(self): print("testNoOverlap") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - z = model.NewIntVar(0, 3, "y") - i = model.NewIntervalVar(x, 3, y, "i") - j = model.NewIntervalVar(x, 5, z, "j") - ct = model.AddNoOverlap([i, j]) - self.assertEqual(4, ct.Index()) - self.assertLen(ct.Proto().no_overlap.intervals, 2) - self.assertEqual(1, ct.Proto().no_overlap.intervals[0]) - self.assertEqual(3, ct.Proto().no_overlap.intervals[1]) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + z = model.new_int_var(0, 3, "y") + i = model.new_interval_var(x, 3, y, "i") + j = model.new_interval_var(x, 5, z, "j") + ct = model.add_no_overlap([i, j]) + self.assertEqual(4, ct.index) + self.assertLen(ct.proto.no_overlap.intervals, 2) + self.assertEqual(1, ct.proto.no_overlap.intervals[0]) + self.assertEqual(3, ct.proto.no_overlap.intervals[1]) def testNoOverlap2D(self): print("testNoOverlap2D") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - z = model.NewIntVar(0, 3, "y") - i = model.NewIntervalVar(x, 3, y, "i") - j = model.NewIntervalVar(x, 5, z, "j") - ct = model.AddNoOverlap2D([i, j], [j, i]) - self.assertEqual(4, ct.Index()) - self.assertLen(ct.Proto().no_overlap_2d.x_intervals, 2) - self.assertEqual(1, ct.Proto().no_overlap_2d.x_intervals[0]) - self.assertEqual(3, ct.Proto().no_overlap_2d.x_intervals[1]) - self.assertLen(ct.Proto().no_overlap_2d.y_intervals, 2) - self.assertEqual(3, ct.Proto().no_overlap_2d.y_intervals[0]) - self.assertEqual(1, ct.Proto().no_overlap_2d.y_intervals[1]) + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + z = model.new_int_var(0, 3, "y") + i = model.new_interval_var(x, 3, y, "i") + j = model.new_interval_var(x, 5, z, "j") + ct = model.add_no_overlap_2d([i, j], [j, i]) + self.assertEqual(4, ct.index) + self.assertLen(ct.proto.no_overlap_2d.x_intervals, 2) + self.assertEqual(1, ct.proto.no_overlap_2d.x_intervals[0]) + self.assertEqual(3, ct.proto.no_overlap_2d.x_intervals[1]) + self.assertLen(ct.proto.no_overlap_2d.y_intervals, 2) + self.assertEqual(3, ct.proto.no_overlap_2d.y_intervals[0]) + self.assertEqual(1, ct.proto.no_overlap_2d.y_intervals[1]) def testCumulative(self): print("testCumulative") model = cp_model.CpModel() intervals = [ - model.NewIntervalVar( - model.NewIntVar(0, 10, f"s_{i}"), + model.new_interval_var( + model.new_int_var(0, 10, f"s_{i}"), 5, - model.NewIntVar(5, 15, f"e_{i}"), + model.new_int_var(5, 15, f"e_{i}"), f"interval[{i}]", ) for i in range(10) ] demands = [1, 3, 5, 2, 4, 5, 3, 4, 2, 3] capacity = 4 - ct = model.AddCumulative(intervals, demands, capacity) - self.assertEqual(20, ct.Index()) - self.assertLen(ct.Proto().cumulative.intervals, 10) - self.assertRaises(TypeError, model.AddCumulative, [intervals[0], 3], [2, 3], 3) + ct = model.add_cumulative(intervals, demands, capacity) + self.assertEqual(20, ct.index) + self.assertLen(ct.proto.cumulative.intervals, 10) + self.assertRaises(TypeError, model.add_cumulative, [intervals[0], 3], [2, 3], 3) def testGetOrMakeIndexFromConstant(self): print("testGetOrMakeIndexFromConstant") model = cp_model.CpModel() - self.assertEqual(0, model.GetOrMakeIndexFromConstant(3)) - self.assertEqual(0, model.GetOrMakeIndexFromConstant(3)) - self.assertEqual(1, model.GetOrMakeIndexFromConstant(5)) - model_var = model.Proto().variables[0] + self.assertEqual(0, model.get_or_make_index_from_constant(3)) + self.assertEqual(0, model.get_or_make_index_from_constant(3)) + self.assertEqual(1, model.get_or_make_index_from_constant(5)) + model_var = model.proto.variables[0] self.assertLen(model_var.domain, 2) self.assertEqual(3, model_var.domain[0]) self.assertEqual(3, model_var.domain[1]) @@ -1020,7 +1036,7 @@ def testGetOrMakeIndexFromConstant(self): def testStr(self): print("testStr") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") + x = model.new_int_var(0, 4, "x") self.assertEqual(str(x == 2), "x == 2") self.assertEqual(str(x >= 2), "x >= 2") self.assertEqual(str(x <= 2), "x <= 2") @@ -1033,71 +1049,71 @@ def testStr(self): self.assertEqual(str(x <= cp_model.INT_MAX), "True (unbounded expr x)") self.assertEqual(str(x != 9223372036854775807), "x <= 9223372036854775806") self.assertEqual(str(x != -9223372036854775808), "x >= -9223372036854775807") - y = model.NewIntVar(0, 4, "y") + y = model.new_int_var(0, 4, "y") self.assertEqual( - str(cp_model.LinearExpr.WeightedSum([x, y + 1, 2], [1, -2, 3])), + str(cp_model.LinearExpr.weighted_sum([x, y + 1, 2], [1, -2, 3])), "x - 2 * (y + 1) + 6", ) - self.assertEqual(str(cp_model.LinearExpr.Term(x, 3)), "(3 * x)") + self.assertEqual(str(cp_model.LinearExpr.term(x, 3)), "(3 * x)") self.assertEqual(str(x != y), "(x + -y) != 0") self.assertEqual( "0 <= x <= 10", str(cp_model.BoundedLinearExpression(x, [0, 10])) ) print(str(model)) - b = model.NewBoolVar("b") - self.assertEqual(str(cp_model.LinearExpr.Term(b.Not(), 3)), "(3 * not(b))") + b = model.new_bool_var("b") + self.assertEqual(str(cp_model.LinearExpr.term(b.negated(), 3)), "(3 * not(b))") - i = model.NewIntervalVar(x, 2, y, "i") + i = model.new_interval_var(x, 2, y, "i") self.assertEqual(str(i), "i") def testRepr(self): print("testRepr") model = cp_model.CpModel() - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - z = model.NewIntVar(0, 3, "z") + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + z = model.new_int_var(0, 3, "z") self.assertEqual(repr(x), "x(0..4)") self.assertEqual(repr(x * 2), "ProductCst(x(0..4), 2)") - self.assertEqual(repr(x + y), "Sum(x(0..4), y(0..3))") + self.assertEqual(repr(x + y), "sum(x(0..4), y(0..3))") self.assertEqual( - repr(cp_model.LinearExpr.Sum([x, y, z])), + repr(cp_model.LinearExpr.sum([x, y, z])), "SumArray(x(0..4), y(0..3), z(0..3), 0)", ) self.assertEqual( - repr(cp_model.LinearExpr.WeightedSum([x, y, 2], [1, 2, 3])), - "WeightedSum([x(0..4), y(0..3)], [1, 2], 6)", + repr(cp_model.LinearExpr.weighted_sum([x, y, 2], [1, 2, 3])), + "weighted_sum([x(0..4), y(0..3)], [1, 2], 6)", ) - i = model.NewIntervalVar(x, 2, y, "i") + i = model.new_interval_var(x, 2, y, "i") self.assertEqual(repr(i), "i(start = x, size = 2, end = y)") - b = model.NewBoolVar("b") - x1 = model.NewIntVar(0, 4, "x1") - y1 = model.NewIntVar(0, 3, "y1") - j = model.NewOptionalIntervalVar(x1, 2, y1, b, "j") + b = model.new_bool_var("b") + x1 = model.new_int_var(0, 4, "x1") + y1 = model.new_int_var(0, 3, "y1") + j = model.new_optional_interval_var(x1, 2, y1, b, "j") self.assertEqual(repr(j), "j(start = x1, size = 2, end = y1, is_present = b)") - x2 = model.NewIntVar(0, 4, "x2") - y2 = model.NewIntVar(0, 3, "y2") - k = model.NewOptionalIntervalVar(x2, 2, y2, b.Not(), "k") + x2 = model.new_int_var(0, 4, "x2") + y2 = model.new_int_var(0, 3, "y2") + k = model.new_optional_interval_var(x2, 2, y2, b.negated(), "k") self.assertEqual( - repr(k), "k(start = x2, size = 2, end = y2, is_present = Not(b))" + repr(k), "k(start = x2, size = 2, end = y2, is_present = not(b))" ) def testDisplayBounds(self): print("testDisplayBounds") - self.assertEqual("10..20", cp_model.DisplayBounds([10, 20])) - self.assertEqual("10", cp_model.DisplayBounds([10, 10])) - self.assertEqual("10..15, 20..30", cp_model.DisplayBounds([10, 15, 20, 30])) + self.assertEqual("10..20", cp_model.display_bounds([10, 20])) + self.assertEqual("10", cp_model.display_bounds([10, 10])) + self.assertEqual("10..15, 20..30", cp_model.display_bounds([10, 15, 20, 30])) def testShortName(self): print("testShortName") model = cp_model.CpModel() - model.Proto().variables.add(domain=[5, 10]) - self.assertEqual("[5..10]", cp_model.ShortName(model.Proto(), 0)) + model.proto.variables.add(domain=[5, 10]) + self.assertEqual("[5..10]", cp_model.short_name(model.proto, 0)) def testIntegerExpressionErrors(self): print("testIntegerExpressionErrors") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(0, 3, "y") + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(0, 3, "y") self.assertRaises(TypeError, x.__mul__, y) self.assertRaises(NotImplementedError, x.__div__, y) self.assertRaises(NotImplementedError, x.__truediv__, y) @@ -1116,118 +1132,116 @@ def testIntegerExpressionErrors(self): def testModelErrors(self): print("testModelErrors") model = cp_model.CpModel() - self.assertRaises(TypeError, model.Add, "dummy") - self.assertRaises(TypeError, model.GetOrMakeIndex, "dummy") - self.assertRaises(TypeError, model.Minimize, "dummy") + self.assertRaises(TypeError, model.add, "dummy") + self.assertRaises(TypeError, model.get_or_make_index, "dummy") + self.assertRaises(TypeError, model.minimize, "dummy") def testSolverErrors(self): print("testSolverErrors") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - model.Minimize(y) + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + model.minimize(y) solver = cp_model.CpSolver() - self.assertRaises(RuntimeError, solver.Value, x) - solver.Solve(model) - self.assertRaises(TypeError, solver.Value, "not_a_variable") - self.assertRaises(TypeError, model.AddBoolOr, [x, y]) + self.assertRaises(RuntimeError, solver.value, x) + solver.solve(model) + self.assertRaises(TypeError, solver.value, "not_a_variable") + self.assertRaises(TypeError, model.add_bool_or, [x, y]) def testHasObjectiveMinimize(self): print("testHasObjectiveMinimizs") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - self.assertFalse(model.HasObjective()) - model.Minimize(y) - self.assertTrue(model.HasObjective()) + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + self.assertFalse(model.has_objective()) + model.minimize(y) + self.assertTrue(model.has_objective()) def testHasObjectiveMaximize(self): print("testHasObjectiveMaximizs") model = cp_model.CpModel() - x = model.NewIntVar(0, 1, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - self.assertFalse(model.HasObjective()) - model.Maximize(y) - self.assertTrue(model.HasObjective()) + x = model.new_int_var(0, 1, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + self.assertFalse(model.has_objective()) + model.maximize(y) + self.assertTrue(model.has_objective()) def testSearchForAllSolutions(self): print("testSearchForAllSolutions") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) solver = cp_model.CpSolver() + solver.parameters.enumerate_all_solutions = True solution_counter = SolutionCounter() - status = solver.SearchForAllSolutions(model, solution_counter) + status = solver.solve(model, solution_counter) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(5, solution_counter.SolutionCount()) - model.Minimize(x) - self.assertRaises( - TypeError, solver.SearchForAllSolutions, model, solution_counter - ) + self.assertEqual(5, solution_counter.solution_count) + model.minimize(x) def testSolveWithSolutionCallback(self): print("testSolveWithSolutionCallback") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) solver = cp_model.CpSolver() solution_sum = SolutionSum([x, y]) - self.assertRaises(RuntimeError, solution_sum.Value, x) - status = solver.SolveWithSolutionCallback(model, solution_sum) + self.assertRaises(RuntimeError, solution_sum.value, x) + status = solver.solve(model, solution_sum) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(6, solution_sum.Sum()) + self.assertEqual(6, solution_sum.sum) def testValue(self): print("testValue") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - model.Add(x + 2 * y == 29) + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + model.add(x + 2 * y == 29) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(solver.Value(x), 9) - self.assertEqual(solver.Value(y), 10) - self.assertEqual(solver.Value(2), 2) + self.assertEqual(solver.value(x), 9) + self.assertEqual(solver.value(y), 10) + self.assertEqual(solver.value(2), 2) def testBooleanValue(self): print("testBooleanValue") model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - z = model.NewBoolVar("z") - model.AddBoolOr([x, z.Not()]) - model.AddBoolOr([x, z]) - model.AddBoolOr([x.Not(), y.Not()]) + x = model.new_bool_var("x") + y = model.new_bool_var("y") + z = model.new_bool_var("z") + model.add_bool_or([x, z.negated()]) + model.add_bool_or([x, z]) + model.add_bool_or([x.negated(), y.negated()]) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(solver.BooleanValue(x), True) - self.assertEqual(solver.Value(x), 1 - solver.Value(x.Not())) - self.assertEqual(solver.Value(y), 1 - solver.Value(y.Not())) - self.assertEqual(solver.Value(z), 1 - solver.Value(z.Not())) - self.assertEqual(solver.BooleanValue(y), False) - self.assertEqual(solver.BooleanValue(True), True) - self.assertEqual(solver.BooleanValue(False), False) - self.assertEqual(solver.BooleanValue(2), True) - self.assertEqual(solver.BooleanValue(0), False) + self.assertEqual(solver.boolean_value(x), True) + self.assertEqual(solver.value(x), 1 - solver.value(x.negated())) + self.assertEqual(solver.value(y), 1 - solver.value(y.negated())) + self.assertEqual(solver.value(z), 1 - solver.value(z.negated())) + self.assertEqual(solver.boolean_value(y), False) + self.assertEqual(solver.boolean_value(True), True) + self.assertEqual(solver.boolean_value(False), False) + self.assertEqual(solver.boolean_value(2), True) + self.assertEqual(solver.boolean_value(0), False) def testUnsupportedOperators(self): print("testUnsupportedOperators") model = cp_model.CpModel() - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - z = model.NewIntVar(0, 10, "z") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + z = model.new_int_var(0, 10, "z") with self.assertRaises(NotImplementedError): - model.Add(x == min(y, z)) + model.add(x == min(y, z)) with self.assertRaises(NotImplementedError): if x > y: print("passed1") @@ -1238,76 +1252,76 @@ def testUnsupportedOperators(self): def testIsLiteralTrueFalse(self): print("testIsLiteralTrueFalse") model = cp_model.CpModel() - x = model.NewConstant(0) - self.assertFalse(cp_model.ObjectIsATrueLiteral(x)) - self.assertTrue(cp_model.ObjectIsAFalseLiteral(x)) - self.assertTrue(cp_model.ObjectIsATrueLiteral(x.Not())) - self.assertFalse(cp_model.ObjectIsAFalseLiteral(x.Not())) - self.assertTrue(cp_model.ObjectIsATrueLiteral(True)) - self.assertTrue(cp_model.ObjectIsAFalseLiteral(False)) - self.assertFalse(cp_model.ObjectIsATrueLiteral(False)) - self.assertFalse(cp_model.ObjectIsAFalseLiteral(True)) + x = model.new_constant(0) + self.assertFalse(cp_model.object_is_a_true_literal(x)) + self.assertTrue(cp_model.object_is_a_false_literal(x)) + self.assertTrue(cp_model.object_is_a_true_literal(x.negated())) + self.assertFalse(cp_model.object_is_a_false_literal(x.negated())) + self.assertTrue(cp_model.object_is_a_true_literal(True)) + self.assertTrue(cp_model.object_is_a_false_literal(False)) + self.assertFalse(cp_model.object_is_a_true_literal(False)) + self.assertFalse(cp_model.object_is_a_false_literal(True)) def testSolveMinimizeWithSolutionCallback(self): print("testSolveMinimizeWithSolutionCallback") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.Maximize(x + 2 * y) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.maximize(x + 2 * y) solver = cp_model.CpSolver() solution_obj = SolutionObjective() - status = solver.SolveWithSolutionCallback(model, solution_obj) + status = solver.solve(model, solution_obj) self.assertEqual(cp_model.OPTIMAL, status) - print("obj = ", solution_obj.Obj()) - self.assertEqual(11, solution_obj.Obj()) + print("obj = ", solution_obj.obj) + self.assertEqual(11, solution_obj.obj) def testSolutionHinting(self): print("testSolutionHinting") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.AddHint(x, 2) - model.AddHint(y, 4) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.add_hint(x, 2) + model.add_hint(y, 4) solver = cp_model.CpSolver() solver.parameters.cp_model_presolve = False - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(2, solver.Value(x)) - self.assertEqual(4, solver.Value(y)) + self.assertEqual(2, solver.value(x)) + self.assertEqual(4, solver.value(y)) def testStats(self): print("testStats") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 4, 6) - model.AddLinearConstraint(2 * x + y, 0, 10) - model.Maximize(x + 2 * y) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 4, 6) + model.add_linear_constraint(2 * x + y, 0, 10) + model.maximize(x + 2 * y) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) self.assertEqual(cp_model.OPTIMAL, status) - self.assertEqual(solver.NumBooleans(), 0) - self.assertEqual(solver.NumConflicts(), 0) - self.assertEqual(solver.NumBranches(), 0) - self.assertGreater(solver.WallTime(), 0.0) + self.assertEqual(solver.num_booleans, 0) + self.assertEqual(solver.num_conflicts, 0) + self.assertEqual(solver.num_branches, 0) + self.assertGreater(solver.wall_time, 0.0) def testSearchStrategy(self): print("testSearchStrategy") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddDecisionStrategy( + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_decision_strategy( [y, x], cp_model.CHOOSE_MIN_DOMAIN_SIZE, cp_model.SELECT_MAX_VALUE ) - self.assertLen(model.Proto().search_strategy, 1) - strategy = model.Proto().search_strategy[0] + self.assertLen(model.proto.search_strategy, 1) + strategy = model.proto.search_strategy[0] self.assertLen(strategy.variables, 2) - self.assertEqual(y.Index(), strategy.variables[0]) - self.assertEqual(x.Index(), strategy.variables[1]) + self.assertEqual(y.index, strategy.variables[0]) + self.assertEqual(x.index, strategy.variables[1]) self.assertEqual( cp_model.CHOOSE_MIN_DOMAIN_SIZE, strategy.variable_selection_strategy ) @@ -1316,181 +1330,181 @@ def testSearchStrategy(self): def testModelAndResponseStats(self): print("testStats") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.Maximize(x + 2 * y) - self.assertTrue(model.ModelStats()) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.maximize(x + 2 * y) + self.assertTrue(model.model_stats()) solver = cp_model.CpSolver() - solver.Solve(model) - self.assertTrue(solver.ResponseStats()) + solver.solve(model) + self.assertTrue(solver.response_stats()) def testValidateModel(self): print("testValidateModel") model = cp_model.CpModel() - x = model.NewIntVar(0, 5, "x") - y = model.NewIntVar(0, 5, "y") - model.AddLinearConstraint(x + y, 6, 6) - model.Maximize(x + 2 * y) - self.assertFalse(model.Validate()) + x = model.new_int_var(0, 5, "x") + y = model.new_int_var(0, 5, "y") + model.add_linear_constraint(x + y, 6, 6) + model.maximize(x + 2 * y) + self.assertFalse(model.validate()) def testValidateModelWithOverflow(self): print("testValidateModel") model = cp_model.CpModel() - x = model.NewIntVar(0, cp_model.INT_MAX, "x") - y = model.NewIntVar(0, 10, "y") - model.AddLinearConstraint(x + y, 6, cp_model.INT_MAX) - model.Maximize(x + 2 * y) - self.assertTrue(model.Validate()) + x = model.new_int_var(0, cp_model.INT_MAX, "x") + y = model.new_int_var(0, 10, "y") + model.add_linear_constraint(x + y, 6, cp_model.INT_MAX) + model.maximize(x + 2 * y) + self.assertTrue(model.validate()) def testCopyModel(self): print("testCopyModel") model = cp_model.CpModel() - b = model.NewBoolVar("b") - x = model.NewIntVar(0, 4, "x") - y = model.NewIntVar(0, 3, "y") - i = model.NewOptionalIntervalVar(x, 12, y, b, "i") - lin = model.Add(x + y <= 10) + b = model.new_bool_var("b") + x = model.new_int_var(0, 4, "x") + y = model.new_int_var(0, 3, "y") + i = model.new_optional_interval_var(x, 12, y, b, "i") + lin = model.add(x + y <= 10) - new_model = model.Clone() - copy_b = new_model.GetBoolVarFromProtoIndex(b.Index()) - copy_x = new_model.GetIntVarFromProtoIndex(x.Index()) - copy_y = new_model.GetIntVarFromProtoIndex(y.Index()) - copy_i = new_model.GetIntervalVarFromProtoIndex(i.Index()) + new_model = model.clone() + copy_b = new_model.get_bool_var_from_proto_index(b.index) + copy_x = new_model.get_int_var_from_proto_index(x.index) + copy_y = new_model.get_int_var_from_proto_index(y.index) + copy_i = new_model.get_interval_var_from_proto_index(i.index) - self.assertEqual(b.Index(), copy_b.Index()) - self.assertEqual(x.Index(), copy_x.Index()) - self.assertEqual(y.Index(), copy_y.Index()) - self.assertEqual(i.Index(), copy_i.Index()) + self.assertEqual(b.index, copy_b.index) + self.assertEqual(x.index, copy_x.index) + self.assertEqual(y.index, copy_y.index) + self.assertEqual(i.index, copy_i.index) with self.assertRaises(ValueError): - new_model.GetBoolVarFromProtoIndex(-1) + new_model.get_bool_var_from_proto_index(-1) with self.assertRaises(ValueError): - new_model.GetIntVarFromProtoIndex(-1) + new_model.get_int_var_from_proto_index(-1) with self.assertRaises(ValueError): - new_model.GetIntervalVarFromProtoIndex(-1) + new_model.get_interval_var_from_proto_index(-1) with self.assertRaises(ValueError): - new_model.GetBoolVarFromProtoIndex(x.Index()) + new_model.get_bool_var_from_proto_index(x.index) with self.assertRaises(ValueError): - new_model.GetIntervalVarFromProtoIndex(lin.Index()) + new_model.get_interval_var_from_proto_index(lin.index) - interval_ct = new_model.Proto().constraints[copy_i.Index()].interval + interval_ct = new_model.proto.constraints[copy_i.index].interval self.assertEqual(12, interval_ct.size.offset) def testCustomLog(self): print("testCustomLog") model = cp_model.CpModel() - x = model.NewIntVar(-10, 10, "x") - y = model.NewIntVar(-10, 10, "y") - model.AddLinearConstraint(x + 2 * y, 0, 10) - model.Minimize(y) + x = model.new_int_var(-10, 10, "x") + y = model.new_int_var(-10, 10, "y") + model.add_linear_constraint(x + 2 * y, 0, 10) + model.minimize(y) solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.log_to_stdout = False log_callback = LogToString() - solver.log_callback = log_callback.NewMessage + solver.log_callback = log_callback.new_message - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - self.assertEqual(10, solver.Value(x)) - self.assertEqual(-5, solver.Value(y)) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + self.assertEqual(10, solver.value(x)) + self.assertEqual(-5, solver.value(y)) - self.assertRegex(log_callback.Log(), ".*log_to_stdout.*") + self.assertRegex(log_callback.log, ".*log_to_stdout.*") def testIssue2762(self): print("testIssue2762") model = cp_model.CpModel() - x = [model.NewBoolVar("a"), model.NewBoolVar("b")] + x = [model.new_bool_var("a"), model.new_bool_var("b")] with self.assertRaises(NotImplementedError): - model.Add((x[0] != 0) or (x[1] != 0)) + model.add((x[0] != 0) or (x[1] != 0)) def testModelError(self): print("TestModelError") model = cp_model.CpModel() - x = [model.NewIntVar(0, -2, "x%i" % i) for i in range(100)] - model.Add(sum(x) <= 1) + x = [model.new_int_var(0, -2, "x%i" % i) for i in range(100)] + model.add(sum(x) <= 1) solver = cp_model.CpSolver() solver.parameters.log_search_progress = True - self.assertEqual(cp_model.MODEL_INVALID, solver.Solve(model)) - self.assertEqual(solver.SolutionInfo(), 'var #0 has no domain(): name: "x0"') + self.assertEqual(cp_model.MODEL_INVALID, solver.solve(model)) + self.assertEqual(solver.solution_info(), 'var #0 has no domain(): name: "x0"') def testIntVarSeries(self): print("testIntVarSeries") df = pd.DataFrame([1, -1, 1], columns=["coeffs"]) model = cp_model.CpModel() - x = model.NewIntVarSeries( + x = model.new_int_var_series( name="x", index=df.index, lower_bounds=0, upper_bounds=5 ) - model.Minimize(df.coeffs.dot(x)) + model.minimize(df.coeffs.dot(x)) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - solution = solver.Values(x) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + solution = solver.values(x) self.assertTrue((solution.values == [0, 5, 0]).all()) def testBoolVarSeries(self): print("testBoolVarSeries") df = pd.DataFrame([1, -1, 1], columns=["coeffs"]) model = cp_model.CpModel() - x = model.NewBoolVarSeries(name="x", index=df.index) - model.Minimize(df.coeffs.dot(x)) + x = model.new_bool_var_series(name="x", index=df.index) + model.minimize(df.coeffs.dot(x)) solver = cp_model.CpSolver() - self.assertEqual(cp_model.OPTIMAL, solver.Solve(model)) - solution = solver.BooleanValues(x) + self.assertEqual(cp_model.OPTIMAL, solver.solve(model)) + solution = solver.boolean_values(x) self.assertTrue((solution.values == [False, True, False]).all()) def testFixedSizeIntervalVarSeries(self): print("testFixedSizeIntervalVarSeries") df = pd.DataFrame([2, 4, 6], columns=["size"]) model = cp_model.CpModel() - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", index=df.index, lower_bounds=0, upper_bounds=5 ) - presences = model.NewBoolVarSeries(name="rresences", index=df.index) - fixed_size_intervals = model.NewFixedSizeIntervalVarSeries( + presences = model.new_bool_var_series(name="rresences", index=df.index) + fixed_size_intervals = model.new_fixed_size_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, sizes=df.size, ) - opt_fixed_size_intervals = model.NewOptionalFixedSizeIntervalVarSeries( + opt_fixed_size_intervals = model.new_optional_fixed_size_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, sizes=df.size, are_present=presences, ) - model.AddNoOverlap( + model.add_no_overlap( fixed_size_intervals.to_list() + opt_fixed_size_intervals.to_list() ) - self.assertLen(model.Proto().constraints, 7) + self.assertLen(model.proto.constraints, 7) def testIntervalVarSeries(self): print("testIntervalVarSeries") df = pd.DataFrame([2, 4, 6], columns=["size"]) model = cp_model.CpModel() - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", index=df.index, lower_bounds=0, upper_bounds=5 ) - sizes = model.NewIntVarSeries( + sizes = model.new_int_var_series( name="sizes", index=df.index, lower_bounds=2, upper_bounds=4 ) - ends = model.NewIntVarSeries( + ends = model.new_int_var_series( name="ends", index=df.index, lower_bounds=0, upper_bounds=10 ) - presences = model.NewBoolVarSeries(name="rresences", index=df.index) - intervals = model.NewIntervalVarSeries( + presences = model.new_bool_var_series(name="rresences", index=df.index) + intervals = model.new_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, sizes=sizes, ends=ends, ) - opt_intervals = model.NewOptionalIntervalVarSeries( + opt_intervals = model.new_optional_interval_var_series( name="fixed_size_intervals", index=df.index, starts=starts, @@ -1498,8 +1512,8 @@ def testIntervalVarSeries(self): ends=ends, are_present=presences, ) - model.AddNoOverlap(intervals.to_list() + opt_intervals.to_list()) - self.assertLen(model.Proto().constraints, 13) + model.add_no_overlap(intervals.to_list() + opt_intervals.to_list()) + self.assertLen(model.proto.constraints, 13) if __name__ == "__main__": diff --git a/ortools/sat/python/swig_helper.cc b/ortools/sat/python/swig_helper.cc index 1612d6dedd5..073c5800fe8 100644 --- a/ortools/sat/python/swig_helper.cc +++ b/ortools/sat/python/swig_helper.cc @@ -28,7 +28,9 @@ #include "absl/strings/string_view.h" #include "ortools/sat/cp_model.pb.h" #include "ortools/util/sorted_interval_list.h" +#include "pybind11/cast.h" #include "pybind11/functional.h" +#include "pybind11/gil.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" #include "pybind11_protobuf/native_proto_caster.h" @@ -86,27 +88,28 @@ PYBIND11_MODULE(swig_helper, m) { pybind11::class_(m, "SolveWrapper") .def(pybind11::init<>()) - .def("AddLogCallback", &SolveWrapper::AddLogCallback, arg("log_callback")) - .def("AddSolutionCallback", &SolveWrapper::AddSolutionCallback, + .def("add_log_callback", &SolveWrapper::AddLogCallback, + arg("log_callback")) + .def("add_solution_callback", &SolveWrapper::AddSolutionCallback, arg("callback")) - .def("ClearSolutionCallback", &SolveWrapper::ClearSolutionCallback) - .def("SetParameters", &SolveWrapper::SetParameters, arg("parameters")) - .def("Solve", + .def("clear_solution_callback", &SolveWrapper::ClearSolutionCallback) + .def("set_parameters", &SolveWrapper::SetParameters, arg("parameters")) + .def("solve", [](SolveWrapper* solve_wrapper, const CpModelProto& model_proto) -> CpSolverResponse { ::pybind11::gil_scoped_release release; return solve_wrapper->Solve(model_proto); }) - .def("StopSearch", &SolveWrapper::StopSearch); + .def("stop_search", &SolveWrapper::StopSearch); pybind11::class_(m, "CpSatHelper") - .def_static("ModelStats", &CpSatHelper::ModelStats, arg("model_proto")) - .def_static("SolverResponseStats", &CpSatHelper::SolverResponseStats, + .def_static("model_stats", &CpSatHelper::ModelStats, arg("model_proto")) + .def_static("solver_response_stats", &CpSatHelper::SolverResponseStats, arg("response")) - .def_static("ValidateModel", &CpSatHelper::ValidateModel, + .def_static("validate_model", &CpSatHelper::ValidateModel, arg("model_proto")) - .def_static("VariableDomain", &CpSatHelper::VariableDomain, + .def_static("variable_domain", &CpSatHelper::VariableDomain, arg("variable_proto")) - .def_static("WriteModelToFile", &CpSatHelper::WriteModelToFile, + .def_static("write_model_to_file", &CpSatHelper::WriteModelToFile, arg("model_proto"), arg("filename")); } diff --git a/ortools/sat/python/swig_helper_test.py b/ortools/sat/python/swig_helper_test.py index a44900ed9e0..8479885ff0a 100644 --- a/ortools/sat/python/swig_helper_test.py +++ b/ortools/sat/python/swig_helper_test.py @@ -30,7 +30,7 @@ def OnSolutionCallback(self): print("New Solution") self.__solution_count += 1 - def SolutionCount(self): + def solution_count(self): return self.__solution_count @@ -43,11 +43,11 @@ def testVariableDomain(self): model = cp_model_pb2.CpModelProto() self.assertTrue(text_format.Parse(model_string, model)) - d0 = swig_helper.CpSatHelper.VariableDomain(model.variables[0]) - d1 = swig_helper.CpSatHelper.VariableDomain(model.variables[1]) + d0 = swig_helper.CpSatHelper.variable_domain(model.variables[0]) + d1 = swig_helper.CpSatHelper.variable_domain(model.variables[1]) - self.assertEqual(d0.FlattenedIntervals(), [-10, 10]) - self.assertEqual(d1.FlattenedIntervals(), [-5, -5, 3, 6]) + self.assertEqual(d0.flattened_intervals(), [-10, 10]) + self.assertEqual(d1.flattened_intervals(), [-5, -5, 3, 6]) def testSimpleSolve(self): model_string = """ @@ -85,7 +85,7 @@ def testSimpleSolve(self): self.assertTrue(text_format.Parse(model_string, model)) solve_wrapper = swig_helper.SolveWrapper() - solution = solve_wrapper.Solve(model) + solution = solve_wrapper.solve(model) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) self.assertEqual(30.0, solution.objective_value) @@ -125,12 +125,11 @@ def testSimpleSolveWithCore(self): model = cp_model_pb2.CpModelProto() self.assertTrue(text_format.Parse(model_string, model)) - parameters = sat_parameters_pb2.SatParameters() - parameters.optimize_with_core = True + parameters = sat_parameters_pb2.SatParameters(optimize_with_core=True) solve_wrapper = swig_helper.SolveWrapper() - solve_wrapper.SetParameters(parameters) - solution = solve_wrapper.Solve(model) + solve_wrapper.set_parameters(parameters) + solution = solve_wrapper.solve(model) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) self.assertEqual(30.0, solution.objective_value) @@ -152,7 +151,7 @@ def testSimpleSolveWithProtoApi(self): model.objective.scaling_factor = -1 solve_wrapper = swig_helper.SolveWrapper() - solution = solve_wrapper.Solve(model) + solution = solve_wrapper.solve(model) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) self.assertEqual(30.0, solution.objective_value) @@ -170,13 +169,13 @@ def testSolutionCallback(self): solve_wrapper = swig_helper.SolveWrapper() callback = Callback() - solve_wrapper.AddSolutionCallback(callback) + solve_wrapper.add_solution_callback(callback) params = sat_parameters_pb2.SatParameters() params.enumerate_all_solutions = True - solve_wrapper.SetParameters(params) - solution = solve_wrapper.Solve(model) + solve_wrapper.set_parameters(params) + solution = solve_wrapper.solve(model) - self.assertEqual(5, callback.SolutionCount()) + self.assertEqual(5, callback.solution_count()) self.assertEqual(cp_model_pb2.OPTIMAL, solution.status) def testModelStats(self): @@ -215,7 +214,7 @@ def testModelStats(self): """ model = cp_model_pb2.CpModelProto() self.assertTrue(text_format.Parse(model_string, model)) - stats = swig_helper.CpSatHelper.ModelStats(model) + stats = swig_helper.CpSatHelper.model_stats(model) self.assertTrue(stats) diff --git a/ortools/sat/samples/assignment_groups_sat.py b/ortools/sat/samples/assignment_groups_sat.py index f240ceb2e3c..8358d0bf799 100644 --- a/ortools/sat/samples/assignment_groups_sat.py +++ b/ortools/sat/samples/assignment_groups_sat.py @@ -13,7 +13,7 @@ # limitations under the License. # [START program] -"""Solve assignment problem for given group of workers.""" +"""Solves an assignment problem for given group of workers.""" # [START import] from ortools.sat.python import cp_model # [END import] @@ -77,34 +77,34 @@ def main(): x = {} for worker in range(num_workers): for task in range(num_tasks): - x[worker, task] = model.NewBoolVar(f"x[{worker},{task}]") + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for worker in range(num_workers): - model.AddAtMostOne(x[worker, task] for task in range(num_tasks)) + model.add_at_most_one(x[worker, task] for task in range(num_tasks)) # Each task is assigned to exactly one worker. for task in range(num_tasks): - model.AddExactlyOne(x[worker, task] for worker in range(num_workers)) + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) # [END constraints] # [START assignments] # Create variables for each worker, indicating whether they work on some task. work = {} for worker in range(num_workers): - work[worker] = model.NewBoolVar(f"work[{worker}]") + work[worker] = model.new_bool_var(f"work[{worker}]") for worker in range(num_workers): for task in range(num_tasks): - model.Add(work[worker] == sum(x[worker, task] for task in range(num_tasks))) + model.add(work[worker] == sum(x[worker, task] for task in range(num_tasks))) # Define the allowed groups of worders - model.AddAllowedAssignments([work[0], work[1], work[2], work[3]], group1) - model.AddAllowedAssignments([work[4], work[5], work[6], work[7]], group2) - model.AddAllowedAssignments([work[8], work[9], work[10], work[11]], group3) + model.add_allowed_assignments([work[0], work[1], work[2], work[3]], group1) + model.add_allowed_assignments([work[4], work[5], work[6], work[7]], group2) + model.add_allowed_assignments([work[8], work[9], work[10], work[11]], group3) # [END assignments] # Objective @@ -113,22 +113,22 @@ def main(): for worker in range(num_workers): for task in range(num_tasks): objective_terms.append(costs[worker][task] * x[worker, task]) - model.Minimize(sum(objective_terms)) + model.minimize(sum(objective_terms)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") + print(f"Total cost = {solver.objective_value}\n") for worker in range(num_workers): for task in range(num_tasks): - if solver.BooleanValue(x[worker, task]): + if solver.boolean_value(x[worker, task]): print( f"Worker {worker} assigned to task {task}." + f" Cost = {costs[worker][task]}" diff --git a/ortools/sat/samples/assignment_sat.py b/ortools/sat/samples/assignment_sat.py index f8cbfe7f3b7..bc7dbaaf4a3 100644 --- a/ortools/sat/samples/assignment_sat.py +++ b/ortools/sat/samples/assignment_sat.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a simple assignment problem with CP-SAT.""" +"""Solves a simple assignment problem with CP-SAT.""" + # [START program] # [START import] import io @@ -60,36 +61,36 @@ def main(): # Variables # [START variables] - x = model.NewBoolVarSeries(name="x", index=data.index) + x = model.new_bool_var_series(name="x", index=data.index) # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for unused_name, tasks in data.groupby("worker"): - model.AddAtMostOne(x[tasks.index]) + model.add_at_most_one(x[tasks.index]) # Each task is assigned to exactly one worker. for unused_name, workers in data.groupby("task"): - model.AddExactlyOne(x[workers.index]) + model.add_exactly_one(x[workers.index]) # [END constraints] # Objective # [START objective] - model.Minimize(data.cost.dot(x)) + model.minimize(data.cost.dot(x)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") - selected = data.loc[solver.BooleanValues(x).loc[lambda x: x].index] + print(f"Total cost = {solver.objective_value}\n") + selected = data.loc[solver.boolean_values(x).loc[lambda x: x].index] for unused_index, row in selected.iterrows(): print(f"{row.task} assigned to {row.worker} with a cost of {row.cost}") elif status == cp_model.INFEASIBLE: diff --git a/ortools/sat/samples/assignment_task_sizes_sat.py b/ortools/sat/samples/assignment_task_sizes_sat.py index a8a97d30f10..7bc0ea9e578 100644 --- a/ortools/sat/samples/assignment_task_sizes_sat.py +++ b/ortools/sat/samples/assignment_task_sizes_sat.py @@ -13,7 +13,7 @@ # limitations under the License. # [START program] -"""Solve a simple assignment problem.""" +"""Solves a simple assignment problem.""" # [START import] from ortools.sat.python import cp_model # [END import] @@ -52,21 +52,21 @@ def main(): x = {} for worker in range(num_workers): for task in range(num_tasks): - x[worker, task] = model.NewBoolVar(f"x[{worker},{task}]") + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for worker in range(num_workers): - model.Add( + model.add( sum(task_sizes[task] * x[worker, task] for task in range(num_tasks)) <= total_size_max ) # Each task is assigned to exactly one worker. for task in range(num_tasks): - model.AddExactlyOne(x[worker, task] for worker in range(num_workers)) + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) # [END constraints] # Objective @@ -75,22 +75,22 @@ def main(): for worker in range(num_workers): for task in range(num_tasks): objective_terms.append(costs[worker][task] * x[worker, task]) - model.Minimize(sum(objective_terms)) + model.minimize(sum(objective_terms)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") + print(f"Total cost = {solver.objective_value}\n") for worker in range(num_workers): for task in range(num_tasks): - if solver.BooleanValue(x[worker, task]): + if solver.boolean_value(x[worker, task]): print( f"Worker {worker} assigned to task {task}." + f" Cost = {costs[worker][task]}" diff --git a/ortools/sat/samples/assignment_teams_sat.py b/ortools/sat/samples/assignment_teams_sat.py index 583325aca33..a75d72bf1a1 100644 --- a/ortools/sat/samples/assignment_teams_sat.py +++ b/ortools/sat/samples/assignment_teams_sat.py @@ -13,7 +13,7 @@ # limitations under the License. # [START program] -"""Solve a simple assignment problem.""" +"""Solves a simple assignment problem.""" # [START import] from ortools.sat.python import cp_model # [END import] @@ -49,31 +49,31 @@ def main(): x = {} for worker in range(num_workers): for task in range(num_tasks): - x[worker, task] = model.NewBoolVar(f"x[{worker},{task}]") + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") # [END variables] # Constraints # [START constraints] # Each worker is assigned to at most one task. for worker in range(num_workers): - model.AddAtMostOne(x[worker, task] for task in range(num_tasks)) + model.add_at_most_one(x[worker, task] for task in range(num_tasks)) # Each task is assigned to exactly one worker. for task in range(num_tasks): - model.AddExactlyOne(x[worker, task] for worker in range(num_workers)) + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) # Each team takes at most two tasks. team1_tasks = [] for worker in team1: for task in range(num_tasks): team1_tasks.append(x[worker, task]) - model.Add(sum(team1_tasks) <= team_max) + model.add(sum(team1_tasks) <= team_max) team2_tasks = [] for worker in team2: for task in range(num_tasks): team2_tasks.append(x[worker, task]) - model.Add(sum(team2_tasks) <= team_max) + model.add(sum(team2_tasks) <= team_max) # [END constraints] # Objective @@ -82,22 +82,22 @@ def main(): for worker in range(num_workers): for task in range(num_tasks): objective_terms.append(costs[worker][task] * x[worker, task]) - model.Minimize(sum(objective_terms)) + model.minimize(sum(objective_terms)) # [END objective] # Solve # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Total cost = {solver.ObjectiveValue()}\n") + print(f"Total cost = {solver.objective_value}\n") for worker in range(num_workers): for task in range(num_tasks): - if solver.BooleanValue(x[worker, task]): + if solver.boolean_value(x[worker, task]): print( f"Worker {worker} assigned to task {task}." + f" Cost = {costs[worker][task]}" diff --git a/ortools/sat/samples/assumptions_sample_sat.py b/ortools/sat/samples/assumptions_sample_sat.py old mode 100755 new mode 100644 index 8007ba32a3b..ee50dfa0126 --- a/ortools/sat/samples/assumptions_sample_sat.py +++ b/ortools/sat/samples/assumptions_sample_sat.py @@ -28,37 +28,37 @@ def main(): # Creates the variables. # [START variables] - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") - z = model.NewIntVar(0, 10, "z") - a = model.NewBoolVar("a") - b = model.NewBoolVar("b") - c = model.NewBoolVar("c") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") + z = model.new_int_var(0, 10, "z") + a = model.new_bool_var("a") + b = model.new_bool_var("b") + c = model.new_bool_var("c") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x > y).OnlyEnforceIf(a) - model.Add(y > z).OnlyEnforceIf(b) - model.Add(z > x).OnlyEnforceIf(c) + model.add(x > y).only_enforce_if(a) + model.add(y > z).only_enforce_if(b) + model.add(z > x).only_enforce_if(c) # [END constraints] # Add assumptions - model.AddAssumptions([a, b, c]) + model.add_assumptions([a, b, c]) # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # Print solution. # [START print_solution] - print(f"Status = {solver.StatusName(status)}") + print(f"Status = {solver.status_name(status)}") if status == cp_model.INFEASIBLE: print( - "SufficientAssumptionsForInfeasibility = " - f"{solver.SufficientAssumptionsForInfeasibility()}" + "sufficient_assumptions_for_infeasibility = " + f"{solver.sufficient_assumptions_for_infeasibility()}" ) # [END print_solution] diff --git a/ortools/sat/samples/bin_packing_sat.py b/ortools/sat/samples/bin_packing_sat.py index 0efeb0315a7..97f1d37cb80 100644 --- a/ortools/sat/samples/bin_packing_sat.py +++ b/ortools/sat/samples/bin_packing_sat.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a simple bin packing problem using CP-SAT.""" +"""Solves a simple bin packing problem using CP-SAT.""" + # [START program] # [START import] import io @@ -78,22 +79,22 @@ def main(): items_x_bins = pd.MultiIndex.from_product( [items.index, bins.index], names=["item", "bin"] ) - x = model.NewBoolVarSeries(name="x", index=items_x_bins) + x = model.new_bool_var_series(name="x", index=items_x_bins) # y[j] = 1 if bin j is used. - y = model.NewBoolVarSeries(name="y", index=bins.index) + y = model.new_bool_var_series(name="y", index=bins.index) # [END variables] # [START constraints] # Constraints # Each item must be in exactly one bin. for unused_name, all_copies in x.groupby("item"): - model.AddExactlyOne(x[all_copies.index]) + model.add_exactly_one(x[all_copies.index]) # The amount packed in each bin cannot exceed its capacity. for selected_bin in bins.index: items_in_bin = x.xs(selected_bin, level="bin") - model.Add( + model.add( items_in_bin.dot(items.weight) <= bins.loc[selected_bin].capacity * y[selected_bin] ) @@ -101,21 +102,21 @@ def main(): # [START objective] # Objective: minimize the number of bins used. - model.Minimize(y.sum()) + model.minimize(y.sum()) # [END objective] # [START solve] # Create the solver and solve the model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Number of bins used = {solver.ObjectiveValue()}") + print(f"Number of bins used = {solver.objective_value}") - x_values = solver.BooleanValues(x) - y_values = solver.BooleanValues(y) + x_values = solver.boolean_values(x) + y_values = solver.boolean_values(y) active_bins = y_values.loc[lambda x: x].index for b in active_bins: @@ -128,7 +129,7 @@ def main(): print(f"Total packed weight: {items.weight.sum()}") print() - print(f"Time = {solver.WallTime()} seconds") + print(f"Time = {solver.wall_time} seconds") elif status == cp_model.INFEASIBLE: print("No solution found") else: diff --git a/ortools/sat/samples/binpacking_problem_sat.py b/ortools/sat/samples/binpacking_problem_sat.py index 228ab369fba..12ebc9aa0b8 100644 --- a/ortools/sat/samples/binpacking_problem_sat.py +++ b/ortools/sat/samples/binpacking_problem_sat.py @@ -38,43 +38,43 @@ def BinpackingProblemSat(): for i in all_items: num_copies = items[i][1] for b in all_bins: - x[(i, b)] = model.NewIntVar(0, num_copies, f"x[{i},{b}]") + x[(i, b)] = model.new_int_var(0, num_copies, f"x[{i},{b}]") # Load variables. - load = [model.NewIntVar(0, bin_capacity, f"load[{b}]") for b in all_bins] + load = [model.new_int_var(0, bin_capacity, f"load[{b}]") for b in all_bins] # Slack variables. - slacks = [model.NewBoolVar(f"slack[{b}]") for b in all_bins] + slacks = [model.new_bool_var(f"slack[{b}]") for b in all_bins] # Links load and x. for b in all_bins: - model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) + model.add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items)) # Place all items. for i in all_items: - model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1]) + model.add(sum(x[(i, b)] for b in all_bins) == items[i][1]) # Links load and slack through an equivalence relation. safe_capacity = bin_capacity - slack_capacity for b in all_bins: # slack[b] => load[b] <= safe_capacity. - model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b]) + model.add(load[b] <= safe_capacity).only_enforce_if(slacks[b]) # not(slack[b]) => load[b] > safe_capacity. - model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not()) + model.add(load[b] > safe_capacity).only_enforce_if(slacks[b].negated()) # Maximize sum of slacks. - model.Maximize(sum(slacks)) + model.maximize(sum(slacks)) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) - print(f"Solve status: {solver.StatusName(status)}") + status = solver.solve(model) + print(f"solve status: {solver.status_name(status)}") if status == cp_model.OPTIMAL: - print(f"Optimal objective value: {solver.ObjectiveValue()}") + print(f"Optimal objective value: {solver.objective_value}") print("Statistics") - print(f" - conflicts : {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time : {solver.WallTime()}s") + print(f" - conflicts : {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time : {solver.wall_time}s") BinpackingProblemSat() diff --git a/ortools/sat/samples/bool_or_sample_sat.py b/ortools/sat/samples/bool_or_sample_sat.py index e692be0c6b4..ad21c3a13d4 100644 --- a/ortools/sat/samples/bool_or_sample_sat.py +++ b/ortools/sat/samples/bool_or_sample_sat.py @@ -21,10 +21,10 @@ def BoolOrSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") + x = model.new_bool_var("x") + y = model.new_bool_var("y") - model.AddBoolOr([x, y.Not()]) + model.add_bool_or([x, y.negated()]) BoolOrSampleSat() diff --git a/ortools/sat/samples/boolean_product_sample_sat.py b/ortools/sat/samples/boolean_product_sample_sat.py index 3994c5eb370..e91c5bcbf86 100644 --- a/ortools/sat/samples/boolean_product_sample_sat.py +++ b/ortools/sat/samples/boolean_product_sample_sat.py @@ -24,22 +24,22 @@ def BooleanProductSampleSat(): p == x * y, which is the same as p <=> x and y """ model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - p = model.NewBoolVar("p") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + p = model.new_bool_var("p") # x and y implies p, rewrite as not(x and y) or p. - model.AddBoolOr(x.Not(), y.Not(), p) + model.add_bool_or(x.negated(), y.negated(), p) # p implies x and y, expanded into two implications. - model.AddImplication(p, x) - model.AddImplication(p, y) + model.add_implication(p, x) + model.add_implication(p, y) # Create a solver and solve. solver = cp_model.CpSolver() solution_printer = cp_model.VarArraySolutionPrinter([x, y, p]) solver.parameters.enumerate_all_solutions = True - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) BooleanProductSampleSat() diff --git a/ortools/sat/samples/channeling_sample_sat.py b/ortools/sat/samples/channeling_sample_sat.py index d0602b66a31..99f359c9144 100644 --- a/ortools/sat/samples/channeling_sample_sat.py +++ b/ortools/sat/samples/channeling_sample_sat.py @@ -21,20 +21,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def ChannelingSampleSat(): """Demonstrates how to link integer constraints together.""" @@ -43,24 +38,24 @@ def ChannelingSampleSat(): model = cp_model.CpModel() # Declare our two primary variables. - x = model.NewIntVar(0, 10, "x") - y = model.NewIntVar(0, 10, "y") + x = model.new_int_var(0, 10, "x") + y = model.new_int_var(0, 10, "y") # Declare our intermediate boolean variable. - b = model.NewBoolVar("b") + b = model.new_bool_var("b") # Implement b == (x >= 5). - model.Add(x >= 5).OnlyEnforceIf(b) - model.Add(x < 5).OnlyEnforceIf(b.Not()) + model.add(x >= 5).only_enforce_if(b) + model.add(x < 5).only_enforce_if(b.negated()) # Create our two half-reified constraints. # First, b implies (y == 10 - x). - model.Add(y == 10 - x).OnlyEnforceIf(b) + model.add(y == 10 - x).only_enforce_if(b) # Second, not(b) implies y == 0. - model.Add(y == 0).OnlyEnforceIf(b.Not()) + model.add(y == 0).only_enforce_if(b.negated()) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -72,7 +67,7 @@ def ChannelingSampleSat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, y, b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) ChannelingSampleSat() diff --git a/ortools/sat/samples/clone_model_sample_sat.py b/ortools/sat/samples/clone_model_sample_sat.py index 2bca1dac6c1..ad0ea1e6781 100644 --- a/ortools/sat/samples/clone_model_sample_sat.py +++ b/ortools/sat/samples/clone_model_sample_sat.py @@ -28,43 +28,43 @@ def CloneModelSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # [START objective] - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # [END objective] # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] if status == cp_model.OPTIMAL: - print("Optimal value of the original model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the original model: {}".format(solver.objective_value)) - # Clone the model. + # Clones the model. # [START clone] - copy = model.Clone() + copy = model.clone() - copy_x = copy.GetIntVarFromProtoIndex(x.Index()) - copy_y = copy.GetIntVarFromProtoIndex(y.Index()) + copy_x = copy.get_int_var_from_proto_index(x.index) + copy_y = copy.get_int_var_from_proto_index(y.index) - copy.Add(copy_x + copy_y <= 1) + copy.add(copy_x + copy_y <= 1) # [END clone] - status = solver.Solve(copy) + status = solver.solve(copy) if status == cp_model.OPTIMAL: - print("Optimal value of the modified model: {}".format(solver.ObjectiveValue())) + print("Optimal value of the modified model: {}".format(solver.objective_value)) CloneModelSampleSat() diff --git a/ortools/sat/samples/cp_is_fun_sat.py b/ortools/sat/samples/cp_is_fun_sat.py index 586f3db272e..9a6489b5edf 100644 --- a/ortools/sat/samples/cp_is_fun_sat.py +++ b/ortools/sat/samples/cp_is_fun_sat.py @@ -29,24 +29,25 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count # [END solution_printer] def main(): - """Solve the CP+IS+FUN==TRUE cryptarithm.""" + """solve the CP+IS+FUN==TRUE cryptarithm.""" # Constraint programming engine # [START model] model = cp_model.CpModel() @@ -55,16 +56,16 @@ def main(): # [START variables] base = 10 - c = model.NewIntVar(1, base - 1, "C") - p = model.NewIntVar(0, base - 1, "P") - i = model.NewIntVar(1, base - 1, "I") - s = model.NewIntVar(0, base - 1, "S") - f = model.NewIntVar(1, base - 1, "F") - u = model.NewIntVar(0, base - 1, "U") - n = model.NewIntVar(0, base - 1, "N") - t = model.NewIntVar(1, base - 1, "T") - r = model.NewIntVar(0, base - 1, "R") - e = model.NewIntVar(0, base - 1, "E") + c = model.new_int_var(1, base - 1, "C") + p = model.new_int_var(0, base - 1, "P") + i = model.new_int_var(1, base - 1, "I") + s = model.new_int_var(0, base - 1, "S") + f = model.new_int_var(1, base - 1, "F") + u = model.new_int_var(0, base - 1, "U") + n = model.new_int_var(0, base - 1, "N") + t = model.new_int_var(1, base - 1, "T") + r = model.new_int_var(0, base - 1, "R") + e = model.new_int_var(0, base - 1, "E") # We need to group variables in a list to use the constraint AllDifferent. letters = [c, p, i, s, f, u, n, t, r, e] @@ -75,10 +76,10 @@ def main(): # Define constraints. # [START constraints] - model.AddAllDifferent(letters) + model.add_all_different(letters) # CP + IS + FUN = TRUE - model.Add( + model.add( c * base + p + i * base + s + f * base * base + u * base + n == t * base * base * base + r * base * base + u * base + e ) @@ -91,17 +92,17 @@ def main(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] # Statistics. # [START statistics] print("\nStatistics") - print(f" status : {solver.StatusName(status)}") - print(f" conflicts: {solver.NumConflicts()}") - print(f" branches : {solver.NumBranches()}") - print(f" wall time: {solver.WallTime()} s") - print(f" sol found: {solution_printer.solution_count()}") + print(f" status : {solver.status_name(status)}") + print(f" conflicts: {solver.num_conflicts}") + print(f" branches : {solver.num_branches}") + print(f" wall time: {solver.wall_time} s") + print(f" sol found: {solution_printer.solution_count}") # [END statistics] diff --git a/ortools/sat/samples/cp_sat_example.py b/ortools/sat/samples/cp_sat_example.py index 0e26f113ea7..406491ef6f1 100755 --- a/ortools/sat/samples/cp_sat_example.py +++ b/ortools/sat/samples/cp_sat_example.py @@ -29,34 +29,34 @@ def main(): # Creates the variables. # [START variables] var_upper_bound = max(50, 45, 37) - x = model.NewIntVar(0, var_upper_bound, "x") - y = model.NewIntVar(0, var_upper_bound, "y") - z = model.NewIntVar(0, var_upper_bound, "z") + x = model.new_int_var(0, var_upper_bound, "x") + y = model.new_int_var(0, var_upper_bound, "y") + z = model.new_int_var(0, var_upper_bound, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(2 * x + 7 * y + 3 * z <= 50) - model.Add(3 * x - 5 * y + 7 * z <= 45) - model.Add(5 * x + 2 * y - 6 * z <= 37) + model.add(2 * x + 7 * y + 3 * z <= 50) + model.add(3 * x - 5 * y + 7 * z <= 45) + model.add(5 * x + 2 * y - 6 * z <= 37) # [END constraints] # [START objective] - model.Maximize(2 * x + 2 * y + 3 * z) + model.maximize(2 * x + 2 * y + 3 * z) # [END objective] # Creates a solver and solves the model. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"Maximum of objective function: {solver.ObjectiveValue()}\n") - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"Maximum of objective function: {solver.objective_value}\n") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") else: print("No solution found.") # [END print_solution] @@ -64,10 +64,10 @@ def main(): # Statistics. # [START statistics] print("\nStatistics") - print(f" status : {solver.StatusName(status)}") - print(f" conflicts: {solver.NumConflicts()}") - print(f" branches : {solver.NumBranches()}") - print(f" wall time: {solver.WallTime()} s") + print(f" status : {solver.status_name(status)}") + print(f" conflicts: {solver.num_conflicts}") + print(f" branches : {solver.num_branches}") + print(f" wall time: {solver.wall_time} s") # [END statistics] diff --git a/ortools/sat/samples/cumulative_variable_profile_sample_sat.py b/ortools/sat/samples/cumulative_variable_profile_sample_sat.py index 49484443a09..77f6c771159 100644 --- a/ortools/sat/samples/cumulative_variable_profile_sample_sat.py +++ b/ortools/sat/samples/cumulative_variable_profile_sample_sat.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Solve a simple scheduling problem with a variable work load.""" +"""Solves a simple scheduling problem with a variable work load.""" + # [START program] # [START import] import io @@ -107,12 +108,12 @@ def main(): # [START program_part2] # [START variables] # Variables - starts = model.NewIntVarSeries( + starts = model.new_int_var_series( name="starts", lower_bounds=0, upper_bounds=horizon, index=tasks_df.index ) - performed = model.NewBoolVarSeries(name="performed", index=tasks_df.index) + performed = model.new_bool_var_series(name="performed", index=tasks_df.index) - intervals = model.NewOptionalFixedSizeIntervalVarSeries( + intervals = model.new_optional_fixed_size_interval_var_series( name="intervals", index=tasks_df.index, starts=starts, @@ -124,7 +125,7 @@ def main(): # [START constraints] # Set up the profile. We use fixed (intervals, demands) to fill in the space # between the actual load profile and the max capacity. - time_period_intervals = model.NewFixedSizeIntervalVarSeries( + time_period_intervals = model.new_fixed_size_interval_var_series( name="time_period_intervals", index=capacity_df.index, starts=capacity_df.start_hour * minutes_per_period, @@ -133,7 +134,7 @@ def main(): time_period_heights = max_capacity - capacity_df.capacity # Cumulative constraint. - model.AddCumulative( + model.add_cumulative( intervals.to_list() + time_period_intervals.to_list(), tasks_df.load.to_list() + time_period_heights.to_list(), max_capacity, @@ -144,7 +145,7 @@ def main(): # Objective: maximize the value of performed intervals. # 1 is the max priority. max_priority = max(tasks_df.priority) - model.Maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) + model.maximize(sum(performed * (max_priority + 1 - tasks_df.priority))) # [END objective] # [START solve] @@ -153,13 +154,13 @@ def main(): solver.parameters.log_search_progress = True solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 30.0 - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - start_values = solver.Values(starts) - performed_values = solver.BooleanValues(performed) + start_values = solver.values(starts) + performed_values = solver.boolean_values(performed) for task in tasks_df.index: if performed_values[task]: print(f"task {task} starts at {start_values[task]}") diff --git a/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py b/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py index 3fdb214a94f..6da9b6ff1f7 100644 --- a/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py +++ b/ortools/sat/samples/earliness_tardiness_cost_sample_sat.py @@ -21,20 +21,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def earliness_tardiness_cost_sample_sat(): """Encode the piecewise linear expression.""" @@ -48,7 +43,7 @@ def earliness_tardiness_cost_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the piecewise linear function. # @@ -57,24 +52,24 @@ def earliness_tardiness_cost_sample_sat(): # ed ld # large_constant = 1000 - expr = model.NewIntVar(0, large_constant, "expr") + expr = model.new_int_var(0, large_constant, "expr") # First segment. - s1 = model.NewIntVar(-large_constant, large_constant, "s1") - model.Add(s1 == earliness_cost * (earliness_date - x)) + s1 = model.new_int_var(-large_constant, large_constant, "s1") + model.add(s1 == earliness_cost * (earliness_date - x)) # Second segment. s2 = 0 # Third segment. - s3 = model.NewIntVar(-large_constant, large_constant, "s3") - model.Add(s3 == lateness_cost * (x - lateness_date)) + s3 = model.new_int_var(-large_constant, large_constant, "s3") + model.add(s3 == lateness_cost * (x - lateness_date)) # Link together expr and x through s1, s2, and s3. - model.AddMaxEquality(expr, [s1, s2, s3]) + model.add_max_equality(expr, [s1, s2, s3]) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -86,7 +81,7 @@ def earliness_tardiness_cost_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) earliness_tardiness_cost_sample_sat() diff --git a/ortools/sat/samples/interval_sample_sat.py b/ortools/sat/samples/interval_sample_sat.py index 13fb88af87a..43dfc98bbcb 100644 --- a/ortools/sat/samples/interval_sample_sat.py +++ b/ortools/sat/samples/interval_sample_sat.py @@ -24,22 +24,22 @@ def IntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - interval_var = model.NewIntervalVar(start_var, duration, end_var + 2, "interval") + end_var = model.new_int_var(0, horizon, "end") + interval_var = model.new_interval_var(start_var, duration, end_var + 2, "interval") print(f"interval = {repr(interval_var)}") # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewFixedSizeIntervalVar( + fixed_size_interval_var = model.new_fixed_size_interval_var( start_var, 10, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewFixedSizeIntervalVar(5, 10, "fixed_interval") + fixed_interval = model.new_fixed_size_interval_var(5, 10, "fixed_interval") print(f"fixed_interval = {repr(fixed_interval)}") diff --git a/ortools/sat/samples/literal_sample_sat.py b/ortools/sat/samples/literal_sample_sat.py index 04ca45513f2..3c83cf44145 100644 --- a/ortools/sat/samples/literal_sample_sat.py +++ b/ortools/sat/samples/literal_sample_sat.py @@ -20,8 +20,8 @@ def LiteralSampleSat(): model = cp_model.CpModel() - x = model.NewBoolVar("x") - not_x = x.Not() + x = model.new_bool_var("x") + not_x = x.negated() print(x) print(not_x) diff --git a/ortools/sat/samples/minimal_jobshop_sat.py b/ortools/sat/samples/minimal_jobshop_sat.py index 782f1b9a07a..4ef60a3cfa5 100644 --- a/ortools/sat/samples/minimal_jobshop_sat.py +++ b/ortools/sat/samples/minimal_jobshop_sat.py @@ -57,9 +57,9 @@ def main(): for task_id, task in enumerate(job): machine, duration = task suffix = f"_{job_id}_{task_id}" - start_var = model.NewIntVar(0, horizon, "start" + suffix) - end_var = model.NewIntVar(0, horizon, "end" + suffix) - interval_var = model.NewIntervalVar( + start_var = model.new_int_var(0, horizon, "start" + suffix) + end_var = model.new_int_var(0, horizon, "end" + suffix) + interval_var = model.new_interval_var( start_var, duration, end_var, "interval" + suffix ) all_tasks[job_id, task_id] = task_type( @@ -71,30 +71,30 @@ def main(): # [START constraints] # Create and add disjunctive constraints. for machine in all_machines: - model.AddNoOverlap(machine_to_intervals[machine]) + model.add_no_overlap(machine_to_intervals[machine]) # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): - model.Add( + model.add( all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end ) # [END constraints] # [START objective] # Makespan objective. - obj_var = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality( + obj_var = model.new_int_var(0, horizon, "makespan") + model.add_max_equality( obj_var, [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)], ) - model.Minimize(obj_var) + model.minimize(obj_var) # [END objective] # Creates the solver and solve. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] @@ -107,7 +107,7 @@ def main(): machine = task[0] assigned_jobs[machine].append( assigned_task_type( - start=solver.Value(all_tasks[job_id, task_id].start), + start=solver.value(all_tasks[job_id, task_id].start), job=job_id, index=task_id, duration=task[1], @@ -124,13 +124,13 @@ def main(): for assigned_task in assigned_jobs[machine]: name = f"job_{assigned_task.job}_task_{assigned_task.index}" - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line_tasks += f"{name:15}" start = assigned_task.start duration = assigned_task.duration sol_tmp = f"[{start},{start + duration}]" - # Add spaces to output to align columns. + # add spaces to output to align columns. sol_line += f"{sol_tmp:15}" sol_line += "\n" @@ -139,7 +139,7 @@ def main(): output += sol_line # Finally print the solution found. - print(f"Optimal Schedule Length: {solver.ObjectiveValue()}") + print(f"Optimal Schedule Length: {solver.objective_value}") print(output) else: print("No solution found.") @@ -148,9 +148,9 @@ def main(): # Statistics. # [START statistics] print("\nStatistics") - print(f" - conflicts: {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time: {solver.WallTime()}s") + print(f" - conflicts: {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time: {solver.wall_time}s") # [END statistics] diff --git a/ortools/sat/samples/multiple_knapsack_sat.py b/ortools/sat/samples/multiple_knapsack_sat.py index 293a7e1bc20..c1513c0def4 100644 --- a/ortools/sat/samples/multiple_knapsack_sat.py +++ b/ortools/sat/samples/multiple_knapsack_sat.py @@ -43,18 +43,18 @@ def main(): x = {} for i in data["all_items"]: for b in data["all_bins"]: - x[i, b] = model.NewBoolVar(f"x_{i}_{b}") + x[i, b] = model.new_bool_var(f"x_{i}_{b}") # [END variables] # Constraints. # [START constraints] # Each item is assigned to at most one bin. for i in data["all_items"]: - model.AddAtMostOne(x[i, b] for b in data["all_bins"]) + model.add_at_most_one(x[i, b] for b in data["all_bins"]) # The amount packed in each bin cannot exceed its capacity. for b in data["all_bins"]: - model.Add( + model.add( sum(x[i, b] * data["weights"][i] for i in data["all_items"]) <= data["bin_capacities"][b] ) @@ -62,31 +62,31 @@ def main(): # Objective. # [START objective] - # Maximize total value of packed items. + # maximize total value of packed items. objective = [] for i in data["all_items"]: for b in data["all_bins"]: - objective.append(cp_model.LinearExpr.Term(x[i, b], data["values"][i])) - model.Maximize(cp_model.LinearExpr.Sum(objective)) + objective.append(cp_model.LinearExpr.term(x[i, b], data["values"][i])) + model.maximize(cp_model.LinearExpr.sum(objective)) # [END objective] # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL: - print(f"Total packed value: {solver.ObjectiveValue()}") + print(f"Total packed value: {solver.objective_value}") total_weight = 0 for b in data["all_bins"]: print(f"Bin {b}") bin_weight = 0 bin_value = 0 for i in data["all_items"]: - if solver.Value(x[i, b]) > 0: + if solver.value(x[i, b]) > 0: print( - f"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}" + f"Item:{i} weight:{data['weights'][i]} value:{data['values'][i]}" ) bin_weight += data["weights"][i] bin_value += data["values"][i] diff --git a/ortools/sat/samples/no_overlap_sample_sat.py b/ortools/sat/samples/no_overlap_sample_sat.py index 7b2c77b6db6..24d5f63fb32 100644 --- a/ortools/sat/samples/no_overlap_sample_sat.py +++ b/ortools/sat/samples/no_overlap_sample_sat.py @@ -23,45 +23,45 @@ def NoOverlapSampleSat(): horizon = 21 # 3 weeks. # Task 0, duration 2. - start_0 = model.NewIntVar(0, horizon, "start_0") + start_0 = model.new_int_var(0, horizon, "start_0") duration_0 = 2 # Python cp/sat code accepts integer variables or constants. - end_0 = model.NewIntVar(0, horizon, "end_0") - task_0 = model.NewIntervalVar(start_0, duration_0, end_0, "task_0") + end_0 = model.new_int_var(0, horizon, "end_0") + task_0 = model.new_interval_var(start_0, duration_0, end_0, "task_0") # Task 1, duration 4. - start_1 = model.NewIntVar(0, horizon, "start_1") + start_1 = model.new_int_var(0, horizon, "start_1") duration_1 = 4 # Python cp/sat code accepts integer variables or constants. - end_1 = model.NewIntVar(0, horizon, "end_1") - task_1 = model.NewIntervalVar(start_1, duration_1, end_1, "task_1") + end_1 = model.new_int_var(0, horizon, "end_1") + task_1 = model.new_interval_var(start_1, duration_1, end_1, "task_1") # Task 2, duration 3. - start_2 = model.NewIntVar(0, horizon, "start_2") + start_2 = model.new_int_var(0, horizon, "start_2") duration_2 = 3 # Python cp/sat code accepts integer variables or constants. - end_2 = model.NewIntVar(0, horizon, "end_2") - task_2 = model.NewIntervalVar(start_2, duration_2, end_2, "task_2") + end_2 = model.new_int_var(0, horizon, "end_2") + task_2 = model.new_interval_var(start_2, duration_2, end_2, "task_2") # Weekends. - weekend_0 = model.NewIntervalVar(5, 2, 7, "weekend_0") - weekend_1 = model.NewIntervalVar(12, 2, 14, "weekend_1") - weekend_2 = model.NewIntervalVar(19, 2, 21, "weekend_2") + weekend_0 = model.new_interval_var(5, 2, 7, "weekend_0") + weekend_1 = model.new_interval_var(12, 2, 14, "weekend_1") + weekend_2 = model.new_interval_var(19, 2, 21, "weekend_2") # No Overlap constraint. - model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) + model.add_no_overlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) # Makespan objective. - obj = model.NewIntVar(0, horizon, "makespan") - model.AddMaxEquality(obj, [end_0, end_1, end_2]) - model.Minimize(obj) + obj = model.new_int_var(0, horizon, "makespan") + model.add_max_equality(obj, [end_0, end_1, end_2]) + model.minimize(obj) # Solve model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Print out makespan and the start times for all tasks. - print(f"Optimal Schedule Length: {solver.ObjectiveValue()}") - print(f"Task 0 starts at {solver.Value(start_0)}") - print(f"Task 1 starts at {solver.Value(start_1)}") - print(f"Task 2 starts at {solver.Value(start_2)}") + print(f"Optimal Schedule Length: {solver.objective_value}") + print(f"Task 0 starts at {solver.value(start_0)}") + print(f"Task 1 starts at {solver.value(start_1)}") + print(f"Task 2 starts at {solver.value(start_2)}") else: print(f"Solver exited with nonoptimal status: {status}") diff --git a/ortools/sat/samples/non_linear_sat.py b/ortools/sat/samples/non_linear_sat.py index 5fa2e76ffab..3899063a9a0 100644 --- a/ortools/sat/samples/non_linear_sat.py +++ b/ortools/sat/samples/non_linear_sat.py @@ -15,7 +15,7 @@ """Non linear example. Finds a rectangle with maximum available area for given perimeter using -AddMultiplicationEquality(). +add_multiplication_equality(). """ from ortools.sat.python import cp_model @@ -27,23 +27,23 @@ def non_linear_sat(): model = cp_model.CpModel() - x = model.NewIntVar(0, perimeter, "x") - y = model.NewIntVar(0, perimeter, "y") - model.Add(2 * (x + y) == perimeter) + x = model.new_int_var(0, perimeter, "x") + y = model.new_int_var(0, perimeter, "y") + model.add(2 * (x + y) == perimeter) - area = model.NewIntVar(0, perimeter * perimeter, "s") - model.AddMultiplicationEquality(area, x, y) + area = model.new_int_var(0, perimeter * perimeter, "s") + model.add_multiplication_equality(area, x, y) - model.Maximize(area) + model.maximize(area) solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"s = {solver.Value(area)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"s = {solver.value(area)}") else: print("No solution found.") diff --git a/ortools/sat/samples/nqueens_sat.py b/ortools/sat/samples/nqueens_sat.py index da13a1043b5..675e40f335b 100644 --- a/ortools/sat/samples/nqueens_sat.py +++ b/ortools/sat/samples/nqueens_sat.py @@ -25,13 +25,14 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, queens): + def __init__(self, queens: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__queens = queens self.__solution_count = 0 self.__start_time = time.time() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count def on_solution_callback(self): @@ -45,7 +46,7 @@ def on_solution_callback(self): all_queens = range(len(self.__queens)) for i in all_queens: for j in all_queens: - if self.Value(self.__queens[j]) == i: + if self.value(self.__queens[j]) == i: # There is a queen in column j, row i. print("Q", end=" ") else: @@ -66,17 +67,17 @@ def main(board_size): # [START variables] # There are `board_size` number of variables, one for a queen in each column # of the board. The value of each variable is the row that the queen is in. - queens = [model.NewIntVar(0, board_size - 1, f"x_{i}") for i in range(board_size)] + queens = [model.new_int_var(0, board_size - 1, f"x_{i}") for i in range(board_size)] # [END variables] # Creates the constraints. # [START constraints] # All rows must be different. - model.AddAllDifferent(queens) + model.add_all_different(queens) # No two queens can be on the same diagonal. - model.AddAllDifferent(queens[i] + i for i in range(board_size)) - model.AddAllDifferent(queens[i] - i for i in range(board_size)) + model.add_all_different(queens[i] + i for i in range(board_size)) + model.add_all_different(queens[i] - i for i in range(board_size)) # [END constraints] # Solve the model. @@ -84,16 +85,16 @@ def main(board_size): solver = cp_model.CpSolver() solution_printer = NQueenSolutionPrinter(queens) solver.parameters.enumerate_all_solutions = True - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) # [END solve] # Statistics. # [START statistics] print("\nStatistics") - print(f" conflicts : {solver.NumConflicts()}") - print(f" branches : {solver.NumBranches()}") - print(f" wall time : {solver.WallTime()} s") - print(f" solutions found: {solution_printer.solution_count()}") + print(f" conflicts : {solver.num_conflicts}") + print(f" branches : {solver.num_branches}") + print(f" wall time : {solver.wall_time} s") + print(f" solutions found: {solution_printer.solution_count}") # [END statistics] diff --git a/ortools/sat/samples/nurses_sat.py b/ortools/sat/samples/nurses_sat.py index 3b27b718345..494d84e1efe 100644 --- a/ortools/sat/samples/nurses_sat.py +++ b/ortools/sat/samples/nurses_sat.py @@ -42,21 +42,21 @@ def main(): for n in all_nurses: for d in all_days: for s in all_shifts: - shifts[(n, d, s)] = model.NewBoolVar(f"shift_n{n}_d{d}_s{s}") + shifts[(n, d, s)] = model.new_bool_var(f"shift_n{n}_d{d}_s{s}") # [END variables] # Each shift is assigned to exactly one nurse in the schedule period. # [START exactly_one_nurse] for d in all_days: for s in all_shifts: - model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses) + model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses) # [END exactly_one_nurse] # Each nurse works at most one shift per day. # [START at_most_one_shift] for n in all_nurses: for d in all_days: - model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts) + model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts) # [END at_most_one_shift] # [START assign_nurses_evenly] @@ -74,8 +74,8 @@ def main(): for d in all_days: for s in all_shifts: shifts_worked.append(shifts[(n, d, s)]) - model.Add(min_shifts_per_nurse <= sum(shifts_worked)) - model.Add(sum(shifts_worked) <= max_shifts_per_nurse) + model.add(min_shifts_per_nurse <= sum(shifts_worked)) + model.add(sum(shifts_worked) <= max_shifts_per_nurse) # [END assign_nurses_evenly] # Creates the solver and solve. @@ -107,16 +107,16 @@ def on_solution_callback(self): for n in range(self._num_nurses): is_working = False for s in range(self._num_shifts): - if self.Value(self._shifts[(n, d, s)]): + if self.value(self._shifts[(n, d, s)]): is_working = True print(f" Nurse {n} works shift {s}") if not is_working: print(f" Nurse {n} does not work") if self._solution_count >= self._solution_limit: print(f"Stop search after {self._solution_limit} solutions") - self.StopSearch() + self.stop_search() - def solution_count(self): + def solutionCount(self): return self._solution_count # Display the first five solutions. @@ -127,16 +127,16 @@ def solution_count(self): # [END solution_printer] # [START solve] - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) # [END solve] # Statistics. # [START statistics] print("\nStatistics") - print(f" - conflicts : {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time : {solver.WallTime()} s") - print(f" - solutions found: {solution_printer.solution_count()}") + print(f" - conflicts : {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time : {solver.wall_time} s") + print(f" - solutions found: {solution_printer.solutionCount()}") # [END statistics] diff --git a/ortools/sat/samples/optional_interval_sample_sat.py b/ortools/sat/samples/optional_interval_sample_sat.py index ad78866a1f5..05b0e823daf 100644 --- a/ortools/sat/samples/optional_interval_sample_sat.py +++ b/ortools/sat/samples/optional_interval_sample_sat.py @@ -23,11 +23,11 @@ def OptionalIntervalSampleSat(): horizon = 100 # An interval can be created from three affine expressions. - start_var = model.NewIntVar(0, horizon, "start") + start_var = model.new_int_var(0, horizon, "start") duration = 10 # Python cp/sat code accept integer variables or constants. - end_var = model.NewIntVar(0, horizon, "end") - presence_var = model.NewBoolVar("presence") - interval_var = model.NewOptionalIntervalVar( + end_var = model.new_int_var(0, horizon, "end") + presence_var = model.new_bool_var("presence") + interval_var = model.new_optional_interval_var( start_var, duration, end_var + 2, presence_var, "interval" ) @@ -35,13 +35,13 @@ def OptionalIntervalSampleSat(): # If the size is fixed, a simpler version uses the start expression and the # size. - fixed_size_interval_var = model.NewOptionalFixedSizeIntervalVar( + fixed_size_interval_var = model.new_optional_fixed_size_interval_var( start_var, 10, presence_var, "fixed_size_interval_var" ) print(f"fixed_size_interval_var = {repr(fixed_size_interval_var)}") # A fixed interval can be created using the same API. - fixed_interval = model.NewOptionalFixedSizeIntervalVar( + fixed_interval = model.new_optional_fixed_size_interval_var( 5, 10, presence_var, "fixed_interval" ) print(f"fixed_interval = {repr(fixed_interval)}") diff --git a/ortools/sat/samples/overlapping_intervals_sample_sat.py b/ortools/sat/samples/overlapping_intervals_sample_sat.py index 23dd02c3a05..41197344649 100644 --- a/ortools/sat/samples/overlapping_intervals_sample_sat.py +++ b/ortools/sat/samples/overlapping_intervals_sample_sat.py @@ -20,20 +20,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def OverlappingIntervals(): """Create the overlapping Boolean variables and enumerate all states.""" @@ -42,45 +37,47 @@ def OverlappingIntervals(): horizon = 7 # First interval. - start_var_a = model.NewIntVar(0, horizon, "start_a") + start_var_a = model.new_int_var(0, horizon, "start_a") duration_a = 3 - end_var_a = model.NewIntVar(0, horizon, "end_a") - unused_interval_var_a = model.NewIntervalVar( + end_var_a = model.new_int_var(0, horizon, "end_a") + unused_interval_var_a = model.new_interval_var( start_var_a, duration_a, end_var_a, "interval_a" ) # Second interval. - start_var_b = model.NewIntVar(0, horizon, "start_b") + start_var_b = model.new_int_var(0, horizon, "start_b") duration_b = 2 - end_var_b = model.NewIntVar(0, horizon, "end_b") - unused_interval_var_b = model.NewIntervalVar( + end_var_b = model.new_int_var(0, horizon, "end_b") + unused_interval_var_b = model.new_interval_var( start_var_b, duration_b, end_var_b, "interval_b" ) # a_after_b Boolean variable. - a_after_b = model.NewBoolVar("a_after_b") - model.Add(start_var_a >= end_var_b).OnlyEnforceIf(a_after_b) - model.Add(start_var_a < end_var_b).OnlyEnforceIf(a_after_b.Not()) + a_after_b = model.new_bool_var("a_after_b") + model.add(start_var_a >= end_var_b).only_enforce_if(a_after_b) + model.add(start_var_a < end_var_b).only_enforce_if(a_after_b.negated()) # b_after_a Boolean variable. - b_after_a = model.NewBoolVar("b_after_a") - model.Add(start_var_b >= end_var_a).OnlyEnforceIf(b_after_a) - model.Add(start_var_b < end_var_a).OnlyEnforceIf(b_after_a.Not()) + b_after_a = model.new_bool_var("b_after_a") + model.add(start_var_b >= end_var_a).only_enforce_if(b_after_a) + model.add(start_var_b < end_var_a).only_enforce_if(b_after_a.negated()) # Result Boolean variable. - a_overlaps_b = model.NewBoolVar("a_overlaps_b") + a_overlaps_b = model.new_bool_var("a_overlaps_b") # Option a: using only clauses - model.AddBoolOr(a_after_b, b_after_a, a_overlaps_b) - model.AddImplication(a_after_b, a_overlaps_b.Not()) - model.AddImplication(b_after_a, a_overlaps_b.Not()) + model.add_bool_or(a_after_b, b_after_a, a_overlaps_b) + model.add_implication(a_after_b, a_overlaps_b.negated()) + model.add_implication(b_after_a, a_overlaps_b.negated()) # Option b: using an exactly one constraint. - # model.AddExactlyOne(a_after_b, b_after_a, a_overlaps_b) + # model.add_exactly_one(a_after_b, b_after_a, a_overlaps_b) # Search for start values in increasing order for the two intervals. - model.AddDecisionStrategy( - [start_var_a, start_var_b], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + model.add_decision_strategy( + [start_var_a, start_var_b], + cp_model.CHOOSE_FIRST, + cp_model.SELECT_MIN_VALUE, ) # Create a solver and solve with a fixed search. @@ -93,7 +90,7 @@ def OverlappingIntervals(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([start_var_a, start_var_b, a_overlaps_b]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) OverlappingIntervals() diff --git a/ortools/sat/samples/rabbits_and_pheasants_sat.py b/ortools/sat/samples/rabbits_and_pheasants_sat.py old mode 100755 new mode 100644 index bcecbfe130b..71dd74ffb5c --- a/ortools/sat/samples/rabbits_and_pheasants_sat.py +++ b/ortools/sat/samples/rabbits_and_pheasants_sat.py @@ -21,20 +21,20 @@ def RabbitsAndPheasantsSat(): """Solves the rabbits + pheasants problem.""" model = cp_model.CpModel() - r = model.NewIntVar(0, 100, "r") - p = model.NewIntVar(0, 100, "p") + r = model.new_int_var(0, 100, "r") + p = model.new_int_var(0, 100, "p") # 20 heads. - model.Add(r + p == 20) + model.add(r + p == 20) # 56 legs. - model.Add(4 * r + 2 * p == 56) + model.add(4 * r + 2 * p == 56) # Solves and prints out the solution. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"{solver.Value(r)} rabbits and {solver.Value(p)} pheasants") + print(f"{solver.value(r)} rabbits and {solver.value(p)} pheasants") RabbitsAndPheasantsSat() diff --git a/ortools/sat/samples/ranking_circuit_sample_sat.py b/ortools/sat/samples/ranking_circuit_sample_sat.py index 6ed5ad9c9e8..7d5bd951e02 100644 --- a/ortools/sat/samples/ranking_circuit_sample_sat.py +++ b/ortools/sat/samples/ranking_circuit_sample_sat.py @@ -60,26 +60,26 @@ def rank_tasks_with_circuit( arcs: List[cp_model.ArcT] = [] for i in all_tasks: # if node i is first. - start_lit = model.NewBoolVar(f"start_{i}") + start_lit = model.new_bool_var(f"start_{i}") arcs.append((0, i + 1, start_lit)) - model.Add(ranks[i] == 0).OnlyEnforceIf(start_lit) + model.add(ranks[i] == 0).only_enforce_if(start_lit) # As there are no other constraints on the problem, we can add this # redundant constraint. - model.Add(starts[i] == 0).OnlyEnforceIf(start_lit) + model.add(starts[i] == 0).only_enforce_if(start_lit) # if node i is last. - end_lit = model.NewBoolVar(f"end_{i}") + end_lit = model.new_bool_var(f"end_{i}") arcs.append((i + 1, 0, end_lit)) for j in all_tasks: if i == j: - arcs.append((i + 1, i + 1, presences[i].Not())) - model.Add(ranks[i] == -1).OnlyEnforceIf(presences[i].Not()) + arcs.append((i + 1, i + 1, presences[i].negated())) + model.add(ranks[i] == -1).only_enforce_if(presences[i].negated()) else: - literal = model.NewBoolVar(f"arc_{i}_to_{j}") + literal = model.new_bool_var(f"arc_{i}_to_{j}") arcs.append((i + 1, j + 1, literal)) - model.Add(ranks[j] == ranks[i] + 1).OnlyEnforceIf(literal) + model.add(ranks[j] == ranks[i] + 1).only_enforce_if(literal) # To perform the transitive reduction from precedences to successors, # we need to tie the starts of the tasks with 'literal'. @@ -88,17 +88,19 @@ def rank_tasks_with_circuit( # # Note that we could use this literal to penalize the transition, add an # extra delay to the precedence. - model.Add(starts[j] >= starts[i] + durations[i]).OnlyEnforceIf(literal) + model.add(starts[j] >= starts[i] + durations[i]).only_enforce_if( + literal + ) # Manage the empty circuit - empty = model.NewBoolVar("empty") + empty = model.new_bool_var("empty") arcs.append((0, 0, empty)) for i in all_tasks: - model.AddImplication(empty, presences[i].Not()) + model.add_implication(empty, presences[i].negated()) # Add the circuit constraint. - model.AddCircuit(arcs) + model.add_circuit(arcs) def ranking_sample_sat(): @@ -117,14 +119,14 @@ def ranking_sample_sat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalFixedSizeIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_fixed_size_interval_var( start, duration, presence, f"opt_interval[{t}]" ) if t < num_tasks // 2: - model.Add(presence == 1) + model.add(presence == 1) starts.append(start) durations.append(duration) @@ -132,45 +134,44 @@ def ranking_sample_sat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. rank_tasks_with_circuit(model, starts, durations, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(starts[t] + durations[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(starts[t] + durations[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") diff --git a/ortools/sat/samples/ranking_sample_sat.py b/ortools/sat/samples/ranking_sample_sat.py index 549e67d897b..4687d9c2fcf 100644 --- a/ortools/sat/samples/ranking_sample_sat.py +++ b/ortools/sat/samples/ranking_sample_sat.py @@ -17,7 +17,12 @@ from ortools.sat.python import cp_model -def RankTasks(model, starts, presences, ranks): +def RankTasks( + model: cp_model.CpModel, + starts: list[cp_model.IntVar], + presences: list[cp_model.IntVar], + ranks: list[cp_model.IntVar], +): """This method adds constraints and variables to links tasks and ranks. This method assumes that all starts are disjoint, meaning that all tasks have @@ -41,36 +46,44 @@ def RankTasks(model, starts, presences, ranks): if i == j: precedences[(i, j)] = presences[i] else: - prec = model.NewBoolVar(f"{i} before {j}") + prec = model.new_bool_var(f"{i} before {j}") precedences[(i, j)] = prec - model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec) + model.add(starts[i] < starts[j]).only_enforce_if(prec) # Treats optional intervals. for i in range(num_tasks - 1): for j in range(i + 1, num_tasks): tmp_array = [precedences[(i, j)], precedences[(j, i)]] - if not cp_model.ObjectIsATrueLiteral(presences[i]): - tmp_array.append(presences[i].Not()) + if not cp_model.object_is_a_true_literal(presences[i]): + tmp_array.append(presences[i].negated()) # Makes sure that if i is not performed, all precedences are false. - model.AddImplication(presences[i].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[i].Not(), precedences[(j, i)].Not()) - if not cp_model.ObjectIsATrueLiteral(presences[j]): - tmp_array.append(presences[j].Not()) + model.add_implication( + presences[i].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[i].negated(), precedences[(j, i)].negated() + ) + if not cp_model.object_is_a_true_literal(presences[j]): + tmp_array.append(presences[j].negated()) # Makes sure that if j is not performed, all precedences are false. - model.AddImplication(presences[j].Not(), precedences[(i, j)].Not()) - model.AddImplication(presences[j].Not(), precedences[(j, i)].Not()) + model.add_implication( + presences[j].negated(), precedences[(i, j)].negated() + ) + model.add_implication( + presences[j].negated(), precedences[(j, i)].negated() + ) # The following bool_or will enforce that for any two intervals: # i precedes j or j precedes i or at least one interval is not # performed. - model.AddBoolOr(tmp_array) + model.add_bool_or(tmp_array) # Redundant constraint: it propagates early that at most one precedence # is true. - model.AddImplication(precedences[(i, j)], precedences[(j, i)].Not()) - model.AddImplication(precedences[(j, i)], precedences[(i, j)].Not()) + model.add_implication(precedences[(i, j)], precedences[(j, i)].negated()) + model.add_implication(precedences[(j, i)], precedences[(i, j)].negated()) # Links precedences and ranks. for i in all_tasks: - model.Add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) + model.add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1) def RankingSampleSat(): @@ -89,15 +102,15 @@ def RankingSampleSat(): # Creates intervals, half of them are optional. for t in all_tasks: - start = model.NewIntVar(0, horizon, f"start[{t}]") + start = model.new_int_var(0, horizon, f"start[{t}]") duration = t + 1 - end = model.NewIntVar(0, horizon, f"end[{t}]") + end = model.new_int_var(0, horizon, f"end[{t}]") if t < num_tasks // 2: - interval = model.NewIntervalVar(start, duration, end, f"interval[{t}]") + interval = model.new_interval_var(start, duration, end, f"interval[{t}]") presence = True else: - presence = model.NewBoolVar(f"presence[{t}]") - interval = model.NewOptionalIntervalVar( + presence = model.new_bool_var(f"presence[{t}]") + interval = model.new_optional_interval_var( start, duration, end, presence, f"o_interval[{t}]" ) starts.append(start) @@ -106,45 +119,44 @@ def RankingSampleSat(): presences.append(presence) # Ranks = -1 if and only if the tasks is not performed. - ranks.append(model.NewIntVar(-1, num_tasks - 1, f"rank[{t}]")) + ranks.append(model.new_int_var(-1, num_tasks - 1, f"rank[{t}]")) # Adds NoOverlap constraint. - model.AddNoOverlap(intervals) + model.add_no_overlap(intervals) # Adds ranking constraint. RankTasks(model, starts, presences, ranks) # Adds a constraint on ranks. - model.Add(ranks[0] < ranks[1]) + model.add(ranks[0] < ranks[1]) # Creates makespan variable. - makespan = model.NewIntVar(0, horizon, "makespan") + makespan = model.new_int_var(0, horizon, "makespan") for t in all_tasks: - model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t]) + model.add(ends[t] <= makespan).only_enforce_if(presences[t]) # Minimizes makespan - fixed gain per tasks performed. # As the fixed cost is less that the duration of the last interval, # the solver will not perform the last interval. - model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) + model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks)) # Solves the model model. solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: # Prints out the makespan and the start times and ranks of all tasks. - print(f"Optimal cost: {solver.ObjectiveValue()}") - print(f"Makespan: {solver.Value(makespan)}") + print(f"Optimal cost: {solver.objective_value}") + print(f"Makespan: {solver.value(makespan)}") for t in all_tasks: - if solver.Value(presences[t]): + if solver.value(presences[t]): print( - f"Task {t} starts at {solver.Value(starts[t])} " - f"with rank {solver.Value(ranks[t])}" + f"Task {t} starts at {solver.value(starts[t])} " + f"with rank {solver.value(ranks[t])}" ) else: print( - f"Task {t} in not performed " - f"and ranked at {solver.Value(ranks[t])}" + f"Task {t} in not performed and ranked at {solver.value(ranks[t])}" ) else: print(f"Solver exited with nonoptimal status: {status}") diff --git a/ortools/sat/samples/reified_sample_sat.py b/ortools/sat/samples/reified_sample_sat.py index c01949c9a30..19894bb700a 100644 --- a/ortools/sat/samples/reified_sample_sat.py +++ b/ortools/sat/samples/reified_sample_sat.py @@ -21,20 +21,20 @@ def ReifiedSampleSat(): """Showcase creating a reified constraint.""" model = cp_model.CpModel() - x = model.NewBoolVar("x") - y = model.NewBoolVar("y") - b = model.NewBoolVar("b") + x = model.new_bool_var("x") + y = model.new_bool_var("y") + b = model.new_bool_var("b") # First version using a half-reified bool and. - model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b) + model.add_bool_and(x, y.negated()).only_enforce_if(b) # Second version using implications. - model.AddImplication(b, x) - model.AddImplication(b, y.Not()) + model.add_implication(b, x) + model.add_implication(b, y.negated()) # Third version using bool or. - model.AddBoolOr(b.Not(), x) - model.AddBoolOr(b.Not(), y.Not()) + model.add_bool_or(b.negated(), x) + model.add_bool_or(b.negated(), y.negated()) ReifiedSampleSat() diff --git a/ortools/sat/samples/schedule_requests_sat.py b/ortools/sat/samples/schedule_requests_sat.py index 04ff4f53ab4..4c4038e246f 100644 --- a/ortools/sat/samples/schedule_requests_sat.py +++ b/ortools/sat/samples/schedule_requests_sat.py @@ -52,21 +52,21 @@ def main(): for n in all_nurses: for d in all_days: for s in all_shifts: - shifts[(n, d, s)] = model.NewBoolVar(f"shift_n{n}_d{d}_s{s}") + shifts[(n, d, s)] = model.new_bool_var(f"shift_n{n}_d{d}_s{s}") # [END variables] # Each shift is assigned to exactly one nurse in . # [START exactly_one_nurse] for d in all_days: for s in all_shifts: - model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses) + model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses) # [END exactly_one_nurse] # Each nurse works at most one shift per day. # [START at_most_one_shift] for n in all_nurses: for d in all_days: - model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts) + model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts) # [END at_most_one_shift] # [START assign_nurses_evenly] @@ -84,13 +84,12 @@ def main(): for d in all_days: for s in all_shifts: num_shifts_worked += shifts[(n, d, s)] - model.Add(min_shifts_per_nurse <= num_shifts_worked) - model.Add(num_shifts_worked <= max_shifts_per_nurse) + model.add(min_shifts_per_nurse <= num_shifts_worked) + model.add(num_shifts_worked <= max_shifts_per_nurse) # [END assign_nurses_evenly] # [START objective] - # pylint: disable=g-complex-comprehension - model.Maximize( + model.maximize( sum( shift_requests[n][d][s] * shifts[(n, d, s)] for n in all_nurses @@ -103,7 +102,7 @@ def main(): # Creates the solver and solve. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] @@ -113,14 +112,14 @@ def main(): print("Day", d) for n in all_nurses: for s in all_shifts: - if solver.Value(shifts[(n, d, s)]) == 1: + if solver.value(shifts[(n, d, s)]) == 1: if shift_requests[n][d][s] == 1: print("Nurse", n, "works shift", s, "(requested).") else: print("Nurse", n, "works shift", s, "(not requested).") print() print( - f"Number of shift requests met = {solver.ObjectiveValue()}", + f"Number of shift requests met = {solver.objective_value}", f"(out of {num_nurses * min_shifts_per_nurse})", ) else: @@ -130,9 +129,9 @@ def main(): # Statistics. # [START statistics] print("\nStatistics") - print(f" - conflicts: {solver.NumConflicts()}") - print(f" - branches : {solver.NumBranches()}") - print(f" - wall time: {solver.WallTime()}s") + print(f" - conflicts: {solver.num_conflicts}") + print(f" - branches : {solver.num_branches}") + print(f" - wall time: {solver.wall_time}s") # [END statistics] diff --git a/ortools/sat/samples/scheduling_with_calendar_sample_sat.py b/ortools/sat/samples/scheduling_with_calendar_sample_sat.py index 86f1c3a334f..c7302f7bbff 100644 --- a/ortools/sat/samples/scheduling_with_calendar_sample_sat.py +++ b/ortools/sat/samples/scheduling_with_calendar_sample_sat.py @@ -20,20 +20,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def SchedulingWithCalendarSampleSat(): """Interval spanning across a lunch break.""" @@ -47,25 +42,27 @@ def SchedulingWithCalendarSampleSat(): # Because the duration is at least 3 hours, work cannot start after 15h. # Because of the break, work cannot start at 13h. - start = model.NewIntVarFromDomain( - cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), "start" + start = model.new_int_var_from_domain( + cp_model.Domain.from_intervals([(8, 12), (14, 15)]), "start" ) - duration = model.NewIntVar(3, 4, "duration") - end = model.NewIntVar(8, 18, "end") - unused_interval = model.NewIntervalVar(start, duration, end, "interval") + duration = model.new_int_var(3, 4, "duration") + end = model.new_int_var(8, 18, "end") + unused_interval = model.new_interval_var(start, duration, end, "interval") # We have 2 states (spanning across lunch or not) - across = model.NewBoolVar("across") - non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15]) - model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf( - across.Not() + across = model.new_bool_var("across") + non_spanning_hours = cp_model.Domain.from_values([8, 9, 10, 14, 15]) + model.add_linear_expression_in_domain(start, non_spanning_hours).only_enforce_if( + across.negated() ) - model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across) - model.Add(duration == 3).OnlyEnforceIf(across.Not()) - model.Add(duration == 4).OnlyEnforceIf(across) + model.add_linear_constraint(start, 11, 12).only_enforce_if(across) + model.add(duration == 3).only_enforce_if(across.negated()) + model.add(duration == 4).only_enforce_if(across) # Search for x values in increasing order. - model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy( + [start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE + ) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -77,7 +74,7 @@ def SchedulingWithCalendarSampleSat(): # Search and print all solutions. solution_printer = VarArraySolutionPrinter([start, duration, across]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) SchedulingWithCalendarSampleSat() diff --git a/ortools/sat/samples/search_for_all_solutions_sample_sat.py b/ortools/sat/samples/search_for_all_solutions_sample_sat.py index 75ce0f31bc7..145738acfdb 100644 --- a/ortools/sat/samples/search_for_all_solutions_sample_sat.py +++ b/ortools/sat/samples/search_for_all_solutions_sample_sat.py @@ -22,18 +22,19 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count # [END print_solution] @@ -48,14 +49,14 @@ def SearchForAllSolutionsSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Create the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # Create a solver and solve. @@ -65,11 +66,11 @@ def SearchForAllSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SearchForAllSolutionsSampleSat() diff --git a/ortools/sat/samples/simple_sat_program.py b/ortools/sat/samples/simple_sat_program.py index 22bd3c45465..f16b677a4fe 100644 --- a/ortools/sat/samples/simple_sat_program.py +++ b/ortools/sat/samples/simple_sat_program.py @@ -29,27 +29,27 @@ def SimpleSatProgram(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # Creates a solver and solves the model. # [START solve] solver = cp_model.CpSolver() - status = solver.Solve(model) + status = solver.solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") else: print("No solution found.") # [END print_solution] diff --git a/ortools/sat/samples/solution_hinting_sample_sat.py b/ortools/sat/samples/solution_hinting_sample_sat.py index 25717665678..9b45bec90d0 100644 --- a/ortools/sat/samples/solution_hinting_sample_sat.py +++ b/ortools/sat/samples/solution_hinting_sample_sat.py @@ -28,33 +28,33 @@ def SolutionHintingSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # [START objective] - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # [END objective] # Solution hinting: x <- 1, y <- 2 - model.AddHint(x, 1) - model.AddHint(y, 2) + model.add_hint(x, 1) + model.add_hint(y, 2) # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolutionHintingSampleSat() diff --git a/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py b/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py index 8431fdf82aa..fb47d1caa72 100644 --- a/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py +++ b/ortools/sat/samples/solve_and_print_intermediate_solutions_sample_sat.py @@ -23,20 +23,21 @@ class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 - def on_solution_callback(self): + def on_solution_callback(self) -> None: print(f"Solution {self.__solution_count}") - print(f" objective value = {self.ObjectiveValue()}") + print(f" objective value = {self.objective_value}") for v in self.__variables: - print(f" {v}={self.Value(v)}", end=" ") + print(f" {v}={self.value(v)}", end=" ") print() self.__solution_count += 1 - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count # [END print_solution] @@ -51,29 +52,29 @@ def SolveAndPrintIntermediateSolutionsSampleSat(): # Creates the variables. # [START variables] num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # [END variables] # Creates the constraints. # [START constraints] - model.Add(x != y) + model.add(x != y) # [END constraints] # [START objective] - model.Maximize(x + 2 * y + 3 * z) + model.maximize(x + 2 * y + 3 * z) # [END objective] # Creates a solver and solves. # [START solve] solver = cp_model.CpSolver() solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z]) - status = solver.Solve(model, solution_printer) + status = solver.solve(model, solution_printer) # [END solve] - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") SolveAndPrintIntermediateSolutionsSampleSat() diff --git a/ortools/sat/samples/solve_with_time_limit_sample_sat.py b/ortools/sat/samples/solve_with_time_limit_sample_sat.py index 6688e49989b..ba46aa7e8c5 100644 --- a/ortools/sat/samples/solve_with_time_limit_sample_sat.py +++ b/ortools/sat/samples/solve_with_time_limit_sample_sat.py @@ -24,11 +24,11 @@ def SolveWithTimeLimitSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Adds an all-different constraint. - model.Add(x != y) + model.add(x != y) # Creates a solver and solves the model. solver = cp_model.CpSolver() @@ -36,12 +36,12 @@ def SolveWithTimeLimitSampleSat(): # Sets a time limit of 10 seconds. solver.parameters.max_time_in_seconds = 10.0 - status = solver.Solve(model) + status = solver.solve(model) if status == cp_model.OPTIMAL: - print(f"x = {solver.Value(x)}") - print(f"y = {solver.Value(y)}") - print(f"z = {solver.Value(z)}") + print(f"x = {solver.value(x)}") + print(f"y = {solver.value(y)}") + print(f"z = {solver.value(z)}") SolveWithTimeLimitSampleSat() diff --git a/ortools/sat/samples/step_function_sample_sat.py b/ortools/sat/samples/step_function_sample_sat.py index a6e32e71997..0eba41d9a05 100644 --- a/ortools/sat/samples/step_function_sample_sat.py +++ b/ortools/sat/samples/step_function_sample_sat.py @@ -20,20 +20,15 @@ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables): + def __init__(self, variables: list[cp_model.IntVar]): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables - self.__solution_count = 0 - def on_solution_callback(self): - self.__solution_count += 1 + def on_solution_callback(self) -> None: for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() - def solution_count(self): - return self.__solution_count - def step_function_sample_sat(): """Encode the step function.""" @@ -42,7 +37,7 @@ def step_function_sample_sat(): model = cp_model.CpModel() # Declare our primary variable. - x = model.NewIntVar(0, 20, "x") + x = model.new_int_var(0, 20, "x") # Create the expression variable and implement the step function # Note it is not defined for x == 2. @@ -53,32 +48,32 @@ def step_function_sample_sat(): # -- --- 0 # 0 ================ 20 # - expr = model.NewIntVar(0, 3, "expr") + expr = model.new_int_var(0, 3, "expr") # expr == 0 on [5, 6] U [8, 10] - b0 = model.NewBoolVar("b0") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)]) - ).OnlyEnforceIf(b0) - model.Add(expr == 0).OnlyEnforceIf(b0) + b0 = model.new_bool_var("b0") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(5, 6), (8, 10)]) + ).only_enforce_if(b0) + model.add(expr == 0).only_enforce_if(b0) # expr == 2 on [0, 1] U [3, 4] U [11, 20] - b2 = model.NewBoolVar("b2") - model.AddLinearExpressionInDomain( - x, cp_model.Domain.FromIntervals([(0, 1), (3, 4), (11, 20)]) - ).OnlyEnforceIf(b2) - model.Add(expr == 2).OnlyEnforceIf(b2) + b2 = model.new_bool_var("b2") + model.add_linear_expression_in_domain( + x, cp_model.Domain.from_intervals([(0, 1), (3, 4), (11, 20)]) + ).only_enforce_if(b2) + model.add(expr == 2).only_enforce_if(b2) # expr == 3 when x == 7 - b3 = model.NewBoolVar("b3") - model.Add(x == 7).OnlyEnforceIf(b3) - model.Add(expr == 3).OnlyEnforceIf(b3) + b3 = model.new_bool_var("b3") + model.add(x == 7).only_enforce_if(b3) + model.add(expr == 3).only_enforce_if(b3) # At least one bi is true. (we could use an exactly one constraint). - model.AddBoolOr(b0, b2, b3) + model.add_bool_or(b0, b2, b3) # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) + model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() @@ -90,7 +85,7 @@ def step_function_sample_sat(): # Search and print out all solutions. solution_printer = VarArraySolutionPrinter([x, expr]) - solver.Solve(model, solution_printer) + solver.solve(model, solution_printer) step_function_sample_sat() diff --git a/ortools/sat/samples/stop_after_n_solutions_sample_sat.py b/ortools/sat/samples/stop_after_n_solutions_sample_sat.py index 5b38f856121..36beb1414f4 100644 --- a/ortools/sat/samples/stop_after_n_solutions_sample_sat.py +++ b/ortools/sat/samples/stop_after_n_solutions_sample_sat.py @@ -21,22 +21,23 @@ class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback): """Print intermediate solutions.""" - def __init__(self, variables, limit): + def __init__(self, variables: list[cp_model.IntVar], limit: int): cp_model.CpSolverSolutionCallback.__init__(self) self.__variables = variables self.__solution_count = 0 self.__solution_limit = limit - def on_solution_callback(self): + def on_solution_callback(self) -> None: self.__solution_count += 1 for v in self.__variables: - print(f"{v}={self.Value(v)}", end=" ") + print(f"{v}={self.value(v)}", end=" ") print() if self.__solution_count >= self.__solution_limit: print(f"Stop search after {self.__solution_limit} solutions") - self.StopSearch() + self.stop_search() - def solution_count(self): + @property + def solution_count(self) -> int: return self.__solution_count @@ -46,9 +47,9 @@ def StopAfterNSolutionsSampleSat(): model = cp_model.CpModel() # Creates the variables. num_vals = 3 - x = model.NewIntVar(0, num_vals - 1, "x") - y = model.NewIntVar(0, num_vals - 1, "y") - z = model.NewIntVar(0, num_vals - 1, "z") + x = model.new_int_var(0, num_vals - 1, "x") + y = model.new_int_var(0, num_vals - 1, "y") + z = model.new_int_var(0, num_vals - 1, "z") # Create a solver and solve. solver = cp_model.CpSolver() @@ -56,10 +57,10 @@ def StopAfterNSolutionsSampleSat(): # Enumerate all solutions. solver.parameters.enumerate_all_solutions = True # Solve. - status = solver.Solve(model, solution_printer) - print(f"Status = {solver.StatusName(status)}") - print(f"Number of solutions found: {solution_printer.solution_count()}") - assert solution_printer.solution_count() == 5 + status = solver.solve(model, solution_printer) + print(f"Status = {solver.status_name(status)}") + print(f"Number of solutions found: {solution_printer.solution_count}") + assert solution_printer.solution_count == 5 StopAfterNSolutionsSampleSat() diff --git a/ortools/util/fp_utils.cc b/ortools/util/fp_utils.cc index 336ce097954..c9eb39b7e75 100644 --- a/ortools/util/fp_utils.cc +++ b/ortools/util/fp_utils.cc @@ -26,6 +26,7 @@ #include "absl/base/casts.h" #include "absl/base/internal/endian.h" +#include "absl/log/check.h" #include "ortools/util/bitset.h" namespace operations_research { @@ -113,6 +114,9 @@ void GetBestScalingOfDoublesToInt64(const std::vector& input, // round(fabs(c).2^candidate) <= max_absolute_sum. const double c = std::max(-min_term, max_term); int candidate = msb - ilogb(c); + if (candidate >= std::numeric_limits::max_exponent) { + candidate = std::numeric_limits::max_exponent - 1; + } if (std::round(ldexp(std::abs(c), candidate)) > max_absolute_sum) { --candidate; } @@ -185,6 +189,7 @@ double GetBestScalingOfDoublesToInt64(const std::vector& input, double scaling_factor; GetBestScalingOfDoublesToInt64(input, lb, ub, max_absolute_sum, &scaling_factor); + DCHECK(std::isfinite(scaling_factor)); return scaling_factor; } @@ -197,10 +202,12 @@ void GetBestScalingOfDoublesToInt64(const std::vector& input, scaling_factor); ComputeScalingErrors(input, {}, {}, *scaling_factor, max_relative_coeff_error, &max_scaled_sum_error); + DCHECK(std::isfinite(*scaling_factor)); } int64_t ComputeGcdOfRoundedDoubles(const std::vector& x, double scaling_factor) { + DCHECK(std::isfinite(scaling_factor)); int64_t gcd = 0; const int size = static_cast(x.size()); for (int i = 0; i < size && gcd != 1; ++i) { diff --git a/ortools/util/python/sorted_interval_list.cc b/ortools/util/python/sorted_interval_list.cc index 282fea72cee..ed3d54b2926 100644 --- a/ortools/util/python/sorted_interval_list.cc +++ b/ortools/util/python/sorted_interval_list.cc @@ -16,6 +16,7 @@ #include #include "ortools/util/python/sorted_interval_list_doc.h" +#include "pybind11/cast.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" @@ -24,36 +25,49 @@ using ::pybind11::arg; PYBIND11_MODULE(sorted_interval_list, m) { pybind11::class_(m, "Domain", DOC(operations_research, Domain)) - .def_static("AllValues", &Domain::AllValues, + .def_static("all_values", &Domain::AllValues, DOC(operations_research, Domain, AllValues)) - .def_static("FromValues", &Domain::FromValues, + .def_static("from_values", &Domain::FromValues, DOC(operations_research, Domain, FromValues), arg("values")) - .def_static("FromIntervals", &Domain::FromVectorIntervals, + .def_static("from_intervals", &Domain::FromVectorIntervals, DOC(operations_research, Domain, FromVectorIntervals), arg("intervals")) - .def_static("FromFlatIntervals", &Domain::FromFlatIntervals, + .def_static("from_flat_intervals", &Domain::FromFlatIntervals, DOC(operations_research, Domain, FromFlatIntervals), arg("flat_intervals")) .def(pybind11::init(), DOC(operations_research, Domain, Domain)) - .def("AdditionWith", &Domain::AdditionWith, + .def("addition_with", &Domain::AdditionWith, DOC(operations_research, Domain, AdditionWith), arg("domain")) - .def("Complement", &Domain::Complement, + .def("complement", &Domain::Complement, DOC(operations_research, Domain, Complement)) - .def("Contains", &Domain::Contains, + .def("contains", &Domain::Contains, DOC(operations_research, Domain, Contains), arg("value")) - .def("FlattenedIntervals", &Domain::FlattenedIntervals, + .def("flattened_intervals", &Domain::FlattenedIntervals, DOC(operations_research, Domain, FlattenedIntervals)) - .def("IntersectionWith", &Domain::IntersectionWith, + .def("intersection_with", &Domain::IntersectionWith, DOC(operations_research, Domain, IntersectionWith), arg("domain")) - .def("IsEmpty", &Domain::IsEmpty, + .def("is_empty", &Domain::IsEmpty, DOC(operations_research, Domain, IsEmpty)) - .def("Size", &Domain::Size, DOC(operations_research, Domain, Size)) - .def("Max", &Domain::Max, DOC(operations_research, Domain, Max)) - .def("Min", &Domain::Min, DOC(operations_research, Domain, Min)) - .def("Negation", &Domain::Negation, + .def("size", &Domain::Size, DOC(operations_research, Domain, Size)) + .def("max", &Domain::Max, DOC(operations_research, Domain, Max)) + .def("min", &Domain::Min, DOC(operations_research, Domain, Min)) + .def("negation", &Domain::Negation, DOC(operations_research, Domain, Negation)) - .def("UnionWith", &Domain::UnionWith, + .def("union_with", &Domain::UnionWith, DOC(operations_research, Domain, UnionWith), arg("domain")) - .def("__str__", &Domain::ToString); + .def("__str__", &Domain::ToString) + // Compatibility with pre PEP8 APIs. + .def_static("AllValues", &Domain::AllValues, + DOC(operations_research, Domain, AllValues)) + .def_static("FromValues", &Domain::FromValues, + DOC(operations_research, Domain, FromValues), arg("values")) + .def_static("FromIntervals", &Domain::FromVectorIntervals, + DOC(operations_research, Domain, FromVectorIntervals), + arg("intervals")) + .def_static("FromFlatIntervals", &Domain::FromFlatIntervals, + DOC(operations_research, Domain, FromFlatIntervals), + arg("flat_intervals")) + .def("FlattenedIntervals", &Domain::FlattenedIntervals, + DOC(operations_research, Domain, FlattenedIntervals)); } diff --git a/ortools/util/python/sorted_interval_list_test.py b/ortools/util/python/sorted_interval_list_test.py index 5cd4f7affa9..90dbeb4d932 100755 --- a/ortools/util/python/sorted_interval_list_test.py +++ b/ortools/util/python/sorted_interval_list_test.py @@ -21,68 +21,68 @@ class SortedIntervalListTest(absltest.TestCase): def testCtorAndGetter(self): bool_domain = sorted_interval_list.Domain(0, 1) - self.assertEqual(2, bool_domain.Size()) - self.assertEqual(0, bool_domain.Min()) - self.assertEqual(1, bool_domain.Max()) - self.assertFalse(bool_domain.IsEmpty()) + self.assertEqual(2, bool_domain.size()) + self.assertEqual(0, bool_domain.min()) + self.assertEqual(1, bool_domain.max()) + self.assertFalse(bool_domain.is_empty()) self.assertEqual(str(bool_domain), "[0,1]") def testFromValues(self): domain = sorted_interval_list.Domain.FromValues([1, 3, -5, 5]) - self.assertEqual(4, domain.Size()) - self.assertEqual(-5, domain.Min()) - self.assertEqual(5, domain.Max()) - self.assertEqual([-5, -5, 1, 1, 3, 3, 5, 5], domain.FlattenedIntervals()) - self.assertTrue(domain.Contains(1)) - self.assertFalse(domain.Contains(0)) + self.assertEqual(4, domain.size()) + self.assertEqual(-5, domain.min()) + self.assertEqual(5, domain.max()) + self.assertEqual([-5, -5, 1, 1, 3, 3, 5, 5], domain.flattened_intervals()) + self.assertTrue(domain.contains(1)) + self.assertFalse(domain.contains(0)) def testFromIntervals(self): - domain = sorted_interval_list.Domain.FromIntervals([[2, 4], [-2, 0]]) - self.assertEqual(6, domain.Size()) - self.assertEqual(-2, domain.Min()) - self.assertEqual(4, domain.Max()) - self.assertEqual([-2, 0, 2, 4], domain.FlattenedIntervals()) + domain = sorted_interval_list.Domain.from_intervals([[2, 4], [-2, 0]]) + self.assertEqual(6, domain.size()) + self.assertEqual(-2, domain.min()) + self.assertEqual(4, domain.max()) + self.assertEqual([-2, 0, 2, 4], domain.flattened_intervals()) def testFromFlatIntervals(self): - domain = sorted_interval_list.Domain.FromFlatIntervals([2, 4, -2, 0]) - self.assertEqual(6, domain.Size()) - self.assertEqual(-2, domain.Min()) - self.assertEqual(4, domain.Max()) - self.assertEqual([-2, 0, 2, 4], domain.FlattenedIntervals()) + domain = sorted_interval_list.Domain.from_flat_intervals([2, 4, -2, 0]) + self.assertEqual(6, domain.size()) + self.assertEqual(-2, domain.min()) + self.assertEqual(4, domain.max()) + self.assertEqual([-2, 0, 2, 4], domain.flattened_intervals()) def testNegation(self): domain = sorted_interval_list.Domain(5, 20) - self.assertEqual([-20, -5], domain.Negation().FlattenedIntervals()) + self.assertEqual([-20, -5], domain.negation().flattened_intervals()) def testUnion(self): d1 = sorted_interval_list.Domain(0, 5) d2 = sorted_interval_list.Domain(10, 15) - d3 = d1.UnionWith(d2) - self.assertEqual([0, 5], d1.FlattenedIntervals()) - self.assertEqual([10, 15], d2.FlattenedIntervals()) - self.assertEqual([0, 5, 10, 15], d3.FlattenedIntervals()) + d3 = d1.union_with(d2) + self.assertEqual([0, 5], d1.flattened_intervals()) + self.assertEqual([10, 15], d2.flattened_intervals()) + self.assertEqual([0, 5, 10, 15], d3.flattened_intervals()) def testIntersection(self): d1 = sorted_interval_list.Domain(0, 10) d2 = sorted_interval_list.Domain(5, 15) - d3 = d1.IntersectionWith(d2) - self.assertEqual([0, 10], d1.FlattenedIntervals()) - self.assertEqual([5, 15], d2.FlattenedIntervals()) - self.assertEqual([5, 10], d3.FlattenedIntervals()) + d3 = d1.intersection_with(d2) + self.assertEqual([0, 10], d1.flattened_intervals()) + self.assertEqual([5, 15], d2.flattened_intervals()) + self.assertEqual([5, 10], d3.flattened_intervals()) def testAddition(self): d1 = sorted_interval_list.Domain(0, 5) d2 = sorted_interval_list.Domain(10, 15) - d3 = d1.AdditionWith(d2) - self.assertEqual([0, 5], d1.FlattenedIntervals()) - self.assertEqual([10, 15], d2.FlattenedIntervals()) - self.assertEqual([10, 20], d3.FlattenedIntervals()) + d3 = d1.addition_with(d2) + self.assertEqual([0, 5], d1.flattened_intervals()) + self.assertEqual([10, 15], d2.flattened_intervals()) + self.assertEqual([10, 20], d3.flattened_intervals()) def testComplement(self): d1 = sorted_interval_list.Domain(-9223372036854775808, 5) - d2 = d1.Complement() - self.assertEqual([-9223372036854775808, 5], d1.FlattenedIntervals()) - self.assertEqual([6, 9223372036854775807], d2.FlattenedIntervals()) + d2 = d1.complement() + self.assertEqual([-9223372036854775808, 5], d1.flattened_intervals()) + self.assertEqual([6, 9223372036854775807], d2.flattened_intervals()) if __name__ == "__main__": diff --git a/ortools/util/zvector.h b/ortools/util/zvector.h index 25fb6701d9d..78a66cafde2 100644 --- a/ortools/util/zvector.h +++ b/ortools/util/zvector.h @@ -16,18 +16,15 @@ #if (defined(__APPLE__) || defined(__FreeBSD__)) && defined(__GNUC__) #include -#elif !defined(_MSC_VER) +#elif !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__MINGW64__) #include #endif -#include -#include -#include +#include #include -#include +#include // IWYU pragma: keep +#include "absl/log/check.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" -#include "ortools/base/types.h" // An array class for storing arrays of integers. // From 2505eaba6a429d8b3681fbecd0758c51e5d57bdf Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Thu, 16 Nov 2023 14:51:32 +0100 Subject: [PATCH 21/21] use XPRSmipoptimize and XPRSlpoptimize instead of XPRSminim and XPRSmaxim (#114) * use XPRSmipoptimize and XPRSlpoptimize instead of XPRSminim and XPRSmaxim * clean xpress/environment files * accept changes: empty char* parameter for XPRS*optimize --- ortools/linear_solver/xpress_interface.cc | 10 ++-------- ortools/xpress/environment.cc | 9 +++++---- ortools/xpress/environment.h | 4 ++-- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index c28cab27fdf..dd73c03a33a 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -1912,16 +1912,10 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { int xpress_stat = 0; if (mMip) { - if (this->maximize_) - status = XPRSmaxim(mLp, "g"); - else - status = XPRSminim(mLp, "g"); + status = XPRSmipoptimize(mLp,""); XPRSgetintattrib(mLp, XPRS_MIPSTATUS, &xpress_stat); } else { - if (this->maximize_) - status = XPRSmaxim(mLp, ""); - else - status = XPRSminim(mLp, ""); + status = XPRSlpoptimize(mLp,""); XPRSgetintattrib(mLp, XPRS_LPSTATUS, &xpress_stat); } diff --git a/ortools/xpress/environment.cc b/ortools/xpress/environment.cc index 0f3fcbba4de..c7aad2d8b43 100644 --- a/ortools/xpress/environment.cc +++ b/ortools/xpress/environment.cc @@ -92,8 +92,8 @@ std::function XPRSaddcbintsol = nullptr; std::function XPRSremovecbintsol = nullptr; std::function XPRSaddcbmessage = nullptr; -std::function XPRSminim = nullptr; -std::function XPRSmaxim = nullptr; +std::function XPRSlpoptimize = nullptr; +std::function XPRSmipoptimize = nullptr; absl::Status LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) { // This was generated with the parse_header_xpress.py script. @@ -156,8 +156,9 @@ absl::Status LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) { xpress_dynamic_library->GetFunction(&XPRSaddcbintsol, "XPRSaddcbintsol"); xpress_dynamic_library->GetFunction(&XPRSremovecbintsol, "XPRSremovecbintsol"); xpress_dynamic_library->GetFunction(&XPRSaddcbmessage, "XPRSaddcbmessage"); - xpress_dynamic_library->GetFunction(&XPRSminim, "XPRSminim"); - xpress_dynamic_library->GetFunction(&XPRSmaxim, "XPRSmaxim"); + xpress_dynamic_library->GetFunction(&XPRSlpoptimize, "XPRSlpoptimize"); + xpress_dynamic_library->GetFunction(&XPRSmipoptimize, "XPRSmipoptimize"); + auto notFound = xpress_dynamic_library->FunctionsNotFound(); if (!notFound.empty()) { diff --git a/ortools/xpress/environment.h b/ortools/xpress/environment.h index faee0a7e521..fcbec406c74 100644 --- a/ortools/xpress/environment.h +++ b/ortools/xpress/environment.h @@ -491,8 +491,8 @@ extern std::function XPRSaddcbintsol; extern std::function XPRSremovecbintsol; extern std::function XPRSaddcbmessage; -extern std::function XPRSminim; -extern std::function XPRSmaxim; +extern std::function XPRSlpoptimize; +extern std::function XPRSmipoptimize; } // namespace operations_research