diff --git a/CHANGELOG.md b/CHANGELOG.md index eb138ce88..83bc76e98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased ### Added +- Added method for adding piecewise linear constraints - Add SCIP function SCIPgetTreesizeEstimation and wrapper getTreesizeEstimation +- Add recipes sub-package ### Fixed ### Changed ### Removed diff --git a/README.md b/README.md index deedffd47..d30ebdbb5 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Building and solving a model There are several [examples](https://github.com/scipopt/PySCIPOpt/blob/master/examples/finished) and [tutorials](https://github.com/scipopt/PySCIPOpt/blob/master/examples/tutorial). These display some functionality of the -interface and can serve as an entry point for writing more complex code. +interface and can serve as an entry point for writing more complex code. Some of the common usecases are also available in the [recipes](https://github.com/scipopt/PySCIPOpt/blob/master/src/pyscipopt/recipes) sub-package. You might also want to have a look at this article about PySCIPOpt: . The following steps are always required when using the interface: diff --git a/setup.py b/setup.py index 099bb66fd..32f4ab109 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, Extension +from setuptools import find_packages, setup, Extension import os, platform, sys, re # look for environment variable that specifies path to SCIP @@ -124,7 +124,7 @@ "Topic :: Scientific/Engineering :: Mathematics", ], ext_modules=extensions, - packages=["pyscipopt"], + packages=find_packages(where="src"), package_dir={"pyscipopt": packagedir}, package_data={"pyscipopt": ["scip.pyx", "scip.pxd", "*.pxi", "scip/lib/*"]}, include_package_data=True, diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 788f48844..a370c60b7 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -9,6 +9,8 @@ # export user-relevant objects: from pyscipopt.Multidict import multidict from pyscipopt.scip import Model +from pyscipopt.scip import Variable +from pyscipopt.scip import Constraint from pyscipopt.scip import Benders from pyscipopt.scip import Benderscut from pyscipopt.scip import Branchrule diff --git a/src/pyscipopt/recipes/README.md b/src/pyscipopt/recipes/README.md new file mode 100644 index 000000000..9942db0c3 --- /dev/null +++ b/src/pyscipopt/recipes/README.md @@ -0,0 +1,3 @@ +# Recipes sub-package + +This sub-package provides a set of functions for common usecases for pyscipopt. This sub-package is for all functions that don't necessarily reflect the core functionality of SCIP, but are useful for working with the solver. The functions implemented in this sub-package might not be the most efficient way to solve/formulate a problem but would provide a good starting point. diff --git a/src/pyscipopt/recipes/__init__.py b/src/pyscipopt/recipes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/pyscipopt/recipes/piecewise.py b/src/pyscipopt/recipes/piecewise.py new file mode 100644 index 000000000..7540414da --- /dev/null +++ b/src/pyscipopt/recipes/piecewise.py @@ -0,0 +1,36 @@ + +from pyscipopt import Model, quicksum, Variable, Constraint + +def add_piecewise_linear_cons(model: Model, X: Variable, Y: Variable, a: list[float], b: list[float]) -> Constraint: + """add constraint of the form y = f(x), where f is a piecewise linear function + + :param X: x variable + :param Y: y variable + :param a: array with x-coordinates of the points in the piecewise linear relation + :param b: array with y-coordinate of the points in the piecewise linear relation + + Disclaimer: For the moment, can only model 2d piecewise linear functions + Adapted from https://github.com/scipopt/PySCIPOpt/blob/master/examples/finished/piecewise.py + """ + assert len(a) == len(b), "Must have the same number of x and y-coordinates" + + K = len(a)-1 + w,z = {},{} + for k in range(K): + w[k] = model.addVar(lb=-model.infinity()) + z[k] = model.addVar(vtype="B") + + for k in range(K): + model.addCons(w[k] >= a[k]*z[k]) + model.addCons(w[k] <= a[k+1]*z[k]) + + model.addCons(quicksum(z[k] for k in range(K)) == 1) + + model.addCons(X == quicksum(w[k] for k in range(K))) + + c = [float(b[k+1]-b[k]) / (a[k+1]-a[k]) for k in range(K)] + d = [b[k] - c[k]*a[k] for k in range(K)] + + new_cons = model.addCons(Y == quicksum(d[k]*z[k] + c[k]*w[k] for k in range(K))) + + return new_cons diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index f8c821538..4f92e53b1 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -2621,7 +2621,7 @@ cdef class Model: PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, scip_cons, var.scip_var, weights[i])) PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) - return Constraint.create(scip_cons) + return Constraint.create(scip_cons) def addConsAnd(self, vars, resvar, name="ANDcons", initial=True, separate=True, enforce=True, check=True, @@ -2853,7 +2853,7 @@ cdef class Model: PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) return pyCons - + def getSlackVarIndicator(self, Constraint cons): """Get slack variable of an indicator constraint. diff --git a/tests/test_piecewise.py b/tests/test_piecewise.py new file mode 100644 index 000000000..b2aa11be4 --- /dev/null +++ b/tests/test_piecewise.py @@ -0,0 +1,30 @@ + +from pyscipopt import Model +from pyscipopt.recipes.piecewise import add_piecewise_linear_cons + +def test_add_piecewise_linear_cons(): + m = Model() + + xpoints = [1, 3, 5] + ypoints = [1, 2, 4] + x = m.addVar(lb=xpoints[0], ub=xpoints[-1], obj=2) + y = m.addVar(lb=-m.infinity(), obj=-3) + add_piecewise_linear_cons(m, x, y, xpoints, ypoints) + + m.optimize() + assert m.isEQ(m.getObjVal(), -2) + + +def test_add_piecewise_linear_cons2(): + m = Model() + + xpoints = [1, 3, 5] + ypoints = [1, 2, 4] + x = m.addVar(lb=xpoints[0], ub=xpoints[-1], obj=2) + y = m.addVar(lb=-m.infinity(), obj=-3) + add_piecewise_linear_cons(m, x, y, xpoints, ypoints) + + m.setMaximize() + + m.optimize() + assert m.isEQ(m.getObjVal(), 0)