diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bcb30cb14..a6a48ba07 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,6 @@ name: Run tests with coverage env: - version: 9.2.4 + version: 10.0.0 on: push: @@ -25,8 +25,8 @@ jobs: - name: Install dependencies (SCIPOptSuite) run: | - wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Linux-ubuntu22.deb - sudo apt-get update && sudo apt install -y ./SCIPOptSuite-${{ env.version }}-Linux-ubuntu22.deb + wget --quiet --no-check-certificate "https://github.com/scipopt/scip/releases/download/v${{ env.version }}/scipoptsuite_${{ env.version }}-1+jammy_amd64.deb" + sudo apt-get update && sudo apt install -y ./scipoptsuite_${{ env.version }}-1+jammy_amd64.deb - name: Setup python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -40,7 +40,7 @@ jobs: - name: Install PySCIPOpt run: | - export CFLAGS="-O0 -ggdb -Wall -Wextra -Werror" # Debug mode. More warnings. Warnings as errors. + export CFLAGS="-O0 -ggdb -Wall -Wextra -Werror -Wno-error=deprecated-declarations" # Debug mode. More warnings. Warnings as errors, but allow deprecated declarations. python -m pip install . -v 2>&1 | tee build.log grep -i "warning" build.log || true diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index e374983e4..3f17e85cc 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -1,7 +1,7 @@ name: Integration test env: - version: 9.2.4 + version: 10.0.0 on: push: @@ -23,8 +23,8 @@ jobs: - name: Install dependencies (SCIPOptSuite) run: | - wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Linux-ubuntu22.deb - sudo apt-get update && sudo apt install -y ./SCIPOptSuite-${{ env.version }}-Linux-ubuntu22.deb + wget --quiet --no-check-certificate "https://github.com/scipopt/scip/releases/download/v${version}/scipoptsuite_${version}-1+jammy_amd64.deb" + sudo apt-get update && sudo apt install -y ./scipoptsuite_${version}-1+jammy_amd64.deb - name: Setup python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -54,7 +54,7 @@ jobs: - name: Download dependencies (SCIPOptSuite) shell: powershell - run: wget https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-win64.exe -outfile scipopt-installer.exe + run: wget https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/scipoptsuite-${{ env.version }}-win-x64.exe -outfile scipopt-installer.exe - name: Install dependencies (SCIPOptSuite) shell: cmd @@ -92,10 +92,11 @@ jobs: - name: Install dependencies (SCIPOptSuite) run: | brew install tbb boost bison - wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Darwin.sh - chmod +x SCIPOptSuite-${{ env.version }}-Darwin.sh - ./SCIPOptSuite-${{ env.version }}-Darwin.sh --skip-license --include-subdir - mv SCIPOptSuite-${{ env.version }}-Darwin ${{ github.workspace }}/scipoptsuite + wget --quiet --no-check-certificate "https://github.com/scipopt/scip/releases/download/v${version}/scipoptsuite-${version}-macos13-arm64.tgz" + tar xzf "scipoptsuite-${version}-macos13-arm64.tgz" + chmod +x "scipoptsuite-${version}-macos13-arm64.sh" + "./scipoptsuite-${version}-macos13-arm64.sh" --skip-license --include-subdir + mv "scipoptsuite-${version}-macos13-arm64" "${{ github.workspace }}/scipoptsuite" - name: Setup python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -122,4 +123,3 @@ jobs: ### if you need valgrind on mac, you can install it via # brew tap LouisBrunner/valgrind # brew install --HEAD LouisBrunner/valgrind/valgrind - diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 89c880a29..e837cac4f 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -1,7 +1,7 @@ name: TestPyPI release env: - version: 9.2.4 + version: 10.0.0 # runs only when a release is published, not on drafts @@ -17,8 +17,8 @@ jobs: - name: Install dependencies (SCIPOptSuite) run: | - wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Linux-ubuntu20.deb - sudo apt-get update && sudo apt install -y ./SCIPOptSuite-${{ env.version }}-Linux-ubuntu20.deb + wget --quiet --no-check-certificate "https://github.com/scipopt/scip/releases/download/v${version}/scipoptsuite_${version}-1+jammy_amd64.deb" + sudo apt-get update && sudo apt install -y ./scipoptsuite_${version}-1+jammy_amd64.deb - name: Setup python 3 uses: actions/setup-python@v4 diff --git a/.github/workflows/update-packages-and-documentation.yml b/.github/workflows/update-packages-and-documentation.yml index 402321635..6d518c25f 100644 --- a/.github/workflows/update-packages-and-documentation.yml +++ b/.github/workflows/update-packages-and-documentation.yml @@ -1,7 +1,7 @@ name: Test and Release PyPI Package env: - version: 9.2.4 + version: 10.0.0 # runs only when a release is published, not on drafts @@ -38,8 +38,8 @@ jobs: - name: Install dependencies (SCIPOptSuite) run: | - wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Linux-ubuntu20.deb - sudo apt-get update && sudo apt install -y ./SCIPOptSuite-${{ env.version }}-Linux-ubuntu20.deb + wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/scipoptsuite_${{ env.version }}-1+jammy_amd64.deb + sudo apt-get update && sudo apt install -y ./scipoptsuite_${{ env.version }}-1+jammy_amd64.deb - name: Setup python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -71,7 +71,9 @@ jobs: - name: Download dependencies (SCIPOptSuite) shell: powershell - run: wget https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-win64-VS15.exe -outfile scipopt-installer.exe + run: | + $url = "https://github.com/scipopt/scip/releases/download/v${{ env.version }}/scipoptsuite-${{ env.version }}-win-x64.exe" + Invoke-WebRequest -Uri $url -OutFile scipopt-installer.exe - name: Install dependencies (SCIPOptSuite) shell: cmd @@ -107,8 +109,8 @@ jobs: - name: Install dependencies (SCIPOptSuite) run: | - wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Linux-ubuntu20.deb - sudo apt-get update && sudo apt install -y ./SCIPOptSuite-${{ env.version }}-Linux-ubuntu20.deb + wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/scipoptsuite_${{ env.version }}-1+jammy_amd64.deb + sudo apt-get update && sudo apt install -y ./scipoptsuite_${{ env.version }}-1+jammy_amd64.deb - name: Setup python 3 uses: actions/setup-python@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index cddd24531..cc0dfcb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ ### Changed ### Removed +## 6.0.0 - 2025.xx.yy +### Added +- Support for SCIP 10.0.0 +- Added support for IIS - Irreducible Inconsistent Subsystems +- Added 4 new events: TYPECHANGED, IMPLTYPECHANGED, DUALBOUNDIMPROVED, GAPUPDATED. +- Support for new implied integrality +- Wrapped varIsBinary(), varIsIntegral(), varIsImpliedIntegral(), varIsNonImpliedIntegral(), varGetImplType() +- Interfaced some exact SCIP methods +- wrapped SCIPprintStatisticsJson +### Fixed +### Changed +### Removed +- Removed methods chgAndConsCheckFlagWhenUpgr, chgAndConsRemovableFlagWhenUpgr + ## 5.7.0 - 2025.11.17 ### Added - Added possibility of having variables in exponent. diff --git a/docs/build.rst b/docs/build.rst index 5c377d9b9..f849b1895 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -21,6 +21,8 @@ To download SCIP please either use the pre-built SCIP Optimization Suite availab * - SCIP - PySCIPOpt + * - 10.0.0 + - 6.0 * - 9.2 - 5.3, 5.4, 5.5, 5.6, 5.7 * - 9.1 diff --git a/docs/tutorials/iis.rst b/docs/tutorials/iis.rst new file mode 100644 index 000000000..04dbcdb99 --- /dev/null +++ b/docs/tutorials/iis.rst @@ -0,0 +1,222 @@ +############### +Irreducible Infeasible Subsystems (IIS) +############### + +For the following, let us assume that a Model object is available, which is created as follows: + +.. code-block:: python + + from pyscipopt import Model, IISfinder, SCIP_RESULT + model = Model() + +.. contents:: Contents + +What is an IIS? +=============== + +It is a common issue for integer programming practitioners to (unexpectedly) encounter infeasible problems. +Often it is desirable to better understand exactly why the problem is infeasible. +Was it an error in the input data, was the underlying formulation incorrect, or was the model simply infeasible by construction? + +A common tool for helping diagnose the reason for infeasibility is an **Irreducible Infeasible Subsystem (IIS)**. +An IIS is a subset of constraints and variable bounds from the original problem that: + +1. Remains infeasible when considered together +2. Cannot be further reduced without the subsystem becoming feasible + +Practitioners can use IIS finders to narrow their focus onto a smaller, more manageable problem. +Note, however, that there are potentially many different irreducible subsystems for a given infeasible problem, and that IIS finders may not provide a guarantee of an IIS of minimum size. + +Generating an IIS +================= +Let us create a simple infeasible model and then generate an IIS for it. + +.. code-block:: python + + from pyscipopt import Model + + m = Model() + x1 = m.addVar("x1", vtype="B") + x2 = m.addVar("x2", vtype="B") + x3 = m.addVar("x3", vtype="B") + + # These four constraints cannot be satisfied simultaneously + m.addCons(x1 + x2 == 1, name="c1") + m.addCons(x2 + x3 == 1, name="c2") + m.addCons(x1 + x3 == 1, name="c3") + m.addCons(x1 + x2 + x3 <= 0, name="c4") + + model.optimize() + iis = model.generateIIS() + +When you run this code, SCIP will output a log showing the progress of finding the IIS: + +.. code-block:: text + + presolving: + presolving (1 rounds: 1 fast, 0 medium, 0 exhaustive): + 2 deleted vars, 2 deleted constraints, 0 added constraints, 0 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients + 0 implications, 0 cliques, 0 implied integral variables (0 bin, 0 int, 0 cont) + presolving detected infeasibility + Presolving Time: 0.00 + + SCIP Status : problem is solved [infeasible] + Solving Time (sec) : 0.00 + Solving Nodes : 0 + Primal Bound : +1.00000000000000e+20 (0 solutions) + Dual Bound : +1.00000000000000e+20 + Gap : 0.00 % + time(s)| node | cons | vars | bounds| infeasible + 0.0| 0| 1| 3| 6| no + 0.0| 0| 2| 3| 6| no + 0.0| 0| 4| 3| 6| no + 0.0| 0| 3| 3| 6| yes + 0.0| 0| 2| 3| 6| yes + 0.0| 0| 2| 3| 6| yes + + IIS Status : irreducible infeasible subsystem (IIS) found + IIS irreducible : yes + Generation Time (sec) : 0.01 + Generation Nodes : 0 + Num. Cons. in IIS : 2 + Num. Vars. in IIS : 3 + Num. Bounds in IIS : 6 + +.. note:: + While an already optimized infeasible model is not required to use the IIS functionality, it is + encouraged to call this functionality only after ``model.optimize()``. Otherwise, SCIP will naturally optimize + the base problem first to ensure that it is actually infeasible. + + After SCIP finds that the model is infeasible, see that SCIP's IIS finders alternate between including constraints to make the problem feasible, and removing constraints to make the problem as small as possible. + You see in the final statistics that the IIS is indeed irreducible, with 3 variables and 2 constraints. + +The IIS Object +============== + +The ``IIS`` object returned by ``generateIIS()`` can be queried to access the following information: + +- **time**: The CPU time spent finding the IIS +- **irreducible**: Boolean indicating if the IIS is irreducible +- **nodes**: Number of nodes explored during IIS generation +- **model**: A ``Model`` object containing the subscip with the IIS constraints + +You can interact with the subscip to examine which constraints and variables are part of the IIS: + +.. code-block:: python + + iis = model.generateIIS() + subscip = iis.getSubscip() + + # Get constraints in the IIS + for cons in subscip.getConss(): + print(f"Constraint: {cons.name}") + + # Get variables in the IIS + for var in subscip.getVars(): + print(f"Variable: {var.name}") + +Creating a Custom IIS Finder +============================= + +You may want to implement your own algorithm to find an IIS. +PySCIPOpt supports this through the ``IISfinder`` class, which allows you to define custom logic +for identifying infeasible subsystems. + +Basic Structure +--------------- + +To create a custom IIS finder, inherit from the ``IISfinder`` class and implement the ``iisfinderexec`` method: + +.. code-block:: python + + from pyscipopt import Model, IISfinder, SCIP_RESULT + + class SimpleIISFinder(IISfinder): + """ + A simple IIS finder that removes constraints one by one + until the problem becomes feasible. + """ + + def iisfinderexec(self): + subscip = self.iis.getSubscip() + constraints = subscip.getConss() + + # Start with all constraints + active_constraints = set(constraints) + + for cons in constraints: + # Temporarily remove the constraint + active_constraints.discard(cons) + + # Check if remaining constraints are still infeasible + # (This would require setting up a sub-problem with only active_constraints) + # For simplicity, we use the full solve approach here + + subscip.freeTransform() + subscip.delCons(cons) + subscip.optimize() + + if subscip.getStatus() == SCIP_STATUS.INFEASIBLE: + # Still infeasible without this constraint + # Keep it removed + pass + else: + # Feasible without it, so constraint is needed in IIS + active_constraints.add(cons) + # In practice, you'd recreate the subscip with all active constraints + + iis.markIrreducible() + return {"result": SCIP_RESULT.SUCCESS} + +Including Your Custom IIS Finder +--------------------------------- + +To use your custom IIS finder, include it in the model before calling ``generateIIS()``: + +.. code-block:: python + + # Create model + model = Model() + + # Add variables and constraints (infeasible problem) + x1 = model.addVar("x1", vtype="B") + x2 = model.addVar("x2", vtype="B") + x3 = model.addVar("x3", vtype="B") + + model.addCons(x1 + x2 == 1, name="c1") + model.addCons(x2 + x3 == 1, name="c2") + model.addCons(x1 + x3 == 1, name="c3") + + # Create and include the custom IIS finder + simple_iis = SimpleIISFinder() + model.includeIISfinder( + simple_iis, + name="simpleiis", + desc="Simple greedy IIS finder", + priority=1000000 # Higher priority means it will be used first + ) + + # Solve to verify infeasibility + model.optimize() + + # Generate IIS using our custom finder + iis = model.generateIIS() + + # Examine the result + print(f"\nIIS Information:") + print(f" Time: {iis.getTime():.2f} seconds") + print(f" Nodes: {iis.getNNodes()}") + print(f" Irreducible: {iis.isSubscipIrreducible()}") + print(f" Number of constraints: {iis.getSubscip().getNConss()}") + +Key Methods in IISfinder +------------------------- + +When implementing a custom IIS finder, you have access to several important methods: + +- ``subscip=self.iis.getSubscip()``: Get the sub-problem (Model) containing the candidate IIS +- ``subscip.getConss()``: Get all constraints in the subscip +- ``subscip.delCons(cons)``: Remove a constraint from the subscip +- ``subscip.addCons(cons)``: Add a constraint back to the subscip +- ``subscip.optimize()``: Solve the subscip +- ``subscip.getStatus()``: Check if the subscip is infeasible diff --git a/pyproject.toml b/pyproject.toml index cd57229c5..cb8ae2906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,9 +51,9 @@ AARCH=$(uname -m) echo "------" echo $AARCH if [[ $AARCH == "aarch64" ]]; then - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.9.0/libscip-linux-arm.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-linux-arm.zip -O scip.zip else - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.9.0/libscip-linux.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-linux.zip -O scip.zip fi unzip scip.zip mv scip_install scip @@ -67,10 +67,10 @@ before-all = ''' #!/bin/bash brew install wget zlib gcc if [[ $CIBW_ARCHS == *"arm"* ]]; then - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.9.0/libscip-macos-arm.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-macos-arm.zip -O scip.zip export MACOSX_DEPLOYMENT_TARGET=14.0 else - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.9.0/libscip-macos-intel.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-macos-intel.zip -O scip.zip export MACOSX_DEPLOYMENT_TARGET=14.0 fi unzip scip.zip @@ -96,7 +96,7 @@ repair-wheel-command = ''' skip="pp* cp36* cp37*" before-all = [ "choco install 7zip wget", - "wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.9.0/libscip-windows.zip -O scip.zip", + "wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-windows.zip -O scip.zip", "\"C:\\Program Files\\7-Zip\\7z.exe\" x \"scip.zip\" -o\"scip-test\"", "mv .\\scip-test\\scip_install .\\test", "mv .\\test .\\scip" diff --git a/setup.py b/setup.py index 9605ef1b2..341877d97 100644 --- a/setup.py +++ b/setup.py @@ -118,7 +118,7 @@ setup( name="PySCIPOpt", - version="5.7.1", + version="6.0.0", description="Python interface and modeling environment for SCIP", long_description=long_description, long_description_content_type="text/markdown", diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 5dc094506..7e32ca6c1 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -26,6 +26,7 @@ from pyscipopt.scip import Reader as Reader from pyscipopt.scip import Sepa as Sepa from pyscipopt.scip import LP as LP +from pyscipopt.scip import IISfinder as IISfinder from pyscipopt.scip import PY_SCIP_LPPARAM as SCIP_LPPARAM from pyscipopt.scip import readStatistics as readStatistics from pyscipopt.scip import Expr as Expr @@ -56,3 +57,4 @@ from pyscipopt.scip import PY_SCIP_ROWORIGINTYPE as SCIP_ROWORIGINTYPE from pyscipopt.scip import PY_SCIP_SOLORIGIN as SCIP_SOLORIGIN from pyscipopt.scip import PY_SCIP_NODETYPE as SCIP_NODETYPE +from pyscipopt.scip import PY_SCIP_IMPLINTTYPE as SCIP_IMPLINTTYPE diff --git a/src/pyscipopt/_version.py b/src/pyscipopt/_version.py index 8fb9b511b..74233fb54 100644 --- a/src/pyscipopt/_version.py +++ b/src/pyscipopt/_version.py @@ -1 +1 @@ -__version__: str = '5.7.1' +__version__: str = '6.0.0' diff --git a/src/pyscipopt/iisfinder.pxi b/src/pyscipopt/iisfinder.pxi new file mode 100644 index 000000000..e06ac83e3 --- /dev/null +++ b/src/pyscipopt/iisfinder.pxi @@ -0,0 +1,36 @@ +##@file iisfinder.pxi +#@brief Base class of the IIS finder Plugin +cdef class IISfinder: + cdef public IIS iis + cdef SCIP_IIS* scip_iis + cdef SCIP_IISFINDER* scip_iisfinder + + def iisfinderfree(self): + '''calls destructor and frees memory of iis finder''' + pass + + def iisfinderexec(self): + '''calls execution method of iis finder''' + raise NotImplementedError("iisfinderexec() is a fundamental callback and should be implemented in the derived class") + + +cdef SCIP_RETCODE PyiisfinderCopy (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: + return SCIP_OKAY + +cdef SCIP_RETCODE PyiisfinderFree (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: + cdef SCIP_IISFINDERDATA* iisfinderdata + iisfinderdata = SCIPiisfinderGetData(iisfinder) + PyIIS = iisfinderdata + PyIIS.iisfinderfree() + Py_DECREF(PyIIS) + return SCIP_OKAY + +cdef SCIP_RETCODE PyiisfinderExec (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_IISFINDERDATA* iisfinderdata + iisfinderdata = SCIPiisfinderGetData(iisfinder) + PyIIS = iisfinderdata + + PyIIS.iis._iis = iis + result_dict = PyIIS.iisfinderexec() + assert isinstance(result_dict, dict), "iisfinderexec() must return a dictionary." + return SCIP_OKAY \ No newline at end of file diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi index 13fc13d1b..a9b60bb41 100644 --- a/src/pyscipopt/reader.pxi +++ b/src/pyscipopt/reader.pxi @@ -12,7 +12,7 @@ cdef class Reader: '''calls read method of reader''' return {} - def readerwrite(self, file, name, transformed, objsense, objscale, objoffset, binvars, intvars, + def readerwrite(self, file, name, transformed, objsense, objoffset, objscale, binvars, intvars, implvars, contvars, fixedvars, startnvars, conss, maxnconss, startnconss, genericnames): '''calls write method of reader''' return {} @@ -39,10 +39,11 @@ cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* fil return SCIP_OKAY cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, - const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, - SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, - SCIP_VAR** fixedvars, int nfixedvars, int startnvars, + const char* filename, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, + SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, + SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, + int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, SCIP_Bool genericnames, SCIP_RESULT* result) noexcept with gil: cdef SCIP_READERDATA* readerdata = SCIPreaderGetData(reader) @@ -58,7 +59,8 @@ cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, PyFixedVars = [Variable.create(fixedvars[i]) for i in range(nfixedvars)] PyConss = [Constraint.create(conss[i]) for i in range(nconss)] PyReader = readerdata - result_dict = PyReader.readerwrite(PyFile, PyName, transformed, objsense, objscale, objoffset, + #TODO: provide rational objoffsetexact and objscaleexact + result_dict = PyReader.readerwrite(PyFile, PyName, transformed, objsense, objoffset, objscale, PyBinVars, PyIntVars, PyImplVars, PyContVars, PyFixedVars, startnvars, PyConss, maxnconss, startnconss, genericnames) result[0] = result_dict.get("result", result[0]) diff --git a/src/pyscipopt/recipes/infeasibilities.py b/src/pyscipopt/recipes/infeasibilities.py index eed31bad0..2d50de7ab 100644 --- a/src/pyscipopt/recipes/infeasibilities.py +++ b/src/pyscipopt/recipes/infeasibilities.py @@ -36,7 +36,7 @@ def get_infeasible_constraints(orig_model: Model, verbose: bool = False): n_infeasibilities_detected = 0 for c in binary: - if model.isGT(model.getVal(binary[c]), 0): + if model.isInfinity(model.getVal(binary[c])) or model.isGT(model.getVal(binary[c]), 0): n_infeasibilities_detected += 1 print("Constraint %s is causing an infeasibility." % c) diff --git a/src/pyscipopt/relax.pxi b/src/pyscipopt/relax.pxi index 81695e8bb..5ff2724af 100644 --- a/src/pyscipopt/relax.pxi +++ b/src/pyscipopt/relax.pxi @@ -25,10 +25,8 @@ cdef class Relax: pass def relaxexec(self): - '''callls execution method of relaxation handler''' - print("python error in relaxexec: this method needs to be implemented") - return{} - + '''calls execution method of relaxation handler''' + raise NotImplementedError("relaxexec() is a fundamental callback and should be implemented in the derived class") cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: return SCIP_OKAY diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index a37e3c8df..b9bffc1d6 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -28,6 +28,7 @@ cdef extern from "scip/scip.h": SCIP_VARTYPE SCIP_VARTYPE_BINARY SCIP_VARTYPE SCIP_VARTYPE_INTEGER SCIP_VARTYPE SCIP_VARTYPE_IMPLINT + SCIP_VARTYPE SCIP_DEPRECATED_VARTYPE_IMPLINT SCIP_VARTYPE SCIP_VARTYPE_CONTINUOUS ctypedef int SCIP_VARSTATUS @@ -267,12 +268,15 @@ cdef extern from "scip/scip.h": SCIP_EVENTTYPE SCIP_EVENTTYPE_LHOLEADDED SCIP_EVENTTYPE SCIP_EVENTTYPE_LHOLEREMOVED SCIP_EVENTTYPE SCIP_EVENTTYPE_IMPLADDED + SCIP_EVENTTYPE SCIP_EVENTTYPE_TYPECHANGED + SCIP_EVENTTYPE SCIP_EVENTTYPE_IMPLTYPECHANGED SCIP_EVENTTYPE SCIP_EVENTTYPE_PRESOLVEROUND SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEFOCUSED SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEFEASIBLE SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEINFEASIBLE SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEBRANCHED SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEDELETE + SCIP_EVENTTYPE SCIP_EVENTTYPE_DUALBOUNDIMPROVED SCIP_EVENTTYPE SCIP_EVENTTYPE_FIRSTLPSOLVED SCIP_EVENTTYPE SCIP_EVENTTYPE_LPSOLVED SCIP_EVENTTYPE SCIP_EVENTTYPE_POORSOLFOUND @@ -302,6 +306,7 @@ cdef extern from "scip/scip.h": SCIP_EVENTTYPE SCIP_EVENTTYPE_LPEVENT SCIP_EVENTTYPE SCIP_EVENTTYPE_SOLFOUND SCIP_EVENTTYPE SCIP_EVENTTYPE_SOLEVENT + SCIP_EVENTTYPE SCIP_EVENTTYPE_GAPUPDATED SCIP_EVENTTYPE SCIP_EVENTTYPE_ROWCHANGED SCIP_EVENTTYPE SCIP_EVENTTYPE_ROWEVENT @@ -314,6 +319,12 @@ cdef extern from "scip/scip.h": cdef extern from "scip/type_var.h": SCIP_LOCKTYPE SCIP_LOCKTYPE_MODEL SCIP_LOCKTYPE SCIP_LOCKTYPE_CONFLICT + + ctypedef int SCIP_IMPLINTTYPE + cdef extern from "scip/type_var.h": + SCIP_IMPLINTTYPE SCIP_IMPLINTTYPE_NONE + SCIP_IMPLINTTYPE SCIP_IMPLINTTYPE_WEAK + SCIP_IMPLINTTYPE SCIP_IMPLINTTYPE_STRONG ctypedef int SCIP_BENDERSENFOTYPE cdef extern from "scip/type_benders.h": @@ -370,6 +381,9 @@ cdef extern from "scip/scip.h": ctypedef double SCIP_Real + ctypedef struct SCIP_RATIONAL: + pass + ctypedef struct SCIP: pass @@ -381,12 +395,18 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_ROW: pass + + ctypedef struct SCIP_ROWEXACT: + pass ctypedef struct SCIP_NLROW: pass ctypedef struct SCIP_COL: pass + + ctypedef struct SCIP_COLEXACT: + pass ctypedef struct SCIP_SOL: pass @@ -430,6 +450,15 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_HEURDATA: pass + ctypedef struct SCIP_IISFINDER: + pass + + ctypedef struct SCIP_IISFINDERDATA: + pass + + ctypedef struct SCIP_IIS: + pass + ctypedef struct SCIP_RELAX: pass @@ -508,6 +537,9 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_LPI: pass + ctypedef struct SCIP_LPEXACT: + pass + ctypedef struct BMS_BLKMEM: pass @@ -538,6 +570,9 @@ cdef extern from "scip/scip.h": ctypedef union SCIP_DOMCHG: pass + ctypedef struct SCIP_RATIONAL: + pass + ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR* messagehdlr, FILE* file, const char* msg) noexcept ctypedef void (*errormessagecallback) (void* data, FILE* file, const char* msg) ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR* messagehdlr) @@ -802,6 +837,11 @@ cdef extern from "scip/scip.h": int SCIPgetNImplVars(SCIP* scip) int SCIPgetNContVars(SCIP* scip) SCIP_VARTYPE SCIPvarGetType(SCIP_VAR* var) + SCIP_Bool SCIPvarIsBinary(SCIP_VAR* var) + SCIP_Bool SCIPvarIsIntegral(SCIP_VAR* var) + SCIP_Bool SCIPvarIsImpliedIntegral(SCIP_VAR* var) + SCIP_Bool SCIPvarIsNonimpliedIntegral(SCIP_VAR* var) + SCIP_IMPLINTTYPE SCIPvarGetImplType(SCIP_VAR* var) SCIP_VARSTATUS SCIPvarGetStatus(SCIP_VAR* var) SCIP_Bool SCIPvarIsOriginal(SCIP_VAR* var) SCIP_Bool SCIPvarIsTransformed(SCIP_VAR* var) @@ -970,11 +1010,12 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*readercopy) (SCIP* scip, SCIP_READER* reader), SCIP_RETCODE (*readerfree) (SCIP* scip, SCIP_READER* reader), SCIP_RETCODE (*readerread) (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result), - SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, + SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, const char* filename, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, - SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, - SCIP_VAR** fixedvars, int nfixedvars, int startnvars, + SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, + SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, + int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, SCIP_Bool genericnames, SCIP_RESULT* result), SCIP_READERDATA* readerdata) @@ -1167,6 +1208,31 @@ cdef extern from "scip/scip.h": SCIP_HEURTIMING SCIPheurGetTimingmask(SCIP_HEUR* heur) void SCIPheurSetTimingmask(SCIP_HEUR* heur, SCIP_HEURTIMING timingmask) + #IIS finder plugin + SCIP_RETCODE SCIPincludeIISfinder(SCIP* scip, + const char* name, + const char* desc, + int priority, + SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), + SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), + SCIP_RETCODE (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_RESULT* result), + SCIP_IISFINDERDATA* iisfinderdata) + + SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) + SCIP_RETCODE SCIPgenerateIIS(SCIP* scip) + SCIP_RETCODE SCIPiisGreedyMakeIrreducible(SCIP_IIS* iis) + SCIP_Bool SCIPiisIsSubscipInfeasible(SCIP_IIS* iis) + SCIP_Bool SCIPiisIsSubscipIrreducible(SCIP_IIS* iis) + SCIP_RETCODE SCIPiisSetSubscipIrreducible(SCIP_IIS* iis, SCIP_Bool irreducible) + SCIP_RETCODE SCIPiisSetSubscipInfeasible(SCIP_IIS* iis, SCIP_Bool infeasible) + SCIP_IIS* SCIPgetIIS(SCIP* scip) + SCIP_IISFINDER* SCIPfindIISfinder(SCIP* scip, const char* name) + SCIP_Real SCIPiisGetTime(SCIP_IIS* scip) + SCIP_Bool SCIPiisIsSubscipIrreducible(SCIP_IIS* scip) + SCIP_Bool SCIPiisIsSubscipInfeasible(SCIP_IIS* scip) + SCIP_Longint SCIPiisGetNNodes(SCIP_IIS* scip) + SCIP* SCIPiisGetSubscip(SCIP_IIS* iis) + #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, const char* name, @@ -1366,8 +1432,33 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPisIntegral(SCIP* scip, SCIP_Real val) SCIP_Real SCIPgetTreesizeEstimation(SCIP* scip) + # Exact SCIP methods + SCIP_RETCODE SCIPenableExactSolving(SCIP* scip, SCIP_Bool enable); + SCIP_Bool SCIPisExact(SCIP* scip); + SCIP_Bool SCIPallowNegSlack(SCIP* scip); + SCIP_RETCODE SCIPbranchLPExact(SCIP* scip, SCIP_RESULT* result); + SCIP_RETCODE SCIPaddRowExact(SCIP* scip, SCIP_ROWEXACT* rowexact); + + # Exact LP SCIP methods + SCIP_VAR* SCIPcolExactGetVar(SCIP_COLEXACT* col); + SCIP_RATIONAL* SCIProwExactGetLhs(SCIP_ROWEXACT* row); + SCIP_RATIONAL* SCIProwExactGetRhs(SCIP_ROWEXACT* row); + SCIP_RATIONAL* SCIProwExactGetConstant(SCIP_ROWEXACT* row); + int SCIProwExactGetNNonz(SCIP_ROWEXACT* row); + SCIP_RATIONAL** SCIProwExactGetVals(SCIP_ROWEXACT* row); + SCIP_Bool SCIProwExactIsInLP(SCIP_ROWEXACT* row); + void SCIProwExactSort(SCIP_ROWEXACT* row); + SCIP_COLEXACT** SCIProwExactGetCols(SCIP_ROWEXACT* row); + void SCIProwExactLock(SCIP_ROWEXACT* row); + void SCIProwExactUnlock(SCIP_ROWEXACT* row); + SCIP_ROW* SCIProwExactGetRow(SCIP_ROWEXACT* row); + SCIP_ROW* SCIProwExactGetRowRhs(SCIP_ROWEXACT* row); + SCIP_Bool SCIProwExactHasFpRelax(SCIP_ROWEXACT* row); + SCIP_Bool SCIPlpExactDiving(SCIP_LPEXACT* lpexact); + # Statistic Methods SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) + SCIP_RETCODE SCIPprintStatisticsJson(SCIP* scip, FILE* file) SCIP_Longint SCIPgetNNodes(SCIP* scip) SCIP_Longint SCIPgetNTotalNodes(SCIP* scip) SCIP_Longint SCIPgetNFeasibleLeaves(SCIP* scip) @@ -1446,11 +1537,6 @@ cdef extern from "scip/scip.h": BMS_BLKMEM* SCIPblkmem(SCIP* scip) - # pub_misc.h - SCIP_RETCODE SCIPhashmapCreate(SCIP_HASHMAP** hashmap, BMS_BLKMEM* blkmem, int mapsize) - void SCIPhashmapFree(SCIP_HASHMAP** hashmap) - - cdef extern from "scip/tree.h": int SCIPnodeGetNAddedConss(SCIP_NODE* node) @@ -1610,7 +1696,6 @@ cdef extern from "scip/cons_sos1.h": SCIP_CONS* cons, SCIP_VAR* var) - cdef extern from "scip/cons_sos2.h": SCIP_RETCODE SCIPcreateConsSOS2(SCIP* scip, SCIP_CONS** cons, @@ -1677,8 +1762,6 @@ cdef extern from "scip/cons_and.h": SCIP_VAR* SCIPgetResultantAnd(SCIP* scip, SCIP_CONS* cons) SCIP_Bool SCIPisAndConsSorted(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPsortAndCons(SCIP* scip, SCIP_CONS* cons) - SCIP_RETCODE SCIPchgAndConsCheckFlagWhenUpgr(SCIP* scip, SCIP_CONS* cons, SCIP_Bool flag) - SCIP_RETCODE SCIPchgAndConsRemovableFlagWhenUpgr(SCIP* scip, SCIP_CONS* cons, SCIP_Bool flag) cdef extern from "scip/cons_or.h": SCIP_RETCODE SCIPcreateConsOr(SCIP* scip, @@ -1715,6 +1798,7 @@ cdef extern from "scip/cons_xor.h": SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode) + cdef extern from "scip/scip_cons.h": SCIP_RETCODE SCIPprintCons(SCIP* scip, SCIP_CONS* cons, @@ -1869,7 +1953,6 @@ cdef extern from "scip/scip_nlp.h": SCIP_RETCODE SCIPgetNlRowActivityBounds(SCIP* scip, SCIP_NLROW* nlrow, SCIP_Real* minactivity, SCIP_Real* maxactivity) SCIP_RETCODE SCIPprintNlRow(SCIP* scip, SCIP_NLROW* nlrow, FILE* file) - cdef extern from "scip/cons_cardinality.h": SCIP_RETCODE SCIPcreateConsCardinality(SCIP* scip, SCIP_CONS** cons, @@ -2042,6 +2125,14 @@ cdef class Column: @staticmethod cdef create(SCIP_COL* scipcol) +cdef class ColumnExact: + cdef SCIP_COLEXACT* scip_col_exact + # can be used to store problem data + cdef public object data + + @staticmethod + cdef create(SCIP_COLEXACT* scip_col_exact) + cdef class Row: cdef SCIP_ROW* scip_row # can be used to store problem data @@ -2050,6 +2141,14 @@ cdef class Row: @staticmethod cdef create(SCIP_ROW* sciprow) +cdef class RowExact: + cdef SCIP_ROWEXACT* scip_row_exact + # can be used to store problem data + cdef public object data + + @staticmethod + cdef create(SCIP_ROWEXACT* scip_row_exact) + cdef class NLRow: cdef SCIP_NLROW* scip_nlrow # can be used to store problem data @@ -2103,6 +2202,12 @@ cdef class Constraint: @staticmethod cdef create(SCIP_CONS* scipcons) +cdef class IIS: + cdef SCIP_IIS* _iis + + @staticmethod + cdef create(SCIP_IIS* iis) + cdef class Model: cdef SCIP* _scip cdef SCIP_Bool* _valid @@ -2121,6 +2226,8 @@ cdef class Model: cdef int _generated_event_handlers_count # store references to Benders subproblem Models for proper cleanup cdef _benders_subproblems + # store iis, if found + cdef SCIP_IIS* _iis @staticmethod cdef create(SCIP* scip) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 648bf858a..14741531d 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -31,6 +31,7 @@ include "conshdlr.pxi" include "cutsel.pxi" include "event.pxi" include "heuristic.pxi" +include "iisfinder.pxi" include "presol.pxi" include "pricer.pxi" include "propagator.pxi" @@ -41,9 +42,9 @@ include "nodesel.pxi" include "matrix.pxi" # recommended SCIP version; major version is required -MAJOR = 9 -MINOR = 2 -PATCH = 4 +MAJOR = 10 +MINOR = 0 +PATCH = 0 # for external user functions use def; for functions used only inside the interface (starting with _) use cdef # todo: check whether this is currently done like this @@ -207,65 +208,72 @@ cdef class PY_SCIP_HEURTIMING: EventNames = {} cdef class PY_SCIP_EVENTTYPE: - DISABLED = SCIP_EVENTTYPE_DISABLED - VARADDED = SCIP_EVENTTYPE_VARADDED - VARDELETED = SCIP_EVENTTYPE_VARDELETED - VARFIXED = SCIP_EVENTTYPE_VARFIXED - VARUNLOCKED = SCIP_EVENTTYPE_VARUNLOCKED - OBJCHANGED = SCIP_EVENTTYPE_OBJCHANGED - GLBCHANGED = SCIP_EVENTTYPE_GLBCHANGED - GUBCHANGED = SCIP_EVENTTYPE_GUBCHANGED - LBTIGHTENED = SCIP_EVENTTYPE_LBTIGHTENED - LBRELAXED = SCIP_EVENTTYPE_LBRELAXED - UBTIGHTENED = SCIP_EVENTTYPE_UBTIGHTENED - UBRELAXED = SCIP_EVENTTYPE_UBRELAXED - GHOLEADDED = SCIP_EVENTTYPE_GHOLEADDED - GHOLEREMOVED = SCIP_EVENTTYPE_GHOLEREMOVED - LHOLEADDED = SCIP_EVENTTYPE_LHOLEADDED - LHOLEREMOVED = SCIP_EVENTTYPE_LHOLEREMOVED - IMPLADDED = SCIP_EVENTTYPE_IMPLADDED - PRESOLVEROUND = SCIP_EVENTTYPE_PRESOLVEROUND - NODEFOCUSED = SCIP_EVENTTYPE_NODEFOCUSED - NODEFEASIBLE = SCIP_EVENTTYPE_NODEFEASIBLE - NODEINFEASIBLE = SCIP_EVENTTYPE_NODEINFEASIBLE - NODEBRANCHED = SCIP_EVENTTYPE_NODEBRANCHED - NODEDELETE = SCIP_EVENTTYPE_NODEDELETE - FIRSTLPSOLVED = SCIP_EVENTTYPE_FIRSTLPSOLVED - LPSOLVED = SCIP_EVENTTYPE_LPSOLVED - LPEVENT = SCIP_EVENTTYPE_LPEVENT - POORSOLFOUND = SCIP_EVENTTYPE_POORSOLFOUND - BESTSOLFOUND = SCIP_EVENTTYPE_BESTSOLFOUND - ROWADDEDSEPA = SCIP_EVENTTYPE_ROWADDEDSEPA - ROWDELETEDSEPA = SCIP_EVENTTYPE_ROWDELETEDSEPA - ROWADDEDLP = SCIP_EVENTTYPE_ROWADDEDLP - ROWDELETEDLP = SCIP_EVENTTYPE_ROWDELETEDLP - ROWCOEFCHANGED = SCIP_EVENTTYPE_ROWCOEFCHANGED - ROWCONSTCHANGED = SCIP_EVENTTYPE_ROWCONSTCHANGED - ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED - SYNC = SCIP_EVENTTYPE_SYNC - GBDCHANGED = SCIP_EVENTTYPE_GBDCHANGED - LBCHANGED = SCIP_EVENTTYPE_LBCHANGED - UBCHANGED = SCIP_EVENTTYPE_UBCHANGED - BOUNDTIGHTENED = SCIP_EVENTTYPE_BOUNDTIGHTENED - BOUNDRELAXED = SCIP_EVENTTYPE_BOUNDRELAXED - BOUNDCHANGED = SCIP_EVENTTYPE_BOUNDCHANGED - GHOLECHANGED = SCIP_EVENTTYPE_GHOLECHANGED - LHOLECHANGED = SCIP_EVENTTYPE_LHOLECHANGED - HOLECHANGED = SCIP_EVENTTYPE_HOLECHANGED - DOMCHANGED = SCIP_EVENTTYPE_DOMCHANGED - VARCHANGED = SCIP_EVENTTYPE_VARCHANGED - VAREVENT = SCIP_EVENTTYPE_VAREVENT - NODESOLVED = SCIP_EVENTTYPE_NODESOLVED - NODEEVENT = SCIP_EVENTTYPE_NODEEVENT - SOLFOUND = SCIP_EVENTTYPE_SOLFOUND - SOLEVENT = SCIP_EVENTTYPE_SOLEVENT - ROWCHANGED = SCIP_EVENTTYPE_ROWCHANGED - ROWEVENT = SCIP_EVENTTYPE_ROWEVENT + DISABLED = SCIP_EVENTTYPE_DISABLED + VARADDED = SCIP_EVENTTYPE_VARADDED + VARDELETED = SCIP_EVENTTYPE_VARDELETED + VARFIXED = SCIP_EVENTTYPE_VARFIXED + VARUNLOCKED = SCIP_EVENTTYPE_VARUNLOCKED + OBJCHANGED = SCIP_EVENTTYPE_OBJCHANGED + GLBCHANGED = SCIP_EVENTTYPE_GLBCHANGED + GUBCHANGED = SCIP_EVENTTYPE_GUBCHANGED + LBTIGHTENED = SCIP_EVENTTYPE_LBTIGHTENED + LBRELAXED = SCIP_EVENTTYPE_LBRELAXED + UBTIGHTENED = SCIP_EVENTTYPE_UBTIGHTENED + UBRELAXED = SCIP_EVENTTYPE_UBRELAXED + GHOLEADDED = SCIP_EVENTTYPE_GHOLEADDED + GHOLEREMOVED = SCIP_EVENTTYPE_GHOLEREMOVED + LHOLEADDED = SCIP_EVENTTYPE_LHOLEADDED + LHOLEREMOVED = SCIP_EVENTTYPE_LHOLEREMOVED + IMPLADDED = SCIP_EVENTTYPE_IMPLADDED + PRESOLVEROUND = SCIP_EVENTTYPE_PRESOLVEROUND + NODEFOCUSED = SCIP_EVENTTYPE_NODEFOCUSED + NODEFEASIBLE = SCIP_EVENTTYPE_NODEFEASIBLE + NODEINFEASIBLE = SCIP_EVENTTYPE_NODEINFEASIBLE + NODEBRANCHED = SCIP_EVENTTYPE_NODEBRANCHED + NODEDELETE = SCIP_EVENTTYPE_NODEDELETE + DUALBOUNDIMPROVED = SCIP_EVENTTYPE_DUALBOUNDIMPROVED + FIRSTLPSOLVED = SCIP_EVENTTYPE_FIRSTLPSOLVED + LPSOLVED = SCIP_EVENTTYPE_LPSOLVED + LPEVENT = SCIP_EVENTTYPE_LPEVENT + POORSOLFOUND = SCIP_EVENTTYPE_POORSOLFOUND + BESTSOLFOUND = SCIP_EVENTTYPE_BESTSOLFOUND + ROWADDEDSEPA = SCIP_EVENTTYPE_ROWADDEDSEPA + ROWDELETEDSEPA = SCIP_EVENTTYPE_ROWDELETEDSEPA + ROWADDEDLP = SCIP_EVENTTYPE_ROWADDEDLP + ROWDELETEDLP = SCIP_EVENTTYPE_ROWDELETEDLP + ROWCOEFCHANGED = SCIP_EVENTTYPE_ROWCOEFCHANGED + ROWCONSTCHANGED = SCIP_EVENTTYPE_ROWCONSTCHANGED + ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED + SYNC = SCIP_EVENTTYPE_SYNC + GBDCHANGED = SCIP_EVENTTYPE_GBDCHANGED + LBCHANGED = SCIP_EVENTTYPE_LBCHANGED + UBCHANGED = SCIP_EVENTTYPE_UBCHANGED + BOUNDTIGHTENED = SCIP_EVENTTYPE_BOUNDTIGHTENED + BOUNDRELAXED = SCIP_EVENTTYPE_BOUNDRELAXED + BOUNDCHANGED = SCIP_EVENTTYPE_BOUNDCHANGED + GHOLECHANGED = SCIP_EVENTTYPE_GHOLECHANGED + LHOLECHANGED = SCIP_EVENTTYPE_LHOLECHANGED + HOLECHANGED = SCIP_EVENTTYPE_HOLECHANGED + DOMCHANGED = SCIP_EVENTTYPE_DOMCHANGED + VARCHANGED = SCIP_EVENTTYPE_VARCHANGED + VAREVENT = SCIP_EVENTTYPE_VAREVENT + NODESOLVED = SCIP_EVENTTYPE_NODESOLVED + NODEEVENT = SCIP_EVENTTYPE_NODEEVENT + SOLFOUND = SCIP_EVENTTYPE_SOLFOUND + SOLEVENT = SCIP_EVENTTYPE_SOLEVENT + GAPUPDATED = SCIP_EVENTTYPE_GAPUPDATED + ROWCHANGED = SCIP_EVENTTYPE_ROWCHANGED + ROWEVENT = SCIP_EVENTTYPE_ROWEVENT cdef class PY_SCIP_LOCKTYPE: MODEL = SCIP_LOCKTYPE_MODEL CONFLICT = SCIP_LOCKTYPE_CONFLICT +cdef class PY_SCIP_IMPLINTTYPE: + NONE = SCIP_IMPLINTTYPE_NONE + WEAK = SCIP_IMPLINTTYPE_WEAK + STRONG = SCIP_IMPLINTTYPE_STRONG + cdef class PY_SCIP_LPSOLSTAT: NOTSOLVED = SCIP_LPSOLSTAT_NOTSOLVED OPTIMAL = SCIP_LPSOLSTAT_OPTIMAL @@ -626,6 +634,31 @@ cdef class Column: return (self.__class__ == other.__class__ and self.scip_col == (other).scip_col) +cdef class ColumnExact: + """Base class holding a pointer to corresponding SCIP_COLEXACT.""" + + @staticmethod + cdef create(SCIP_COLEXACT* scipcolexact): + """ + Main method for creating a ColumnExact class. Is used instead of __init__. + + Parameters + ---------- + scipcolexact : SCIP_COLEXACT* + A pointer to the SCIP_COLEXACT + + Returns + ------- + col : ColumnExact + The Python representative of the SCIP_COLEXACT + + """ + if scipcolexact == NULL: + raise Warning("cannot create ColumnExact with SCIP_COLEXACT* == NULL") + col = ColumnExact() + col.scip_col_exact = scipcolexact + return col + cdef class Row: """Base class holding a pointer to corresponding SCIP_ROW.""" @@ -910,6 +943,31 @@ cdef class Row: return (self.__class__ == other.__class__ and self.scip_row == (other).scip_row) +cdef class RowExact: + """Base class holding a pointer to corresponding SCIP_ROW.""" + + @staticmethod + cdef create(SCIP_ROWEXACT* sciprowexact): + """ + Main method for creating a RowExact class. Is used instead of __init__. + + Parameters + ---------- + sciprow : SCIP_ROWEXACT* + A pointer to the SCIP_ROWEXACT + + Returns + ------- + row : Row + The Python representative of the SCIP_ROWEXACT + + """ + if sciprowexact == NULL: + raise Warning("cannot create Row with SCIP_ROWEXACT* == NULL") + row_exact = RowExact() + row_exact.scip_row_exact = sciprowexact + return row_exact + cdef class NLRow: """Base class holding a pointer to corresponding SCIP_NLROW.""" @@ -1519,7 +1577,7 @@ cdef class Variable(Expr): def vtype(self): """ - Retrieve the variables type (BINARY, INTEGER, IMPLINT or CONTINUOUS) + Retrieve the variables type (BINARY, INTEGER, CONTINUOUS, or IMPLINT) Returns ------- @@ -1534,8 +1592,58 @@ cdef class Variable(Expr): return "INTEGER" elif vartype == SCIP_VARTYPE_CONTINUOUS: return "CONTINUOUS" - elif vartype == SCIP_VARTYPE_IMPLINT: + elif vartype == SCIP_DEPRECATED_VARTYPE_IMPLINT: return "IMPLINT" + + def isBinary(self): + """ + Returns whether variable is of BINARY type. + + Returns + ------- + bool + """ + return SCIPvarIsBinary(self.scip_var) + + def isIntegral(self): + """ + Returns whether variable is of INTEGER type. + + Returns + ------- + bool + """ + return SCIPvarIsIntegral(self.scip_var) + + def isImpliedIntegral(self): + """ + Returns whether variable is implied integral (weakly or strongly). + + Returns + ------- + bool + """ + return SCIPvarIsImpliedIntegral(self.scip_var) + + def isNonImpliedIntegral(self): + """ + Returns TRUE if the variable is integral, but not implied integral.. + + Returns + ------- + bool + """ + return SCIPvarIsNonimpliedIntegral(self.scip_var) + + def getImplType(self): + """ + Returns the implied integral type of the variable + + Returns + ------- + PY_SCIP_IMPLINTTYPE + """ + return SCIPvarGetImplType(self.scip_var) def getStatus(self): """ @@ -2555,6 +2663,115 @@ cdef class _VarArray: if self.ptr != NULL: free(self.ptr) +cdef class IIS: + + @staticmethod + cdef create(SCIP_IIS* scip_iis): + """ + Main method for creating an IIS class. + + Parameters + ---------- + scip : SCIP_IIS* + A pointer to the SCIP_IIS + + Returns + ------- + sol : IIS + The Python representative of the IIS + + """ + iis = IIS() + iis._iis = scip_iis + return iis + + def getTime(self): + """ + Retrieve the solving time of the IIS. + + Returns + ------- + float + """ + return SCIPiisGetTime(self._iis) + + def isSubscipIrreducible(self): + """ + Returns whether the IIS is irreducible. + + Returns + ------- + bool + """ + return SCIPiisIsSubscipIrreducible(self._iis) + + def isSubscipInfeasible(self): + """ + Returns whether the IIS is infeasible. + + Returns + ------- + bool + """ + return SCIPiisIsSubscipInfeasible(self._iis) + + def setSubscipIrreducible(self, irreducible): + """ + Sets the flag that states whether the IIS subscip is irreducible. + + Parameters + ---------- + irreducible : bool + the value to set the irreducible flag to + """ + + SCIPiisSetSubscipIrreducible(self._iis, irreducible) + + def setSubscipInfeasible(self, infeasible): + """ + Sets the flag that states whether the IIS subscip is infeasible. + + Parameters + ---------- + infeasible : bool + the value to set the infeasible flag to + """ + + SCIPiisSetSubscipInfeasible(self._iis, infeasible) + + def getNNodes(self): + """ + Gets number of nodes in the IIS solve. + + Returns + ------- + int + + """ + return SCIPiisGetNNodes(self._iis) + + def getSubscip(self): + """ + Get the subscip of an IIS. + + Returns + ------- + Model + """ + cdef SCIP* subscip + + subscip = SCIPiisGetSubscip(self._iis) + model = Model.create(subscip) + return model + + def greedyMakeIrreducible(self): + """ + Perform the greedy deletion algorithm with singleton batches to obtain an irreducible infeasible subsystem (IIS) + """ + + PY_SCIP_CALL(SCIPiisGreedyMakeIrreducible(self._iis)) + + # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" # - interface SCIPfreeProb() @@ -2598,6 +2815,7 @@ cdef class Model: self._modelvars = {} self._generated_event_handlers_count = 0 self._benders_subproblems = [] # Keep references to Benders subproblem Models + self._iis = NULL if not createscip: # if no SCIP instance should be created, then an empty Model object is created. @@ -4008,7 +4226,7 @@ cdef class Model: elif vtype in ['I', 'INTEGER']: PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_INTEGER)) elif vtype in ['M', 'IMPLINT']: - PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_IMPLINT)) + PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_DEPRECATED_VARTYPE_IMPLINT)) else: raise Warning("unrecognized variable type") @@ -4456,7 +4674,7 @@ cdef class Model: elif vtype in ['I', 'INTEGER']: PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_INTEGER, &infeasible)) elif vtype in ['M', 'IMPLINT']: - PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_IMPLINT, &infeasible)) + PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_DEPRECATED_VARTYPE_IMPLINT, &infeasible)) else: raise Warning("unrecognized variable type") if infeasible: @@ -6415,38 +6633,6 @@ cdef class Model: """ PY_SCIP_CALL(SCIPsortAndCons(self._scip, and_cons.scip_cons)) - - def chgAndConsCheckFlagWhenUpgr(self, Constraint cons, flag): - """ - when 'upgrading' the given AND-constraint, should the check flag for the upgraded - constraint be set to TRUE, even if the check flag of this AND-constraint is set to FALSE? - - Parameters - ---------- - cons : Constraint - The AND constraint to change. - flag : bool - The new value for the check flag. - - """ - - PY_SCIP_CALL(SCIPchgAndConsCheckFlagWhenUpgr(self._scip, cons.scip_cons, flag)) - - def chgAndConsRemovableFlagWhenUpgr(self, Constraint cons, flag): - """ - when 'upgrading' the given AND-constraint, should the removable flag for the upgraded - constraint be set to TRUE, even if the removable flag of this AND-constraint is set to FALSE? - - Parameters - ---------- - cons : Constraint - The AND constraint to change. - flag : bool - The new value for the removable flag. - - """ - - PY_SCIP_CALL(SCIPchgAndConsRemovableFlagWhenUpgr(self._scip, cons.scip_cons, flag)) def printCons(self, Constraint constraint): """ @@ -9154,7 +9340,10 @@ cdef class Model: maxdepth : int, optional maximal depth level to call heuristic at (Default value = -1) timingmask : PY_SCIP_HEURTIMING, optional - positions in the node solving loop where heuristic should be executed + positions in the node solvingreturn { + 'result': SCIP_RESULT.SUCCESS, + 'lowerbound': 10e4 + } loop where heuristic should be executed (Default value = SCIP_HEURTIMING_BEFORENODE) usessubscip : bool, optional does the heuristic use a secondary SCIP instance? (Default value = False) @@ -9172,6 +9361,72 @@ cdef class Model: heur.model = weakref.proxy(self) heur.name = name Py_INCREF(heur) + + def includeIISfinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1): + """ + Include an IIS (Irreducible Infeasible Set) finder handler. + + Parameters + ---------- + iisfinder : IISfinder + IIS finder + name : str + name of IIS finder + desc : str + description of IIS finder + priority : int, optional + priority of the IISfinder (#todo description) + freq : int, optional + frequency for calling IIS finder + + """ + cdef SCIP_IISFINDER* scip_iisfinder + + nam = str_conversion(name) + des = str_conversion(desc) + + iisfinder.iis = IIS() + + PY_SCIP_CALL(SCIPincludeIISfinder(self._scip, nam, des, priority, PyiisfinderCopy, PyiisfinderFree, + PyiisfinderExec, iisfinder)) + + scip_iisfinder = SCIPfindIISfinder(self._scip, nam) + iisfinder.name = name + Py_INCREF(iisfinder) + iisfinder.scip_iisfinder = scip_iisfinder + + def generateIIS(self): + """ + Generates an Irreducible Infeasible Subsystem (IIS) from the current + problem. + + Returns + ------- + IIS + + """ + cdef SCIP_IIS* _iis + + PY_SCIP_CALL( SCIPgenerateIIS(self._scip) ) + + _iis = SCIPgetIIS(self._scip) + return IIS.create(_iis) + + def getIIS(self): + """ + Get the IIS object. + Note: Needs to be called after generateIIS, or after a single execution of the iisfinderExec. + + Returns + ------- + IIS + """ + cdef SCIP_IIS* _iis + + _iis = SCIPgetIIS(self._scip) + assert _iis != NULL, "No IIS exists. You need to first call generateIIS() or run the iisfinderexec method of your custom IISfinder class." + + return IIS.create(_iis) def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): """ @@ -10863,12 +11118,58 @@ cdef class Model: # Statistic Methods - def printStatistics(self): - """Print statistics.""" + def printStatistics(self, filename=None): + """ + Print statistics. + + Parameters + ---------- + filename : str, optional + name of the output file (Default = None) + + """ + + user_locale = locale.getlocale(category=locale.LC_NUMERIC) + locale.setlocale(locale.LC_NUMERIC, "C") + + if not filename: + PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) + else: + with open(filename, "w") as f: + cfile = fdopen(f.fileno(), "w") + PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) + + locale.setlocale(locale.LC_NUMERIC,user_locale) + + def printStatisticsJson(self, filename=None): + """ + Print statistics in JSON format. + + Parameters + ---------- + filename : str, optional + name of the output file (Default = None) + + """ + user_locale = locale.getlocale(category=locale.LC_NUMERIC) locale.setlocale(locale.LC_NUMERIC, "C") - PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) + if not filename: + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL)) + else: + with open(filename, "w") as f: + cfile = fdopen(f.fileno(), "w") + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, cfile)) + + locale.setlocale(locale.LC_NUMERIC,user_locale) + + def printStatisticsJson(self): + """Print statistics in JSON format.""" + user_locale = locale.getlocale(category=locale.LC_NUMERIC) + locale.setlocale(locale.LC_NUMERIC, "C") + + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL)) locale.setlocale(locale.LC_NUMERIC,user_locale) @@ -10892,6 +11193,28 @@ cdef class Model: PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) locale.setlocale(locale.LC_NUMERIC,user_locale) + + + def writeStatisticsJson(self, filename="origprob.stats.json"): + """ + Write statistics to a JSON file. + + Parameters + ---------- + filename : str, optional + name of the output file (Default = "origprob.stats.json") + + """ + user_locale = locale.getlocale(category=locale.LC_NUMERIC) + locale.setlocale(locale.LC_NUMERIC, "C") + + # use this doubled opening pattern to ensure that IOErrors are + # triggered early and in Python not in C,Cython or SCIP. + with open(filename, "w") as f: + cfile = fdopen(f.fileno(), "w") + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, cfile)) + + locale.setlocale(locale.LC_NUMERIC,user_locale) def getNLPs(self): """ @@ -11562,6 +11885,65 @@ cdef class Model: """ return SCIPgetTreesizeEstimation(self._scip) + + # Exact SCIP methods + def enableExactSolving(self, SCIP_Bool enable): + """ + Enables or disables exact solving mode in SCIP. + + Parameters + ---------- + enable : SCIP_Bool + Whether to enable exact solving mode (True) or disable it (False). + """ + + PY_SCIP_CALL(SCIPenableExactSolving(self._scip, enable)) + + def isExact(self): + """ + Returns whether exact solving mode is enabled in SCIP. + + Returns + ------- + bool + """ + + return SCIPisExact(self._scip) + + def allowNegSlackExact(self): + """ + Returns whether negative slack is allowed in exact solving mode. + + Returns + ------- + bool + """ + + return SCIPallowNegSlack(self._scip) + + def branchLPExact(self): + """ + Performs exact LP branching. + + Returns + ------- + SCIP_RESULT + """ + cdef SCIP_RESULT result + PY_SCIP_CALL(SCIPbranchLPExact(self._scip, &result)) + return result + + def addRowExact(self, RowExact rowexact): + """ + Adds an exact row to the LP. + + Parameters + ---------- + rowexact : RowExact + The exact row to add. + """ + PY_SCIP_CALL(SCIPaddRowExact(self._scip, rowexact.scip_row_exact)) + def getBipartiteGraphRepresentation(self, prev_col_features=None, prev_edge_features=None, prev_row_features=None, static_only=False, suppress_warnings=False): """ @@ -11669,7 +12051,7 @@ cdef class Model: col_features[col_i][col_feature_map["integer"]] = 1 elif vtype == SCIP_VARTYPE_CONTINUOUS: col_features[col_i][col_feature_map["continuous"]] = 1 - elif vtype == SCIP_VARTYPE_IMPLINT: + elif vtype == SCIP_DEPRECATED_VARTYPE_IMPLINT: col_features[col_i][col_feature_map["implicit_integer"]] = 1 # Objective coefficient col_features[col_i][col_feature_map["obj_coef"]] = SCIPcolGetObj(cols[i]) diff --git a/tests/test_event.py b/tests/test_event.py index b4b292ffd..7b11980b9 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -3,16 +3,73 @@ from pyscipopt import Model, Eventhdlr, SCIP_RESULT, SCIP_EVENTTYPE, SCIP_PARAMSETTING, quicksum calls = [] +var_events = [SCIP_EVENTTYPE.VARCHANGED, + SCIP_EVENTTYPE.VAREVENT, + SCIP_EVENTTYPE.BOUNDCHANGED, + SCIP_EVENTTYPE.BOUNDRELAXED, + SCIP_EVENTTYPE.BOUNDTIGHTENED, + SCIP_EVENTTYPE.LBCHANGED, + SCIP_EVENTTYPE.DOMCHANGED, + SCIP_EVENTTYPE.GBDCHANGED, + SCIP_EVENTTYPE.GHOLEADDED, + SCIP_EVENTTYPE.GHOLECHANGED, + SCIP_EVENTTYPE.GHOLEREMOVED, + SCIP_EVENTTYPE.GLBCHANGED, + SCIP_EVENTTYPE.GUBCHANGED, + SCIP_EVENTTYPE.HOLECHANGED, + SCIP_EVENTTYPE.IMPLADDED, + SCIP_EVENTTYPE.LBCHANGED, + SCIP_EVENTTYPE.LBRELAXED, + SCIP_EVENTTYPE.LBTIGHTENED, + SCIP_EVENTTYPE.LHOLEADDED, + SCIP_EVENTTYPE.LHOLECHANGED, + SCIP_EVENTTYPE.LHOLEREMOVED, + SCIP_EVENTTYPE.OBJCHANGED, + SCIP_EVENTTYPE.UBCHANGED, + SCIP_EVENTTYPE.UBRELAXED, + SCIP_EVENTTYPE.UBTIGHTENED, + SCIP_EVENTTYPE.VARDELETED, + SCIP_EVENTTYPE.VARFIXED, + SCIP_EVENTTYPE.VARUNLOCKED] + +row_events = [SCIP_EVENTTYPE.ROWCHANGED, + SCIP_EVENTTYPE.ROWADDEDLP, + SCIP_EVENTTYPE.ROWADDEDSEPA, + SCIP_EVENTTYPE.ROWCHANGED, + SCIP_EVENTTYPE.ROWCOEFCHANGED, + SCIP_EVENTTYPE.ROWCONSTCHANGED, + SCIP_EVENTTYPE.ROWDELETEDLP, + SCIP_EVENTTYPE.ROWDELETEDSEPA, + SCIP_EVENTTYPE.ROWEVENT, + SCIP_EVENTTYPE.ROWSIDECHANGED] class MyEvent(Eventhdlr): + def __init__(self): + super().__init__() + self.event_type = None def eventinit(self): calls.append('eventinit') - self.model.catchEvent(self.event_type, self) + print("init ", self.event_type) + + if self.event_type in [SCIP_EVENTTYPE.VAREVENT, SCIP_EVENTTYPE.VARCHANGED]: + print(f"Skipping composite event type: {self.event_type}") + return + + if self.event_type in var_events: + var = self.model.getTransformedVar(self.model.getVars()[0]) + self.model.catchVarEvent(var, self.event_type, self) + elif self.event_type in row_events: + print(str(self.event_type), " requires row") + pass + # self.model.catchRowEvent(row, self.event_type, self) + else: + self.model.catchEvent(self.event_type, self) def eventexit(self): # PR #828 fixes an error here, but the underlying cause might not be solved (self.model being deleted before dropEvent is called) - self.model.dropEvent(self.event_type, self) + # self.model.dropEvent(self.event_type, self) # <- gives an UnraisableExceptionWarning: weakly-referenced object no longer exists + pass def eventexec(self, event): assert str(event) == event.getName() @@ -32,7 +89,7 @@ def eventexec(self, event): elif self.event_type == SCIP_EVENTTYPE.BOUNDRELAXED: assert event.getType() in [SCIP_EVENTTYPE.LBRELAXED, SCIP_EVENTTYPE.UBRELAXED] elif self.event_type == SCIP_EVENTTYPE.BOUNDCHANGED: - assert event.getType() in [SCIP_EVENTTYPE.LBCHANGED, SCIP_EVENTTYPE.UBCHANGED] + assert event.getType() in [SCIP_EVENTTYPE.LBCHANGED, SCIP_EVENTTYPE.UBCHANGED, SCIP_EVENTTYPE.UBTIGHTENED, SCIP_EVENTTYPE.UBRELAXED, SCIP_EVENTTYPE.LBTIGHTENED, SCIP_EVENTTYPE.LBRELAXED] elif self.event_type == SCIP_EVENTTYPE.GHOLECHANGED: assert event.getType() in [SCIP_EVENTTYPE.GHOLEADDED, SCIP_EVENTTYPE.GHOLEREMOVED] elif self.event_type == SCIP_EVENTTYPE.LHOLECHANGED: @@ -40,11 +97,7 @@ def eventexec(self, event): elif self.event_type == SCIP_EVENTTYPE.HOLECHANGED: assert event.getType() in [SCIP_EVENTTYPE.GHOLECHANGED, SCIP_EVENTTYPE.LHOLECHANGED] elif self.event_type == SCIP_EVENTTYPE.DOMCHANGED: - assert event.getType() in [SCIP_EVENTTYPE.BOUNDCHANGED, SCIP_EVENTTYPE.HOLECHANGED] - elif self.event_type == SCIP_EVENTTYPE.VARCHANGED: - assert event.getType() in [SCIP_EVENTTYPE.VARFIXED, SCIP_EVENTTYPE.VARUNLOCKED, SCIP_EVENTTYPE.OBJCHANGED, SCIP_EVENTTYPE.GBDCHANGED, SCIP_EVENTTYPE.DOMCHANGED, SCIP_EVENTTYPE.IMPLADDED, SCIP_EVENTTYPE.VARDELETED, SCIP_EVENTTYPE.TYPECHANGED] - elif self.event_type == SCIP_EVENTTYPE.VAREVENT: - assert event.getType() in [SCIP_EVENTTYPE.VARADDED, SCIP_EVENTTYPE.VARCHANGED, SCIP_EVENTTYPE.TYPECHANGED] + assert event.getType() in [SCIP_EVENTTYPE.BOUNDCHANGED, SCIP_EVENTTYPE.HOLECHANGED, SCIP_EVENTTYPE.UBTIGHTENED, SCIP_EVENTTYPE.UBRELAXED, SCIP_EVENTTYPE.LBTIGHTENED, SCIP_EVENTTYPE.LBRELAXED] elif self.event_type == SCIP_EVENTTYPE.NODESOLVED: assert event.getType() in [SCIP_EVENTTYPE.NODEFEASIBLE, SCIP_EVENTTYPE.NODEINFEASIBLE, SCIP_EVENTTYPE.NODEBRANCHED] elif self.event_type == SCIP_EVENTTYPE.NODEEVENT: @@ -57,21 +110,27 @@ def eventexec(self, event): assert event.getType() in [SCIP_EVENTTYPE.ROWCOEFCHANGED, SCIP_EVENTTYPE.ROWCONSTCHANGED, SCIP_EVENTTYPE.ROWSIDECHANGED] elif self.event_type == SCIP_EVENTTYPE.ROWEVENT: assert event.getType() in [SCIP_EVENTTYPE.ROWADDEDSEPA, SCIP_EVENTTYPE.ROWDELETEDSEPA, SCIP_EVENTTYPE.ROWADDEDLP, SCIP_EVENTTYPE.ROWDELETEDLP, SCIP_EVENTTYPE.ROWCHANGED] + elif self.event_type == SCIP_EVENTTYPE.GAPUPDATED: + assert event.getType() in [SCIP_EVENTTYPE.DUALBOUNDIMPROVED, SCIP_EVENTTYPE.BESTSOLFOUND] else: - assert event.getType() == self.event_type + pass def test_event(): - all_events = [SCIP_EVENTTYPE.DISABLED,SCIP_EVENTTYPE.VARADDED,SCIP_EVENTTYPE.VARDELETED,SCIP_EVENTTYPE.VARFIXED,SCIP_EVENTTYPE.VARUNLOCKED,SCIP_EVENTTYPE.OBJCHANGED,SCIP_EVENTTYPE.GLBCHANGED,SCIP_EVENTTYPE.GUBCHANGED,SCIP_EVENTTYPE.LBTIGHTENED,SCIP_EVENTTYPE.LBRELAXED,SCIP_EVENTTYPE.UBTIGHTENED,SCIP_EVENTTYPE.UBRELAXED,SCIP_EVENTTYPE.GHOLEADDED,SCIP_EVENTTYPE.GHOLEREMOVED,SCIP_EVENTTYPE.LHOLEADDED,SCIP_EVENTTYPE.LHOLEREMOVED,SCIP_EVENTTYPE.IMPLADDED,SCIP_EVENTTYPE.PRESOLVEROUND,SCIP_EVENTTYPE.NODEFOCUSED,SCIP_EVENTTYPE.NODEFEASIBLE,SCIP_EVENTTYPE.NODEINFEASIBLE,SCIP_EVENTTYPE.NODEBRANCHED,SCIP_EVENTTYPE.NODEDELETE,SCIP_EVENTTYPE.FIRSTLPSOLVED,SCIP_EVENTTYPE.LPSOLVED,SCIP_EVENTTYPE.POORSOLFOUND,SCIP_EVENTTYPE.BESTSOLFOUND,SCIP_EVENTTYPE.ROWADDEDSEPA,SCIP_EVENTTYPE.ROWDELETEDSEPA,SCIP_EVENTTYPE.ROWADDEDLP,SCIP_EVENTTYPE.ROWDELETEDLP,SCIP_EVENTTYPE.ROWCOEFCHANGED,SCIP_EVENTTYPE.ROWCONSTCHANGED,SCIP_EVENTTYPE.ROWSIDECHANGED,SCIP_EVENTTYPE.SYNC,SCIP_EVENTTYPE.GBDCHANGED,SCIP_EVENTTYPE.LBCHANGED,SCIP_EVENTTYPE.UBCHANGED,SCIP_EVENTTYPE.BOUNDTIGHTENED,SCIP_EVENTTYPE.BOUNDRELAXED,SCIP_EVENTTYPE.BOUNDCHANGED,SCIP_EVENTTYPE.LHOLECHANGED,SCIP_EVENTTYPE.HOLECHANGED,SCIP_EVENTTYPE.DOMCHANGED,SCIP_EVENTTYPE.VARCHANGED,SCIP_EVENTTYPE.VAREVENT,SCIP_EVENTTYPE.NODESOLVED,SCIP_EVENTTYPE.NODEEVENT,SCIP_EVENTTYPE.LPEVENT,SCIP_EVENTTYPE.SOLFOUND,SCIP_EVENTTYPE.SOLEVENT,SCIP_EVENTTYPE.ROWCHANGED,SCIP_EVENTTYPE.ROWEVENT] - + all_events = {} + for attr_name in dir(SCIP_EVENTTYPE): + if not attr_name.startswith('_'): + attr = getattr(SCIP_EVENTTYPE, attr_name) + if isinstance(attr, int): + all_events[attr_name] = attr + all_event_hdlrs = [] - for event in all_events: + for event_name, event in all_events.items(): s = Model() s.hideOutput() - s.setPresolve(SCIP_PARAMSETTING.OFF) all_event_hdlrs.append(MyEvent()) - all_event_hdlrs[-1].event_type = event - s.includeEventhdlr(all_event_hdlrs[-1], str(event), "python event handler to catch %s" % str(event)) + all_event_hdlrs[-1].event_type = all_events[event_name] + s.includeEventhdlr(all_event_hdlrs[-1], str(event), "python event handler to catch %s" % str(event_name)) x = {} for i in range(100): @@ -80,6 +139,12 @@ def test_event(): for j in range(1,20): s.addCons(quicksum(x[i] for i in range(100) if i%j==0) >= random.randint(10,100)) + if event in var_events or event in row_events: + s.presolve() + else: + s.setPresolve(SCIP_PARAMSETTING.OFF) + all_event_hdlrs[-1].var = None + s.optimize() def test_event_handler_callback(): @@ -98,3 +163,30 @@ def callback(model, event): m.optimize() assert number_of_calls == 2 + +def test_raise_error_catch_var_event(): + m = Model() + m.hideOutput() + m.setPresolve(SCIP_PARAMSETTING.OFF) + + class MyEventVar(Eventhdlr): + def __init__(self, var): + super().__init__() + self.var = var + + def eventinit(self): + self.model.catchEvent(SCIP_EVENTTYPE.VAREVENT, self) + + def eventexit(self): + pass + # self.model..dropEvent(SCIP_EVENTTYPE.VAREVENT, self) + + def eventexec(self, event): + pass + + v = m.addVar("x", vtype="I") + ev = MyEventVar(v) + m.includeEventhdlr(ev, "var_event", "event handler for var events") + + with pytest.raises(Exception): + m.optimize() \ No newline at end of file diff --git a/tests/test_iis.py b/tests/test_iis.py new file mode 100644 index 000000000..2538c44ba --- /dev/null +++ b/tests/test_iis.py @@ -0,0 +1,85 @@ +import pytest + +from pyscipopt import Model, SCIP_RESULT, IISfinder + +def infeasible_model(): + m = Model() + x1 = m.addVar("x1", vtype="B") + x2 = m.addVar("x2", vtype="B") + x3 = m.addVar("x3", vtype="B") + + m.addCons(x1 + x2 == 1, name="c1") + m.addCons(x2 + x3 == 1, name="c2") + m.addCons(x1 + x3 == 1, name="c3") + m.addCons(x1 + x2 + x3 <= 0, name="c4") + + return m + +def test_generate_iis(): + m = infeasible_model() + + m.optimize() + + # make sure IIS generation doesn't raise any exceptions + iis = m.generateIIS() + subscip = iis.getSubscip() + assert iis.isSubscipIrreducible() + assert subscip.getNConss() == 2 + assert iis.getNNodes() == 0 + assert m.isGE(iis.getTime(), 0) + +class myIIS(IISfinder): + def __init__(self, skip=False): + self.skip = skip + self.called = False + + def iisfinderexec(self): + self.called = True + if self.skip: + self.iis.setSubscipInfeasible(True) + self.iis.setSubscipIrreducible(False) + return {"result": SCIP_RESULT.SUCCESS} # success to attempt to skip further processing + + subscip = self.iis.getSubscip() + for c in subscip.getConss(): + if c.name in ["c2", "c4"]: + subscip.delCons(c) + + self.iis.setSubscipInfeasible(True) + self.iis.setSubscipIrreducible(True) + return {"result": SCIP_RESULT.SUCCESS} + +def test_custom_iis_finder(): + + m = infeasible_model() + my_iis = myIIS() + + m.setParam("iis/irreducible", False) + m.setParam("iis/greedy/priority", -1000000) # lowering priority of greedy iis finder + m.includeIISfinder(my_iis, "", "") + + m.generateIIS() + assert my_iis.called + + iis = m.getIIS() + assert iis.isSubscipIrreducible() + assert iis.isSubscipInfeasible() + subscip = iis.getSubscip() + assert subscip.getNConss() == 2 + +def test_iisGreddyMakeIrreducible(): + m = infeasible_model() + m.setParam("iis/irreducible", False) + m.setParam("iis/greedy/priority", 1) # lowering priority of greedy iis finder + + my_iis = myIIS(skip=True) + m.includeIISfinder(my_iis, "", "", priority=10000) + + iis = m.generateIIS() + with pytest.raises(AssertionError): + assert not iis.isSubscipIrreducible() # this should not fail + + assert iis.isSubscipInfeasible() + + iis.greedyMakeIrreducible() + assert iis.isSubscipIrreducible() diff --git a/tests/test_model.py b/tests/test_model.py index 0a6a0f71d..573a507e3 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -558,6 +558,7 @@ def test_getVarPseudocost(): p = m.getVarPseudocost(var, SCIP_BRANCHDIR.UPWARDS) assert m.isEQ(p, 1) + m.optimize() m.updateVarPseudocost(var, 1, 12, 1) p = m.getVarPseudocost(var, SCIP_BRANCHDIR.UPWARDS) diff --git a/tests/test_pricer.py b/tests/test_pricer.py index 647e26e90..9445c278a 100644 --- a/tests/test_pricer.py +++ b/tests/test_pricer.py @@ -42,8 +42,6 @@ def pricerredcost(self): assert type(self.model.getNSolsFound()) == int assert type(self.model.getNBestSolsFound()) == int assert self.model.getNBestSolsFound() <= self.model.getNSolsFound() - - self.model.data["nSols"] = self.model.getNSolsFound() # Adding the column to the master problem (model.LT because of numerics) if self.model.isLT(objval, 0): @@ -51,7 +49,6 @@ def pricerredcost(self): # Creating new var; must set pricedVar to True newVar = self.model.addVar("NewPattern_" + str(currentNumVar), vtype = "C", obj = 1.0, pricedVar = True) - # Adding the new variable to the constraints of the master problem newPattern = [] for i, c in enumerate(self.data['cons']): @@ -87,7 +84,6 @@ def test_cuttingstock(): s.setPresolve(0) s.data = {} - s.data["nSols"] = 0 # creating a pricer pricer = CutPricer() @@ -164,7 +160,7 @@ def test_cuttingstock(): assert s.getObjVal() == 452.25 assert type(s.getNSols()) == int - assert s.getNSols() == s.data["nSols"] + assert s.getNSols() == s.getNSolsFound() # Testing freeTransform s.freeTransform() @@ -189,7 +185,6 @@ def test_deactivate_pricer(): s.setPresolve(0) s.data = {} - s.data["nSols"] = 0 # creating a pricer pricer = CutPricer() diff --git a/tests/test_reader.py b/tests/test_reader.py index 93d10c84b..d157e9df4 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -1,7 +1,9 @@ import pytest import os +from json import load from pyscipopt import Model, quicksum, Reader, SCIP_RESULT, readStatistics +from helpers.utils import random_lp_1 class SudokuReader(Reader): @@ -136,4 +138,16 @@ def test_readStatistics(): m.writeStatistics(os.path.join("tests", "data", "readStatistics.stats")) result = readStatistics(os.path.join("tests", "data", "readStatistics.stats")) assert result.status == "user_interrupt" - assert result.gap == None \ No newline at end of file + assert result.gap == None + +def test_writeStatisticsJson(): + + model = random_lp_1() + model.optimize() + model.writeStatisticsJson("statistics.json") + + with open("statistics.json", "r") as f: + data = load(f) + assert data["origprob"]["problem_name"] == "model" + + os.remove("statistics.json") \ No newline at end of file diff --git a/tests/test_relax.py b/tests/test_relax.py index 3ae38674c..614088546 100644 --- a/tests/test_relax.py +++ b/tests/test_relax.py @@ -1,4 +1,4 @@ -from pyscipopt import Model, SCIP_RESULT +from pyscipopt import Model, SCIP_RESULT, SCIP_PARAMSETTING from pyscipopt.scip import Relax import pytest from helpers.utils import random_mip_1 @@ -17,6 +17,10 @@ def relaxexec(self): def test_relaxator(): m = Model() + m.setPresolve(SCIP_PARAMSETTING.OFF) + m.setHeuristics(SCIP_PARAMSETTING.OFF) + m.setSeparating(SCIP_PARAMSETTING.OFF) + m.setParam("limits/nodes", 1) m.hideOutput() # include relaxator @@ -38,15 +42,14 @@ def test_relaxator(): assert 'relaxexec' in calls assert len(calls) >= 1 - assert m.getObjVal() > 10e4 + assert m.isGE(m.getDualbound(), 10e4) class EmptyRelaxator(Relax): - def relaxexec(self): - pass - # doesn't return anything + pass def test_empty_relaxator(): m = Model() + m.setPresolve(SCIP_PARAMSETTING.OFF) m.hideOutput() m.includeRelax(EmptyRelaxator(), "", "") diff --git a/tests/test_statistics.py b/tests/test_statistics.py new file mode 100644 index 000000000..fb4194547 --- /dev/null +++ b/tests/test_statistics.py @@ -0,0 +1,14 @@ +import os +from helpers.utils import random_mip_1 +from json import load + +def test_statistics_json(): + model = random_mip_1() + model.optimize() + model.writeStatisticsJson("statistics.json") + + with open("statistics.json", "r") as f: + data = load(f) + assert data["origprob"]["problem_name"] == "model" + + os.remove("statistics.json") diff --git a/tests/test_vars.py b/tests/test_vars.py index 7d129e325..d142413bc 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -1,4 +1,4 @@ -from pyscipopt import Model, SCIP_PARAMSETTING, SCIP_BRANCHDIR +from pyscipopt import Model, SCIP_PARAMSETTING, SCIP_BRANCHDIR, SCIP_IMPLINTTYPE from helpers.utils import random_mip_1 def test_variablebounds(): @@ -58,14 +58,23 @@ def test_vtype(): assert x.vtype() == "CONTINUOUS" assert y.vtype() == "INTEGER" assert z.vtype() == "BINARY" - assert w.vtype() == "IMPLINT" + assert w.vtype() == "CONTINUOUS" + + is_int = lambda x: x.isIntegral() + is_implint = lambda x: x.isImpliedIntegral() + # is_nonimplint = lambda x: x.isNonImpliedIntegral() + is_bin = lambda x: x.isBinary() + + assert not is_int(x) and not is_implint(x) and not is_bin(x) + assert is_int(y) and not is_implint(y) and not is_bin(y) + assert is_int(z) and not is_implint(z) and is_bin(z) + assert w.vtype() == "CONTINUOUS" and is_int(w) and is_implint(w) and not is_bin(w) + + assert w.getImplType() == SCIP_IMPLINTTYPE.WEAK m.chgVarType(x, 'I') assert x.vtype() == "INTEGER" - m.chgVarType(y, 'M') - assert y.vtype() == "IMPLINT" - def test_markRelaxationOnly(): m = Model()