From 77a5e9aa46dc45dc3f6596505869a44ff4173ddd Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Mon, 9 Apr 2018 20:21:20 +0100 Subject: [PATCH 001/121] adds Benders' decomposition plugin interface functions --- src/pyscipopt/benders.pxi | 148 ++++++++++++++++++++++++++++++++++++++ src/pyscipopt/scip.pxd | 34 +++++++++ src/pyscipopt/scip.pyx | 30 ++++++++ 3 files changed, 212 insertions(+) create mode 100644 src/pyscipopt/benders.pxi diff --git a/src/pyscipopt/benders.pxi b/src/pyscipopt/benders.pxi new file mode 100644 index 000000000..3bbcbc7d4 --- /dev/null +++ b/src/pyscipopt/benders.pxi @@ -0,0 +1,148 @@ +cdef class Benders: + cdef public Model model + + def bendersfree(self): + pass + + def bendersinit(self): + pass + + def bendersexit(self): + pass + + def bendersinitpre(self): + pass + + def bendersexitpre(self): + pass + + def bendersinitsol(self): + pass + + def bendersexitsol(self): + pass + + def benderscreatesub(self, probnumber): + print("python error in benderscreatesub: this method needs to be implemented") + return {} + + def benderspresubsolve(self): + pass + + def benderssolvesub(self, solution, probnumber): + pass + + def benderspostsolve(self, solution, infeasible): + pass + + def bendersfreesub(self, probnumber): + pass + + def bendersgetvar(self, variable, probnumber): + print("python error in bendersgetvar: this method needs to be implemented") + return {} + + +cdef SCIP_RETCODE PyBendersCopy (SCIP* scip, SCIP_BENDERS* benders): + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersfree() + Py_DECREF(PyBenders) + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersInit (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersinit() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersExit (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersexit() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersInitpre (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersinitpre() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersExitpre (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersexitpre() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersInitsol (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersinitsol() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersExitsol (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersexitsol() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersCreatesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.benderscreatesub(probnumber) + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.benderspresubsolve() + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool* infeasible): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + solution = Solution() + solution.sol = sol + result_dict = PyBenders.benderssolvesub(solution, probnumber) + infeasible[0] = result_dict.get("infeasible", infeasible[0]) + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_Bool infeasible): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + solution = Solution() + solution.sol = sol + PyBenders.benderspostsolve(solution, infeasible) + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersfreesub(probnumber) + return SCIP_OKAY + +#TODO: Really need to ask about the passing and returning of variables +cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + variable = Variable() + variable.var = var + result_dict = PyBenders.bendersgetvar(variable, probnumber) + mappedvar[0] = result_dict.get("mappedvar", mappedvar[0]) + return SCIP_OKAY diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index d6cb5d00b..f01665ca4 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -291,6 +291,12 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_EXPRDATA_MONOMIAL: pass + ctypedef struct SCIP_BENDERS: + pass + + ctypedef struct SCIP_BENDERSDATA: + pass + # General SCIP Methods SCIP_RETCODE SCIPcreate(SCIP** scip) SCIP_RETCODE SCIPfree(SCIP** scip) @@ -638,6 +644,34 @@ cdef extern from "scip/scip.h": SCIP_BRANCHRULEDATA* branchruledata) SCIP_BRANCHRULEDATA* SCIPbranchruleGetData(SCIP_BRANCHRULE* branchrule) + # Benders' decomposition plugin + SCIP_RETCODE SCIPincludeBenders(SCIP* scip, + const char* name, + const char* desc, + int priority, + SCIP_Bool cutlp, + SCIP_Bool cutpseudo, + SCIP_Bool cutrelax, + SCIP_Bool shareaux, + SCIP_RETCODE (*benderscopy) (SCIP* scip, SCIP_BENDERS* benders, SCIP_Bool* valid), + SCIP_RETCODE (*bendersfree) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersinit) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersexit) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersinitpre) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersexitpre) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersinitsol) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersexitsol) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*bendersgetvar) (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber), + SCIP_RETCODE (*benderscreatesub) (SCIP* scip, SCIP_BENDERS* benders, iint probnumber), + SCIP_RETCODE (*benderspresubsolve) (SCIP* scip, SCIP_BENDERS* benders), + SCIP_RETCODE (*benderssolvesub) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool* infeasible), + SCIP_RETCODE (*benderspostsolve) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_Bool infeasible), + SCIP_RETCODE (*bendersfreesub) (SCIP* scip, SCIP_BENDERS* benders, int probnumber), + SCIP_BENDERSDATA* bendersdata) + SCIP_BENDERS* SCIPfindBenders(SCIP* scip, const char* name) + SCIP_RETCODE SCIPactivateBenders(SCIP* scip, SCIP_BENDERS* benders) + SCIP_BENDERSDATA* SCIPbendersGetData(SCIP_BENDERS* benders) + # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index c0a579a26..6a99b8d13 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -8,6 +8,7 @@ from libc.stdlib cimport malloc, free include "expr.pxi" include "lp.pxi" +include "benders.pxi" include "branchrule.pxi" include "conshdlr.pxi" include "heuristic.pxi" @@ -1513,6 +1514,35 @@ cdef class Model: branchrule.model = weakref.proxy(self) Py_INCREF(branchrule) + def includeBenders(self, Benders benders, name, desc, priority=1, cutlp=True, cutpseudo=True, cutrelax=True, + shareaux=False): + """Include a Benders' decomposition. + + Keyword arguments: + benders -- the Benders decomposition + name -- the name + desc -- the description + priority -- priority of the Benders' decomposition + cutlp -- should Benders' cuts be generated from LP solutions + cutpseudo -- should Benders' cuts be generated from pseudo solutions + cutrelax -- should Benders' cuts be generated from relaxation solutions + shareaux -- should the Benders' decomposition share the auxiliary variables of the highest priority Benders' decomposition + """ + n = str_conversion(name) + d = str_conversion(desc) + PY_SCIP_CALL(SCIPincludeBenders(self._scip, n, d, + priority, cutlp, cutrelax, cutpseudo, shareaux, + PyBendersCopy, PyBendersFree, PyBendersInit, PyBendersExit, PyBendersInitpre, + PyBendersExitpre, PyBendersInitsol, PyBendersExitsol, PyBendersGetvar, + PyBendersCreatesub, PyBendersPresubsolve, PyBendersSolvesub, + PyBendersPostsolve, PyBendersFreesub, + benders)) + cdef SCIP_BENDERS* scip_benders + scip_benders = SCIPfindBenders(self._scip, n) + PY_SCIP_CALL(SCIPactivateBenders(self._scip, scip_benders)) + benders.model = weakref.proxy(self) + Py_INCREF(benders) + # Solution functions def createSol(self, Heur heur = None): From 822282afb53008c3358034c704cef81a5320c55d Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 24 Apr 2018 15:08:40 +0200 Subject: [PATCH 002/121] fix parameter list of consenfolp --- src/pyscipopt/conshdlr.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/conshdlr.pxi b/src/pyscipopt/conshdlr.pxi index 144d2c52b..bf71bfb65 100644 --- a/src/pyscipopt/conshdlr.pxi +++ b/src/pyscipopt/conshdlr.pxi @@ -40,7 +40,7 @@ cdef class Conshdlr: def conssepasol(self, constraints, nusefulconss, solution): return {} - def consenfolp(self, solution, constraints, nusefulconss, solinfeasible): + def consenfolp(self, constraints, nusefulconss, solinfeasible): print("python error in consenfolp: this method needs to be implemented") return {} From 3eb1aea09f1589fc87a115b36db748d96cd20a48 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 24 Apr 2018 15:42:13 +0200 Subject: [PATCH 003/121] fix error in conshdlr implementation --- src/pyscipopt/conshdlr.pxi | 2 +- src/pyscipopt/scip.pxd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/conshdlr.pxi b/src/pyscipopt/conshdlr.pxi index bf71bfb65..36cb5b85e 100644 --- a/src/pyscipopt/conshdlr.pxi +++ b/src/pyscipopt/conshdlr.pxi @@ -44,7 +44,7 @@ cdef class Conshdlr: print("python error in consenfolp: this method needs to be implemented") return {} - def consenforelax(self, constraints, nusefulconss, solinfeasible): + def consenforelax(self, solution, constraints, nusefulconss, solinfeasible): print("python error in consenforelax: this method needs to be implemented") return {} diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index a7cc218de..9b38709b3 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -659,7 +659,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*conssepalp) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_RESULT* result), SCIP_RETCODE (*conssepasol) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_SOL* sol, SCIP_RESULT* result), SCIP_RETCODE (*consenfolp) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result), - SCIP_RETCODE (*consenfolp) (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result), + SCIP_RETCODE (*consenforelax) (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result), SCIP_RETCODE (*consenfops) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_Bool objinfeasible, SCIP_RESULT* result), SCIP_RETCODE (*conscheck) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_SOL* sol, SCIP_Bool checkintegrality, SCIP_Bool checklprows, SCIP_Bool printreason, SCIP_Bool completely, SCIP_RESULT* result), SCIP_RETCODE (*consprop) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, int nmarkedconss, SCIP_PROPTIMING proptiming, SCIP_RESULT* result), From 910beeb4b770519c55ec25538258bc3a077bb369 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 24 Apr 2018 16:23:32 +0200 Subject: [PATCH 004/121] increase version to 1.4.5 --- src/pyscipopt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 888b735c6..361a9f22d 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.4.4' +__version__ = '1.4.5' # export user-relevant objects: from pyscipopt.Multidict import multidict From 82f236a248e851794837e1eea074e68783ff4133 Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Fri, 4 May 2018 12:16:57 +0200 Subject: [PATCH 005/121] improve support cutting planes - add functions for adding cuts - add function to access tableau information - add test of a separator --- src/pyscipopt/scip.pxd | 68 ++++++++- src/pyscipopt/scip.pyx | 230 +++++++++++++++++++++++++++- src/pyscipopt/sepa.pxi | 1 + tests/test_gomory.py | 339 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 634 insertions(+), 4 deletions(-) create mode 100644 tests/test_gomory.py diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 9b38709b3..fa9ae8e3d 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -180,6 +180,14 @@ cdef extern from "scip/scip.h": SCIP_EXPR_USER = 69 SCIP_EXPR_LAST = 70 + + ctypedef enum SCIP_BASESTAT: + SCIP_BASESTAT_LOWER = 0 + SCIP_BASESTAT_BASIC = 1 + SCIP_BASESTAT_UPPER = 2 + SCIP_BASESTAT_ZERO = 3 + + ctypedef enum SCIP_EVENTTYPE: SCIP_EVENTTYPE_DISABLED = 0x00000000u SCIP_EVENTTYPE_VARADDED = 0x00000001u @@ -461,6 +469,7 @@ cdef extern from "scip/scip.h": # Solve Methods SCIP_RETCODE SCIPsolve(SCIP* scip) SCIP_RETCODE SCIPfreeTransform(SCIP* scip) + SCIP_RETCODE SCIPpresolve(SCIP* scip) # Node Methods SCIP_NODE* SCIPgetCurrentNode(SCIP* scip) @@ -509,6 +518,21 @@ cdef extern from "scip/scip.h": SCIP_Real SCIPvarGetObj(SCIP_VAR* var) SCIP_Real SCIPvarGetLPSol(SCIP_VAR* var) + # LP Methods + SCIP_RETCODE SCIPgetLPColsData(SCIP* scip, SCIP_COL*** cols, int* ncols) + SCIP_RETCODE SCIPgetLPRowsData(SCIP* scip, SCIP_ROW*** rows, int* nrows) + SCIP_RETCODE SCIPgetLPBasisInd(SCIP* scip, int* basisind) + SCIP_RETCODE SCIPgetLPBInvRow(SCIP* scip, int r, SCIP_Real* coefs, int* inds, int* ninds) + SCIP_RETCODE SCIPgetLPBInvARow(SCIP* scip, int r, SCIP_Real* binvrow, SCIP_Real* coefs, int* inds, int* ninds) + SCIP_Bool SCIPisLPSolBasic(SCIP* scip) + int SCIPgetNLPRows(SCIP* scip) + int SCIPgetNLPCols(SCIP* scip) + + # Cutting Plane Methods + SCIP_RETCODE SCIPaddPoolCut(SCIP* scip, SCIP_ROW* row) + SCIP_Real SCIPgetCutEfficacy(SCIP* scip, SCIP_SOL* sol, SCIP_ROW* cut) + SCIP_Bool SCIPisCutEfficacious(SCIP* scip, SCIP_SOL* sol, SCIP_ROW* cut) + # Constraint Methods SCIP_RETCODE SCIPcaptureCons(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPreleaseCons(SCIP* scip, SCIP_CONS** cons) @@ -534,9 +558,6 @@ cdef extern from "scip/scip.h": const char* SCIPconshdlrGetName(SCIP_CONSHDLR* conshdlr) SCIP_RETCODE SCIPdelConsLocal(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPdelCons(SCIP* scip, SCIP_CONS* cons) - SCIP_VAR** SCIPgetVarsLinear(SCIP* scip, SCIP_CONS* cons) - int SCIPgetNVarsLinear(SCIP* scip, SCIP_CONS* cons) - SCIP_Real* SCIPgetValsLinear(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPsetConsChecked(SCIP *scip, SCIP_CONS *cons, SCIP_Bool check) SCIP_RETCODE SCIPsetConsRemovable(SCIP *scip, SCIP_CONS *cons, SCIP_Bool removable) SCIP_RETCODE SCIPsetConsInitial(SCIP *scip, SCIP_CONS *cons, SCIP_Bool initial) @@ -569,6 +590,14 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPcreateRow(SCIP* scip, SCIP_ROW** row, const char* name, int len, SCIP_COL** cols, SCIP_Real* vals, SCIP_Real lhs, SCIP_Real rhs, SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool removable) SCIP_RETCODE SCIPaddRow(SCIP* scip, SCIP_ROW* row, SCIP_Bool forcecut, SCIP_Bool* infeasible) + SCIP_RETCODE SCIPcreateEmptyRowSepa(SCIP* scip, SCIP_ROW** row, SCIP_SEPA* sepa, const char* name, SCIP_Real lhs, SCIP_Real rhs, SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool removable) + SCIP_Real SCIPgetRowActivity(SCIP* scip, SCIP_ROW* row) + SCIP_Real SCIPgetRowLPActivity(SCIP* scip, SCIP_ROW* row) + SCIP_RETCODE SCIPreleaseRow(SCIP* scip, SCIP_ROW** row) + SCIP_RETCODE SCIPcacheRowExtensions(SCIP* scip, SCIP_ROW* row) + SCIP_RETCODE SCIPflushRowExtensions(SCIP* scip, SCIP_ROW* row) + SCIP_RETCODE SCIPaddVarToRow(SCIP* scip, SCIP_ROW* row, SCIP_VAR* var, SCIP_Real val) + SCIP_RETCODE SCIPprintRow(SCIP* scip, SCIP_ROW* row, FILE* file) # Dual Solution Methods SCIP_Real SCIPgetDualbound(SCIP* scip) @@ -720,6 +749,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*sepaexecsol) (SCIP* scip, SCIP_SEPA* sepa, SCIP_SOL* sol, SCIP_RESULT* result, unsigned int allowlocal), SCIP_SEPADATA* sepadata) SCIP_SEPADATA* SCIPsepaGetData(SCIP_SEPA* sepa) + SCIP_SEPA* SCIPfindSepa(SCIP* scip, const char* name) # Propagator plugin SCIP_RETCODE SCIPincludeProp(SCIP* scip, @@ -812,10 +842,18 @@ cdef extern from "scip/scip.h": # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) + SCIP_Real SCIPfrac(SCIP* scip, SCIP_Real val) + SCIP_Real SCIPfeasFrac(SCIP* scip, SCIP_Real val) + SCIP_Bool SCIPisZero(SCIP* scip, SCIP_Real val) + SCIP_Bool SCIPisFeasZero(SCIP* scip, SCIP_Real val) + SCIP_Bool SCIPisFeasNegative(SCIP* scip, SCIP_Real val) + SCIP_Bool SCIPisInfinity(SCIP* scip, SCIP_Real val) + SCIP_Bool SCIPisLE(SCIP* scip, SCIP_Real val1, SCIP_Real val2) # Statistic Methods SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) SCIP_Longint SCIPgetNNodes(SCIP* scip) + SCIP_Longint SCIPgetNLPs(SCIP* scip) # Parameter Functions SCIP_RETCODE SCIPsetBoolParam(SCIP* scip, char* name, SCIP_Bool value) @@ -912,6 +950,9 @@ cdef extern from "scip/cons_linear.h": SCIP_Real SCIPgetLhsLinear(SCIP* scip, SCIP_CONS* cons) SCIP_Real SCIPgetRhsLinear(SCIP* scip, SCIP_CONS* cons) SCIP_Real SCIPgetActivityLinear(SCIP* scip, SCIP_CONS* cons, SCIP_SOL* sol) + SCIP_VAR** SCIPgetVarsLinear(SCIP* scip, SCIP_CONS* cons) + int SCIPgetNVarsLinear(SCIP* scip, SCIP_CONS* cons) + SCIP_Real* SCIPgetValsLinear(SCIP* scip, SCIP_CONS* cons) cdef extern from "scip/cons_quadratic.h": SCIP_RETCODE SCIPcreateConsQuadratic(SCIP* scip, @@ -1141,3 +1182,24 @@ cdef extern from "scip/paramset.h": char SCIPparamGetChar(SCIP_PARAM* param) char* SCIPparamGetString(SCIP_PARAM* param) +cdef extern from "scip/pub_lp.h": + # Row Methods + SCIP_Real SCIProwGetLhs(SCIP_ROW* row) + SCIP_Real SCIProwGetRhs(SCIP_ROW* row) + SCIP_Real SCIProwGetConstant(SCIP_ROW* row) + int SCIProwGetLPPos(SCIP_ROW* row) + SCIP_BASESTAT SCIProwGetBasisStatus(SCIP_ROW* row) + SCIP_Bool SCIProwIsIntegral(SCIP_ROW* row) + SCIP_Bool SCIProwIsModifiable(SCIP_ROW* row) + int SCIProwGetNNonz(SCIP_ROW* row) + int SCIProwGetNLPNonz(SCIP_ROW* row) + SCIP_COL** SCIProwGetCols(SCIP_ROW* row) + SCIP_Real* SCIProwGetVals(SCIP_ROW* row) + # Column Methods + int SCIPcolGetLPPos(SCIP_COL* col) + SCIP_BASESTAT SCIPcolGetBasisStatus(SCIP_COL* col) + SCIP_Bool SCIPcolIsIntegral(SCIP_COL* col) + SCIP_VAR* SCIPcolGetVar(SCIP_COL* col) + SCIP_Real SCIPcolGetPrimsol(SCIP_COL* col) + SCIP_Real SCIPcolGetLb(SCIP_COL* col) + SCIP_Real SCIPcolGetUb(SCIP_COL* col) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 715ae8b84..40a00fd02 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -249,6 +249,41 @@ cdef class Column: col.col = scip_col return col + def getLPPos(self): + return SCIPcolGetLPPos(self.col) + + def getBasisStatus(self): + """Note: returns basis status `zero` for columns not in the current SCIP LP""" + cdef SCIP_BASESTAT stat = SCIPcolGetBasisStatus(self.col) + if stat == SCIP_BASESTAT_LOWER: + return "lower" + elif stat == SCIP_BASESTAT_BASIC: + return "basic" + elif stat == SCIP_BASESTAT_UPPER: + return "upper" + elif stat == SCIP_BASESTAT_ZERO: + return "zero" + else: + raise Exception('SCIP returned unknown base status!') + + def isIntegral(self): + return SCIPcolIsIntegral(self.col) + + def getVar(self): + """gets variable this column represents""" + cdef SCIP_VAR* var = SCIPcolGetVar(self.col) + return Variable.create(var) + + def getPrimsol(self): + """gets the primal LP solution of a column""" + return SCIPcolGetPrimsol(self.col) + + def getLb(self): + return SCIPcolGetLb(self.col) + + def getUb(self): + return SCIPcolGetUb(self.col) + cdef class Row: """Base class holding a pointer to corresponding SCIP_ROW""" cdef SCIP_ROW* row @@ -259,6 +294,57 @@ cdef class Row: row.row = scip_row return row + def getLhs(self): + return SCIProwGetLhs(self.row) + + def getRhs(self): + return SCIProwGetRhs(self.row) + + def getConstant(self): + return SCIProwGetConstant(self.row) + + def getLPPos(self): + return SCIProwGetLPPos(self.row) + + def getBasisStatus(self): + """Note: returns basis status `basic` for rows not in the current SCIP LP""" + cdef SCIP_BASESTAT stat = SCIProwGetBasisStatus(self.row) + if stat == SCIP_BASESTAT_LOWER: + return "lower" + elif stat == SCIP_BASESTAT_BASIC: + return "basic" + elif stat == SCIP_BASESTAT_UPPER: + return "upper" + elif stat == SCIP_BASESTAT_ZERO: + # this shouldn't happen! + raise Exception('SCIP returned base status zero for a row!') + else: + raise Exception('SCIP returned unknown base status!') + + def isIntegral(self): + return SCIProwIsIntegral(self.row) + + def isModifiable(self): + return SCIProwIsModifiable(self.row) + + def getNNonz(self): + """get number of nonzero entries in row vector""" + return SCIProwGetNNonz(self.row) + + def getNLPNonz(self): + """get number of nonzero entries in row vector that correspond to columns currently in the SCIP LP""" + return SCIProwGetNLPNonz(self.row) + + def getCols(self): + """gets list with columns of nonzero entries""" + cdef SCIP_COL** cols = SCIProwGetCols(self.row) + return [Column.create(cols[i]) for i in range(self.getNNonz())] + + def getVals(self): + """gets list with coefficients of nonzero entries""" + cdef SCIP_Real* vals = SCIProwGetVals(self.row) + return [vals[i] for i in range(self.getNNonz())] + cdef class Solution: """Base class holding a pointer to corresponding SCIP_SOL""" cdef SCIP_SOL* sol @@ -583,6 +669,34 @@ cdef class Model: """Retrieve feasibility tolerance""" return SCIPfeastol(self._scip) + def feasFrac(self, value): + """returns fractional part of value, i.e. x - floor(x) in feasible tolerance: x - floor(x+feastol)""" + return SCIPfeasFrac(self._scip, value) + + def frac(self, value): + """returns fractional part of value, i.e. x - floor(x) in epsilon tolerance: x - floor(x+eps)""" + return SCIPfrac(self._scip, value) + + def isZero(self, value): + """returns whether abs(value) < eps""" + return SCIPisZero(self._scip, value) + + def isFeasZero(self, value): + """returns whether abs(value) < feastol""" + return SCIPisFeasZero(self._scip, value) + + def isInfinity(self, value): + """returns whether value is SCIP's infinity""" + return SCIPisInfinity(self._scip, value) + + def isFeasNegative(self, value): + """returns whether value < -feastol""" + return SCIPisFeasNegative(self._scip, value) + + def isLE(self, val1, val2): + """returns whether val1 <= val2 + eps""" + return SCIPisLE(self._scip, val1, val2) + def getCondition(self, exact=False): """Get the current LP's condition number @@ -882,6 +996,112 @@ cdef class Model: return [Variable.create(_vars[i]) for i in range(_nvars)] + # LP Methods + def getLPColsData(self): + """Retrieve current LP columns""" + cdef SCIP_COL** cols + cdef int ncols + + PY_SCIP_CALL(SCIPgetLPColsData(self._scip, &cols, &ncols)) + return [Column.create(cols[i]) for i in range(ncols)] + + def getLPRowsData(self): + """Retrieve current LP rows""" + cdef SCIP_ROW** rows + cdef int nrows + + PY_SCIP_CALL(SCIPgetLPRowsData(self._scip, &rows, &nrows)) + return [Row.create(rows[i]) for i in range(nrows)] + + def getLPBasisInd(self): + """Gets all indices of basic columns and rows: index i >= 0 corresponds to column i, index i < 0 to row -i-1""" + cdef int nrows = SCIPgetNLPRows(self._scip) + cdef int* inds = malloc(nrows * sizeof(int)) + + PY_SCIP_CALL(SCIPgetLPBasisInd(self._scip, inds)) + result = [inds[i] for i in range(nrows)] + free(inds) + return result + + def getLPBInvRow(self, row): + """gets a row from the inverse basis matrix B^-1""" + # TODO: sparsity information + cdef int nrows = SCIPgetNLPRows(self._scip) + cdef SCIP_Real* coefs = malloc(nrows * sizeof(SCIP_Real)) + + PY_SCIP_CALL(SCIPgetLPBInvRow(self._scip, row, coefs, NULL, NULL)) + result = [coefs[i] for i in range(nrows)] + free(coefs) + return result + + def getLPBInvARow(self, row): + """gets a row from B^-1 * A""" + # TODO: sparsity information + cdef int ncols = SCIPgetNLPCols(self._scip) + cdef SCIP_Real* coefs = malloc(ncols * sizeof(SCIP_Real)) + + PY_SCIP_CALL(SCIPgetLPBInvARow(self._scip, row, NULL, coefs, NULL, NULL)) + result = [coefs[i] for i in range(ncols)] + free(coefs) + return result + + def isLPSolBasic(self): + """returns whether the current LP solution is basic, i.e. is defined by a valid simplex basis""" + return SCIPisLPSolBasic(self._scip) + + #TODO: documentation!! + # LP Row Methods + def createEmptyRowSepa(self, Sepa sepa, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True): + cdef SCIP_ROW* row + lhs = -SCIPinfinity(self._scip) if lhs is None else lhs + rhs = SCIPinfinity(self._scip) if rhs is None else rhs + scip_sepa = SCIPfindSepa(self._scip, str_conversion(sepa.name)) + PY_SCIP_CALL(SCIPcreateEmptyRowSepa(self._scip, &row, scip_sepa, str_conversion(name), lhs, rhs, local, modifiable, removable)) + PyRow = Row.create(row) + # TODO: should I release the row???? No, I haven't add it, so it will actually free it + #PY_SCIP_CALL(SCIPreleaseRow(self._scip, &row)) + return PyRow + + def getRowActivity(self, Row row): + return SCIPgetRowActivity(self._scip, row.row) + + def getRowLPActivity(self, Row row): + return SCIPgetRowLPActivity(self._scip, row.row) + + # TODO: do we need this? (also do we need release var??) + def releaseRow(self, Row row not None): + PY_SCIP_CALL(SCIPreleaseRow(self._scip, &row.row)) + + def cacheRowExtensions(self, Row row not None): + PY_SCIP_CALL(SCIPcacheRowExtensions(self._scip, row.row)) + + def flushRowExtensions(self, Row row not None): + PY_SCIP_CALL(SCIPflushRowExtensions(self._scip, row.row)) + + def addVarToRow(self, Row row not None, Variable var not None, value): + PY_SCIP_CALL(SCIPaddVarToRow(self._scip, row.row, var.var, value)) + + def printRow(self, Row row not None): + """Prints row.""" + PY_SCIP_CALL(SCIPprintRow(self._scip, row.row, NULL)) + + # Cutting Plane Methods + def addPoolCut(self, Row row not None): + PY_SCIP_CALL(SCIPaddPoolCut(self._scip, row.row)) + + def getCutEfficacy(self, Row cut not None, Solution sol = None): + return SCIPgetCutEfficacy(self._scip, NULL if sol is None else sol.sol, cut.row) + + def isCutEfficacious(self, Row cut not None, Solution sol = None): + """ returns whether the cut's efficacy with respect to the given primal solution or the current LP solution is greater than the minimal cut efficacy""" + return SCIPisCutEfficacious(self._scip, NULL if sol is None else sol.sol, cut.row) + + def addCut(self, Row cut not None, forcecut = False): + """adds cut to separation storage and returns whether cut has been detected to be infeasible for local bounds""" + cdef SCIP_Bool infeasible + PY_SCIP_CALL(SCIPaddRow(self._scip, cut.row, forcecut, &infeasible)) + return infeasible + # Constraint functions def addCons(self, cons, name='', initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, @@ -1734,6 +1954,10 @@ cdef class Model: PY_SCIP_CALL(SCIPsolve(self._scip)) self._bestSol = Solution.create(SCIPgetBestSol(self._scip)) + def presolve(self): + """Presolve the problem.""" + PY_SCIP_CALL(SCIPpresolve(self._scip)) + def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): """Include an event handler. @@ -1864,7 +2088,7 @@ cdef class Model: presol.model = weakref.proxy(self) Py_INCREF(presol) - def includeSepa(self, Sepa sepa, name, desc, priority, freq, maxbounddist, usessubscip=False, delay=False): + def includeSepa(self, Sepa sepa, name, desc, priority = 0, freq = 10, maxbounddist = 1.0, usessubscip=False, delay=False): """Include a separator :param Sepa sepa: separator @@ -1882,6 +2106,7 @@ cdef class Model: PY_SCIP_CALL(SCIPincludeSepa(self._scip, n, d, priority, freq, maxbounddist, usessubscip, delay, PySepaCopy, PySepaFree, PySepaInit, PySepaExit, PySepaInitsol, PySepaExitsol, PySepaExeclp, PySepaExecsol, sepa)) sepa.model = weakref.proxy(self) + sepa.name = name Py_INCREF(sepa) def includeProp(self, Prop prop, name, desc, presolpriority, presolmaxrounds, @@ -2294,6 +2519,9 @@ cdef class Model: cfile = fdopen(f.fileno(), "w") PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) + def getNLPs(self): + return SCIPgetNLPs(self._scip) + # Verbosity Methods def hideOutput(self, quiet = True): diff --git a/src/pyscipopt/sepa.pxi b/src/pyscipopt/sepa.pxi index 08cea1cc6..6e0dcd7a3 100644 --- a/src/pyscipopt/sepa.pxi +++ b/src/pyscipopt/sepa.pxi @@ -1,5 +1,6 @@ cdef class Sepa: cdef public Model model + cdef public str name def sepafree(self): pass diff --git a/tests/test_gomory.py b/tests/test_gomory.py new file mode 100644 index 000000000..0017eaec4 --- /dev/null +++ b/tests/test_gomory.py @@ -0,0 +1,339 @@ +import pytest + +from pyscipopt import Model, Sepa, SCIP_RESULT, SCIP_PARAMSETTING +from pyscipopt.scip import is_memory_freed + +class GMI(Sepa): + + def __init__(self): + self.ncuts = 0 + + def getGMIFromRow(self, cols, rows, binvrow, binvarow, primsol): + # Given the row (binvarow, binvrow) of the tableau, computes gomory cut + # `primsol` is the rhs of the tableau row. + # `cols` are the variables + # `rows` are the slack variables + # The GMI is given by + # sum(f_j x_j , j in J_I s.t. f_j <= f_0) + + # sum((1-f_j)*f_0/(1 - f_0) x_j, j in J_I s.t. f_j > f_0) + + # sum(a_j x_j, , j in J_C s.t. a_j >= 0) - + # sum(a_j*f_0/(1-f_0) x_j , j in J_C s.t. a_j < 0) >= f_0. + # where J_I are the integer non-basic variables and J_C are the continuous. + # f_0 is the fractional part of primsol + # a_j is the j-th coefficient of the row and f_j its fractional part + # Note: we create -% <= -f_0 !! + # Note: this formula is valid for a problem of the form Ax = b, x>= 0. Since we do not have + # such problem structure in general, we have to (implicitely) transform whatever we are given + # to that form. Specifically, non-basic variables at their lower bound are shifted so that the lower + # bound is 0 and non-basic at their upper bound are complemented. + + # initialize + cutcoefs = [0] * len(cols) + cutrhs = 0 + + # get scip + scip = self.model + + # Compute cut fractionality f0 and f0/(1-f0) + f0 = scip.frac(primsol) + ratiof0compl = f0/(1-f0) + + # rhs of the cut is the fractional part of the LP solution for the basic variable + cutrhs = -f0 + + # Generate cut coefficients for the original variables + for c in range(len(cols)): + col = cols[c] + assert col is not None # is this the equivalent of col != NULL? does it even make sense to have this assert? + status = col.getBasisStatus() + + # Get simplex tableau coefficient + if status == "lower": + # Take coefficient if nonbasic at lower bound + rowelem = binvarow[c] + elif status == "upper": + # Flip coefficient if nonbasic at upper bound: x --> u - x + rowelem = -binvarow[c] + else: + # variable is nonbasic free at zero -> cut coefficient is zero, skip OR + # variable is basic, skip + assert status == "zero" or status == "basic" + continue + + # Integer variables + if col.isIntegral(): + # warning: because of numerics cutelem < 0 is possible (though the fractional part is, mathematically, always positive) + # However, when cutelem < 0 it is also very close to 0, enough that isZero(cutelem) is true, so we ignore + # the coefficient (see below) + cutelem = scip.frac(rowelem) + + if cutelem > f0: + # sum((1-f_j)*f_0/(1 - f_0) x_j, j in J_I s.t. f_j > f_0) + + cutelem = -((1.0 - cutelem) * ratiof0compl) + else: + # sum(f_j x_j , j in J_I s.t. f_j <= f_0) + + cutelem = -cutelem + else: + # Continuous variables + if rowelem < 0.0: + # -sum(a_j*f_0/(1-f_0) x_j , j in J_C s.t. a_j < 0) >= f_0. + cutelem = rowelem * ratiof0compl + else: + # sum(a_j x_j, , j in J_C s.t. a_j >= 0) - + cutelem = -rowelem + + # cut is define when variables are in [0, infty). Translate to general bounds + if not scip.isZero(cutelem): + if col.getBasisStatus() == "upper": + cutelem = -cutelem + cutrhs += cutelem * col.getUb() + else: + cutrhs += cutelem * col.getLb() + # Add coefficient to cut in dense form + cutcoefs[col.getLPPos()] = cutelem + + # Generate cut coefficients for the slack variables; skip basic ones + for c in range(len(rows)): + row = rows[c] + assert row != None + status = row.getBasisStatus() + + # free slack variable shouldn't appear + assert status != "zero" + + # Get simplex tableau coefficient + if status == "lower": + # Take coefficient if nonbasic at lower bound + rowelem = binvrow[row.getLPPos()] + # But if this is a >= or ranged constraint at the lower bound, we have to flip the row element + if not scip.isInfinity(-row.getLhs()): + rowelem = -rowelem + elif status == "upper": + # Take element if nonbasic at upper bound - see notes at beginning of file: only nonpositive slack variables + # can be nonbasic at upper, therefore they should be flipped twice and we can take the element directly. + rowelem = binvrow[row.getLPPos()] + else: + assert status == "basic" + continue + + # if row is integral we can strengthen the cut coefficient + if row.isIntegral() and not row.isModifiable(): + # warning: because of numerics cutelem < 0 is possible (though the fractional part is, mathematically, always positive) + # However, when cutelem < 0 it is also very close to 0, enough that isZero(cutelem) is true (see later) + cutelem = scip.frac(rowelem) + + if cutelem > f0: + # sum((1-f_j)*f_0/(1 - f_0) x_j, j in J_I s.t. f_j > f_0) + + cutelem = -((1.0 - cutelem) * ratiof0compl) + else: + # sum(f_j x_j , j in J_I s.t. f_j <= f_0) + + cutelem = -cutelem + else: + # Continuous variables + if rowelem < 0.0: + # -sum(a_j*f_0/(1-f_0) x_j , j in J_C s.t. a_j < 0) >= f_0. + cutelem = rowelem * ratiof0compl + else: + # sum(a_j x_j, , j in J_C s.t. a_j >= 0) - + cutelem = -rowelem + + # cut is define in original variables, so we replace slack by its definition + if not scip.isZero(cutelem): + # get lhs/rhs + rlhs = row.getLhs() + rrhs = row.getRhs() + assert scip.isLE(rlhs, rrhs) + assert not scip.isInfinity(rlhs) or not scip.isInfinity(rrhs) + + # If the slack variable is fixed, we can ignore this cut coefficient + if scip.isFeasZero(rrhs - rlhs): + continue + + # Unflip slack variable and adjust rhs if necessary: row at lower means the slack variable is at its upper bound. + # Since SCIP adds +1 slacks, this can only happen when constraints have a finite lhs + if row.getBasisStatus() == "lower": + assert not scip.isInfinity(-rlhs) + cutelem = -cutelem + + rowcols = row.getCols() + rowvals = row.getVals() + + assert len(rowcols) == len(rowvals) + + # Eliminate slack variable: rowcols is sorted: [columns in LP, columns not in LP] + for i in range(row.getNLPNonz()): + cutcoefs[rowcols[i].getLPPos()] -= cutelem * rowvals[i] + + act = scip.getRowLPActivity(row) + rhsslack = rrhs - act + if scip.isFeasZero(rhsslack): + assert row.getBasisStatus() == "upper" # cutelem != 0 and row active at upper bound -> slack at lower, row at upper + cutrhs -= cutelem * (rrhs - row.getConstant()) + else: + assert scip.isFeasZero(act - rlhs) + cutrhs -= cutelem * (rlhs - row.getConstant()) + + return cutcoefs, cutrhs + + def sepaexeclp(self): + result = SCIP_RESULT.DIDNOTRUN + scip = self.model + + if not scip.isLPSolBasic(): + return {"result": result} + + #TODO: add SCIPgetNLPBranchCands + # get var data ---> this is the same as getVars! + vars = scip.getVars(transformed = True) + + # get LP data + cols = scip.getLPColsData() + rows = scip.getLPRowsData() + + # exit if LP is trivial + if len(cols) == 0 or len(rows) == 0: + return {"result": result} + + result = SCIP_RESULT.DIDNOTFIND + + # get basis indices + basisind = scip.getLPBasisInd() + + # For all basic columns (not slacks) belonging to integer variables, try to generate a gomory cut + for i in range(len(rows)): + tryrow = False + c = basisind[i] + #primsol = SCIP_INVALID + + #print("Row %d/%d basic index: %d"%(i, len(rows),c)) + #print("Row %d with value %f"%(i, cols[c].getPrimsol() if c >=0 else scip.getRowActivity(rows[-c-1]))) + if c >= 0: + assert c < len(cols) + var = cols[c].getVar() + + if var.vtype() != "CONTINUOUS": + primsol = cols[c].getPrimsol() + assert scip.getSolVal(None, var) == primsol + + #print("var ", var," is not continuous. primsol = ", primsol) + if 0.005 <= scip.frac(primsol) <= 1 - 0.005: + #print("####trying gomory cut for col <%s> [%g] row %i"%(var, primsol, i)) + tryrow = True + + # generate the cut! + if tryrow: + # get the row of B^-1 for this basic integer variable with fractional solution value + binvrow = scip.getLPBInvRow(i) + + # get the tableau row for this basic integer variable with fractional solution value + binvarow = scip.getLPBInvARow(i) + + # get cut's coefficients + cutcoefs, cutrhs = self.getGMIFromRow(cols, rows, binvrow, binvarow, primsol) + + ######################################### + #### This code is for testing only!! #### + # the first two cuts are -x1 + 2y <= 0 and -x2 +2y <= 0 + # the next two (when both previous ones are added) are -2x1 + 6y <=0 and -2x2 + 6y <=0 + assert scip.isZero(cutcoefs[0]) or scip.isZero(cutcoefs[1]) + if self.ncuts < 2: + assert scip.isZero(cutcoefs[0] + 1) or scip.isZero(cutcoefs[1] + 1) + assert scip.isZero(cutcoefs[2] - 2) + else: + assert scip.isZero(cutcoefs[0] + 2) or scip.isZero(cutcoefs[1] + 2) + assert scip.isZero(cutcoefs[2] - 6) + ########################################## + + # add cut + # TODO: here it might make sense just to have a function `addCut` just like `addCons`. Or maybe better `createCut` + # so that then one can ask stuff about it, like its efficacy, etc. This function would receive all coefficients + # and basically do what we do here: cacheRowExtension etc up to releaseRow + cut = scip.createEmptyRowSepa(self, "gmi%d_x%d"%(self.ncuts,c if c >= 0 else -c-1), lhs = None, rhs = cutrhs) + scip.cacheRowExtensions(cut) + + for j in range(len(cutcoefs)): + if scip.isZero(cutcoefs[j]): # maybe here we need isFeasZero + continue + #print("var : ", cols[j].getVar(), " coef: ", cutcoefs[j]) + #print("cut.lhs : ", cut.getLhs(), " cut.rhs: ", cut.getRhs()) + scip.addVarToRow(cut, cols[j].getVar(), cutcoefs[j]) + + if cut.getNNonz() == 0: + assert scip.isFeasNegative(cutrhs) + #print("Gomory cut is infeasible: 0 <= ", cutrhs) + return {"result": SCIP_RESULT.CUTOFF} + + + # Only take efficacious cuts, except for cuts with one non-zero coefficient (= bound changes) + # the latter cuts will be handeled internally in sepastore. + if cut.getNNonz() == 1 or scip.isCutEfficacious(cut): + #print(" -> gomory cut for <%s>: rhs=%f, eff=%f"%(cols[c].getVar() if c >= 0 else rows[-c-1].getName(), + # cutrhs, scip.getCutEfficacy(cut))) + + #SCIPdebugMessage(" -> found gomory cut <%s>: act=%f, rhs=%f, norm=%f, eff=%f, min=%f, max=%f (range=%f)\n", + # cutname, SCIPgetRowLPActivity(scip, cut), SCIProwGetRhs(cut), SCIProwGetNorm(cut), + # SCIPgetCutEfficacy(scip, NULL, cut), + # SCIPgetRowMinCoef(scip, cut), SCIPgetRowMaxCoef(scip, cut), + # SCIPgetRowMaxCoef(scip, cut)/SCIPgetRowMinCoef(scip, cut)) + + # flush all changes before adding the cut + scip.flushRowExtensions(cut) + + infeasible = scip.addCut(cut, forcecut=True) + self.ncuts += 1 + + if infeasible: + result = SCIP_RESULT.CUTOFF + else: + result = SCIP_RESULT.SEPARATED + scip.releaseRow(cut) + + return {"result": result} + +def model(): + # create solver instance + s = Model() + + # include separator + sepa = GMI() + s.includeSepa(sepa, "python_gmi", "generates gomory mixed integer cuts", priority = 1000, freq = 1) + + # turn off presolve + s.setPresolve(SCIP_PARAMSETTING.OFF) + # turn off heuristics + s.setHeuristics(SCIP_PARAMSETTING.OFF) + # turn off propagation + s.setIntParam("propagating/maxrounds", 0) + s.setIntParam("propagating/maxroundsroot", 0) + # turn off some cuts + s.setIntParam("separating/strongcg/freq", -1) + s.setIntParam("separating/gomory/freq", -1) + s.setIntParam("separating/aggregation/freq", -1) + s.setIntParam("separating/mcf/freq", -1) + s.setIntParam("separating/closecuts/freq", -1) + s.setIntParam("separating/clique/freq", -1) + s.setIntParam("separating/zerohalf/freq", -1) + + # only two rounds of cuts + s.setIntParam("separating/maxroundsroot", 2) + + return s + +def test_CKS(): + s = model() + # add variables + x1 = s.addVar("x1", vtype='I') + x2 = s.addVar("x2", vtype='I') + y = s.addVar("y", obj=-1, vtype='C') + + # add constraint + s.addCons(-x1 + y <= 0) + s.addCons( - x2 + y <= 0) + s.addCons( x1 + x2 + y <= 2) + + # solve problem + s.optimize() + s.printStatistics() + +if __name__ == "__main__": + test_CKS() From 6a91655eab7ce1a6d80a1778b92c69b97fb97dda Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Fri, 4 May 2018 16:53:09 +0200 Subject: [PATCH 006/121] code review changes --- src/pyscipopt/scip.pyx | 4 +--- tests/test_gomory.py | 40 +++++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 40a00fd02..cc3e8f493 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1058,8 +1058,6 @@ cdef class Model: scip_sepa = SCIPfindSepa(self._scip, str_conversion(sepa.name)) PY_SCIP_CALL(SCIPcreateEmptyRowSepa(self._scip, &row, scip_sepa, str_conversion(name), lhs, rhs, local, modifiable, removable)) PyRow = Row.create(row) - # TODO: should I release the row???? No, I haven't add it, so it will actually free it - #PY_SCIP_CALL(SCIPreleaseRow(self._scip, &row)) return PyRow def getRowActivity(self, Row row): @@ -2088,7 +2086,7 @@ cdef class Model: presol.model = weakref.proxy(self) Py_INCREF(presol) - def includeSepa(self, Sepa sepa, name, desc, priority = 0, freq = 10, maxbounddist = 1.0, usessubscip=False, delay=False): + def includeSepa(self, Sepa sepa, name, desc, priority=0, freq=10, maxbounddist=1.0, usessubscip=False, delay=False): """Include a separator :param Sepa sepa: separator diff --git a/tests/test_gomory.py b/tests/test_gomory.py index 0017eaec4..b6f209b3c 100644 --- a/tests/test_gomory.py +++ b/tests/test_gomory.py @@ -9,23 +9,28 @@ def __init__(self): self.ncuts = 0 def getGMIFromRow(self, cols, rows, binvrow, binvarow, primsol): - # Given the row (binvarow, binvrow) of the tableau, computes gomory cut - # `primsol` is the rhs of the tableau row. - # `cols` are the variables - # `rows` are the slack variables - # The GMI is given by - # sum(f_j x_j , j in J_I s.t. f_j <= f_0) + - # sum((1-f_j)*f_0/(1 - f_0) x_j, j in J_I s.t. f_j > f_0) + - # sum(a_j x_j, , j in J_C s.t. a_j >= 0) - - # sum(a_j*f_0/(1-f_0) x_j , j in J_C s.t. a_j < 0) >= f_0. - # where J_I are the integer non-basic variables and J_C are the continuous. - # f_0 is the fractional part of primsol - # a_j is the j-th coefficient of the row and f_j its fractional part - # Note: we create -% <= -f_0 !! - # Note: this formula is valid for a problem of the form Ax = b, x>= 0. Since we do not have - # such problem structure in general, we have to (implicitely) transform whatever we are given - # to that form. Specifically, non-basic variables at their lower bound are shifted so that the lower - # bound is 0 and non-basic at their upper bound are complemented. + """ Given the row (binvarow, binvrow) of the tableau, computes gomory cut + + :param primsol: is the rhs of the tableau row. + :param cols: are the variables + :param rows: are the slack variables + :param binvrow: components of the tableau row associated to the basis inverse + :param binvarow: components of the tableau row associated to the basis inverse * A + + The GMI is given by + sum(f_j x_j , j in J_I s.t. f_j <= f_0) + + sum((1-f_j)*f_0/(1 - f_0) x_j, j in J_I s.t. f_j > f_0) + + sum(a_j x_j, , j in J_C s.t. a_j >= 0) - + sum(a_j*f_0/(1-f_0) x_j , j in J_C s.t. a_j < 0) >= f_0. + where J_I are the integer non-basic variables and J_C are the continuous. + f_0 is the fractional part of primsol + a_j is the j-th coefficient of the row and f_j its fractional part + Note: we create -% <= -f_0 !! + Note: this formula is valid for a problem of the form Ax = b, x>= 0. Since we do not have + such problem structure in general, we have to (implicitely) transform whatever we are given + to that form. Specifically, non-basic variables at their lower bound are shifted so that the lower + bound is 0 and non-basic at their upper bound are complemented. + """ # initialize cutcoefs = [0] * len(cols) @@ -319,6 +324,7 @@ def model(): return s +# we use Cook Kannan and Schrijver's example def test_CKS(): s = model() # add variables From 8958b81ba0bf59639dec229b0274f264687ec691 Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Fri, 4 May 2018 11:52:52 -0400 Subject: [PATCH 007/121] close #158 consistent number check in expr.pxi --- src/pyscipopt/expr.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 4a3f9e819..7f0e3731b 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -112,7 +112,7 @@ CONST = Term() # helper function def buildGenExprObj(expr): - if isinstance(expr, int) or isinstance(expr, float): + if _is_number(expr): return Constant(expr) elif isinstance(expr, Expr): # loop over terms and create a sumexpr with the sum of each term From 833cdb4b6fb2890039c110a1b60d16c44d4d61dd Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 7 May 2018 10:46:02 +0200 Subject: [PATCH 008/121] increase version to 1.4.6 --- src/pyscipopt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 361a9f22d..fff3cc02a 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.4.5' +__version__ = '1.4.6' # export user-relevant objects: from pyscipopt.Multidict import multidict From 474ed5c2a3c6701dc6dc313b69d4172bc0c5a9a6 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Wed, 16 May 2018 23:47:54 +0200 Subject: [PATCH 009/121] add quickmul method (in-place Expr multiplication) --- src/pyscipopt/__init__.py | 1 + src/pyscipopt/expr.pxi | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index fff3cc02a..ffcc4d914 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -13,6 +13,7 @@ from pyscipopt.scip import Sepa from pyscipopt.scip import LP from pyscipopt.scip import quicksum +from pyscipopt.scip import quickmul from pyscipopt.scip import exp from pyscipopt.scip import log from pyscipopt.scip import sqrt diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 7f0e3731b..f274c27d4 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -357,6 +357,15 @@ def quicksum(termlist): result += term return result +def quickmul(termlist): + '''multiply linear expressions and constants by avoiding intermediate + data structures and multiplying terms inplace + ''' + result = Expr() + 1 + for term in termlist: + result *= term + return result + class Op: const = 'const' From 42b9973cfbba8e4e26edbf8e379e02cba93ecbad Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 18 May 2018 15:53:29 +0200 Subject: [PATCH 010/121] correct typo in parameter list close #162 --- src/pyscipopt/__init__.py | 2 +- src/pyscipopt/scip.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index fff3cc02a..08ee41612 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.4.6' +__version__ = '1.4.7' # export user-relevant objects: from pyscipopt.Multidict import multidict diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index cc3e8f493..197506c2b 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2178,7 +2178,7 @@ cdef class Model: nam = str_conversion(name) des = str_conversion(desc) PY_SCIP_CALL(SCIPincludeBranchrule(self._scip, nam, des, - maxdepth, maxdepth, maxbounddist, + priority, maxdepth, maxbounddist, PyBranchruleCopy, PyBranchruleFree, PyBranchruleInit, PyBranchruleExit, PyBranchruleInitsol, PyBranchruleExitsol, PyBranchruleExeclp, PyBranchruleExecext, PyBranchruleExecps, branchrule)) From 6a1b40cd71df3f139254295e2eb792c2ad071d87 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 22 May 2018 17:07:39 +0200 Subject: [PATCH 011/121] add README as long description --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index e63552da8..91b4b02ba 100644 --- a/setup.py +++ b/setup.py @@ -53,10 +53,14 @@ extensions = cythonize(extensions) # extensions = cythonize(extensions, compiler_directives={'linetrace': True}) +with open('README.rst') as f: + long_description = f.read() + setup( name = 'PySCIPOpt', version = version, description = 'Python interface and modeling environment for SCIP', + long_description = long_description, url = 'https://github.com/SCIP-Interfaces/PySCIPOpt', author = 'Zuse Institute Berlin', author_email = 'scip@zib.de', From a89f93d8413c28fbeb547a7220872cdbce81a8bd Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 24 May 2018 15:10:02 +0100 Subject: [PATCH 012/121] updating Benders' interface --- src/pyscipopt/benders.pxi | 3 ++- src/pyscipopt/scip.pxd | 17 +++++++++++-- src/pyscipopt/scip.pyx | 53 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/benders.pxi b/src/pyscipopt/benders.pxi index 3bbcbc7d4..4507116f4 100644 --- a/src/pyscipopt/benders.pxi +++ b/src/pyscipopt/benders.pxi @@ -144,5 +144,6 @@ cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* variable = Variable() variable.var = var result_dict = PyBenders.bendersgetvar(variable, probnumber) - mappedvar[0] = result_dict.get("mappedvar", mappedvar[0]) + mappedvariable = result_dict.get("mappedvar") + mappedvar[0] = mappedvariable.var return SCIP_OKAY diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 67b5e0830..2a6125387 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -230,6 +230,12 @@ cdef extern from "scip/scip.h": SCIP_LPSOLQUALITY_ESTIMCONDITION = 0 SCIP_LPSOLQUALITY_EXACTCONDITION = 1 + ctypedef enum SCIP_BENDERSENFOTYPE: + SCIP_BENDERSENFOTYPE_LP = 1 + SCIP_BENDERSENFOTYPE_RELAX = 2 + SCIP_BENDERSENFOTYPE_PSEUDO = 3 + SCIP_BENDERSENFOTYPE_CHECK = 4 + ctypedef bint SCIP_Bool ctypedef long long SCIP_Longint @@ -855,7 +861,7 @@ cdef extern from "scip/scip.h": SCIP_Bool cutpseudo, SCIP_Bool cutrelax, SCIP_Bool shareaux, - SCIP_RETCODE (*benderscopy) (SCIP* scip, SCIP_BENDERS* benders, SCIP_Bool* valid), + SCIP_RETCODE (*benderscopy) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*bendersfree) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*bendersinit) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*bendersexit) (SCIP* scip, SCIP_BENDERS* benders), @@ -864,7 +870,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*bendersinitsol) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*bendersexitsol) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*bendersgetvar) (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber), - SCIP_RETCODE (*benderscreatesub) (SCIP* scip, SCIP_BENDERS* benders, iint probnumber), + SCIP_RETCODE (*benderscreatesub) (SCIP* scip, SCIP_BENDERS* benders, int probnumber), SCIP_RETCODE (*benderspresubsolve) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*benderssolvesub) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool* infeasible), SCIP_RETCODE (*benderspostsolve) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_Bool infeasible), @@ -873,6 +879,13 @@ cdef extern from "scip/scip.h": SCIP_BENDERS* SCIPfindBenders(SCIP* scip, const char* name) SCIP_RETCODE SCIPactivateBenders(SCIP* scip, SCIP_BENDERS* benders) SCIP_BENDERSDATA* SCIPbendersGetData(SCIP_BENDERS* benders) + SCIP_RETCODE SCIPcreateBendersDefault(SCIP* scip, SCIP** subproblems, int nsubproblems) + int SCIPbendersGetNSubproblems(SCIP_BENDERS* benders); + SCIP_RETCODE SCIPsolveBendersSubproblems(SCIP* scip, SCIP_BENDERS* benders, + SCIP_SOL* sol, SCIP_RESULT* result, SCIP_Bool* infeasible, + SCIP_Bool* auxviol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint) + int SCIPgetNActiveBenders(SCIP* scip) + SCIP_BENDERS** SCIPgetBenders(SCIP* scip) # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 9bc34ea80..ce2e601f4 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1957,6 +1957,59 @@ cdef class Model: """Presolve the problem.""" PY_SCIP_CALL(SCIPpresolve(self._scip)) + # Benders' decomposition methods + def initBendersDefault(self, subproblems): + """initialises the default Benders' decomposition with a dictionary of subproblems + + Keyword arguments: + subproblems -- a single Model instance or dictionary of Model instances + """ + cdef SCIP** subprobs + + # checking whether subproblems is a dictionary + if not isinstance(subproblems, dict): + isdict = False + nsubproblems = 1 + else: + isdict = True + nsubproblems = len(subproblems) + + # create array of SCIP instances for the subproblems + subprobs = malloc(nsubproblems * sizeof(SCIP*)) + + # if subproblems is a dictionary, then the dictionary is turned into a c array + if isdict: + for idx, subprob in enumerate(subproblems.values()): + subprobs[idx] = subprob._scip + else: + subprobs[0] = subproblems._scip + + # creating the default Benders' decomposition + PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems)) + + def computeBestSolSubproblems(self): + """Solves the subproblems with the best solution to the master problem. + Afterwards, the best solution from each subproblem can be queried to get + the solution to the original problem. + """ + cdef SCIP_BENDERS** _benders + cdef SCIP_RESULT _result + cdef SCIP_Bool _infeasible + cdef SCIP_Bool _auxviol + cdef int nbenders + + checkint = True + + nbenders = SCIPgetNActiveBenders(self._scip) + _benders = SCIPgetBenders(self._scip) + + # solving all subproblems from all Benders' decompositions + for i in range(nbenders): + PY_SCIP_CALL(SCIPsolveBendersSubproblems(self._scip, _benders[i], + self._bestsol, &_result, &_infeasible, &_auxviol, + SCIP_BENDERSENFOTYPE_CHECK, checkint)) + + def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): """Include an event handler. From 6ebe08c79014e8fc66704749319a763cbadb3a3b Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 24 May 2018 15:10:27 +0100 Subject: [PATCH 013/121] adding Benders example --- examples/finished/flp-benders.py | 116 +++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 examples/finished/flp-benders.py diff --git a/examples/finished/flp-benders.py b/examples/finished/flp-benders.py new file mode 100644 index 000000000..32ae0da9f --- /dev/null +++ b/examples/finished/flp-benders.py @@ -0,0 +1,116 @@ +""" +flp-benders.py: model for solving the capacitated facility location problem using Benders' decomposition + +minimize the total (weighted) travel cost from n customers +to some facilities with fixed costs and capacities. + +Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012 +""" +from pyscipopt import Model, quicksum, multidict + +def flp(I,J,d,M,f,c): + """flp -- model for the capacitated facility location problem + Parameters: + - I: set of customers + - J: set of facilities + - d[i]: demand for customer i + - M[j]: capacity of facility j + - f[j]: fixed cost for using a facility in point j + - c[i,j]: unit cost of servicing demand point i from facility j + Returns a model, ready to be solved. + """ + + master = Model("flp-master") + subprob = Model("flp-subprob") + + # creating the problem + y = {} + for j in J: + y[j] = master.addVar(vtype="B", name="y(%s)"%j) + + master.setObjective( + quicksum(f[j]*y[j] for j in J), + "minimize") + master.data = y + + # creating the subproblem + x,y = {},{} + for j in J: + y[j] = subprob.addVar(vtype="B", name="y(%s)"%j) + for i in I: + x[i,j] = subprob.addVar(vtype="C", name="x(%s,%s)"%(i,j)) + + for i in I: + subprob.addCons(quicksum(x[i,j] for j in J) == d[i], "Demand(%s)"%i) + + for j in M: + subprob.addCons(quicksum(x[i,j] for i in I) <= M[j]*y[j], "Capacity(%s)"%i) + + for (i,j) in x: + subprob.addCons(x[i,j] <= d[i]*y[j], "Strong(%s,%s)"%(i,j)) + + subprob.setObjective( + quicksum(c[i,j]*x[i,j] for i in I for j in J), + "minimize") + subprob.data = x,y + + return master, subprob + + +def make_data(): + I,d = multidict({1:80, 2:270, 3:250, 4:160, 5:180}) # demand + J,M,f = multidict({1:[500,1000], 2:[500,1000], 3:[500,1000]}) # capacity, fixed costs + c = {(1,1):4, (1,2):6, (1,3):9, # transportation costs + (2,1):5, (2,2):4, (2,3):7, + (3,1):6, (3,2):3, (3,3):4, + (4,1):8, (4,2):5, (4,3):3, + (5,1):10, (5,2):8, (5,3):4, + } + return I,J,d,M,f,c + + + +if __name__ == "__main__": + I,J,d,M,f,c = make_data() + master, subprob = flp(I,J,d,M,f,c) + # initializing the default Benders' decomposition with the subproblem + master.initBendersDefault(subprob) + + # optimizing the problem + master.optimize() + + # solving the subproblems to get the best solution + master.computeBestSolSubproblems() + + EPS = 1.e-6 + y = master.data + facilities = [j for j in y if master.getVal(y[j]) > EPS] + + x = subprob.data + edges = [(i,j) for (i,j) in x if subprob.getVal(x[i,j]) > EPS] + + print("Optimal value:", master.getObjVal() + subprob.getObjVal()) + print("Facilities at nodes:", facilities) + print("Edges:", edges) + + try: # plot the result using networkx and matplotlib + import networkx as NX + import matplotlib.pyplot as P + P.clf() + G = NX.Graph() + + other = [j for j in y if j not in facilities] + customers = ["c%s"%i for i in d] + G.add_nodes_from(facilities) + G.add_nodes_from(other) + G.add_nodes_from(customers) + for (i,j) in edges: + G.add_edge("c%s"%i,j) + + position = NX.drawing.layout.spring_layout(G) + NX.draw(G,position,node_color="y",nodelist=facilities) + NX.draw(G,position,node_color="g",nodelist=other) + NX.draw(G,position,node_color="b",nodelist=customers) + P.show() + except ImportError: + print("install 'networkx' and 'matplotlib' for plotting") From 732c8c840559a2e87bccb8b212eba5e97f22b721 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 24 May 2018 15:26:42 +0100 Subject: [PATCH 014/121] add SCIP_LOCKTYPE to includeCondhdlr --- src/pyscipopt/conshdlr.pxi | 8 ++++---- src/pyscipopt/scip.pxd | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/conshdlr.pxi b/src/pyscipopt/conshdlr.pxi index 36cb5b85e..6fa7df666 100644 --- a/src/pyscipopt/conshdlr.pxi +++ b/src/pyscipopt/conshdlr.pxi @@ -67,7 +67,7 @@ cdef class Conshdlr: def consresprop(self): return {} - def conslock(self, constraint, nlockspos, nlocksneg): + def conslock(self, constraint, locktype, nlockspos, nlocksneg): print("python error in conslock: this method needs to be implemented") return {} @@ -330,13 +330,13 @@ cdef SCIP_RETCODE PyConsResprop (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* PyConshdlr.consresprop() return SCIP_OKAY -cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int nlockspos, int nlocksneg): +cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg): PyConshdlr = getPyConshdlr(conshdlr) if cons == NULL: - PyConshdlr.conslock(None, nlockspos, nlocksneg) + PyConshdlr.conslock(None, locktype, nlockspos, nlocksneg) else: PyCons = getPyCons(cons) - PyConshdlr.conslock(PyCons, nlockspos, nlocksneg) + PyConshdlr.conslock(PyCons, locktype, nlockspos, nlocksneg) return SCIP_OKAY cdef SCIP_RETCODE PyConsActive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons): diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index fa9ae8e3d..ee81c7485 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -230,6 +230,11 @@ cdef extern from "scip/scip.h": SCIP_LPSOLQUALITY_ESTIMCONDITION = 0 SCIP_LPSOLQUALITY_EXACTCONDITION = 1 + ctypedef enum SCIP_LOCKTYPE: + SCIP_LOCKTYPE_MODEL = 0 + SCIP_LOCKTYPE_CONFLICT = 1 + + ctypedef bint SCIP_Bool ctypedef long long SCIP_Longint @@ -694,7 +699,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*consprop) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, int nmarkedconss, SCIP_PROPTIMING proptiming, SCIP_RESULT* result), SCIP_RETCODE (*conspresol) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nrounds, SCIP_PRESOLTIMING presoltiming, int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result), SCIP_RETCODE (*consresprop) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR* infervar, int inferinfo, SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result), - SCIP_RETCODE (*conslock) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int nlockspos, int nlocksneg), + SCIP_RETCODE (*conslock) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg), SCIP_RETCODE (*consactive) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons), SCIP_RETCODE (*consdeactive) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons), SCIP_RETCODE (*consenable) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons), From 798320909601b3a13ef59304e4bea8d437dd546d Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 24 May 2018 17:47:04 +0100 Subject: [PATCH 015/121] working Benders interface --- src/pyscipopt/benders.pxi | 50 ++++++++++++++++++++++++++++------- src/pyscipopt/scip.pxd | 12 ++++++--- src/pyscipopt/scip.pyx | 55 +++++++++++++++++++++++++++++---------- 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/pyscipopt/benders.pxi b/src/pyscipopt/benders.pxi index 4507116f4..854891b6d 100644 --- a/src/pyscipopt/benders.pxi +++ b/src/pyscipopt/benders.pxi @@ -26,13 +26,16 @@ cdef class Benders: print("python error in benderscreatesub: this method needs to be implemented") return {} - def benderspresubsolve(self): + def benderspresubsolve(self, solution, enfotype, checkint): + pass + + def benderssolvesubconvex(self, solution, probnumber, onlyconvex): pass def benderssolvesub(self, solution, probnumber): pass - def benderspostsolve(self, solution, infeasible): + def benderspostsolve(self, solution, enfotype, mergecandidates, npriomergecands, checkint, infeasible): pass def bendersfreesub(self, probnumber): @@ -103,30 +106,54 @@ cdef SCIP_RETCODE PyBendersCreatesub (SCIP* scip, SCIP_BENDERS* benders, int pro PyBenders.benderscreatesub(probnumber) return SCIP_OKAY -cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders): +cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint, SCIP_Bool* skipsolve, SCIP_RESULT* result): cdef SCIP_BENDERSDATA* bendersdata bendersdata = SCIPbendersGetData(benders) PyBenders = bendersdata - PyBenders.benderspresubsolve() + solution = Solution() + solution.sol = sol + enfotype = type + result_dict = PyBenders.benderspresubsolve(solution, enfotype, checkint) + skipsolve[0] = result_dict.get("skipsolve", False) + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool onlyconvex, SCIP_Real* objective, SCIP_RESULT* result): + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + solution = Solution() + solution.sol = sol + result_dict = PyBenders.benderssolvesub(solution, probnumber, onlyconvex) + objective[0] = result_dict.get("objective", 1e+20) + result[0] = result_dict.get("result", result[0]) return SCIP_OKAY -cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool* infeasible): +cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Real* objective, SCIP_RESULT* result): cdef SCIP_BENDERSDATA* bendersdata bendersdata = SCIPbendersGetData(benders) PyBenders = bendersdata solution = Solution() solution.sol = sol result_dict = PyBenders.benderssolvesub(solution, probnumber) - infeasible[0] = result_dict.get("infeasible", infeasible[0]) + objective[0] = result_dict.get("objective", 1e+20) + result[0] = result_dict.get("result", result[0]) return SCIP_OKAY -cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_Bool infeasible): +cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, + SCIP_BENDERSENFOTYPE type, int* mergecands, int npriomergecands, int nmergecands, SCIP_Bool checkint, + SCIP_Bool infeasible, SCIP_Bool* merged): cdef SCIP_BENDERSDATA* bendersdata bendersdata = SCIPbendersGetData(benders) PyBenders = bendersdata solution = Solution() solution.sol = sol - PyBenders.benderspostsolve(solution, infeasible) + enfotype = type + mergecandidates = [] + for i in range(nmergecands): + mergecandidates.append(mergecands[i]) + result_dict = PyBenders.benderspostsolve(solution, enfotype, mergecandidates, npriomergecands, checkint, infeasible) + merged[0] = result_dict.get("merged", False) return SCIP_OKAY cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber): @@ -144,6 +171,9 @@ cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* variable = Variable() variable.var = var result_dict = PyBenders.bendersgetvar(variable, probnumber) - mappedvariable = result_dict.get("mappedvar") - mappedvar[0] = mappedvariable.var + mappedvariable = result_dict.get("mappedvar", None) + if mappedvariable is None: + mappedvar[0] = NULL + else: + mappedvar[0] = mappedvariable.var return SCIP_OKAY diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 9870f4c62..e05113ef3 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -875,9 +875,10 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*bendersexitsol) (SCIP* scip, SCIP_BENDERS* benders), SCIP_RETCODE (*bendersgetvar) (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber), SCIP_RETCODE (*benderscreatesub) (SCIP* scip, SCIP_BENDERS* benders, int probnumber), - SCIP_RETCODE (*benderspresubsolve) (SCIP* scip, SCIP_BENDERS* benders), - SCIP_RETCODE (*benderssolvesub) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool* infeasible), - SCIP_RETCODE (*benderspostsolve) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_Bool infeasible), + SCIP_RETCODE (*benderspresubsolve) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint, SCIP_Bool* skipsolve, SCIP_RESULT* result), + SCIP_RETCODE (*benderssolvesubconvex) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool onlyconvex, SCIP_Real* objective, SCIP_RESULT* result), + SCIP_RETCODE (*benderssolvesub) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Real* objective, SCIP_RESULT* result), + SCIP_RETCODE (*benderspostsolve) (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, int* mergecands, int npriomergecands, int nmergecands, SCIP_Bool checkint, SCIP_Bool infeasible, SCIP_Bool* merged), SCIP_RETCODE (*bendersfreesub) (SCIP* scip, SCIP_BENDERS* benders, int probnumber), SCIP_BENDERSDATA* bendersdata) SCIP_BENDERS* SCIPfindBenders(SCIP* scip, const char* name) @@ -888,6 +889,11 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPsolveBendersSubproblems(SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_RESULT* result, SCIP_Bool* infeasible, SCIP_Bool* auxviol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint) + SCIP_RETCODE SCIPsetupBendersSubproblem(SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber) + SCIP_RETCODE SCIPsolveBendersSubproblem(SCIP* scip, SCIP_BENDERS* benders, + SCIP_SOL* sol, int probnumber, SCIP_Bool* infeasible, SCIP_BENDERSENFOTYPE type, + SCIP_Bool solvecip, SCIP_Real* objective) + SCIP_RETCODE SCIPfreeBendersSubproblem(SCIP* scip, SCIP_BENDERS* benders, int probnumber) int SCIPgetNActiveBenders(SCIP* scip) SCIP_BENDERS** SCIPgetBenders(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index ce2e601f4..6f01e307b 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1967,12 +1967,12 @@ cdef class Model: cdef SCIP** subprobs # checking whether subproblems is a dictionary - if not isinstance(subproblems, dict): - isdict = False - nsubproblems = 1 - else: + if isinstance(subproblems, dict): isdict = True nsubproblems = len(subproblems) + else: + isdict = False + nsubproblems = 1 # create array of SCIP instances for the subproblems subprobs = malloc(nsubproblems * sizeof(SCIP*)) @@ -1982,33 +1982,61 @@ cdef class Model: for idx, subprob in enumerate(subproblems.values()): subprobs[idx] = subprob._scip else: - subprobs[0] = subproblems._scip + subprobs[0] = (subproblems)._scip # creating the default Benders' decomposition PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems)) + # activating the Benders' decomposition constraint handlers + self.setBoolParam("constraints/benderslp/active", True) + self.setBoolParam("constraints/benders/active", True) + #self.setIntParam("limits/maxorigsol", 0) + def computeBestSolSubproblems(self): """Solves the subproblems with the best solution to the master problem. Afterwards, the best solution from each subproblem can be queried to get the solution to the original problem. + + If the user wants to resolve the subproblems, they must free them by + calling freeBendersSubproblems() """ cdef SCIP_BENDERS** _benders - cdef SCIP_RESULT _result cdef SCIP_Bool _infeasible - cdef SCIP_Bool _auxviol cdef int nbenders + cdef int nsubproblems - checkint = True + solvecip = True nbenders = SCIPgetNActiveBenders(self._scip) _benders = SCIPgetBenders(self._scip) # solving all subproblems from all Benders' decompositions for i in range(nbenders): - PY_SCIP_CALL(SCIPsolveBendersSubproblems(self._scip, _benders[i], - self._bestsol, &_result, &_infeasible, &_auxviol, - SCIP_BENDERSENFOTYPE_CHECK, checkint)) + nsubproblems = SCIPbendersGetNSubproblems(_benders[i]) + for j in range(nsubproblems): + PY_SCIP_CALL(SCIPsetupBendersSubproblem(self._scip, + _benders[i], self._bestSol.sol, j)) + PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip, + _benders[i], self._bestSol.sol, j, &_infeasible, + SCIP_BENDERSENFOTYPE_CHECK, solvecip, NULL)) + + def freeBendersSubproblems(self): + """Calls the free subproblem function for the Benders' decomposition. + This will free all subproblems for all decompositions. + """ + cdef SCIP_BENDERS** _benders + cdef int nbenders + cdef int nsubproblems + + nbenders = SCIPgetNActiveBenders(self._scip) + _benders = SCIPgetBenders(self._scip) + # solving all subproblems from all Benders' decompositions + for i in range(nbenders): + nsubproblems = SCIPbendersGetNSubproblems(_benders[i]) + for j in range(nsubproblems): + PY_SCIP_CALL(SCIPfreeBendersSubproblem(self._scip, _benders[i], + j)) def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): """Include an event handler. @@ -2259,12 +2287,11 @@ cdef class Model: priority, cutlp, cutrelax, cutpseudo, shareaux, PyBendersCopy, PyBendersFree, PyBendersInit, PyBendersExit, PyBendersInitpre, PyBendersExitpre, PyBendersInitsol, PyBendersExitsol, PyBendersGetvar, - PyBendersCreatesub, PyBendersPresubsolve, PyBendersSolvesub, - PyBendersPostsolve, PyBendersFreesub, + PyBendersCreatesub, PyBendersPresubsolve, PyBendersSolvesubconvex, + PyBendersSolvesub, PyBendersPostsolve, PyBendersFreesub, benders)) cdef SCIP_BENDERS* scip_benders scip_benders = SCIPfindBenders(self._scip, n) - PY_SCIP_CALL(SCIPactivateBenders(self._scip, scip_benders)) benders.model = weakref.proxy(self) Py_INCREF(benders) From f170e4905d46cc26c5c1cd5134bc5a56b8b628c9 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 24 May 2018 17:47:16 +0100 Subject: [PATCH 016/121] working Benders' example --- examples/finished/flp-benders.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/examples/finished/flp-benders.py b/examples/finished/flp-benders.py index 32ae0da9f..35fd3b304 100644 --- a/examples/finished/flp-benders.py +++ b/examples/finished/flp-benders.py @@ -6,7 +6,8 @@ Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012 """ -from pyscipopt import Model, quicksum, multidict +from pyscipopt import Model, quicksum, multidict, SCIP_PARAMSETTING +import pdb def flp(I,J,d,M,f,c): """flp -- model for the capacitated facility location problem @@ -74,9 +75,12 @@ def make_data(): I,J,d,M,f,c = make_data() master, subprob = flp(I,J,d,M,f,c) # initializing the default Benders' decomposition with the subproblem + master.setPresolve(SCIP_PARAMSETTING.OFF) + master.setBoolParam("misc/allowdualreds", False) + master.setBoolParam("benders/copybenders", False) master.initBendersDefault(subprob) - # optimizing the problem + # optimizing the problem using Benders' decomposition master.optimize() # solving the subproblems to get the best solution @@ -86,13 +90,20 @@ def make_data(): y = master.data facilities = [j for j in y if master.getVal(y[j]) > EPS] - x = subprob.data + x, suby = subprob.data edges = [(i,j) for (i,j) in x if subprob.getVal(x[i,j]) > EPS] - print("Optimal value:", master.getObjVal() + subprob.getObjVal()) + print("Optimal value:", master.getObjVal()) print("Facilities at nodes:", facilities) print("Edges:", edges) + master.printStatistics() + + # since computeBestSolSubproblems() was called above, we need to free the + # subproblems. This must happen after the solution is extracted, otherwise + # the solution will be lost + master.freeBendersSubproblems() + try: # plot the result using networkx and matplotlib import networkx as NX import matplotlib.pyplot as P From 25965583f60147869f0c0a385e9e34647d6c0d98 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Mon, 28 May 2018 16:10:30 +0200 Subject: [PATCH 017/121] AND and OR constraints --- src/pyscipopt/scip.pxd | 36 +++++++++++++++++++ src/pyscipopt/scip.pyx | 78 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index fa9ae8e3d..e20bc0a55 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1051,6 +1051,42 @@ cdef extern from "scip/cons_sos2.h": SCIP_CONS* cons, SCIP_VAR* var) +cdef extern from "scip/cons_and.h": + SCIP_RETCODE SCIPcreateConsAnd(SCIP* scip, + SCIP_CONS** cons, + const char* name, + SCIP_VAR* resvar, + int nvars, + SCIP_VAR** vars, + SCIP_Bool initial, + SCIP_Bool separate, + SCIP_Bool enforce, + SCIP_Bool check, + SCIP_Bool propagate, + SCIP_Bool local, + SCIP_Bool modifiable, + SCIP_Bool dynamic, + SCIP_Bool removable, + SCIP_Bool stickingatnode) + +cdef extern from "scip/cons_or.h": + SCIP_RETCODE SCIPcreateConsOr(SCIP* scip, + SCIP_CONS** cons, + const char* name, + SCIP_VAR* resvar, + int nvars, + SCIP_VAR** vars, + SCIP_Bool initial, + SCIP_Bool separate, + SCIP_Bool enforce, + SCIP_Bool check, + SCIP_Bool propagate, + SCIP_Bool local, + SCIP_Bool modifiable, + SCIP_Bool dynamic, + SCIP_Bool removable, + SCIP_Bool stickingatnode) + cdef extern from "blockmemshell/memory.h": void BMScheckEmptyMemory() long long BMSgetMemoryUsed() diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index cc3e8f493..0d0ef6e1f 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1442,6 +1442,84 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) return Constraint.create(scip_cons) + def addConsAnd(self, vars, resvar=None, name="ANDcons", + initial=True, separate=True, enforce=True, check=True, + propagate=True, local=False, modifiable=False, dynamic=False, + removable=False, stickingatnode=False): + """Add an AND-constraint. + :param vars: list of variables to be included + :param name: name of the constraint (Default value = "ANDcons") + :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) + :param separate: should the constraint be separated during LP processing? (Default value = True) + :param enforce: should the constraint be enforced during node processing? (Default value = True) + :param check: should the constraint be checked for feasibility? (Default value = True) + :param propagate: should the constraint be propagated during node processing? (Default value = True) + :param local: is the constraint only valid locally? (Default value = False) + :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) + :param dynamic: is the constraint subject to aging? (Default value = False) + :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) + :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) + """ + cdef SCIP_CONS* scip_cons + + nvars = len(vars) + + _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) + for idx, var in enumerate(vars): + _vars[idx] = (var).var + _resVar = (resvar).var if resvar is not None else NULL + + PY_SCIP_CALL(SCIPcreateConsAnd(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, + initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) + + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) + pyCons = Constraint.create(scip_cons) + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + + free(_vars) + free(_resVar) + + return pyCons + + def addConsOr(self, vars, resvar=None, name="ORcons", + initial=True, separate=True, enforce=True, check=True, + propagate=True, local=False, modifiable=False, dynamic=False, + removable=False, stickingatnode=False): + """Add an OR-constraint. + :param vars: list of variables to be included + :param name: name of the constraint (Default value = "ORcons") + :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) + :param separate: should the constraint be separated during LP processing? (Default value = True) + :param enforce: should the constraint be enforced during node processing? (Default value = True) + :param check: should the constraint be checked for feasibility? (Default value = True) + :param propagate: should the constraint be propagated during node processing? (Default value = True) + :param local: is the constraint only valid locally? (Default value = False) + :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) + :param dynamic: is the constraint subject to aging? (Default value = False) + :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) + :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) + """ + cdef SCIP_CONS* scip_cons + + nvars = len(vars) + + _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) + for idx, var in enumerate(vars): + _vars[idx] = (var).var + _resVar = (resvar).var if resvar is not None else NULL + + PY_SCIP_CALL(SCIPcreateConsOr(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, + initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) + + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) + pyCons = Constraint.create(scip_cons) + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + + free(_vars) + free(_resVar) + + return pyCons + def addConsCardinality(self, consvars, cardval, indvars=None, weights=None, name="CardinalityCons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, dynamic=False, From 1769f72e511ea84d7bfa61262fe9694698f74d41 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Mon, 28 May 2018 19:45:24 +0200 Subject: [PATCH 018/121] test quickmul method --- tests/test_quickmul.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/test_quickmul.py diff --git a/tests/test_quickmul.py b/tests/test_quickmul.py new file mode 100644 index 000000000..f39324bc1 --- /dev/null +++ b/tests/test_quickmul.py @@ -0,0 +1,41 @@ +from pyscipopt import Model, quickmul +from pyscipopt.scip import CONST +from operator import mul + +def test_quickmul_model(): + m = Model("quickmul") + x = m.addVar("x") + y = m.addVar("y") + z = m.addVar("z") + c = 2.3 + + q = quickmul([x,y,z,c]) == 0.0 + s = reduce(mul,[x,y,z,c],1) == 0.0 + + assert(q.expr.terms == s.expr.terms) + +def test_quickmul(): + empty = quickmul(1 for i in []) + assert len(empty.terms) == 1 + assert CONST in empty.terms + +def test_largequadratic(): + # inspired from performance issue on + # http://stackoverflow.com/questions/38434300 + + m = Model("dense_quadratic") + dim = 20 + x = [m.addVar("x_%d" % i) for i in range(dim)] + expr = quickmul((i+j+1)*x[i]*x[j] + for i in range(dim) + for j in range(dim)) + cons = expr <= 1.0 + # upper triangle, diagonal + assert cons.expr.degree() == 2*dim*dim + m.addCons(cons) + # TODO: what can we test beyond the lack of crashes? + +if __name__ == "__main__": + test_quickmul() + test_quickmul_model() + test_largequadratic() From 8bfbe01729f63cf80ce16ac010a7d8da6f3bf78a Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 12:49:38 +0200 Subject: [PATCH 019/121] AND and OR constraints: bugfix, tests, cosmetics --- src/pyscipopt/scip.pyx | 16 +++---- tests/test_connectives.py | 95 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 tests/test_connectives.py diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 038044103..05c6f4cf0 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1442,12 +1442,13 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) return Constraint.create(scip_cons) - def addConsAnd(self, vars, resvar=None, name="ANDcons", + def addConsAnd(self, vars, resvar, name="ANDcons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, modifiable=False, dynamic=False, removable=False, stickingatnode=False): """Add an AND-constraint. - :param vars: list of variables to be included + :param vars: list of BINARY variables to be included (operators) + :param resvar: BINARY variable (resultant) :param name: name of the constraint (Default value = "ANDcons") :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) :param separate: should the constraint be separated during LP processing? (Default value = True) @@ -1467,7 +1468,7 @@ cdef class Model: _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) for idx, var in enumerate(vars): _vars[idx] = (var).var - _resVar = (resvar).var if resvar is not None else NULL + _resVar = (resvar).var PY_SCIP_CALL(SCIPcreateConsAnd(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) @@ -1477,16 +1478,16 @@ cdef class Model: PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) free(_vars) - free(_resVar) return pyCons - def addConsOr(self, vars, resvar=None, name="ORcons", + def addConsOr(self, vars, resvar, name="ORcons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, modifiable=False, dynamic=False, removable=False, stickingatnode=False): """Add an OR-constraint. - :param vars: list of variables to be included + :param vars: list of BINARY variables to be included (operators) + :param resvar: BINARY variable (resultant) :param name: name of the constraint (Default value = "ORcons") :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) :param separate: should the constraint be separated during LP processing? (Default value = True) @@ -1506,7 +1507,7 @@ cdef class Model: _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) for idx, var in enumerate(vars): _vars[idx] = (var).var - _resVar = (resvar).var if resvar is not None else NULL + resVar = (resvar).var PY_SCIP_CALL(SCIPcreateConsOr(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) @@ -1516,7 +1517,6 @@ cdef class Model: PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) free(_vars) - free(_resVar) return pyCons diff --git a/tests/test_connectives.py b/tests/test_connectives.py new file mode 100644 index 000000000..319542b82 --- /dev/null +++ b/tests/test_connectives.py @@ -0,0 +1,95 @@ +from pyscipopt import Model + +################################################################################ +# +# Testing AND/OR constraints +# check whether any error is raised +# see http://scip.zib.de/doc-5.0.1/html/cons__and_8c.php +# for resultant and operators definition +# CAVEAT: ONLY binary variables are allowed +# Integer and continous variables behave unexpectedly (due to SCIP?) +# TBI: automatic assertion of expected resultant VS optimal resultant +# (visual inspection at the moment) +# TBI: implement and test XOR constraint +# +################################################################################ + +### AUXILIARY ### +def setModel(vtype="B", name=None, imax=2): + if name is None: name = "model" + m = Model(name) + m.hideOutput() + i = 0 + m.addVar("r", vtype) + while i < imax: + m.addVar("v%s" % i, vtype) + i+=1 + return m + +def getVarByName(m, name): + try: + return [v for v in m.getVars() if name == v.name][0] + except IndexError: + return None + +def getAllVarsByName(m, name): + try: + return [v for v in m.getVars() if name in v.name] + except IndexError: + return [] + + +def setConss(m, vtype="B", val=0, imax=1): + i = 0 + while i < imax: + vi = getVarByName(m,"v%s" % i) + m.addCons(vi == val, vtype) + i+=1 + return + +def printOutput(m): + status = m.getStatus() + r = getVarByName(m,"r") + rstr = "%d" % round(m.getVal(r)) + vs = getAllVarsByName(m, "v") + vsstr = "".join(["%d" % round(m.getVal(v)) for v in vs]) + print "Status: %s, resultant: %s, operators: %s" % (status, rstr, vsstr) + +### TEST ### +def test_connective(m, connective, sense="min"): + try: + r = getVarByName(m,"r") + vs = getAllVarsByName(m, "v") + ### addConsAnd/Or method (Xor: TBI) ### + _m_method = getattr(m, "addCons%s" % connective.capitalize()) + _m_method(vs,r) + m.setObjective(r, sense="%simize" % sense) + m.optimize() + printOutput(m) + return True + except Exception as e: + print "%s: %s" % (e.__class__.__name__, e) + return False + +### MAIN ### +if __name__ == "__main__": + from itertools import product + lvtype = ["B"]#,"I","C"] #I and C may raise errors: see preamble + lconnective = ["and", "or"] + lsense = ["min","max"] + lnoperators = [2,20,200] + lvconss = [0, 1] + lnconss = [1, 2, None] + cases = list(product(lnoperators, lvtype, lconnective, lsense, lvconss, lnconss)) + for c in cases: + noperators, vtype, connective, sense, vconss, nconss = c + if nconss == None: nconss = noperators + c = (noperators, vtype, connective, sense, vconss, nconss) + m = setModel(vtype, connective, noperators) + setConss(m,vtype, vconss, nconss) + teststr = ', '.join(list(str(ic) for ic in c)) + #print "Test: %s" % teststr + print "Test: %3d operators of vtype %s; %s-constraint and sense %s; %d as constraint for %3d operator/s" % c + success = test_connective(m, connective, sense) + print "Is test successful? %s" % success + print From f1ee10bf0ac5b5805eac55a97ad3346aeaa96201 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 13:14:26 +0200 Subject: [PATCH 020/121] bugfix --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 05c6f4cf0..0858ed447 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1507,7 +1507,7 @@ cdef class Model: _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) for idx, var in enumerate(vars): _vars[idx] = (var).var - resVar = (resvar).var + _resVar = (resvar).var PY_SCIP_CALL(SCIPcreateConsOr(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) From 34ae608af8814fb937662cd0568acf27f99a45fb Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 13:21:31 +0200 Subject: [PATCH 021/121] python3 compliant --- tests/test_connectives.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_connectives.py b/tests/test_connectives.py index 319542b82..5ca0b3e49 100644 --- a/tests/test_connectives.py +++ b/tests/test_connectives.py @@ -53,7 +53,7 @@ def printOutput(m): rstr = "%d" % round(m.getVal(r)) vs = getAllVarsByName(m, "v") vsstr = "".join(["%d" % round(m.getVal(v)) for v in vs]) - print "Status: %s, resultant: %s, operators: %s" % (status, rstr, vsstr) + print("Status: %s, resultant: %s, operators: %s" % (status, rstr, vsstr)) ### TEST ### def test_connective(m, connective, sense="min"): @@ -68,7 +68,7 @@ def test_connective(m, connective, sense="min"): printOutput(m) return True except Exception as e: - print "%s: %s" % (e.__class__.__name__, e) + print("%s: %s" % (e.__class__.__name__, e)) return False ### MAIN ### @@ -88,8 +88,7 @@ def test_connective(m, connective, sense="min"): m = setModel(vtype, connective, noperators) setConss(m,vtype, vconss, nconss) teststr = ', '.join(list(str(ic) for ic in c)) - #print "Test: %s" % teststr - print "Test: %3d operators of vtype %s; %s-constraint and sense %s; %d as constraint for %3d operator/s" % c + print("Test: %3d operators of vtype %s; %s-constraint and sense %s; %d as constraint for %3d operator/s" % c) success = test_connective(m, connective, sense) - print "Is test successful? %s" % success - print + print("Is test successful? %s" % success) + print("\n") From 230817dc7b98328d7be4643ec33365679950eec2 Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Tue, 29 May 2018 14:50:56 +0200 Subject: [PATCH 022/121] fix memory leak --- src/pyscipopt/scip.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 197506c2b..4fa7eb8d8 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1350,6 +1350,7 @@ cdef class Model: PY_SCIP_CALL( SCIPexprtreeFree(&exprtree) ) # free more memory + free(scipexprs) free(vars) return PyCons From 79115e057fcb62e5c865f8e03810a5b0c698813f Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 29 May 2018 15:15:16 +0200 Subject: [PATCH 023/121] increase version to 1.4.8 --- src/pyscipopt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 39854d620..96f0a1a08 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.4.7' +__version__ = '1.4.8' # export user-relevant objects: from pyscipopt.Multidict import multidict From 85c7bce80ef3693e5c1de86c390e015b248046a9 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 15:15:24 +0200 Subject: [PATCH 024/121] quickmul -> quickprod --- src/pyscipopt/__init__.py | 2 +- src/pyscipopt/expr.pxi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 664c7ea1c..f121341f4 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -14,7 +14,7 @@ from pyscipopt.scip import LP from pyscipopt.scip import Expr from pyscipopt.scip import quicksum -from pyscipopt.scip import quickmul +from pyscipopt.scip import quickprod from pyscipopt.scip import exp from pyscipopt.scip import log from pyscipopt.scip import sqrt diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index f274c27d4..9014f1c4e 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -357,7 +357,7 @@ def quicksum(termlist): result += term return result -def quickmul(termlist): +def quickprod(termlist): '''multiply linear expressions and constants by avoiding intermediate data structures and multiplying terms inplace ''' From 1ff4402a986dc89ea8e6024d7eba59bfbc95ab0d Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 17:01:16 +0200 Subject: [PATCH 025/121] AND and OR constraints: tests; .gitignore: .pytest --- .gitignore | 3 + .pytest_cache/v/cache/lastfailed | 190 ++++++++++++++++++ .../{test_connectives.py => test_logical.py} | 72 ++++--- tests/{test_quickmul.py => test_quickprod.py} | 18 +- 4 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 .pytest_cache/v/cache/lastfailed rename tests/{test_connectives.py => test_logical.py} (52%) rename tests/{test_quickmul.py => test_quickprod.py} (75%) diff --git a/.gitignore b/.gitignore index 0158f1749..e5fe46130 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,6 @@ venv.bak/ # mypy .mypy_cache/ + +# pytest +.pytest_chache/ diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed new file mode 100644 index 000000000..654a7f71b --- /dev/null +++ b/.pytest_cache/v/cache/lastfailed @@ -0,0 +1,190 @@ +{ + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-max-or-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-min-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-min-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-min-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-min-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-min-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-0-min-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-max-or-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[1-1-min-or-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-max-or-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-min-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-min-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-min-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-min-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-min-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-0-min-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-max-or-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[2-1-min-or-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-and-max-0-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-and-max-1-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-and-min-0-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-and-min-1-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-or-max-0-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-or-max-1-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-or-min-0-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-1-or-min-1-2]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-and-max-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-and-max-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-and-min-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-and-min-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-or-max-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-or-max-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-or-min-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-2-or-min-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-and-max-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-and-max-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-and-min-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-and-min-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-or-max-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-or-max-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-or-min-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-20-or-min-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-and-max-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-and-max-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-and-min-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-and-min-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-or-max-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-or-max-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-or-min-0-None]": true, + "tests/_NEW_test_logical.py::test_logical[B-200-or-min-1-None]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-max-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-min-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-min-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-min-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-min-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-min-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-0-min-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-max-or-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-200-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-C-200]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-C-20]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-C-2]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-I-200]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-I-20]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-and-I-2]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-or-2-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-or-20-B]": true, + "tests/_NEW_test_logical.py::test_logical[None-1-min-or-200-B]": true +} \ No newline at end of file diff --git a/tests/test_connectives.py b/tests/test_logical.py similarity index 52% rename from tests/test_connectives.py rename to tests/test_logical.py index 5ca0b3e49..1b8ac070f 100644 --- a/tests/test_connectives.py +++ b/tests/test_logical.py @@ -1,5 +1,11 @@ from pyscipopt import Model +import pytest + +itertools = pytest.importorskip("itertools") +product = itertools.product + + ################################################################################ # # Testing AND/OR constraints @@ -14,6 +20,8 @@ # ################################################################################ +verbose = True + ### AUXILIARY ### def setModel(vtype="B", name=None, imax=2): if name is None: name = "model" @@ -55,40 +63,42 @@ def printOutput(m): vsstr = "".join(["%d" % round(m.getVal(v)) for v in vs]) print("Status: %s, resultant: %s, operators: %s" % (status, rstr, vsstr)) -### TEST ### -def test_connective(m, connective, sense="min"): +### MAIN ### +def main_logical(model, logical, sense="min"): try: - r = getVarByName(m,"r") - vs = getAllVarsByName(m, "v") + r = getVarByName(model, "r") + vs = getAllVarsByName(model, "v") ### addConsAnd/Or method (Xor: TBI) ### - _m_method = getattr(m, "addCons%s" % connective.capitalize()) - _m_method(vs,r) - m.setObjective(r, sense="%simize" % sense) - m.optimize() - printOutput(m) + method_name = "addCons%s" % logical.capitalize() + try: + _model_addConsLogical = getattr(model, method_name) + except AttributeError as e: + if method_name == "addConsXor": + pytest.xfail("addCons%s has to be implemented" % method_name) + else: + raise AttributeError("addCons%s not implemented" % method_name) + _model_addConsLogical(vs,r) + model.setObjective(r, sense="%simize" % sense) + model.optimize() + assert model.getStatus() == "optimal" + if verbose: printOutput(model) return True except Exception as e: - print("%s: %s" % (e.__class__.__name__, e)) + if verbose: print("%s: %s" % (e.__class__.__name__, e)) return False -### MAIN ### -if __name__ == "__main__": - from itertools import product - lvtype = ["B"]#,"I","C"] #I and C may raise errors: see preamble - lconnective = ["and", "or"] - lsense = ["min","max"] - lnoperators = [2,20,200] - lvconss = [0, 1] - lnconss = [1, 2, None] - cases = list(product(lnoperators, lvtype, lconnective, lsense, lvconss, lnconss)) - for c in cases: - noperators, vtype, connective, sense, vconss, nconss = c - if nconss == None: nconss = noperators - c = (noperators, vtype, connective, sense, vconss, nconss) - m = setModel(vtype, connective, noperators) - setConss(m,vtype, vconss, nconss) - teststr = ', '.join(list(str(ic) for ic in c)) - print("Test: %3d operators of vtype %s; %s-constraint and sense %s; %d as constraint for %3d operator/s" % c) - success = test_connective(m, connective, sense) - print("Is test successful? %s" % success) - print("\n") +### TEST ### +@pytest.mark.parametrize("nconss", [1, 2, "all"]) +@pytest.mark.parametrize("vconss", [0, 1]) +@pytest.mark.parametrize("sense", ["min","max"]) +@pytest.mark.parametrize("logical", ["and", "or", "xor"]) #xor TBI +@pytest.mark.parametrize("noperators", [2,20,200]) +@pytest.mark.parametrize("vtype", ["B","I","C"]) #I and C may raise errors: see preamble +def test_logical(noperators, vtype, logical, sense, vconss, nconss): + if nconss == "all": nconss = noperators + if vtype in ["I","C"]: + pytest.skip("unsupported vtype: %s" % vtype) + m = setModel(vtype, logical, noperators) + setConss(m,vtype, vconss, nconss) + success = main_logical(m, logical, sense) + assert(success), "Status is not optimal!" diff --git a/tests/test_quickmul.py b/tests/test_quickprod.py similarity index 75% rename from tests/test_quickmul.py rename to tests/test_quickprod.py index f39324bc1..256587913 100644 --- a/tests/test_quickmul.py +++ b/tests/test_quickprod.py @@ -1,21 +1,21 @@ -from pyscipopt import Model, quickmul +from pyscipopt import Model, quickprod from pyscipopt.scip import CONST from operator import mul -def test_quickmul_model(): - m = Model("quickmul") +def test_quickprod_model(): + m = Model("quickprod") x = m.addVar("x") y = m.addVar("y") z = m.addVar("z") c = 2.3 - q = quickmul([x,y,z,c]) == 0.0 + q = quickprod([x,y,z,c]) == 0.0 s = reduce(mul,[x,y,z,c],1) == 0.0 assert(q.expr.terms == s.expr.terms) -def test_quickmul(): - empty = quickmul(1 for i in []) +def test_quickprod(): + empty = quickprod(1 for i in []) assert len(empty.terms) == 1 assert CONST in empty.terms @@ -26,7 +26,7 @@ def test_largequadratic(): m = Model("dense_quadratic") dim = 20 x = [m.addVar("x_%d" % i) for i in range(dim)] - expr = quickmul((i+j+1)*x[i]*x[j] + expr = quickprod((i+j+1)*x[i]*x[j] for i in range(dim) for j in range(dim)) cons = expr <= 1.0 @@ -36,6 +36,6 @@ def test_largequadratic(): # TODO: what can we test beyond the lack of crashes? if __name__ == "__main__": - test_quickmul() - test_quickmul_model() + test_quickprod() + test_quickprod_model() test_largequadratic() From da5bad401419df6e8a918bfad5b305c70be42c1f Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 17:02:15 +0200 Subject: [PATCH 026/121] .pytest_cache deleted --- .pytest_cache/v/cache/lastfailed | 190 ------------------------------- 1 file changed, 190 deletions(-) delete mode 100644 .pytest_cache/v/cache/lastfailed diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed deleted file mode 100644 index 654a7f71b..000000000 --- a/.pytest_cache/v/cache/lastfailed +++ /dev/null @@ -1,190 +0,0 @@ -{ - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-max-or-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-min-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-min-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-min-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-min-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-min-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-0-min-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-max-or-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[1-1-min-or-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-max-or-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-min-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-min-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-min-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-min-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-min-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-0-min-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-max-or-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[2-1-min-or-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-and-max-0-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-and-max-1-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-and-min-0-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-and-min-1-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-or-max-0-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-or-max-1-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-or-min-0-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-1-or-min-1-2]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-and-max-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-and-max-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-and-min-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-and-min-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-or-max-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-or-max-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-or-min-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-2-or-min-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-and-max-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-and-max-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-and-min-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-and-min-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-or-max-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-or-max-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-or-min-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-20-or-min-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-and-max-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-and-max-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-and-min-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-and-min-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-or-max-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-or-max-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-or-min-0-None]": true, - "tests/_NEW_test_logical.py::test_logical[B-200-or-min-1-None]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-max-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-min-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-min-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-min-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-min-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-min-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-0-min-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-max-or-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-200-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-C-200]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-C-20]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-C-2]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-I-200]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-I-20]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-and-I-2]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-or-2-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-or-20-B]": true, - "tests/_NEW_test_logical.py::test_logical[None-1-min-or-200-B]": true -} \ No newline at end of file From 7f372cf339c29881c1abe05486ce414f0731385e Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 17:43:02 +0200 Subject: [PATCH 027/121] python3 compliant --- tests/test_quickprod.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_quickprod.py b/tests/test_quickprod.py index 256587913..90d24faa0 100644 --- a/tests/test_quickprod.py +++ b/tests/test_quickprod.py @@ -1,6 +1,7 @@ from pyscipopt import Model, quickprod from pyscipopt.scip import CONST from operator import mul +from functools import reduce def test_quickprod_model(): m = Model("quickprod") @@ -10,7 +11,7 @@ def test_quickprod_model(): c = 2.3 q = quickprod([x,y,z,c]) == 0.0 - s = reduce(mul,[x,y,z,c],1) == 0.0 + s = functools.reduce(mul,[x,y,z,c],1) == 0.0 assert(q.expr.terms == s.expr.terms) From 75be3189ade9c09947e44786762ae3cfa933fbcf Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 29 May 2018 17:44:49 +0200 Subject: [PATCH 028/121] bugfix --- tests/test_quickprod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_quickprod.py b/tests/test_quickprod.py index 90d24faa0..04b26e2c6 100644 --- a/tests/test_quickprod.py +++ b/tests/test_quickprod.py @@ -1,7 +1,7 @@ from pyscipopt import Model, quickprod from pyscipopt.scip import CONST from operator import mul -from functools import reduce +import functools def test_quickprod_model(): m = Model("quickprod") From 5baa5f87755d9cc0291d3db5029a09a7995107c6 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 29 May 2018 18:35:11 +0200 Subject: [PATCH 029/121] increase version to 1.4.9 --- src/pyscipopt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 88b10e8b8..e0306585c 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.4.8' +__version__ = '1.4.9' # export user-relevant objects: from pyscipopt.Multidict import multidict From f60e4f2a0c0d6765ebf5247d987273943f2e2034 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 30 May 2018 09:25:21 +0200 Subject: [PATCH 030/121] disable codecov and allow PyPI deploy to skip files --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f2d2340b..a4087e07a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,14 +18,14 @@ before_install: - rm -rf scipoptsuite-$VERSION install: - - pip install cython networkx pytest-cov codecov + - pip install cython networkx pytest-cov #codecov - # SCIPOPTDIR=~/scipdir python setup.py build_ext --inplace --define CYTHON_TRACE - SCIPOPTDIR=~/scipdir python setup.py install -script: py.test --cov +script: py.test #--cov -after_success: - - codecov +# after_success: + # - codecov deploy: provider: pypi @@ -34,3 +34,4 @@ deploy: secure: ePfiLq2vOJC4O5zYFChHk5wa+quza+m/lsCGPfKXBVpIyb7TvzTHaFDBYtYVZK7710LIKRIcHxvmJPELyKeK1l9QyLxi1x/jOHwk0VbKpf3f5fJjjPaYfXgAUKMMeUplrdhvzU6cgUMrsGhlUE1EIHxc97x5xOa2xlv3lis3j5yjdFUbP6e7MBCEb6c8yU88CclPU2BeHDATzOtMZp0dsyzFTjP9DI7fWbEvOfGy66e5uB/Cjk07mguBZVAUFoukkwKD0KUgBB7RlrAdE61uFVHG8nE5q+G9SZIhQcwULxPLz4v18osJf1aea0g/grZnnrgdG5F24rCA6dSBlvUhnA6aDJXDSgd/dCJ7FV/w3okwhsn18esnycBeM+i3O1pleHsmkq+yFCf2wTbZlm68Hxu+WSirKjie5AtzlSOHa82jQkTjkZI1AHE2syiShnWGvaWpPtoecJKr7aHdFylbJpKwyGvptsObRerWJH5GARXnOoH+FVJ4LrAKcahwCdx0CB63HU2s5p4JgYqAlQV+hFD6yfTDvcKO97/u+8BKlLe9Jnq+fSefEJW1ndOi4mJQ4xGG93sOCub13UCo6zGLvnFlO7R7vwHJeSMDL9Z0Jqmpo2sLhKmaYMr6PhyWvWpXauZOmLTaJEutcnJZ2cjXTU2VuULWwhNYzgXLu9rnVB0= on: tags: true + skip_existing: true From 0ea4b465535ceae8a831f24d017eeb3cc407bf48 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Mon, 4 Jun 2018 15:48:40 +0200 Subject: [PATCH 031/121] testing info + typo --- INSTALL.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/INSTALL.rst b/INSTALL.rst index 6d8add4c3..50ba0b3d5 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -44,7 +44,7 @@ your ``PATH`` environment variable: On Linux and OS X this is encoded in the generated PySCIPOpt library and therefore not necessary. -Building everything form source +Building everything from source =============================== PySCIPOpt requires `Cython `__, at least version 0.21 (``pip install cython``). @@ -76,3 +76,21 @@ To use debug information in PySCIPOpt you need to build it like this: Be aware that you will need the **debug library** of the SCIP Optimization Suite for this to work (``cmake .. -DCMAKE_BUILD_TYPE=Debug``). + +Testing new installation +======================== + +To test your brand-new installation of PySCIPOpt you need `pytest `__ on your system: + +:: + + sudo apt-get install python-pytest # for Python 2, on Linux + sudo apt-get install python3-pytest # for Python 3, on Linux + +Tests are in the ``tests`` directory. Any test can be run with: +:: + + python -m pytest test_name.py + +where ``test_name.py`` is the filename of your test. Ideally, the status of your tests must be passed or skipped. +Running tests with pytest creates the ``__pycache__`` directory in ``tests`` and, occasionally, a ``model`` file in the working directory. They can be removed harmlessly. From caf52a66432fb12b1ee18438ef063a5de840c8f0 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Mon, 4 Jun 2018 16:56:19 +0200 Subject: [PATCH 032/121] docufix --- INSTALL.rst | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 50ba0b3d5..83ab5e7de 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -80,17 +80,12 @@ Suite for this to work (``cmake .. -DCMAKE_BUILD_TYPE=Debug``). Testing new installation ======================== -To test your brand-new installation of PySCIPOpt you need `pytest `__ on your system: +To test your brand-new installation of PySCIPOpt you need `pytest `__ on your system. Here is the `installation procedure `__. +Tests can be run in the ``PySCIPOpt`` directory with: :: - sudo apt-get install python-pytest # for Python 2, on Linux - sudo apt-get install python3-pytest # for Python 3, on Linux - -Tests are in the ``tests`` directory. Any test can be run with: -:: - - python -m pytest test_name.py + py.test # all the available tests + py.test tests/test_name.py # a specific tests/test_name.py (Unix) -where ``test_name.py`` is the filename of your test. Ideally, the status of your tests must be passed or skipped. -Running tests with pytest creates the ``__pycache__`` directory in ``tests`` and, occasionally, a ``model`` file in the working directory. They can be removed harmlessly. +Ideally, the status of your tests must be passed or skipped. Running tests with pytest creates the ``__pycache__`` directory in ``tests`` and, occasionally, a ``model`` file in the working directory. They can be removed harmlessly. From 7a2c580f4035a6dd58b8b9339902b0005cacef3b Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 4 Jun 2018 18:06:42 +0200 Subject: [PATCH 033/121] add CONTRIBUTING guidelines --- CONTRIBUTING.rst | 11 +++++++++++ README.rst | 15 +++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 000000000..a99063348 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,11 @@ +Contributing to PySCIPOpt +========================= + +Code contributions are very welcome and should comply to a few rules: + +1. Compatibility with both Python-2 and Python-3 +2. All tests defined in the Continuous Integration setup need to pass: + - `.travis.yml <.travis.yml>`__ + - `appveyor.yml `__ +3. New features should be covered by the tests, so please extend `tests `__ +4. New code should be documented in the same style as the rest of the code diff --git a/README.rst b/README.rst index f572eec29..b7c341824 100644 --- a/README.rst +++ b/README.rst @@ -60,12 +60,12 @@ simple examples. Please notice that in most cases one needs to use a ``dictionary`` to specify the return values needed by SCIP. -Extend the interface -==================== +Extending the interface +======================= -The interface python-scip already provides many of the SCIP callable -library methods. You may also extend python-scip to increase the -functionality of this interface.The following will provide some +PySCIPOpt already covers many of the SCIP callable +library methods. You may also extend it to increase the +functionality of this interface. The following will provide some directions on how this can be achieved: The two most important files in PySCIPOpt are the ``scip.pxd`` and @@ -87,6 +87,9 @@ functions in python that reference the SCIP public functions included in ``scip.pxd``. This is achieved by modifying the ``scip.pyx`` file to add the functionality you require. +We are always happy to accept pull request containing patches or extensions! +Please have a look at our `contribution guidelines `__. + Gotchas ======= @@ -122,7 +125,7 @@ Dual values ----------- While PySCIPOpt supports access to the dual values of a solution, there are some limitations involved: - + - Can only be used when presolving and propagation is disabled to ensure that the LP solver - which is providing the dual information - actually solves the unmodified problem. - Heuristics should also be disabled to avoid that the problem is solved before the LP solver is called. From 5132091f270bbe6b9852229639610181f6f192d3 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 4 Jun 2018 18:10:14 +0200 Subject: [PATCH 034/121] some whitespace improvements --- CONTRIBUTING.rst | 4 ++++ README.rst | 1 + 2 files changed, 5 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a99063348..5d3805c63 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -4,8 +4,12 @@ Contributing to PySCIPOpt Code contributions are very welcome and should comply to a few rules: 1. Compatibility with both Python-2 and Python-3 + 2. All tests defined in the Continuous Integration setup need to pass: + - `.travis.yml <.travis.yml>`__ - `appveyor.yml `__ + 3. New features should be covered by the tests, so please extend `tests `__ + 4. New code should be documented in the same style as the rest of the code diff --git a/README.rst b/README.rst index b7c341824..af22c69e2 100644 --- a/README.rst +++ b/README.rst @@ -88,6 +88,7 @@ functions in python that reference the SCIP public functions included in the functionality you require. We are always happy to accept pull request containing patches or extensions! + Please have a look at our `contribution guidelines `__. Gotchas From 598efd62be090992b34508c7174a25972f44bcfb Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 4 Jun 2018 18:13:03 +0200 Subject: [PATCH 035/121] Update CONTRIBUTING.rst --- CONTRIBUTING.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5d3805c63..fbadf81d7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -5,11 +5,11 @@ Code contributions are very welcome and should comply to a few rules: 1. Compatibility with both Python-2 and Python-3 -2. All tests defined in the Continuous Integration setup need to pass: +#. All tests defined in the Continuous Integration setup need to pass: - `.travis.yml <.travis.yml>`__ - `appveyor.yml `__ -3. New features should be covered by the tests, so please extend `tests `__ +#. New features should be covered by the tests, so please extend `tests `__ -4. New code should be documented in the same style as the rest of the code +#. New code should be documented in the same style as the rest of the code From c965ddb6d86e10964b8b0a834d33b0b8840ac1cb Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Sat, 2 Jun 2018 14:28:05 +0200 Subject: [PATCH 036/121] XOR constraint --- .gitignore | 3 ++ src/pyscipopt/scip.pxd | 18 +++++++ src/pyscipopt/scip.pyx | 39 +++++++++++++++ tests/test_logical.py | 107 +++++++++++++++++++++++++++++++---------- 4 files changed, 141 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index e5fe46130..b658d0bee 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,6 @@ venv.bak/ # pytest .pytest_chache/ + +# model (for tests) +model diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index e20bc0a55..c3f2f565e 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1087,6 +1087,24 @@ cdef extern from "scip/cons_or.h": SCIP_Bool removable, SCIP_Bool stickingatnode) +cdef extern from "scip/cons_xor.h": + SCIP_RETCODE SCIPcreateConsXor(SCIP* scip, + SCIP_CONS** cons, + const char* name, + SCIP_Bool rhs, + int nvars, + SCIP_VAR** vars, + SCIP_Bool initial, + SCIP_Bool separate, + SCIP_Bool enforce, + SCIP_Bool check, + SCIP_Bool propagate, + SCIP_Bool local, + SCIP_Bool modifiable, + SCIP_Bool dynamic, + SCIP_Bool removable, + SCIP_Bool stickingatnode) + cdef extern from "blockmemshell/memory.h": void BMScheckEmptyMemory() long long BMSgetMemoryUsed() diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 0858ed447..cc048a438 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1520,6 +1520,45 @@ cdef class Model: return pyCons + def addConsXor(self, vars, rhsvar, name="XORcons", + initial=True, separate=True, enforce=True, check=True, + propagate=True, local=False, modifiable=False, dynamic=False, + removable=False, stickingatnode=False): + """Add a XOR-constraint. + :param vars: list of BINARY variables to be included (operators) + :param rhsvar: BOOLEAN value, explicit True, False or bool(obj) is needed (rhs) + :param name: name of the constraint (Default value = "XORcons") + :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) + :param separate: should the constraint be separated during LP processing? (Default value = True) + :param enforce: should the constraint be enforced during node processing? (Default value = True) + :param check: should the constraint be checked for feasibility? (Default value = True) + :param propagate: should the constraint be propagated during node processing? (Default value = True) + :param local: is the constraint only valid locally? (Default value = False) + :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) + :param dynamic: is the constraint subject to aging? (Default value = False) + :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) + :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) + """ + cdef SCIP_CONS* scip_cons + + nvars = len(vars) + + assert type(rhsvar) is type(bool()), "Provide BOOLEAN value as rhsvar, you gave %s." % type(rhsvar) + _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) + for idx, var in enumerate(vars): + _vars[idx] = (var).var + + PY_SCIP_CALL(SCIPcreateConsXor(self._scip, &scip_cons, str_conversion(name), rhsvar, nvars, _vars, + initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) + + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) + pyCons = Constraint.create(scip_cons) + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + + free(_vars) + + return pyCons + def addConsCardinality(self, consvars, cardval, indvars=None, weights=None, name="CardinalityCons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, dynamic=False, diff --git a/tests/test_logical.py b/tests/test_logical.py index 1b8ac070f..8e6cca7e7 100644 --- a/tests/test_logical.py +++ b/tests/test_logical.py @@ -1,14 +1,17 @@ from pyscipopt import Model +from pyscipopt import quicksum -import pytest - -itertools = pytest.importorskip("itertools") +try: + import pytest + itertools = pytest.importorskip("itertools") +except ImportError: + import itertools product = itertools.product ################################################################################ # -# Testing AND/OR constraints +# Testing AND/OR/XOR constraints # check whether any error is raised # see http://scip.zib.de/doc-5.0.1/html/cons__and_8c.php # for resultant and operators definition @@ -16,19 +19,20 @@ # Integer and continous variables behave unexpectedly (due to SCIP?) # TBI: automatic assertion of expected resultant VS optimal resultant # (visual inspection at the moment) -# TBI: implement and test XOR constraint # ################################################################################ -verbose = True +verbose = True ### py.test ignores this -### AUXILIARY ### +### AUXILIARY FUNCTIONS ### def setModel(vtype="B", name=None, imax=2): + """initialize model and its variables. + imax (int): number of operators""" if name is None: name = "model" m = Model(name) m.hideOutput() i = 0 - m.addVar("r", vtype) + r = m.addVar("r", vtype) while i < imax: m.addVar("v%s" % i, vtype) i+=1 @@ -48,6 +52,9 @@ def getAllVarsByName(m, name): def setConss(m, vtype="B", val=0, imax=1): + """set numeric constraints to the operators. + val (int): number to which the operators are constraint + imax (int): number of operators affected by the constraint""" i = 0 while i < imax: vi = getVarByName(m,"v%s" % i) @@ -56,6 +63,7 @@ def setConss(m, vtype="B", val=0, imax=1): return def printOutput(m): + """print status and values of variables AFTER optimization.""" status = m.getStatus() r = getVarByName(m,"r") rstr = "%d" % round(m.getVal(r)) @@ -63,22 +71,48 @@ def printOutput(m): vsstr = "".join(["%d" % round(m.getVal(v)) for v in vs]) print("Status: %s, resultant: %s, operators: %s" % (status, rstr, vsstr)) -### MAIN ### -def main_logical(model, logical, sense="min"): +### MAIN FUNCTIONS ### +def main_variable(model, logical, sense="min"): + """r is the BINARY resultant variable + v are BINARY operators + cf. http://scip.zib.de/doc-5.0.1/html/cons__and_8h.php""" try: r = getVarByName(model, "r") vs = getAllVarsByName(model, "v") - ### addConsAnd/Or method (Xor: TBI) ### + ### addConsAnd/Or method (Xor: TBI, custom) ### + method_name = "addCons%s" % logical.capitalize() + if method_name == "addConsXor": + n = model.addVar("n","I") + model.addCons(r+quicksum(vs) == 2*n) + else: + try: + _model_addConsLogical = getattr(model, method_name) + _model_addConsLogical(vs,r) + except AttributeError as e: + raise AttributeError("%s not implemented" % method_name) + model.setObjective(r, sense="%simize" % sense) + model.optimize() + assert model.getStatus() == "optimal" + if verbose: printOutput(model) + return True + except Exception as e: + if verbose: print("%s: %s" % (e.__class__.__name__, e)) + return False + +def main_boolean(model, logical, value=False): + """r is the BOOLEAN rhs (NOT a variable!) + v are BINARY operators + cf. http://scip.zib.de/doc-5.0.1/html/cons__xor_8h.php""" + try: + r = value + vs = getAllVarsByName(model, "v") + ### addConsXor method (And/Or: TBI) ### method_name = "addCons%s" % logical.capitalize() try: _model_addConsLogical = getattr(model, method_name) + _model_addConsLogical(vs,r) except AttributeError as e: - if method_name == "addConsXor": - pytest.xfail("addCons%s has to be implemented" % method_name) - else: - raise AttributeError("addCons%s not implemented" % method_name) - _model_addConsLogical(vs,r) - model.setObjective(r, sense="%simize" % sense) + raise AttributeError("%s not implemented" % method_name) model.optimize() assert model.getStatus() == "optimal" if verbose: printOutput(model) @@ -87,18 +121,39 @@ def main_logical(model, logical, sense="min"): if verbose: print("%s: %s" % (e.__class__.__name__, e)) return False -### TEST ### +### TEST FUNCTIONS ### @pytest.mark.parametrize("nconss", [1, 2, "all"]) @pytest.mark.parametrize("vconss", [0, 1]) @pytest.mark.parametrize("sense", ["min","max"]) -@pytest.mark.parametrize("logical", ["and", "or", "xor"]) #xor TBI -@pytest.mark.parametrize("noperators", [2,20,200]) -@pytest.mark.parametrize("vtype", ["B","I","C"]) #I and C may raise errors: see preamble -def test_logical(noperators, vtype, logical, sense, vconss, nconss): - if nconss == "all": nconss = noperators +@pytest.mark.parametrize("logical", ["and", "or", "xor"]) +@pytest.mark.parametrize("noperators", [2,20,51,100]) +@pytest.mark.parametrize("vtype", ["B"]) +def test_variable(noperators, vtype, logical, sense, vconss, nconss): + if nconss == "all": + nconss = noperators + if vtype in ["I","C"]: + pytest.skip("unsupported vtype \"%s\" may raise errors or unexpected results" % vtype) + m = setModel(vtype, logical, noperators) + setConss(m,vtype, vconss, nconss) + success = main_variable(m, logical, sense) + assert(success), "Status is not optimal" + +@pytest.mark.parametrize("nconss", [1, 2, "all"]) +@pytest.mark.parametrize("vconss", [0, 1]) +@pytest.mark.parametrize("value", [False, True]) +@pytest.mark.parametrize("logical", ["xor","and", "or"]) +@pytest.mark.parametrize("noperators", [2,20,51,100]) +@pytest.mark.parametrize("vtype", ["B"]) +def test_boolean(noperators, vtype, logical, value, vconss, nconss): + if nconss == "all": + nconss = noperators if vtype in ["I","C"]: - pytest.skip("unsupported vtype: %s" % vtype) + pytest.skip("unsupported vtype \"%s\" may raise errors or unexpected results" % vtype) + if logical in ["and","or"]: + pytest.skip("unsupported logical: %s" % vtype) + if logical == "xor" and nconss == noperators and noperators%2 & vconss != value: + pytest.xfail("addConsXor cannot be %s if an %s number of variables are all constraint to %s" % (value, noperators, vconss)) m = setModel(vtype, logical, noperators) setConss(m,vtype, vconss, nconss) - success = main_logical(m, logical, sense) - assert(success), "Status is not optimal!" + success = main_boolean(m, logical, value) + assert(success), "Test is not successful" From 441cf006e4743aa183688c96073b042074110bd3 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 5 Jun 2018 15:23:12 +0200 Subject: [PATCH 037/121] example: integer parity --- examples/int-parity.py | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/int-parity.py diff --git a/examples/int-parity.py b/examples/int-parity.py new file mode 100644 index 000000000..c61f2eefc --- /dev/null +++ b/examples/int-parity.py @@ -0,0 +1,51 @@ +from pyscipopt import Model + +################################################################################ +# EVEN OR ODD? +# +# This example is made for newcomers and motivated by: +# - modulus is unsupported for pyscipopt.scip.Variable and int +# - negative integer numbers are not default +# Based on this: +# https://github.com/SCIP-Interfaces/PySCIPOpt/issues/172#issuecomment-394644046 +# +################################################################################ + +verbose = False +sdic = {0:"even",1:"odd"} + +def parity(number): + assert number == int(round(number)) + m = Model() + m.hideOutput() + + ### "None" means -infinity as lower bound + ### N.B.: 0 is the default lower bound + ### thus negative numbers are not allowed + x = m.addVar("x","I", None) #relative integer variable + n = m.addVar("n","I", None) #relative integer variable + s = m.addVar("s","B") + + m.addCons(x==number) #negative integer + + m.addCons(s == x-2*n) + m.setObjective(s) + m.optimize() + + assert m.getStatus() == "optimal" + if verbose: + for v in m.getVars(): + print("%s %d" % (v,m.getVal(v))) + print("%d%%2 == %d?" % (m.getVal(x), m.getVal(s))) + print(m.getVal(s) == m.getVal(x)%2) + + xval = m.getVal(x) + sval = m.getVal(s) + sstr = sdic[sval] + print("%d is %s" % (xval, sstr)) + +for n in [-int(2**31), -101, -15., -10, 0, 1, 1.5, 20, 25, int(2**31-1)]: + try: + parity(n) + except AssertionError: + print("%s is neither even nor odd!" % n) From 710f496bad4e493edaf72a4de1d157771d98f9dc Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Tue, 5 Jun 2018 16:06:58 +0200 Subject: [PATCH 038/121] AND/OR/XOR constraints: examples --- .../{int-parity.py => finished/basiceven.py} | 0 examples/finished/logical.py | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+) rename examples/{int-parity.py => finished/basiceven.py} (100%) create mode 100644 examples/finished/logical.py diff --git a/examples/int-parity.py b/examples/finished/basiceven.py similarity index 100% rename from examples/int-parity.py rename to examples/finished/basiceven.py diff --git a/examples/finished/logical.py b/examples/finished/logical.py new file mode 100644 index 000000000..22dc6c083 --- /dev/null +++ b/examples/finished/logical.py @@ -0,0 +1,64 @@ +from pyscipopt import Model +from pyscipopt import quicksum + +# AND # +model = Model() +model.hideOutput() +x = model.addVar("x","B") +y = model.addVar("y","B") +z = model.addVar("z","B") +r = model.addVar("r","B") +model.addConsAnd([x,y],r) +model.addCons(x==1) +model.setObjective(r,sense="minimize") +model.optimize() +print("* AND *") +for v in model.getVars(): + print("%s: %d" % (v, round(model.getVal(v)))) + +# OR # +model = Model() +model.hideOutput() +x = model.addVar("x","B") +y = model.addVar("y","B") +z = model.addVar("z","B") +r = model.addVar("r","B") +model.addConsOr([x,y],r) +model.addCons(x==0) +model.setObjective(r,sense="maximize") +model.optimize() +print("* OR *") +for v in model.getVars(): + print("%s: %d" % (v, round(model.getVal(v)))) + +# XOR (r as boolean, standard) # +model = Model() +model.hideOutput() +x = model.addVar("x","B") +y = model.addVar("y","B") +z = model.addVar("z","B") +r = True +model.addConsXor([x,y],r) +model.addCons(x==1) +model.optimize() +print("* XOR (as boolean) *") +for v in model.getVars(): + print("%s: %d" % (v, round(model.getVal(v)))) +print("r: %s" % r) + +# XOR (r as variable, custom) # +model = Model() +model.hideOutput() +x = model.addVar("x","B") +y = model.addVar("y","B") +z = model.addVar("z","B") +r = model.addVar("r","B") +n = model.addVar("n","I") #auxiliary +model.addCons(r+quicksum([x,y,z]) == 2*n) +model.addCons(x==0) +model.setObjective(r,sense="maximize") +model.optimize() +print("* XOR (as variable) *") +for v in model.getVars(): + if v.name != "n": + print("%s: %d" % (v, round(model.getVal(v)))) From 9665b2e3dd93ab3cc3eb01375dd19e371871a237 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Wed, 6 Jun 2018 08:40:52 +0200 Subject: [PATCH 039/121] clarifications, cosmetics --- examples/finished/basiceven.py | 51 ------------------------ examples/finished/even.py | 72 ++++++++++++++++++++++++++++++++++ examples/finished/logical.py | 48 +++++++++++++++-------- src/pyscipopt/scip.pyx | 4 +- 4 files changed, 105 insertions(+), 70 deletions(-) delete mode 100644 examples/finished/basiceven.py create mode 100644 examples/finished/even.py diff --git a/examples/finished/basiceven.py b/examples/finished/basiceven.py deleted file mode 100644 index c61f2eefc..000000000 --- a/examples/finished/basiceven.py +++ /dev/null @@ -1,51 +0,0 @@ -from pyscipopt import Model - -################################################################################ -# EVEN OR ODD? -# -# This example is made for newcomers and motivated by: -# - modulus is unsupported for pyscipopt.scip.Variable and int -# - negative integer numbers are not default -# Based on this: -# https://github.com/SCIP-Interfaces/PySCIPOpt/issues/172#issuecomment-394644046 -# -################################################################################ - -verbose = False -sdic = {0:"even",1:"odd"} - -def parity(number): - assert number == int(round(number)) - m = Model() - m.hideOutput() - - ### "None" means -infinity as lower bound - ### N.B.: 0 is the default lower bound - ### thus negative numbers are not allowed - x = m.addVar("x","I", None) #relative integer variable - n = m.addVar("n","I", None) #relative integer variable - s = m.addVar("s","B") - - m.addCons(x==number) #negative integer - - m.addCons(s == x-2*n) - m.setObjective(s) - m.optimize() - - assert m.getStatus() == "optimal" - if verbose: - for v in m.getVars(): - print("%s %d" % (v,m.getVal(v))) - print("%d%%2 == %d?" % (m.getVal(x), m.getVal(s))) - print(m.getVal(s) == m.getVal(x)%2) - - xval = m.getVal(x) - sval = m.getVal(s) - sstr = sdic[sval] - print("%d is %s" % (xval, sstr)) - -for n in [-int(2**31), -101, -15., -10, 0, 1, 1.5, 20, 25, int(2**31-1)]: - try: - parity(n) - except AssertionError: - print("%s is neither even nor odd!" % n) diff --git a/examples/finished/even.py b/examples/finished/even.py new file mode 100644 index 000000000..91dca737b --- /dev/null +++ b/examples/finished/even.py @@ -0,0 +1,72 @@ +from pyscipopt import Model + +################################################################################ +# +# EVEN OR ODD? +# +# If a positional argument is given: +# prints if the argument is even/odd/neither +# else: +# prints if a value is even/odd/neither per each value in a example list +# +# This example is made for newcomers and motivated by: +# - modulus is unsupported for pyscipopt.scip.Variable and int +# - variables are non-integer by default +# Based on this: +# https://github.com/SCIP-Interfaces/PySCIPOpt/issues/172#issuecomment-394644046 +# +################################################################################ + +verbose = False +sdic = {0:"even",1:"odd"} + +def parity(number): + try: + assert number == int(round(number)) + m = Model() + m.hideOutput() + + ### variables are non-negative by default since 0 is the default lb. + ### To allow for negative values, give None as lower bound + ### (None means -infinity as lower bound and +infinity as upper bound) + x = m.addVar("x", vtype="I", lb=None, ub=None) #ub=None is default + n = m.addVar("n", vtype="I", lb=None) + s = m.addVar("s", vtype="B") + + ### CAVEAT: if number is negative, x's lb must be None + ### if x is set by default as non-negative and number is negative: + ### there is no feasible solution (trivial) but the program + ### does not highlight which constraints conflict. + m.addCons(x==number) + + m.addCons(s == x-2*n) + m.setObjective(s) + m.optimize() + + assert m.getStatus() == "optimal" + if verbose: + for v in m.getVars(): + print("%s %d" % (v,m.getVal(v))) + print("%d%%2 == %d?" % (m.getVal(x), m.getVal(s))) + print(m.getVal(s) == m.getVal(x)%2) + + xval = m.getVal(x) + sval = m.getVal(s) + sstr = sdic[sval] + print("%d is %s" % (xval, sstr)) + except (AssertionError, TypeError): + print("%s is neither even nor odd!" % number.__repr__()) + +if __name__ == "__main__": + import sys + from ast import literal_eval as leval + example_values = [0, 1, 1.5, "hallo welt", 20, 25, -101, -15., -10, -int(2**31), int(2**31-1), int(2**63)-1] + try: + try: + n = leval(sys.argv[1]) + except ValueError: + n = sys.argv[1] + parity(n) + except IndexError: + for n in example_values: + parity(n) diff --git a/examples/finished/logical.py b/examples/finished/logical.py index 22dc6c083..eccdda792 100644 --- a/examples/finished/logical.py +++ b/examples/finished/logical.py @@ -1,6 +1,30 @@ from pyscipopt import Model from pyscipopt import quicksum +################################################################################ +# +# AND/OR/XOR CONSTRAINTS +# +# Tutorial example on how to use AND/OR/XOR constraints. +# +# N.B.: standard SCIP XOR constraint works differently from AND/OR by design. +# The constraint is set with a boolean rhs instead of an integer resultant. +# cf. http://listserv.zib.de/pipermail/scip/2018-May/003392.html +# A workaround to get the resultant as variable is here proposed. +# +################################################################################ + +def printFunc(name,m): + print("* %s *" % name) + objSet = bool(m.getObjective().terms.keys()) + print("* Is objective set? %s" % objSet) + if objSet: + print("* Sense: %s" % m.getObjectiveSense()) + for v in m.getVars(): + if v.name != "n": + print("%s: %d" % (v, round(m.getVal(v)))) + print("\n") + # AND # model = Model() model.hideOutput() @@ -8,13 +32,11 @@ y = model.addVar("y","B") z = model.addVar("z","B") r = model.addVar("r","B") -model.addConsAnd([x,y],r) +model.addConsAnd([x,y,z],r) model.addCons(x==1) model.setObjective(r,sense="minimize") model.optimize() -print("* AND *") -for v in model.getVars(): - print("%s: %d" % (v, round(model.getVal(v)))) +printFunc("AND",model) # OR # model = Model() @@ -23,13 +45,11 @@ y = model.addVar("y","B") z = model.addVar("z","B") r = model.addVar("r","B") -model.addConsOr([x,y],r) +model.addConsOr([x,y,z],r) model.addCons(x==0) model.setObjective(r,sense="maximize") model.optimize() -print("* OR *") -for v in model.getVars(): - print("%s: %d" % (v, round(model.getVal(v)))) +printFunc("OR",model) # XOR (r as boolean, standard) # model = Model() @@ -38,13 +58,10 @@ y = model.addVar("y","B") z = model.addVar("z","B") r = True -model.addConsXor([x,y],r) +model.addConsXor([x,y,z],r) model.addCons(x==1) model.optimize() -print("* XOR (as boolean) *") -for v in model.getVars(): - print("%s: %d" % (v, round(model.getVal(v)))) -print("r: %s" % r) +printFunc("Standard XOR (as boolean)",model) # XOR (r as variable, custom) # model = Model() @@ -58,7 +75,4 @@ model.addCons(x==0) model.setObjective(r,sense="maximize") model.optimize() -print("* XOR (as variable) *") -for v in model.getVars(): - if v.name != "n": - print("%s: %d" % (v, round(model.getVal(v)))) +printFunc("Custom XOR (as variable)",model) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index ac94938df..1d27fa557 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -866,7 +866,7 @@ cdef class Model: # Variable Functions def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False): - """Create a new variable. + """Create a new variable. Default variable is non-negative and continuous. :param name: name of the variable, generic if empty (Default value = '') :param vtype: type of the variable (Default value = 'C') @@ -1527,7 +1527,7 @@ cdef class Model: removable=False, stickingatnode=False): """Add a XOR-constraint. :param vars: list of BINARY variables to be included (operators) - :param rhsvar: BOOLEAN value, explicit True, False or bool(obj) is needed (rhs) + :param rhsvar: BOOLEAN value, explicit True, False or bool(obj) is needed (right-hand side) :param name: name of the constraint (Default value = "XORcons") :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) :param separate: should the constraint be separated during LP processing? (Default value = True) From 075f23d197542f6d3caa7ec12871747ac559d301 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 18 Jun 2018 11:46:42 +0200 Subject: [PATCH 040/121] test with SCIP-600 release candidate --- .travis.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f2d2340b..334175dbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,13 @@ python: notifications: email: false +env: + - secure: "CML6W6GUTFcZxZavt2x9vT3pUeg9jA2tber8Wl+34zBI9QxXel8PxKlw896OI2jnGPMvL7ANRElklE6/WNaVvogjgZXKcXnqaGKPoPlJNsGenHw0poxjqrrs9VnuX2XU56h53ESsOZ9mq53oFNaimS6fbtIAs7xlS27nY0KJk42ZEicaS2E9cbzH/XqOIEzdIZCHy8NjViMXFCspE9fhndv04T3ic2opXmGDy2veoZ/oF2zbOcz0e9XLEjTs0yXz5qir8AGEnRS4lwI6hb3jkMBOxbNKIPx63gsno3xUHjXYjiwb4iQV9eybhY0csli/5br8isIX81vlg5xeoEfvSy6sZvZ8rErx3Eos5OdCu4vnxqtMZvpb+2pCVQU2IldZTl9B3/lv4ehZhKurF3l89rnqKW14eh4p2eT6WQ2s0tjPd5NuPdow4hT5x7WWSeS1395exlJJGgv1bt4ASM+KNFfA/4CK4TjszZJ7xLttiJ7nOgo/8KtSd/dM0PfBWeeBQxi/0YgCyD781ieL009ZUPwvKf4B0RJ8pPaSDePypKHvzmcm7UGgT86zz1FnCxsIEmHFJQGazXbdBmi0OvPAo1fCrAdMXipppf+ckAotckWjOLIK6IN9RlrF/E9YFll/SfSiXi6EdB0P+T6m8iBqNEToJbUiRqKhMznr7A4+JLs=" + - VERSION=6.0.0 + + before_install: - - export VERSION=5.0.1 - - wget http://scip.zib.de/download/release/scipoptsuite-$VERSION.tgz + - wget --user=opti-test --password=$PASSWORD http://opti-test.zib.de/scipoptsuite-v600-rc02.tgz - tar xf scipoptsuite-$VERSION.tgz - cd scipoptsuite-$VERSION - mkdir build From 188dace2cacc8606ef12a55aebc165feb85c0523 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 18 Jun 2018 11:50:26 +0200 Subject: [PATCH 041/121] make env vars global --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 334175dbc..3157b537f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ language: python +env: + global: + - secure: "CML6W6GUTFcZxZavt2x9vT3pUeg9jA2tber8Wl+34zBI9QxXel8PxKlw896OI2jnGPMvL7ANRElklE6/WNaVvogjgZXKcXnqaGKPoPlJNsGenHw0poxjqrrs9VnuX2XU56h53ESsOZ9mq53oFNaimS6fbtIAs7xlS27nY0KJk42ZEicaS2E9cbzH/XqOIEzdIZCHy8NjViMXFCspE9fhndv04T3ic2opXmGDy2veoZ/oF2zbOcz0e9XLEjTs0yXz5qir8AGEnRS4lwI6hb3jkMBOxbNKIPx63gsno3xUHjXYjiwb4iQV9eybhY0csli/5br8isIX81vlg5xeoEfvSy6sZvZ8rErx3Eos5OdCu4vnxqtMZvpb+2pCVQU2IldZTl9B3/lv4ehZhKurF3l89rnqKW14eh4p2eT6WQ2s0tjPd5NuPdow4hT5x7WWSeS1395exlJJGgv1bt4ASM+KNFfA/4CK4TjszZJ7xLttiJ7nOgo/8KtSd/dM0PfBWeeBQxi/0YgCyD781ieL009ZUPwvKf4B0RJ8pPaSDePypKHvzmcm7UGgT86zz1FnCxsIEmHFJQGazXbdBmi0OvPAo1fCrAdMXipppf+ckAotckWjOLIK6IN9RlrF/E9YFll/SfSiXi6EdB0P+T6m8iBqNEToJbUiRqKhMznr7A4+JLs=" + - VERSION=6.0.0 + python: - '2.7' - '3.6' @@ -7,11 +12,6 @@ python: notifications: email: false -env: - - secure: "CML6W6GUTFcZxZavt2x9vT3pUeg9jA2tber8Wl+34zBI9QxXel8PxKlw896OI2jnGPMvL7ANRElklE6/WNaVvogjgZXKcXnqaGKPoPlJNsGenHw0poxjqrrs9VnuX2XU56h53ESsOZ9mq53oFNaimS6fbtIAs7xlS27nY0KJk42ZEicaS2E9cbzH/XqOIEzdIZCHy8NjViMXFCspE9fhndv04T3ic2opXmGDy2veoZ/oF2zbOcz0e9XLEjTs0yXz5qir8AGEnRS4lwI6hb3jkMBOxbNKIPx63gsno3xUHjXYjiwb4iQV9eybhY0csli/5br8isIX81vlg5xeoEfvSy6sZvZ8rErx3Eos5OdCu4vnxqtMZvpb+2pCVQU2IldZTl9B3/lv4ehZhKurF3l89rnqKW14eh4p2eT6WQ2s0tjPd5NuPdow4hT5x7WWSeS1395exlJJGgv1bt4ASM+KNFfA/4CK4TjszZJ7xLttiJ7nOgo/8KtSd/dM0PfBWeeBQxi/0YgCyD781ieL009ZUPwvKf4B0RJ8pPaSDePypKHvzmcm7UGgT86zz1FnCxsIEmHFJQGazXbdBmi0OvPAo1fCrAdMXipppf+ckAotckWjOLIK6IN9RlrF/E9YFll/SfSiXi6EdB0P+T6m8iBqNEToJbUiRqKhMznr7A4+JLs=" - - VERSION=6.0.0 - - before_install: - wget --user=opti-test --password=$PASSWORD http://opti-test.zib.de/scipoptsuite-v600-rc02.tgz - tar xf scipoptsuite-$VERSION.tgz From 752d850dd5e01e9ead88208d43a99c7fe757065e Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 18 Jun 2018 11:52:34 +0200 Subject: [PATCH 042/121] specify output name of rc archive --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3157b537f..ea4e2ba26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ notifications: email: false before_install: - - wget --user=opti-test --password=$PASSWORD http://opti-test.zib.de/scipoptsuite-v600-rc02.tgz + - wget --user=opti-test --password=$PASSWORD -O scipoptsuite-$VERSION.tgz http://opti-test.zib.de/scipoptsuite-v600-rc02.tgz - tar xf scipoptsuite-$VERSION.tgz - cd scipoptsuite-$VERSION - mkdir build From 419843cbeec70258b5c1f8fb0ffe29cb65ebdd7d Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 18 Jun 2018 12:44:55 +0200 Subject: [PATCH 043/121] add new parameter locktype to conshdlr tests --- tests/test_alldiff.py | 2 +- tests/test_conshdlr.py | 2 +- tests/test_tsp.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_alldiff.py b/tests/test_alldiff.py index 99f046a40..fdc0083d8 100644 --- a/tests/test_alldiff.py +++ b/tests/test_alldiff.py @@ -203,7 +203,7 @@ def consenfolp(self, constraints, n_useful_conss, sol_infeasible): return {"result": SCIP_RESULT.INFEASIBLE} return {"result": SCIP_RESULT.FEASIBLE} - def conslock(self, constraint, nlockspos, nlocksneg): + def conslock(self, constraint, locktype, nlockspos, nlocksneg): for var in constraint.data.vars: self.model.addVarLocks(var, nlockspos + nlocksneg , nlockspos + nlocksneg) diff --git a/tests/test_conshdlr.py b/tests/test_conshdlr.py index 39b858c44..45c42852f 100644 --- a/tests/test_conshdlr.py +++ b/tests/test_conshdlr.py @@ -64,7 +64,7 @@ def conscheck(self, constraints, solution, checkintegrality, checklprows, printr assert id(constraint) in ids return {"result": SCIP_RESULT.FEASIBLE} - def conslock(self, constraint, nlockspos, nlocksneg): + def conslock(self, constraint, locktype, nlockspos, nlocksneg): calls.add("conslock") assert id(constraint) in ids diff --git a/tests/test_tsp.py b/tests/test_tsp.py index 350b180ff..5e17a06d2 100644 --- a/tests/test_tsp.py +++ b/tests/test_tsp.py @@ -55,7 +55,7 @@ def consenfolp(self, constraints, n_useful_conss, sol_infeasible): else: return {"result": SCIP_RESULT.FEASIBLE} - def conslock(self, constraint, nlockspos, nlocksneg): + def conslock(self, constraint, locktype, nlockspos, nlocksneg): pass From 85f3198e1c61e07baaa708aae9014b1b8634c725 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 18 Jun 2018 13:00:46 +0200 Subject: [PATCH 044/121] clean up test_logical according to pep8 format --- tests/test_logical.py | 73 ++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/tests/test_logical.py b/tests/test_logical.py index 8e6cca7e7..07aec63de 100644 --- a/tests/test_logical.py +++ b/tests/test_logical.py @@ -9,8 +9,6 @@ product = itertools.product -################################################################################ -# # Testing AND/OR/XOR constraints # check whether any error is raised # see http://scip.zib.de/doc-5.0.1/html/cons__and_8c.php @@ -19,31 +17,33 @@ # Integer and continous variables behave unexpectedly (due to SCIP?) # TBI: automatic assertion of expected resultant VS optimal resultant # (visual inspection at the moment) -# -################################################################################ -verbose = True ### py.test ignores this +verbose = True # py.test ignores this -### AUXILIARY FUNCTIONS ### + +# AUXILIARY FUNCTIONS def setModel(vtype="B", name=None, imax=2): """initialize model and its variables. imax (int): number of operators""" - if name is None: name = "model" + if name is None: + name = "model" m = Model(name) m.hideOutput() i = 0 r = m.addVar("r", vtype) while i < imax: m.addVar("v%s" % i, vtype) - i+=1 + i += 1 return m + def getVarByName(m, name): try: return [v for v in m.getVars() if name == v.name][0] except IndexError: return None + def getAllVarsByName(m, name): try: return [v for v in m.getVars() if name in v.name] @@ -57,21 +57,23 @@ def setConss(m, vtype="B", val=0, imax=1): imax (int): number of operators affected by the constraint""" i = 0 while i < imax: - vi = getVarByName(m,"v%s" % i) + vi = getVarByName(m, "v%s" % i) m.addCons(vi == val, vtype) - i+=1 + i += 1 return + def printOutput(m): """print status and values of variables AFTER optimization.""" status = m.getStatus() - r = getVarByName(m,"r") + r = getVarByName(m, "r") rstr = "%d" % round(m.getVal(r)) vs = getAllVarsByName(m, "v") vsstr = "".join(["%d" % round(m.getVal(v)) for v in vs]) print("Status: %s, resultant: %s, operators: %s" % (status, rstr, vsstr)) -### MAIN FUNCTIONS ### + +# MAIN FUNCTIONS def main_variable(model, logical, sense="min"): """r is the BINARY resultant variable v are BINARY operators @@ -79,26 +81,29 @@ def main_variable(model, logical, sense="min"): try: r = getVarByName(model, "r") vs = getAllVarsByName(model, "v") - ### addConsAnd/Or method (Xor: TBI, custom) ### + # addConsAnd/Or method (Xor: TBI, custom) ### method_name = "addCons%s" % logical.capitalize() if method_name == "addConsXor": - n = model.addVar("n","I") + n = model.addVar("n", "I") model.addCons(r+quicksum(vs) == 2*n) else: try: _model_addConsLogical = getattr(model, method_name) - _model_addConsLogical(vs,r) + _model_addConsLogical(vs, r) except AttributeError as e: raise AttributeError("%s not implemented" % method_name) model.setObjective(r, sense="%simize" % sense) model.optimize() assert model.getStatus() == "optimal" - if verbose: printOutput(model) + if verbose: + printOutput(model) return True except Exception as e: - if verbose: print("%s: %s" % (e.__class__.__name__, e)) + if verbose: + print("%s: %s" % (e.__class__.__name__, e)) return False + def main_boolean(model, logical, value=False): """r is the BOOLEAN rhs (NOT a variable!) v are BINARY operators @@ -106,54 +111,58 @@ def main_boolean(model, logical, value=False): try: r = value vs = getAllVarsByName(model, "v") - ### addConsXor method (And/Or: TBI) ### + # addConsXor method (And/Or: TBI) ### method_name = "addCons%s" % logical.capitalize() try: _model_addConsLogical = getattr(model, method_name) - _model_addConsLogical(vs,r) + _model_addConsLogical(vs, r) except AttributeError as e: raise AttributeError("%s not implemented" % method_name) model.optimize() assert model.getStatus() == "optimal" - if verbose: printOutput(model) + if verbose: + printOutput(model) return True except Exception as e: - if verbose: print("%s: %s" % (e.__class__.__name__, e)) + if verbose: + print("%s: %s" % (e.__class__.__name__, e)) return False -### TEST FUNCTIONS ### + +# TEST FUNCTIONS @pytest.mark.parametrize("nconss", [1, 2, "all"]) @pytest.mark.parametrize("vconss", [0, 1]) -@pytest.mark.parametrize("sense", ["min","max"]) +@pytest.mark.parametrize("sense", ["min", "max"]) @pytest.mark.parametrize("logical", ["and", "or", "xor"]) -@pytest.mark.parametrize("noperators", [2,20,51,100]) +@pytest.mark.parametrize("noperators", [2, 20, 51, 100]) @pytest.mark.parametrize("vtype", ["B"]) def test_variable(noperators, vtype, logical, sense, vconss, nconss): if nconss == "all": nconss = noperators - if vtype in ["I","C"]: + if vtype in ["I", "C"]: pytest.skip("unsupported vtype \"%s\" may raise errors or unexpected results" % vtype) m = setModel(vtype, logical, noperators) - setConss(m,vtype, vconss, nconss) + setConss(m, vtype, vconss, nconss) success = main_variable(m, logical, sense) assert(success), "Status is not optimal" + @pytest.mark.parametrize("nconss", [1, 2, "all"]) @pytest.mark.parametrize("vconss", [0, 1]) @pytest.mark.parametrize("value", [False, True]) -@pytest.mark.parametrize("logical", ["xor","and", "or"]) -@pytest.mark.parametrize("noperators", [2,20,51,100]) +@pytest.mark.parametrize("logical", ["xor", "and", "or"]) +@pytest.mark.parametrize("noperators", [2, 20, 51, 100]) @pytest.mark.parametrize("vtype", ["B"]) def test_boolean(noperators, vtype, logical, value, vconss, nconss): if nconss == "all": nconss = noperators - if vtype in ["I","C"]: + if vtype in ["I", "C"]: pytest.skip("unsupported vtype \"%s\" may raise errors or unexpected results" % vtype) - if logical in ["and","or"]: + if logical in ["and", "or"]: pytest.skip("unsupported logical: %s" % vtype) - if logical == "xor" and nconss == noperators and noperators%2 & vconss != value: + if logical == "xor" and nconss == noperators and noperators % 2 & vconss != value: pytest.xfail("addConsXor cannot be %s if an %s number of variables are all constraint to %s" % (value, noperators, vconss)) m = setModel(vtype, logical, noperators) - setConss(m,vtype, vconss, nconss) + setConss(m, vtype, vconss, nconss) success = main_boolean(m, logical, value) assert(success), "Test is not successful" From f78a12bee8879777d954cf5e70ac5de4222afddc Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Mon, 18 Jun 2018 20:45:18 +0200 Subject: [PATCH 045/121] add pep8 note --- CONTRIBUTING.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index fbadf81d7..6653c9b0b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -13,3 +13,5 @@ Code contributions are very welcome and should comply to a few rules: #. New features should be covered by the tests, so please extend `tests `__ #. New code should be documented in the same style as the rest of the code + +#. New code should be `pep8-compliant `__. Help yourself with the `style guide checker `__. From 631e6881301d89f221d3d02858a047fad8fc8a28 Mon Sep 17 00:00:00 2001 From: Maxime Gasse Date: Wed, 25 Apr 2018 15:21:59 -0400 Subject: [PATCH 046/121] String conversion in setStringParam() --- src/pyscipopt/scip.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 1d27fa557..7e4e71ed0 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2708,7 +2708,8 @@ cdef class Model: """ n = str_conversion(name) - PY_SCIP_CALL(SCIPsetStringParam(self._scip, n, value)) + v = str_conversion(value) + PY_SCIP_CALL(SCIPsetStringParam(self._scip, n, v)) def setParam(self, name, value): """Set a parameter with value in int, bool, real, long, char or str. From 2767aeaa2c5e280c8d357bd7fed36d366156620f Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 20 Jun 2018 16:04:57 +0200 Subject: [PATCH 047/121] relax asserts in nonlinear test --- src/pyscipopt/__init__.py | 2 +- tests/test_nonlinear.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index e0306585c..558d0efcb 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.4.9' +__version__ = '1.5.0' # export user-relevant objects: from pyscipopt.Multidict import multidict diff --git a/tests/test_nonlinear.py b/tests/test_nonlinear.py index aaedeb9c7..2cbd6b9c3 100644 --- a/tests/test_nonlinear.py +++ b/tests/test_nonlinear.py @@ -1,7 +1,6 @@ import pytest -from pyscipopt import Model, quicksum -from pyscipopt.scip import Expr, ExprCons, sqrt +from pyscipopt import Model, quicksum, sqrt # test string with polynomial formulation (uses only Expr) def test_string_poly(): @@ -137,9 +136,9 @@ def test_circle(): m.optimize() bestsol = m.getBestSol() - assert abs(m.getSolVal(bestsol, r) - 5.2543) < 1.0e-4 - assert abs(m.getSolVal(bestsol, a) - 6.1242) < 1.0e-4 - assert abs(m.getSolVal(bestsol, b) - 5.4702) < 1.0e-4 + assert abs(m.getSolVal(bestsol, r) - 5.2543) < 1.0e-3 + assert abs(m.getSolVal(bestsol, a) - 6.1242) < 1.0e-3 + assert abs(m.getSolVal(bestsol, b) - 5.4702) < 1.0e-3 # test gastrans: see example in /examples/CallableLibrary/src/gastrans.c # of course there is a more pythonic/elegant way of implementing this, probably From 515b6b29389caaae1bca781e1962ca733240e503 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 21 Jun 2018 14:06:41 +0100 Subject: [PATCH 048/121] adds comparison functions --- src/pyscipopt/scip.pxd | 3 +++ src/pyscipopt/scip.pyx | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index e05113ef3..ba2089788 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -906,6 +906,9 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPisFeasNegative(SCIP* scip, SCIP_Real val) SCIP_Bool SCIPisInfinity(SCIP* scip, SCIP_Real val) SCIP_Bool SCIPisLE(SCIP* scip, SCIP_Real val1, SCIP_Real val2) + SCIP_Bool SCIPisLT(SCIP* scip, SCIP_Real val1, SCIP_Real val2) + SCIP_Bool SCIPisGE(SCIP* scip, SCIP_Real val1, SCIP_Real val2) + SCIP_Bool SCIPisGT(SCIP* scip, SCIP_Real val1, SCIP_Real val2) # Statistic Methods SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 6f01e307b..b4627fcf1 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -698,6 +698,18 @@ cdef class Model: """returns whether val1 <= val2 + eps""" return SCIPisLE(self._scip, val1, val2) + def isLT(self, val1, val2): + """returns whether val1 < val2 - eps""" + return SCIPisLT(self._scip, val1, val2) + + def isGE(self, val1, val2): + """returns whether val1 >= val2 - eps""" + return SCIPisGE(self._scip, val1, val2) + + def isGT(self, val1, val2): + """returns whether val1 > val2 + eps""" + return SCIPisGT(self._scip, val1, val2) + def getCondition(self, exact=False): """Get the current LP's condition number From 8aa6c465f1cbdce0c6d7f84b1fe050e2e950e091 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 21 Jun 2018 14:06:50 +0100 Subject: [PATCH 049/121] fix for Benders' decomposition --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index b4627fcf1..68f81cb5c 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1992,7 +1992,7 @@ cdef class Model: # if subproblems is a dictionary, then the dictionary is turned into a c array if isdict: for idx, subprob in enumerate(subproblems.values()): - subprobs[idx] = subprob._scip + subprobs[idx] = (subprob)._scip else: subprobs[0] = (subproblems)._scip From 6dc15457eb2f6b44e3e338540a2320500640f918 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 21 Jun 2018 14:12:42 +0100 Subject: [PATCH 050/121] adds Benders' decomposition test --- tests/test_benders.py | 127 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 tests/test_benders.py diff --git a/tests/test_benders.py b/tests/test_benders.py new file mode 100644 index 000000000..35fd3b304 --- /dev/null +++ b/tests/test_benders.py @@ -0,0 +1,127 @@ +""" +flp-benders.py: model for solving the capacitated facility location problem using Benders' decomposition + +minimize the total (weighted) travel cost from n customers +to some facilities with fixed costs and capacities. + +Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012 +""" +from pyscipopt import Model, quicksum, multidict, SCIP_PARAMSETTING +import pdb + +def flp(I,J,d,M,f,c): + """flp -- model for the capacitated facility location problem + Parameters: + - I: set of customers + - J: set of facilities + - d[i]: demand for customer i + - M[j]: capacity of facility j + - f[j]: fixed cost for using a facility in point j + - c[i,j]: unit cost of servicing demand point i from facility j + Returns a model, ready to be solved. + """ + + master = Model("flp-master") + subprob = Model("flp-subprob") + + # creating the problem + y = {} + for j in J: + y[j] = master.addVar(vtype="B", name="y(%s)"%j) + + master.setObjective( + quicksum(f[j]*y[j] for j in J), + "minimize") + master.data = y + + # creating the subproblem + x,y = {},{} + for j in J: + y[j] = subprob.addVar(vtype="B", name="y(%s)"%j) + for i in I: + x[i,j] = subprob.addVar(vtype="C", name="x(%s,%s)"%(i,j)) + + for i in I: + subprob.addCons(quicksum(x[i,j] for j in J) == d[i], "Demand(%s)"%i) + + for j in M: + subprob.addCons(quicksum(x[i,j] for i in I) <= M[j]*y[j], "Capacity(%s)"%i) + + for (i,j) in x: + subprob.addCons(x[i,j] <= d[i]*y[j], "Strong(%s,%s)"%(i,j)) + + subprob.setObjective( + quicksum(c[i,j]*x[i,j] for i in I for j in J), + "minimize") + subprob.data = x,y + + return master, subprob + + +def make_data(): + I,d = multidict({1:80, 2:270, 3:250, 4:160, 5:180}) # demand + J,M,f = multidict({1:[500,1000], 2:[500,1000], 3:[500,1000]}) # capacity, fixed costs + c = {(1,1):4, (1,2):6, (1,3):9, # transportation costs + (2,1):5, (2,2):4, (2,3):7, + (3,1):6, (3,2):3, (3,3):4, + (4,1):8, (4,2):5, (4,3):3, + (5,1):10, (5,2):8, (5,3):4, + } + return I,J,d,M,f,c + + + +if __name__ == "__main__": + I,J,d,M,f,c = make_data() + master, subprob = flp(I,J,d,M,f,c) + # initializing the default Benders' decomposition with the subproblem + master.setPresolve(SCIP_PARAMSETTING.OFF) + master.setBoolParam("misc/allowdualreds", False) + master.setBoolParam("benders/copybenders", False) + master.initBendersDefault(subprob) + + # optimizing the problem using Benders' decomposition + master.optimize() + + # solving the subproblems to get the best solution + master.computeBestSolSubproblems() + + EPS = 1.e-6 + y = master.data + facilities = [j for j in y if master.getVal(y[j]) > EPS] + + x, suby = subprob.data + edges = [(i,j) for (i,j) in x if subprob.getVal(x[i,j]) > EPS] + + print("Optimal value:", master.getObjVal()) + print("Facilities at nodes:", facilities) + print("Edges:", edges) + + master.printStatistics() + + # since computeBestSolSubproblems() was called above, we need to free the + # subproblems. This must happen after the solution is extracted, otherwise + # the solution will be lost + master.freeBendersSubproblems() + + try: # plot the result using networkx and matplotlib + import networkx as NX + import matplotlib.pyplot as P + P.clf() + G = NX.Graph() + + other = [j for j in y if j not in facilities] + customers = ["c%s"%i for i in d] + G.add_nodes_from(facilities) + G.add_nodes_from(other) + G.add_nodes_from(customers) + for (i,j) in edges: + G.add_edge("c%s"%i,j) + + position = NX.drawing.layout.spring_layout(G) + NX.draw(G,position,node_color="y",nodelist=facilities) + NX.draw(G,position,node_color="g",nodelist=other) + NX.draw(G,position,node_color="b",nodelist=customers) + P.show() + except ImportError: + print("install 'networkx' and 'matplotlib' for plotting") From dcea6220afc799ed9ff4212de8d594de93f0d438 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Thu, 21 Jun 2018 14:18:04 +0100 Subject: [PATCH 051/121] updates Benders' decomposition test --- tests/test_benders.py | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/tests/test_benders.py b/tests/test_benders.py index 35fd3b304..5f4633dac 100644 --- a/tests/test_benders.py +++ b/tests/test_benders.py @@ -70,8 +70,10 @@ def make_data(): return I,J,d,M,f,c - -if __name__ == "__main__": +def test_flpbenders(): + ''' + test the Benders' decomposition plugins with the facility location problem. + ''' I,J,d,M,f,c = make_data() master, subprob = flp(I,J,d,M,f,c) # initializing the default Benders' decomposition with the subproblem @@ -104,24 +106,8 @@ def make_data(): # the solution will be lost master.freeBendersSubproblems() - try: # plot the result using networkx and matplotlib - import networkx as NX - import matplotlib.pyplot as P - P.clf() - G = NX.Graph() - - other = [j for j in y if j not in facilities] - customers = ["c%s"%i for i in d] - G.add_nodes_from(facilities) - G.add_nodes_from(other) - G.add_nodes_from(customers) - for (i,j) in edges: - G.add_edge("c%s"%i,j) - - position = NX.drawing.layout.spring_layout(G) - NX.draw(G,position,node_color="y",nodelist=facilities) - NX.draw(G,position,node_color="g",nodelist=other) - NX.draw(G,position,node_color="b",nodelist=customers) - P.show() - except ImportError: - print("install 'networkx' and 'matplotlib' for plotting") + assert master.getObjVal() == 5.61e+03 + + +if __name__ == "__main__": + test_flpbenders() From e82af2f0b18a4ceef3b0c445d1503c150f84ece6 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 27 Jun 2018 09:20:18 +0200 Subject: [PATCH 052/121] Appveyor rc ci (#187) * use secure opti-test SCIP-6.0 rc for Appveyor CI * use different env var names * more fun with Appveyor * use differnent password assignment * use differnent password assignment again * use yet another password assignment * use yet another password assignment * test env vars * test env vars * test env vars * test env vars * test env vars * test env vars * test env vars * test env vars * test env vars * clean-up * run appveyor on all branches * specify powershell * specify outfile name --- appveyor.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 73a1c8176..9fbea6e2b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,13 +1,11 @@ version: '{build}' -branches: - only: - - master - environment: SCIPOPTDIR: C:\scipoptdir - password: + pypipw: secure: HEa8MAJyyfSv33snyK3Gleflk9SIfZBxbnTiS39hlWM= + optipw: + secure: mi/mkS8vYK1Yza0A1FB4/Q== matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 @@ -28,7 +26,13 @@ environment: TWINE: C:\Python36-x64\Scripts\twine install: - - ps: wget http://scip.zib.de/download/release/SCIPOptSuite-5.0.1-win64-VS15.exe -outfile scipopt-installer.exe + - ps: $uri = 'http://opti-test.zib.de/v600-rc05/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe' + - ps: $user = 'opti-test' + - ps: $pass = $env:optipw | ConvertTo-SecureString -AsPlainText -Force + - ps: $cred = New-Object Management.Automation.PSCredential ($user, ($pass)) + - ps: Invoke-WebRequest -Uri $uri -Credential $cred -OutFile 'scipopt-installer.exe' + # - ps: wget http://scip.zib.de/download/release/SCIPOptSuite-5.0.1-win64-VS15.exe -outfile scipopt-installer.exe + # - ps: wget --user=opti-test --password=$optipw http://opti-test.zib.de/v600-rc05/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe -outfile scipopt-installer.exe - scipopt-installer.exe /S /D=%SCIPOPTDIR% - set PATH=%SCIPOPTDIR%\bin;%PYTHON%;%PATH% - if [%INCLUDE_REQUIRED%]==[true] copy .\VC9-include\* %SCIPOPTDIR%\include @@ -46,7 +50,7 @@ artifacts: after_test: - cmd: "echo [pypi] > %USERPROFILE%\\.pypirc" - cmd: "echo username: pyscipopt >> %USERPROFILE%\\.pypirc" - - cmd: "echo password: %password% >> %USERPROFILE%\\.pypirc" + - cmd: "echo password: %pypipw% >> %USERPROFILE%\\.pypirc" - python setup.py bdist_wheel on_success: From 14bb392d44a012cc5546df26547061b8c96c29ca Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 27 Jun 2018 09:22:41 +0200 Subject: [PATCH 053/121] clean up appveyor script --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9fbea6e2b..705da4286 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,8 +31,6 @@ install: - ps: $pass = $env:optipw | ConvertTo-SecureString -AsPlainText -Force - ps: $cred = New-Object Management.Automation.PSCredential ($user, ($pass)) - ps: Invoke-WebRequest -Uri $uri -Credential $cred -OutFile 'scipopt-installer.exe' - # - ps: wget http://scip.zib.de/download/release/SCIPOptSuite-5.0.1-win64-VS15.exe -outfile scipopt-installer.exe - # - ps: wget --user=opti-test --password=$optipw http://opti-test.zib.de/v600-rc05/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe -outfile scipopt-installer.exe - scipopt-installer.exe /S /D=%SCIPOPTDIR% - set PATH=%SCIPOPTDIR%\bin;%PYTHON%;%PATH% - if [%INCLUDE_REQUIRED%]==[true] copy .\VC9-include\* %SCIPOPTDIR%\include From 1748db7df1107373e425ef25b0fb8862f4f68dca Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 27 Jun 2018 21:16:39 +0200 Subject: [PATCH 054/121] use latest release candidate for CI tests --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 497a22e65..4ac453220 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ notifications: email: false before_install: - - wget --user=opti-test --password=$PASSWORD -O scipoptsuite-$VERSION.tgz http://opti-test.zib.de/scipoptsuite-v600-rc02.tgz + - wget --user=opti-test --password=$PASSWORD -O scipoptsuite-$VERSION.tgz http://opti-test.zib.de/scipoptsuite-v600-rc06.tgz - tar xf scipoptsuite-$VERSION.tgz - cd scipoptsuite-$VERSION - mkdir build diff --git a/appveyor.yml b/appveyor.yml index 705da4286..5048f372a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ environment: TWINE: C:\Python36-x64\Scripts\twine install: - - ps: $uri = 'http://opti-test.zib.de/v600-rc05/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe' + - ps: $uri = 'http://opti-test.zib.de/v600-rc06/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe' - ps: $user = 'opti-test' - ps: $pass = $env:optipw | ConvertTo-SecureString -AsPlainText -Force - ps: $cred = New-Object Management.Automation.PSCredential ($user, ($pass)) From 82422bb715c6d7828d27f6fb7240e3a25acf31a6 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Sat, 30 Jun 2018 21:19:47 +0200 Subject: [PATCH 055/121] CONTRIBUTING improvement Design principles, examples, references --- CONTRIBUTING.rst | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6653c9b0b..e467a35c6 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -3,15 +3,44 @@ Contributing to PySCIPOpt Code contributions are very welcome and should comply to a few rules: -1. Compatibility with both Python-2 and Python-3 +0. Read PySCIPOpt design principles. (see further) -#. All tests defined in the Continuous Integration setup need to pass: +1. Compatibility with both Python-2 and Python-3. - - `.travis.yml <.travis.yml>`__ - - `appveyor.yml `__ +2. All tests defined in the Continuous Integration setup need to pass: -#. New features should be covered by the tests, so please extend `tests `__ + - `.travis.yml <.travis.yml>`__; + - `appveyor.yml `__. -#. New code should be documented in the same style as the rest of the code +3. New features should be covered by tests *and* examples, so please extend `tests `__ and `examples `__. Tests must be implemented with pytest and examples must be as clear as possible for PySCIPOpt newcomers (even advanced examples). **N.B.**: *this is not optional!* -#. New code should be `pep8-compliant `__. Help yourself with the `style guide checker `__. +4. New code should be documented in the same style as the rest of the code. + +5. New code should be `pep8-compliant `__. Help yourself with the `style guide checker `__. + +6. Before implementing a new PySCIPOpt feature, check whether the feature exists in SCIP. If so, implement it as a pure wrapper, mimicking SCIP whenever possible. If the new feature does not exist in SCIP but it is close to an existin +g one, consider if implementing that way is substantially convenient (e.g. Pythonic). If it does something completely different, you are welcome to pull your request and discuss the implementation. + +For general reference, we suggest: + + - `PySCIPOpt README `__; + - `SCIP documentation `__; + - `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); + - `open and closed PySCIPOpt issues `__; + - `SCIP/PySCIPOpt Stack Exchange `__. + +If you find this contributing guide unclear, please open an issue! :) + +Design principles of PySCIPOpt +============================== + +PySCIPOpt is meant to be a fast-prototyping interface of the pure SCIP C API. By design, we distinguish different functions in PySCIPOPT: + +- pure wrapping functions of SCIP; +- convenience functions. + +*PySCIPOpt wrappers of SCIP functions* should act with an expected behavior - and parameters, returns, attributes, ... - as close to SCIP as possible without *"breaking"* Python and the purpose for what the language it is meant. Ideally speaking, we want every SCIP function to be wrapped in PySCIPOpt. + +*Convenience functions* are additional, non-detrimental features meant to help prototyping the Python way. Since these functions are not in SCIP, we wish to limit them to prevent difference in features between SCIP and PySCIPOPT, which are always difficult to maintain. A few convenience functions survive in PySCIPOpt when keeping them is doubtless beneficial. + +Admittedly, *there is a middle ground where functions are not completely wrappers or just convenient*. That is the case, for instance, of fundamental `Model` methods like `addCons` or `writeProblem`. We want to leave their development to negotiation. From 687c81aa291e994d5a3cb7b0c44f773a2563bf1d Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 3 Jul 2018 10:01:27 +0200 Subject: [PATCH 056/121] use public download and nice cmake commands --- .travis.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ac453220..025598ebf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,17 +13,15 @@ notifications: email: false before_install: - - wget --user=opti-test --password=$PASSWORD -O scipoptsuite-$VERSION.tgz http://opti-test.zib.de/scipoptsuite-v600-rc06.tgz + # - wget --user=opti-test --password=$PASSWORD -O scipoptsuite-$VERSION.tgz http://opti-test.zib.de/scipoptsuite-v600-rc06.tgz + - wget http://scip.zib.de/download/release/scipoptsuite-$VERSION.tgz - tar xf scipoptsuite-$VERSION.tgz - - cd scipoptsuite-$VERSION - - mkdir build - - cd build; cmake .. -DCMAKE_C_STANDARD=99 -DCMAKE_INSTALL_PREFIX=~/scipdir; make -j8 install; cd .. - - cd .. - - rm -rf scipoptsuite-$VERSION + - cmake -Hscipoptsuite-$VERSION -Bbuild -DCMAKE_INSTALL_PREFIX=~/scipdir + - cmake --build build --target install install: - pip install cython networkx pytest-cov #codecov - - # SCIPOPTDIR=~/scipdir python setup.py build_ext --inplace --define CYTHON_TRACE + # - SCIPOPTDIR=~/scipdir python setup.py build_ext --inplace --define CYTHON_TRACE - SCIPOPTDIR=~/scipdir python setup.py install script: py.test #--cov From 5b1e46109a65951eb1d4eb57fa74317d59151976 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 3 Jul 2018 12:10:53 +0200 Subject: [PATCH 057/121] use public download in appveyor CI --- appveyor.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5048f372a..c64f881d4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,11 +26,12 @@ environment: TWINE: C:\Python36-x64\Scripts\twine install: - - ps: $uri = 'http://opti-test.zib.de/v600-rc06/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe' - - ps: $user = 'opti-test' - - ps: $pass = $env:optipw | ConvertTo-SecureString -AsPlainText -Force - - ps: $cred = New-Object Management.Automation.PSCredential ($user, ($pass)) - - ps: Invoke-WebRequest -Uri $uri -Credential $cred -OutFile 'scipopt-installer.exe' + # - ps: $uri = 'http://opti-test.zib.de/v600-rc06/scip/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe' + # - ps: $user = 'opti-test' + # - ps: $pass = $env:optipw | ConvertTo-SecureString -AsPlainText -Force + # - ps: $cred = New-Object Management.Automation.PSCredential ($user, ($pass)) + # - ps: Invoke-WebRequest -Uri $uri -Credential $cred -OutFile 'scipopt-installer.exe' + - wget http://scip.zib.de/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe -outfile scipopt-installer.exe - scipopt-installer.exe /S /D=%SCIPOPTDIR% - set PATH=%SCIPOPTDIR%\bin;%PYTHON%;%PATH% - if [%INCLUDE_REQUIRED%]==[true] copy .\VC9-include\* %SCIPOPTDIR%\include From 80f6bc459b3695b1f10a7e1b12299ccacf8ed340 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 3 Jul 2018 12:15:12 +0200 Subject: [PATCH 058/121] fix typo in appveyor yaml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c64f881d4..228e8c477 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ install: # - ps: $pass = $env:optipw | ConvertTo-SecureString -AsPlainText -Force # - ps: $cred = New-Object Management.Automation.PSCredential ($user, ($pass)) # - ps: Invoke-WebRequest -Uri $uri -Credential $cred -OutFile 'scipopt-installer.exe' - - wget http://scip.zib.de/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe -outfile scipopt-installer.exe + - ps: wget http://scip.zib.de/download/release/SCIPOptSuite-6.0.0-win64-VS15.exe -outfile scipopt-installer.exe - scipopt-installer.exe /S /D=%SCIPOPTDIR% - set PATH=%SCIPOPTDIR%\bin;%PYTHON%;%PATH% - if [%INCLUDE_REQUIRED%]==[true] copy .\VC9-include\* %SCIPOPTDIR%\include From 66ce12ee99a81a19678b5daa61e8e127052ebe05 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 3 Jul 2018 12:49:38 +0200 Subject: [PATCH 059/121] update recommended/required SCIP version to 6.0.0 --- src/pyscipopt/scip.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 49eb10b51..1b6f73efe 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -21,9 +21,9 @@ include "propagator.pxi" include "sepa.pxi" # recommended SCIP version; major version is required -MAJOR = 5 +MAJOR = 6 MINOR = 0 -PATCH = 1 +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 From 4108e90fbbcfde9ff5c3c13252f131be918d8e93 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 3 Jul 2018 12:51:28 +0200 Subject: [PATCH 060/121] increase version to 2.0.0 --- src/pyscipopt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 558d0efcb..81b17bf74 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.5.0' +__version__ = '2.0.0' # export user-relevant objects: from pyscipopt.Multidict import multidict From 51387b3b242b2917906bc487879ead27ab3f856a Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 4 Jul 2018 09:52:34 +0200 Subject: [PATCH 061/121] use correct output file name in writeProblem() --- src/pyscipopt/scip.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 1b6f73efe..35b8d1f79 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -867,14 +867,14 @@ cdef class Model: fn = str_conversion(filename) fn, ext = splitext(fn) if len(ext) == 0: - filename += '.cip' ext = str_conversion('.cip') + fn = fn + ext ext = ext[1:] if trans: PY_SCIP_CALL(SCIPwriteTransProblem(self._scip, fn, ext, False)) else: PY_SCIP_CALL(SCIPwriteOrigProblem(self._scip, fn, ext, False)) - print('wrote problem to file ' + filename) + print('wrote problem to file ' + str(fn)) # Variable Functions From bb2da7ddf141908dfde230ca7f72d41f2bc53372 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Fri, 6 Jul 2018 10:34:13 +0200 Subject: [PATCH 062/121] cosmetics --- CONTRIBUTING.rst | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e467a35c6..09a4b094b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -3,23 +3,22 @@ Contributing to PySCIPOpt Code contributions are very welcome and should comply to a few rules: -0. Read PySCIPOpt design principles. (see further) +0. Read `Design principles of PySCIPOpt`_. 1. Compatibility with both Python-2 and Python-3. 2. All tests defined in the Continuous Integration setup need to pass: - - `.travis.yml <.travis.yml>`__; - - `appveyor.yml `__. + - `.travis.yml <.travis.yml>`__ + - `appveyor.yml `__ -3. New features should be covered by tests *and* examples, so please extend `tests `__ and `examples `__. Tests must be implemented with pytest and examples must be as clear as possible for PySCIPOpt newcomers (even advanced examples). **N.B.**: *this is not optional!* +3. New features should be covered by tests *and* examples. Please extend `tests `__ and `examples `__. Tests uses pytest and examples are meant to be accessible for PySCIPOpt newcomers (even advanced examples). 4. New code should be documented in the same style as the rest of the code. 5. New code should be `pep8-compliant `__. Help yourself with the `style guide checker `__. -6. Before implementing a new PySCIPOpt feature, check whether the feature exists in SCIP. If so, implement it as a pure wrapper, mimicking SCIP whenever possible. If the new feature does not exist in SCIP but it is close to an existin -g one, consider if implementing that way is substantially convenient (e.g. Pythonic). If it does something completely different, you are welcome to pull your request and discuss the implementation. +6. Before implementing a new PySCIPOpt feature, check whether the feature exists in SCIP. If so, implement it as a pure wrapper, mimicking SCIP whenever possible. If the new feature does not exist in SCIP but it is close to an existing one, consider if implementing that way is substantially convenient (e.g. Pythonic). If it does something completely different, you are welcome to pull your request and discuss the implementation. For general reference, we suggest: @@ -39,8 +38,13 @@ PySCIPOpt is meant to be a fast-prototyping interface of the pure SCIP C API. By - pure wrapping functions of SCIP; - convenience functions. -*PySCIPOpt wrappers of SCIP functions* should act with an expected behavior - and parameters, returns, attributes, ... - as close to SCIP as possible without *"breaking"* Python and the purpose for what the language it is meant. Ideally speaking, we want every SCIP function to be wrapped in PySCIPOpt. +**PySCIPOpt wrappers of SCIP functions** should act: -*Convenience functions* are additional, non-detrimental features meant to help prototyping the Python way. Since these functions are not in SCIP, we wish to limit them to prevent difference in features between SCIP and PySCIPOPT, which are always difficult to maintain. A few convenience functions survive in PySCIPOpt when keeping them is doubtless beneficial. +- with an expected behavior - and parameters, returns, attributes, ... - as close to SCIP as possible +- without *"breaking"* Python and the purpose for what the language it is meant. -Admittedly, *there is a middle ground where functions are not completely wrappers or just convenient*. That is the case, for instance, of fundamental `Model` methods like `addCons` or `writeProblem`. We want to leave their development to negotiation. +Ideally speaking, we want every SCIP function to be wrapped in PySCIPOpt. + +**Convenience functions** are additional, non-detrimental features meant to help prototyping the Python way. Since these functions are not in SCIP, we wish to limit them to prevent difference in features between SCIP and PySCIPOPT, which are always difficult to maintain. A few convenience functions survive in PySCIPOpt when keeping them is doubtless beneficial. + +Admittedly, *there is a middle ground where functions are not completely wrappers or just convenient*. That is the case, for instance, of fundamental :code:`Model` methods like :code:`addCons` or :code:`writeProblem`. We want to leave their development to negotiation. From ed64699354777b4489a91f1a4d9712fc2ca08203 Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Sun, 8 Jul 2018 08:20:43 +0200 Subject: [PATCH 063/121] fix markowitz example --- examples/finished/markowitz_soco.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/finished/markowitz_soco.py b/examples/finished/markowitz_soco.py index 1de14c7f5..ef8db726f 100644 --- a/examples/finished/markowitz_soco.py +++ b/examples/finished/markowitz_soco.py @@ -25,7 +25,10 @@ def markowitz(I,sigma,r,alpha): model.addCons(quicksum(r[i]*x[i] for i in I) >= alpha) model.addCons(quicksum(x[i] for i in I) == 1) - model.setObjective(quicksum(sigma[i]**2 * x[i] * x[i] for i in I), "minimize") + # set nonlinear objective: SCIP only allow for linear objectives hence the following + obj = model.addVar(vtype="C", name="objective", lb = None, ub = None) # auxiliary variable to represent objective + model.addCons(quicksum(sigma[i]**2 * x[i] * x[i] for i in I) <= obj) + model.setObjective(obj, "minimize") model.data = x return model From fea0fe809445588d2343e57864c9712369af7b70 Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Thu, 14 Jun 2018 16:51:22 +0200 Subject: [PATCH 064/121] add methods for probing and diving --- src/pyscipopt/__init__.py | 1 + src/pyscipopt/scip.pxd | 32 +++++++++++ src/pyscipopt/scip.pyx | 117 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 81b17bf74..77217bc6c 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -27,3 +27,4 @@ from pyscipopt.scip import PY_SCIP_PRESOLTIMING as SCIP_PRESOLTIMING from pyscipopt.scip import PY_SCIP_HEURTIMING as SCIP_HEURTIMING from pyscipopt.scip import PY_SCIP_EVENTTYPE as SCIP_EVENTTYPE +from pyscipopt.scip import PY_SCIP_LPSOLSTAT as SCIP_LPSOLSTAT diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 31da4c885..f7830c340 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -240,6 +240,16 @@ cdef extern from "scip/scip.h": SCIP_BENDERSENFOTYPE_PSEUDO = 3 SCIP_BENDERSENFOTYPE_CHECK = 4 + ctypedef enum SCIP_LPSOLSTAT: + SCIP_LPSOLSTAT_NOTSOLVED = 0 + SCIP_LPSOLSTAT_OPTIMAL = 1 + SCIP_LPSOLSTAT_INFEASIBLE = 2 + SCIP_LPSOLSTAT_UNBOUNDEDRAY = 3 + SCIP_LPSOLSTAT_OBJLIMIT = 4 + SCIP_LPSOLSTAT_ITERLIMIT = 5 + SCIP_LPSOLSTAT_TIMELIMIT = 6 + SCIP_LPSOLSTAT_ERROR = 7 + ctypedef bint SCIP_Bool ctypedef long long SCIP_Longint @@ -421,6 +431,25 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPsetProbName(SCIP* scip, char* name) const char* SCIPgetProbName(SCIP* scip) + # Diving methods + SCIP_RETCODE SCIPstartDive(SCIP* scip) + SCIP_RETCODE SCIPchgVarObjDive(SCIP* scip, SCIP_VAR* var, SCIP_Real newobj) + SCIP_RETCODE SCIPchgVarLbDive(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound) + SCIP_RETCODE SCIPchgVarUbDive(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound) + SCIP_Real SCIPgetVarLbDive(SCIP* scip, SCIP_VAR* var) + SCIP_Real SCIPgetVarUbDive(SCIP* scip, SCIP_VAR* var) + SCIP_RETCODE SCIPsolveDiveLP(SCIP* scip, int itlim, SCIP_Bool* lperror, SCIP_Bool* cutoff) + SCIP_RETCODE SCIPchgRowLhsDive(SCIP* scip, SCIP_ROW* row, SCIP_Real newlhs) + SCIP_RETCODE SCIPchgRowRhsDive(SCIP* scip, SCIP_ROW* row, SCIP_Real newrhs) + SCIP_RETCODE SCIPaddRowDive(SCIP* scip, SCIP_ROW* row) + SCIP_RETCODE SCIPendDive(SCIP* scip) + + # Probing methods + SCIP_RETCODE SCIPstartProbing(SCIP* scip) + SCIP_RETCODE SCIPchgVarObjProbing(SCIP* scip, SCIP_VAR* var, SCIP_Real newobj) + SCIP_RETCODE SCIPsolveProbingLP(SCIP* scip, int itlim, SCIP_Bool* lperror, SCIP_Bool* cutoff) + SCIP_RETCODE SCIPendProbing(SCIP* scip) + # Event Methods SCIP_RETCODE SCIPcatchEvent(SCIP* scip, SCIP_EVENTTYPE eventtype, @@ -469,6 +498,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPsetObjsense(SCIP* scip, SCIP_OBJSENSE objsense) SCIP_OBJSENSE SCIPgetObjsense(SCIP* scip) SCIP_RETCODE SCIPsetObjlimit(SCIP* scip, SCIP_Real objlimit) + SCIP_RETCODE SCIPgetObjlimit(SCIP* scip) SCIP_RETCODE SCIPaddObjoffset(SCIP* scip, SCIP_Real addval) SCIP_RETCODE SCIPaddOrigObjoffset(SCIP* scip, SCIP_Real addval) SCIP_Real SCIPgetOrigObjoffset(SCIP* scip) @@ -541,6 +571,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPgetLPBInvRow(SCIP* scip, int r, SCIP_Real* coefs, int* inds, int* ninds) SCIP_RETCODE SCIPgetLPBInvARow(SCIP* scip, int r, SCIP_Real* binvrow, SCIP_Real* coefs, int* inds, int* ninds) SCIP_Bool SCIPisLPSolBasic(SCIP* scip) + SCIP_LPSOLSTAT SCIPgetLPSolstat(SCIP* scip) int SCIPgetNLPRows(SCIP* scip) int SCIPgetNLPCols(SCIP* scip) @@ -607,6 +638,7 @@ cdef extern from "scip/scip.h": SCIP_Real lhs, SCIP_Real rhs, SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool removable) SCIP_RETCODE SCIPaddRow(SCIP* scip, SCIP_ROW* row, SCIP_Bool forcecut, SCIP_Bool* infeasible) SCIP_RETCODE SCIPcreateEmptyRowSepa(SCIP* scip, SCIP_ROW** row, SCIP_SEPA* sepa, const char* name, SCIP_Real lhs, SCIP_Real rhs, SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool removable) + SCIP_RETCODE SCIPcreateEmptyRowUnspec(SCIP* scip, SCIP_ROW** row, const char* name, SCIP_Real lhs, SCIP_Real rhs, SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool removable) SCIP_Real SCIPgetRowActivity(SCIP* scip, SCIP_ROW* row) SCIP_Real SCIPgetRowLPActivity(SCIP* scip, SCIP_ROW* row) SCIP_RETCODE SCIPreleaseRow(SCIP* scip, SCIP_ROW** row) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 35b8d1f79..1ef32089c 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -183,6 +183,16 @@ cdef class PY_SCIP_EVENTTYPE: ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED SYNC = SCIP_EVENTTYPE_SYNC +cdef class PY_SCIP_LPSOLSTAT: + NOTSOLVED = SCIP_LPSOLSTAT_NOTSOLVED + OPTIMAL = SCIP_LPSOLSTAT_OPTIMAL + INFEASIBLE = SCIP_LPSOLSTAT_INFEASIBLE + UNBOUNDEDRAY = SCIP_LPSOLSTAT_UNBOUNDEDRAY + OBJLIMIT = SCIP_LPSOLSTAT_OBJLIMIT + ITERLIMIT = SCIP_LPSOLSTAT_ITERLIMIT + TIMELIMIT = SCIP_LPSOLSTAT_TIMELIMIT + ERROR = SCIP_LPSOLSTAT_ERROR + def PY_SCIP_CALL(SCIP_RETCODE rc): if rc == SCIP_OKAY: @@ -745,6 +755,10 @@ cdef class Model: """ PY_SCIP_CALL(SCIPsetObjlimit(self._scip, objlimit)) + def setObjlimit(self, objlimit): + """returns current limit on objective function.""" + return SCIPgetObjlimit(self._scip) + def setObjective(self, coeffs, sense = 'minimize', clear = 'true'): """Establish the objective function as a linear expression. @@ -1010,6 +1024,9 @@ cdef class Model: return [Variable.create(_vars[i]) for i in range(_nvars)] # LP Methods + def getLPSolstat(self): + return SCIPgetLPSolstat(self._scip) + def getLPColsData(self): """Retrieve current LP columns""" cdef SCIP_COL** cols @@ -1073,6 +1090,15 @@ cdef class Model: PyRow = Row.create(row) return PyRow + def createEmptyRowUnspec(self, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True): + """creates and captures an LP row without any coefficients from an unspecified source""" + cdef SCIP_ROW* row + lhs = -SCIPinfinity(self._scip) if lhs is None else lhs + rhs = SCIPinfinity(self._scip) if rhs is None else rhs + PY_SCIP_CALL(SCIPcreateEmptyRowUnspec(self._scip, &row, str_conversion(name), lhs, rhs, local, modifiable, removable)) + PyRow = Row.create(row) + return PyRow + def getRowActivity(self, Row row): return SCIPgetRowActivity(self._scip, row.row) @@ -2425,6 +2451,97 @@ cdef class Model: benders.model = weakref.proxy(self) Py_INCREF(benders) + # Diving methods (Diving is LP related) + def startDive(self): + """Initiates LP diving + It allows the user to change the LP in several ways, solve, change again, etc, without affecting the actual LP that has. When endDive() is called, + SCIP will undo all changes done and recover the LP it had before startDive + """ + PY_SCIP_CALL(SCIPstartDive(self._scip)) + + def endDive(self): + """Quits probing and resets bounds and constraints to the focus node's environment""" + PY_SCIP_CALL(SCIPendDive(self._scip)) + + def chgVarObjDive(self, Variable var, newobj): + """changes (column) variable's objective value in current dive""" + PY_SCIP_CALL(SCIPchgVarObjDive(self._scip, var.var, newobj)) + + def chgVarLbDive(self, Variable var, newbound): + """changes variable's current lb in current dive""" + PY_SCIP_CALL(SCIPchgVarLbDive(self._scip, var.var, newbound)) + + def chgVarUbDive(self, Variable var, newbound): + """changes variable's current ub in current dive""" + PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, var.var, newbound)) + + def getVarLbDive(self, Variable var): + """returns variable's current lb in current dive""" + return SCIPgetVarLbDive(self._scip, var.var) + + def getVarUbDive(self, Variable var): + """returns variable's current ub in current dive""" + return SCIPgetVarUbDive(self._scip, var.var) + + def chgRowLhsDive(self, Row row, newlhs): + """changes row lhs in current dive, change will be undone after diving + ends, for permanent changes use SCIPchgRowLhs() + """ + PY_SCIP_CALL(SCIPchgRowLhsDive(self._scip, row.row, newlhs)) + + def chgRowRhsDive(self, Row row, newrhs): + """changes row rhs in current dive, change will be undone after diving + ends, for permanent changes use SCIPchgRowLhs() + """ + PY_SCIP_CALL(SCIPchgRowRhsDive(self._scip, row.row, newrhs)) + + def addRowDive(self, Row row): + """adds a row to the LP in current dive""" + PY_SCIP_CALL(SCIPaddRowDive(self._scip, row.row)) + + def solveDiveLP(self, itlim = -1): + """solves the LP of the current dive no separation or pricing is applied + no separation or pricing is applied + :param itlim: maximal number of LP iterations to perform (Default value = -1, that is, no limit) + returns two booleans: + lperror -- if an unresolved lp error occured + cutoff -- whether the LP was infeasible or the objective limit was reached + """ + cdef SCIP_Bool lperror + cdef SCIP_Bool cutoff + + PY_SCIP_CALL(SCIPsolveDiveLP(self._scip, itlim, &lperror, &cutoff)) + return lperror, cutoff + + # Probing methods (Probing is tree based) + def startProbing(self): + """Initiates probing, making methods SCIPnewProbingNode(), SCIPbacktrackProbing(), SCIPchgVarLbProbing(), + SCIPchgVarUbProbing(), SCIPfixVarProbing(), SCIPpropagateProbing(), SCIPsolveProbingLP(), etc available + """ + PY_SCIP_CALL(SCIPstartProbing(self._scip)) + + def endProbing(self): + """Quits probing and resets bounds and constraints to the focus node's environment""" + PY_SCIP_CALL(SCIPendProbing(self._scip)) + + def chgVarObjProbing(self, Variable var, newobj): + """changes (column) variable's objective value during probing mode""" + PY_SCIP_CALL(SCIPchgVarObjProbing(self._scip, var.var, newobj)) + + def solveProbingLP(self, itlim = -1): + """solves the LP at the current probing node (cannot be applied at preprocessing stage) + no separation or pricing is applied + :param itlim: maximal number of LP iterations to perform (Default value = -1, that is, no limit) + returns two booleans: + lperror -- if an unresolved lp error occured + cutoff -- whether the LP was infeasible or the objective limit was reached + """ + cdef SCIP_Bool lperror + cdef SCIP_Bool cutoff + + PY_SCIP_CALL(SCIPsolveProbingLP(self._scip, itlim, &lperror, &cutoff)) + return lperror, cutoff + # Solution functions def createSol(self, Heur heur = None): From 14a8348435e57783cf70c12478249bf5bcc93acf Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Fri, 20 Jul 2018 10:37:50 +0200 Subject: [PATCH 065/121] add missing comment --- src/pyscipopt/scip.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 1ef32089c..03264a5ed 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1025,6 +1025,7 @@ cdef class Model: # LP Methods def getLPSolstat(self): + """Gets solution status of current LP""" return SCIPgetLPSolstat(self._scip) def getLPColsData(self): From 9b5b51b07ce8eac139b4199f457521c88490fe02 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 20 Jul 2018 12:58:41 +0200 Subject: [PATCH 066/121] perform automatic parameter value conversion in setParam --- src/pyscipopt/scip.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 03264a5ed..ae16e5ddc 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2968,13 +2968,13 @@ cdef class Model: paramtype = SCIPparamGetType(param) if paramtype == SCIP_PARAMTYPE_BOOL: - PY_SCIP_CALL(SCIPsetBoolParam(self._scip, n, value)) + PY_SCIP_CALL(SCIPsetBoolParam(self._scip, n, bool(int(value)))) elif paramtype == SCIP_PARAMTYPE_INT: - PY_SCIP_CALL(SCIPsetIntParam(self._scip, n, value)) + PY_SCIP_CALL(SCIPsetIntParam(self._scip, n, int(value))) elif paramtype == SCIP_PARAMTYPE_LONGINT: - PY_SCIP_CALL(SCIPsetLongintParam(self._scip, n, value)) + PY_SCIP_CALL(SCIPsetLongintParam(self._scip, n, int(value))) elif paramtype == SCIP_PARAMTYPE_REAL: - PY_SCIP_CALL(SCIPsetRealParam(self._scip, n, value)) + PY_SCIP_CALL(SCIPsetRealParam(self._scip, n, float(value))) elif paramtype == SCIP_PARAMTYPE_CHAR: PY_SCIP_CALL(SCIPsetCharParam(self._scip, n, value)) elif paramtype == SCIP_PARAMTYPE_STRING: From 1f7fd5dc0e3288559291f8c47086a4b7ffac1da3 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 20 Jul 2018 15:18:25 +0200 Subject: [PATCH 067/121] Update CONTRIBUTING.rst --- CONTRIBUTING.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 09a4b094b..d9947709e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -9,8 +9,8 @@ Code contributions are very welcome and should comply to a few rules: 2. All tests defined in the Continuous Integration setup need to pass: - - `.travis.yml <.travis.yml>`__ - - `appveyor.yml `__ + - `.travis.yml <.travis.yml>`__ + - `appveyor.yml `__ 3. New features should be covered by tests *and* examples. Please extend `tests `__ and `examples `__. Tests uses pytest and examples are meant to be accessible for PySCIPOpt newcomers (even advanced examples). @@ -22,11 +22,11 @@ Code contributions are very welcome and should comply to a few rules: For general reference, we suggest: - - `PySCIPOpt README `__; - - `SCIP documentation `__; - - `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); - - `open and closed PySCIPOpt issues `__; - - `SCIP/PySCIPOpt Stack Exchange `__. +- `PySCIPOpt README `__; +- `SCIP documentation `__; +- `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); +- `open and closed PySCIPOpt issues `__; +- `SCIP/PySCIPOpt Stack Exchange `__. If you find this contributing guide unclear, please open an issue! :) From 1facb3e8dca10b4d9df3dae8ccff98f74317bcf9 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 20 Jul 2018 15:19:11 +0200 Subject: [PATCH 068/121] Update CONTRIBUTING.rst --- CONTRIBUTING.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d9947709e..c54f5898c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,13 +20,13 @@ Code contributions are very welcome and should comply to a few rules: 6. Before implementing a new PySCIPOpt feature, check whether the feature exists in SCIP. If so, implement it as a pure wrapper, mimicking SCIP whenever possible. If the new feature does not exist in SCIP but it is close to an existing one, consider if implementing that way is substantially convenient (e.g. Pythonic). If it does something completely different, you are welcome to pull your request and discuss the implementation. -For general reference, we suggest: + For general reference, we suggest: -- `PySCIPOpt README `__; -- `SCIP documentation `__; -- `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); -- `open and closed PySCIPOpt issues `__; -- `SCIP/PySCIPOpt Stack Exchange `__. + - `PySCIPOpt README `__; + - `SCIP documentation `__; + - `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); + - `open and closed PySCIPOpt issues `__; + - `SCIP/PySCIPOpt Stack Exchange `__. If you find this contributing guide unclear, please open an issue! :) From d882fa7da542279953c5c5a58923e80f12d1c52a Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 20 Jul 2018 15:20:36 +0200 Subject: [PATCH 069/121] Revert "Update CONTRIBUTING.rst" This reverts commit 1facb3e8dca10b4d9df3dae8ccff98f74317bcf9. --- CONTRIBUTING.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c54f5898c..d9947709e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,13 +20,13 @@ Code contributions are very welcome and should comply to a few rules: 6. Before implementing a new PySCIPOpt feature, check whether the feature exists in SCIP. If so, implement it as a pure wrapper, mimicking SCIP whenever possible. If the new feature does not exist in SCIP but it is close to an existing one, consider if implementing that way is substantially convenient (e.g. Pythonic). If it does something completely different, you are welcome to pull your request and discuss the implementation. - For general reference, we suggest: +For general reference, we suggest: - - `PySCIPOpt README `__; - - `SCIP documentation `__; - - `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); - - `open and closed PySCIPOpt issues `__; - - `SCIP/PySCIPOpt Stack Exchange `__. +- `PySCIPOpt README `__; +- `SCIP documentation `__; +- `SCIP mailing list `__ which can be easily searched with search engines (e.g. `Google `__); +- `open and closed PySCIPOpt issues `__; +- `SCIP/PySCIPOpt Stack Exchange `__. If you find this contributing guide unclear, please open an issue! :) From d446b3d4c6bcc786cf0b490887a76877afbef134 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 20 Jul 2018 20:28:32 +0200 Subject: [PATCH 070/121] add VSCode directory to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b658d0bee..e3a3dc5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,6 @@ venv.bak/ # model (for tests) model + +# VSCode +.vscode/ \ No newline at end of file From 92d62ff9a7197a3546628c2a581e29950a768a3b Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Fri, 27 Jul 2018 15:10:20 +0100 Subject: [PATCH 071/121] adds BendersDecomp class and update lower bound function --- src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pyx | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 31da4c885..bb8f1804c 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -896,6 +896,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPfreeBendersSubproblem(SCIP* scip, SCIP_BENDERS* benders, int probnumber) int SCIPgetNActiveBenders(SCIP* scip) SCIP_BENDERS** SCIPgetBenders(SCIP* scip) + void SCIPbendersUpdateSubproblemLowerbound(SCIP_BENDERS* benders, int probnumber, SCIP_Real lowerbound) # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 35b8d1f79..c3182e373 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -557,6 +557,25 @@ cdef class Constraint: constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.cons))).decode('UTF-8') return constype == 'quadratic' +cdef class BendersDecomp: + cdef SCIP_BENDERS* benders + cdef public object data #storage for python user + + @staticmethod + cdef create(SCIP_BENDERS* scipbenders): + if scipbenders == NULL: + raise Warning("cannot create Benders with SCIP_BENDERS* == NULL") + benders = BendersDecomp() + benders.benders = scipbenders + return benders + + def updateLowerbounds(self, lowerbounds): + """"updates the subproblem lower bounds using the lowerbounds dict""" + assert type(lowerbounds) is dict + for d in lowerbounds.keys(): + SCIPbendersUpdateSubproblemLowerbound(self.benders, d, + lowerbounds[d]) + # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" # - interface SCIPfreeProb() @@ -2095,6 +2114,7 @@ cdef class Model: subproblems -- a single Model instance or dictionary of Model instances """ cdef SCIP** subprobs + cdef SCIP_BENDERS* scipbenders # checking whether subproblems is a dictionary if isinstance(subproblems, dict): @@ -2116,12 +2136,16 @@ cdef class Model: # creating the default Benders' decomposition PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems)) + scipbenders = SCIPfindBenders(self._scip, "default") + pyBenders = BendersDecomp.create(scipbenders) # activating the Benders' decomposition constraint handlers self.setBoolParam("constraints/benderslp/active", True) self.setBoolParam("constraints/benders/active", True) #self.setIntParam("limits/maxorigsol", 0) + return pyBenders + def computeBestSolSubproblems(self): """Solves the subproblems with the best solution to the master problem. Afterwards, the best solution from each subproblem can be queried to get From 997fed4de98d80737cbabbfd6d59e2e52392a28d Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Mon, 30 Jul 2018 12:24:56 +0100 Subject: [PATCH 072/121] changes scipbenders to benders --- src/pyscipopt/scip.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index c3182e373..95078b637 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2114,7 +2114,7 @@ cdef class Model: subproblems -- a single Model instance or dictionary of Model instances """ cdef SCIP** subprobs - cdef SCIP_BENDERS* scipbenders + cdef SCIP_BENDERS* benders # checking whether subproblems is a dictionary if isinstance(subproblems, dict): @@ -2136,8 +2136,8 @@ cdef class Model: # creating the default Benders' decomposition PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems)) - scipbenders = SCIPfindBenders(self._scip, "default") - pyBenders = BendersDecomp.create(scipbenders) + benders = SCIPfindBenders(self._scip, "default") + pyBenders = BendersDecomp.create(benders) # activating the Benders' decomposition constraint handlers self.setBoolParam("constraints/benderslp/active", True) From ae3f4e0bb3c52155dd6cd10ebb7e7e6bfb88dcbc Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Mon, 30 Jul 2018 14:58:25 +0100 Subject: [PATCH 073/121] moves update Benders lower bounds to Model --- src/pyscipopt/benders.pxi | 1 + src/pyscipopt/scip.pyx | 41 ++++++++++++++++++--------------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/pyscipopt/benders.pxi b/src/pyscipopt/benders.pxi index 854891b6d..f74f46d77 100644 --- a/src/pyscipopt/benders.pxi +++ b/src/pyscipopt/benders.pxi @@ -1,5 +1,6 @@ cdef class Benders: cdef public Model model + cdef public str name def bendersfree(self): pass diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 95078b637..0ce96d593 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -557,25 +557,6 @@ cdef class Constraint: constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.cons))).decode('UTF-8') return constype == 'quadratic' -cdef class BendersDecomp: - cdef SCIP_BENDERS* benders - cdef public object data #storage for python user - - @staticmethod - cdef create(SCIP_BENDERS* scipbenders): - if scipbenders == NULL: - raise Warning("cannot create Benders with SCIP_BENDERS* == NULL") - benders = BendersDecomp() - benders.benders = scipbenders - return benders - - def updateLowerbounds(self, lowerbounds): - """"updates the subproblem lower bounds using the lowerbounds dict""" - assert type(lowerbounds) is dict - for d in lowerbounds.keys(): - SCIPbendersUpdateSubproblemLowerbound(self.benders, d, - lowerbounds[d]) - # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" # - interface SCIPfreeProb() @@ -2137,15 +2118,12 @@ cdef class Model: # creating the default Benders' decomposition PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems)) benders = SCIPfindBenders(self._scip, "default") - pyBenders = BendersDecomp.create(benders) # activating the Benders' decomposition constraint handlers self.setBoolParam("constraints/benderslp/active", True) self.setBoolParam("constraints/benders/active", True) #self.setIntParam("limits/maxorigsol", 0) - return pyBenders - def computeBestSolSubproblems(self): """Solves the subproblems with the best solution to the master problem. Afterwards, the best solution from each subproblem can be queried to get @@ -2192,6 +2170,24 @@ cdef class Model: PY_SCIP_CALL(SCIPfreeBendersSubproblem(self._scip, _benders[i], j)) + def updateBendersLowerbounds(self, lowerbounds, Benders benders = None): + """"updates the subproblem lower bounds for benders using + the lowerbounds dict. If benders is None, then the default + Benders' decomposition is updated + """ + cdef SCIP_BENDERS* _benders + + assert type(lowerbounds) is dict + + if benders is None: + _benders = SCIPfindBenders(self._scip, "default") + else: + n = str_conversion(benders.name) + _benders = SCIPfindBenders(self._scip, n) + + for d in lowerbounds.keys(): + SCIPbendersUpdateSubproblemLowerbound(_benders, d, lowerbounds[d]) + def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): """Include an event handler. @@ -2447,6 +2443,7 @@ cdef class Model: cdef SCIP_BENDERS* scip_benders scip_benders = SCIPfindBenders(self._scip, n) benders.model = weakref.proxy(self) + benders.name = name Py_INCREF(benders) # Solution functions From b6ed098c3edab8c05ff966a28c3a7ce0486c197e Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Mon, 30 Jul 2018 15:06:20 +0100 Subject: [PATCH 074/121] fix for PEP8 style --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 0ce96d593..ff692ae77 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2170,7 +2170,7 @@ cdef class Model: PY_SCIP_CALL(SCIPfreeBendersSubproblem(self._scip, _benders[i], j)) - def updateBendersLowerbounds(self, lowerbounds, Benders benders = None): + def updateBendersLowerbounds(self, lowerbounds, Benders benders=None): """"updates the subproblem lower bounds for benders using the lowerbounds dict. If benders is None, then the default Benders' decomposition is updated From e5d803a4d30c4b2423eb3a4208b55da34d73d235 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Thu, 2 Aug 2018 12:56:26 +0200 Subject: [PATCH 075/121] add section about common installation errors --- INSTALL.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 83ab5e7de..4a383b14a 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -80,12 +80,22 @@ Suite for this to work (``cmake .. -DCMAKE_BUILD_TYPE=Debug``). Testing new installation ======================== -To test your brand-new installation of PySCIPOpt you need `pytest `__ on your system. Here is the `installation procedure `__. +To test your brand-new installation of PySCIPOpt you need `pytest `__ on your system. Here is the `installation procedure `__. Tests can be run in the ``PySCIPOpt`` directory with: :: py.test # all the available tests py.test tests/test_name.py # a specific tests/test_name.py (Unix) - + Ideally, the status of your tests must be passed or skipped. Running tests with pytest creates the ``__pycache__`` directory in ``tests`` and, occasionally, a ``model`` file in the working directory. They can be removed harmlessly. + +Common errors +============= + +- readline: ``libreadline.so.6: undefined symbol: PC`` + This is a readline/ncurses compatibility issue that can be fixed like this (when using ``conda``): + + :: + + conda install -c conda-forge readline=6.2 From 6455b8629e12ab11bad3e7c7ae2e6739af626294 Mon Sep 17 00:00:00 2001 From: Stanley Schade Date: Mon, 13 Aug 2018 19:45:02 +0200 Subject: [PATCH 076/121] Add function to redirect SCIP output via Python compare issue https://github.com/SCIP-Interfaces/PySCIPOpt/issues/116 --- src/pyscipopt/scip.pxd | 19 +++++++++++++++++++ src/pyscipopt/scip.pyx | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index f7830c340..0d86c0820 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -379,6 +379,9 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_MESSAGEHDLR: pass + ctypedef struct SCIP_MESSAGEHDLRDATA: + pass + ctypedef struct SCIP_LPI: pass @@ -417,10 +420,26 @@ cdef extern from "scip/scip.h": SCIP_VAR* var2 SCIP_Real coef + ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) + ctypedef void (*errormessagecallback) (void *data, FILE *file, const char *msg) + ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR *messagehdlr) + # General SCIP Methods SCIP_RETCODE SCIPcreate(SCIP** scip) SCIP_RETCODE SCIPfree(SCIP** scip) + SCIP_RETCODE SCIPmessagehdlrCreate(SCIP_MESSAGEHDLR **messagehdlr, + SCIP_Bool bufferedoutput, + const char *filename, + SCIP_Bool quiet, + messagecallback, + messagecallback, + messagecallback, + messagehdlrfree, + SCIP_MESSAGEHDLRDATA *messagehdlrdata) + + SCIP_RETCODE SCIPsetMessagehdlr(SCIP* scip, SCIP_MESSAGEHDLR* messagehdlr) void SCIPsetMessagehdlrQuiet(SCIP* scip, SCIP_Bool quiet) + void SCIPmessageSetErrorPrinting(errormessagecallback, void* data) SCIP_Real SCIPversion() void SCIPprintVersion(SCIP* scip, FILE* outfile) SCIP_Real SCIPgetTotalTime(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index ae16e5ddc..5a30e0ea4 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1,3 +1,4 @@ +from __future__ import print_function import weakref from os.path import abspath from os.path import splitext @@ -567,6 +568,13 @@ cdef class Constraint: constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.cons))).decode('UTF-8') return constype == 'quadratic' + +cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg): + print(msg, end='') + +cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): + print(msg, end='', file=sys.stderr) + # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" # - interface SCIPfreeProb() @@ -2888,6 +2896,17 @@ cdef class Model: """ SCIPsetMessagehdlrQuiet(self._scip, quiet) + # Output Methods + + def redirectOutput(self): + """Send output to python instead of terminal.""" + + cdef SCIP_MESSAGEHDLR *myMessageHandler + + PY_SCIP_CALL(SCIPmessagehdlrCreate(&myMessageHandler, False, NULL, False, relayMessage, relayMessage, relayMessage, NULL, NULL)) + PY_SCIP_CALL(SCIPsetMessagehdlr(self._scip, myMessageHandler)) + SCIPmessageSetErrorPrinting(relayErrorMessage, NULL) + # Parameter Methods def setBoolParam(self, name, value): From be64b46fe477ebe6bac57080858b51ed3bd35c4c Mon Sep 17 00:00:00 2001 From: Stanley Schade Date: Wed, 15 Aug 2018 17:37:33 +0200 Subject: [PATCH 077/121] Properly decode byte objects --- src/pyscipopt/scip.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 5a30e0ea4..13edbcfde 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -570,10 +570,10 @@ cdef class Constraint: cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg): - print(msg, end='') + print(msg.decode('UTF-8'), end='') cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): - print(msg, end='', file=sys.stderr) + print(msg.decode('UTF-8'), end='', file=sys.stderr) # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" From 6f5c7863ca1eacae96929ffca91c2737d39abd9c Mon Sep 17 00:00:00 2001 From: Stanley Schade Date: Wed, 15 Aug 2018 18:31:51 +0200 Subject: [PATCH 078/121] Replaced print by more generic functions --- src/pyscipopt/scip.pyx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 13edbcfde..4b6ebecc8 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1,4 +1,3 @@ -from __future__ import print_function import weakref from os.path import abspath from os.path import splitext @@ -570,10 +569,10 @@ cdef class Constraint: cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg): - print(msg.decode('UTF-8'), end='') + sys.stdout.write(msg.decode('UTF-8')) cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): - print(msg.decode('UTF-8'), end='', file=sys.stderr) + sys.stderr.write(msg.decode('UTF-8')) # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" From ee400c6c3928f796a9886308c661363faf3d8a3c Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 15 Aug 2018 19:01:28 +0200 Subject: [PATCH 079/121] increase version to 2.0.1 --- src/pyscipopt/__init__.py | 2 +- src/pyscipopt/scip.pxd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 77217bc6c..9d7be575a 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.0.0' +__version__ = '2.0.1' # export user-relevant objects: from pyscipopt.Multidict import multidict diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 0d86c0820..a0d0f0342 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -436,7 +436,7 @@ cdef extern from "scip/scip.h": messagecallback, messagehdlrfree, SCIP_MESSAGEHDLRDATA *messagehdlrdata) - + SCIP_RETCODE SCIPsetMessagehdlr(SCIP* scip, SCIP_MESSAGEHDLR* messagehdlr) void SCIPsetMessagehdlrQuiet(SCIP* scip, SCIP_Bool quiet) void SCIPmessageSetErrorPrinting(errormessagecallback, void* data) From 045ae56e5c62cd3684154779390780b6dad84ac5 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 24 Aug 2018 11:21:28 +0200 Subject: [PATCH 080/121] travis: try deb package on ubuntu xenial --- .travis.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 025598ebf..600e1c2fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,32 @@ +os: linux +dist: xenial +sudo: true + language: python +python: + - 2.7 + - 3.5 + - 3.6 + - 3.7 + env: global: - secure: "CML6W6GUTFcZxZavt2x9vT3pUeg9jA2tber8Wl+34zBI9QxXel8PxKlw896OI2jnGPMvL7ANRElklE6/WNaVvogjgZXKcXnqaGKPoPlJNsGenHw0poxjqrrs9VnuX2XU56h53ESsOZ9mq53oFNaimS6fbtIAs7xlS27nY0KJk42ZEicaS2E9cbzH/XqOIEzdIZCHy8NjViMXFCspE9fhndv04T3ic2opXmGDy2veoZ/oF2zbOcz0e9XLEjTs0yXz5qir8AGEnRS4lwI6hb3jkMBOxbNKIPx63gsno3xUHjXYjiwb4iQV9eybhY0csli/5br8isIX81vlg5xeoEfvSy6sZvZ8rErx3Eos5OdCu4vnxqtMZvpb+2pCVQU2IldZTl9B3/lv4ehZhKurF3l89rnqKW14eh4p2eT6WQ2s0tjPd5NuPdow4hT5x7WWSeS1395exlJJGgv1bt4ASM+KNFfA/4CK4TjszZJ7xLttiJ7nOgo/8KtSd/dM0PfBWeeBQxi/0YgCyD781ieL009ZUPwvKf4B0RJ8pPaSDePypKHvzmcm7UGgT86zz1FnCxsIEmHFJQGazXbdBmi0OvPAo1fCrAdMXipppf+ckAotckWjOLIK6IN9RlrF/E9YFll/SfSiXi6EdB0P+T6m8iBqNEToJbUiRqKhMznr7A4+JLs=" - VERSION=6.0.0 -python: - - '2.7' - - '3.6' - notifications: email: false before_install: - # - wget --user=opti-test --password=$PASSWORD -O scipoptsuite-$VERSION.tgz http://opti-test.zib.de/scipoptsuite-v600-rc06.tgz - - wget http://scip.zib.de/download/release/scipoptsuite-$VERSION.tgz - - tar xf scipoptsuite-$VERSION.tgz - - cmake -Hscipoptsuite-$VERSION -Bbuild -DCMAKE_INSTALL_PREFIX=~/scipdir - - cmake --build build --target install + - export VERSION=6.0.0 + - wget http://scip.zib.de/download/release/SCIPOptSuite-$VERSION-Linux.deb + - sudo dpkg -i SCIPOptSuite-$VERSION-Linux.deb install: - pip install cython networkx pytest-cov #codecov # - SCIPOPTDIR=~/scipdir python setup.py build_ext --inplace --define CYTHON_TRACE - - SCIPOPTDIR=~/scipdir python setup.py install + - python setup.py install script: py.test #--cov From 41c50b60ea9c6994d326239036e4f08a47e88f1d Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 24 Aug 2018 11:27:38 +0200 Subject: [PATCH 081/121] install blas, lapack and gfortran --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 600e1c2fb..98d1a0a05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ notifications: email: false before_install: + - sudo apt-get install libblas-dev liblapack-dev gfortran - export VERSION=6.0.0 - wget http://scip.zib.de/download/release/SCIPOptSuite-$VERSION-Linux.deb - sudo dpkg -i SCIPOptSuite-$VERSION-Linux.deb From 9001a4f99156dad47bf27738801722e0eb0f37f9 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 24 Aug 2018 11:35:29 +0200 Subject: [PATCH 082/121] clean up travis --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98d1a0a05..004e1f058 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ before_install: - sudo dpkg -i SCIPOptSuite-$VERSION-Linux.deb install: - - pip install cython networkx pytest-cov #codecov - # - SCIPOPTDIR=~/scipdir python setup.py build_ext --inplace --define CYTHON_TRACE + - pip install cython networkx pytest-cov - python setup.py install script: py.test #--cov From d844d3ad6d5932107c7fbbfba17bee68489f9d72 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 11 Sep 2018 12:53:09 +0200 Subject: [PATCH 083/121] compute activity before comparison with lhs/rhs for correct dualsols --- src/pyscipopt/scip.pyx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index ae2a00d05..fb4bc2eef 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2056,6 +2056,7 @@ cdef class Model: # TODO this should ideally be handled on the SCIP side cdef int _nvars cdef SCIP_VAR** _vars + cdef SCIP_Real* _vals cdef SCIP_Bool _success dual = 0.0 @@ -2072,13 +2073,14 @@ cdef class Model: dual = SCIPgetDualsolLinear(self._scip, transcons.cons) if dual == 0.0 and _nvars == 1: _vars = SCIPgetVarsLinear(self._scip, transcons.cons) - LPsol = SCIPvarGetLPSol(_vars[0]) + _vals = SCIPgetValsLinear(self._scip, transcons.cons) + activity = SCIPvarGetLPSol(_vars[0]) * _vals[0] rhs = SCIPgetRhsLinear(self._scip, transcons.cons) lhs = SCIPgetLhsLinear(self._scip, transcons.cons) - if (LPsol == rhs) or (LPsol == lhs): + if (activity == rhs) or (activity == lhs): dual = SCIPgetVarRedcost(self._scip, _vars[0]) - if self.getObjectiveSense() == "maximize": + if self.getObjectiveSense() == "maximize" and not dual == 0.0: dual = -dual except: raise Warning("no dual solution available for constraint " + cons.name) From 1f1569128764f8180bbb9715788b692eca2c9c4d Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 11 Sep 2018 15:40:28 +0200 Subject: [PATCH 084/121] use SCIP internal method for next release --- src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pyx | 58 +++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index f1bd897e2..79dca3bf2 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -670,6 +670,7 @@ cdef extern from "scip/scip.h": SCIP_Real SCIPgetDualbound(SCIP* scip) SCIP_Real SCIPgetDualboundRoot(SCIP* scip) SCIP_Real SCIPgetVarRedcost(SCIP* scip, SCIP_VAR* var) + SCIP_RETCODE SCIPgetDualSolVal(SCIP* scip, SCIP_CONS* cons, SCIP_Real* dualsolval, SCIP_Bool* boundconstraint) # Reader plugin SCIP_RETCODE SCIPincludeReader(SCIP* scip, diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index fb4bc2eef..503fdfe21 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2053,38 +2053,44 @@ cdef class Model: :param Constraint cons: linear constraint """ - # TODO this should ideally be handled on the SCIP side + cdef SCIP_Real dualsolval + cdef SCIP_Bool boundconstraint cdef int _nvars cdef SCIP_VAR** _vars cdef SCIP_Real* _vals cdef SCIP_Bool _success - dual = 0.0 - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') - if not constype == 'linear': - raise Warning("dual solution values not available for constraints of type ", constype) + if self.version() > 6.0: + PY_SCIP_CALL( SCIPgetDualSolVal(self._scip, cons.cons, &dualsolval, &boundconstraint) ) + return dualsolval + else: + dual = 0.0 - try: - _nvars = SCIPgetNVarsLinear(self._scip, cons.cons) - if cons.isOriginal(): - transcons = self.getTransformedCons(cons) - else: - transcons = cons - dual = SCIPgetDualsolLinear(self._scip, transcons.cons) - if dual == 0.0 and _nvars == 1: - _vars = SCIPgetVarsLinear(self._scip, transcons.cons) - _vals = SCIPgetValsLinear(self._scip, transcons.cons) - activity = SCIPvarGetLPSol(_vars[0]) * _vals[0] - rhs = SCIPgetRhsLinear(self._scip, transcons.cons) - lhs = SCIPgetLhsLinear(self._scip, transcons.cons) - if (activity == rhs) or (activity == lhs): - dual = SCIPgetVarRedcost(self._scip, _vars[0]) - - if self.getObjectiveSense() == "maximize" and not dual == 0.0: - dual = -dual - except: - raise Warning("no dual solution available for constraint " + cons.name) - return dual + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + if not constype == 'linear': + raise Warning("dual solution values not available for constraints of type ", constype) + + try: + _nvars = SCIPgetNVarsLinear(self._scip, cons.cons) + if cons.isOriginal(): + transcons = self.getTransformedCons(cons) + else: + transcons = cons + dual = SCIPgetDualsolLinear(self._scip, transcons.cons) + if dual == 0.0 and _nvars == 1: + _vars = SCIPgetVarsLinear(self._scip, transcons.cons) + _vals = SCIPgetValsLinear(self._scip, transcons.cons) + activity = SCIPvarGetLPSol(_vars[0]) * _vals[0] + rhs = SCIPgetRhsLinear(self._scip, transcons.cons) + lhs = SCIPgetLhsLinear(self._scip, transcons.cons) + if (activity == rhs) or (activity == lhs): + dual = SCIPgetVarRedcost(self._scip, _vars[0]) + + if self.getObjectiveSense() == "maximize" and not dual == 0.0: + dual = -dual + except: + raise Warning("no dual solution available for constraint " + cons.name) + return dual def getDualfarkasLinear(self, Constraint cons): """Retrieve the dual farkas value to a linear constraint. From 99a6887e2bef7715a4bd262f530cb32d19919ad9 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 1 Oct 2018 11:11:52 +0200 Subject: [PATCH 085/121] extend note in README about dual values --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index af22c69e2..1f5b36a14 100644 --- a/README.rst +++ b/README.rst @@ -129,6 +129,7 @@ While PySCIPOpt supports access to the dual values of a solution, there are some - Can only be used when presolving and propagation is disabled to ensure that the LP solver - which is providing the dual information - actually solves the unmodified problem. - Heuristics should also be disabled to avoid that the problem is solved before the LP solver is called. +- There should be no bound constraints, i.e., constraints with only one variable. This can cause incorrect values as explained in `#136 `__ Therefore, you should use the following settings when trying to work with dual information: From b6e1718124c4f06c3875972c8e4d577f9b714b2d Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Thu, 18 Oct 2018 22:41:25 +0200 Subject: [PATCH 086/121] different additions --- src/pyscipopt/scip.pxd | 43 ++++++++ src/pyscipopt/scip.pyx | 220 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 1 deletion(-) mode change 100644 => 100755 src/pyscipopt/scip.pxd mode change 100644 => 100755 src/pyscipopt/scip.pyx diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd old mode 100644 new mode 100755 index 79dca3bf2..e3aa49cfc --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -468,6 +468,10 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPchgVarObjProbing(SCIP* scip, SCIP_VAR* var, SCIP_Real newobj) SCIP_RETCODE SCIPsolveProbingLP(SCIP* scip, int itlim, SCIP_Bool* lperror, SCIP_Bool* cutoff) SCIP_RETCODE SCIPendProbing(SCIP* scip) + SCIP_RETCODE SCIPfixVarProbing(SCIP* scip, SCIP_VAR* var, SCIP_Real fixedval) + SCIP_Bool SCIPisObjChangedProbing(SCIP* scip) + SCIP_Bool SCIPinProbing(SCIP* scip) + # Event Methods SCIP_RETCODE SCIPcatchEvent(SCIP* scip, @@ -506,6 +510,12 @@ cdef extern from "scip/scip.h": int filterpos) SCIP_EVENTHDLR* SCIPfindEventhdlr(SCIP* scip, const char* name) SCIP_EVENTTYPE SCIPeventGetType(SCIP_EVENT* event) + SCIP_Real SCIPeventGetNewbound(SCIP_EVENT* event) + SCIP_Real SCIPeventGetOldbound(SCIP_EVENT* event) + SCIP_VAR* SCIPeventGetVar(SCIP_EVENT* event) + SCIP_NODE* SCIPeventGetNode(SCIP_EVENT* event) + SCIP_RETCODE SCIPinterruptSolve(SCIP* scip) + # Global Problem Methods SCIP_RETCODE SCIPcreateProbBasic(SCIP* scip, char* name) @@ -542,10 +552,13 @@ cdef extern from "scip/scip.h": SCIP_Longint SCIPnodeGetNumber(SCIP_NODE* node) int SCIPnodeGetDepth(SCIP_NODE* node) SCIP_Real SCIPnodeGetLowerbound(SCIP_NODE* node) + SCIP_RETCODE SCIPupdateNodeLowerbound(SCIP* scip, SCIP_NODE* node, SCIP_Real newbound) SCIP_Real SCIPnodeGetEstimate(SCIP_NODE* node) SCIP_NODETYPE SCIPnodeGetType(SCIP_NODE* node) SCIP_Bool SCIPnodeIsActive(SCIP_NODE* node) SCIP_Bool SCIPnodeIsPropagatedAgain(SCIP_NODE* node) + SCIP_RETCODE SCIPcreateChild(SCIP* scip, SCIP_NODE** node, SCIP_Real nodeselprio, SCIP_Real estimate) + SCIP_Bool SCIPinRepropagation(SCIP* scip) # Variable Methods SCIP_RETCODE SCIPcreateVarBasic(SCIP* scip, @@ -558,6 +571,21 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPchgVarObj(SCIP* scip, SCIP_VAR* var, SCIP_Real newobj) SCIP_RETCODE SCIPchgVarLb(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound) SCIP_RETCODE SCIPchgVarUb(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound) + SCIP_RETCODE SCIPchgVarLbGlobal(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound) + SCIP_RETCODE SCIPchgVarUbGlobal(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound) + SCIP_RETCODE SCIPchgVarLbNode(SCIP* scip, SCIP_NODE* node, SCIP_VAR* var, SCIP_Real newbound) + SCIP_RETCODE SCIPchgVarUbNode(SCIP* scip, SCIP_NODE* node, SCIP_VAR* var, SCIP_Real newbound) + SCIP_RETCODE SCIPtightenVarLb(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound, + SCIP_Bool force, SCIP_Bool* infeasible, SCIP_Bool* tightened) + SCIP_RETCODE SCIPtightenVarUb(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound, + SCIP_Bool force, SCIP_Bool* infeasible, SCIP_Bool* tightened) + SCIP_RETCODE SCIPtightenVarLbGlobal(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound, + SCIP_Bool force, SCIP_Bool* infeasible, SCIP_Bool* tightened) + SCIP_RETCODE SCIPtightenVarUbGlobal(SCIP* scip, SCIP_VAR* var, SCIP_Real newbound, + SCIP_Bool force, SCIP_Bool* infeasible, SCIP_Bool* tightened) + SCIP_RETCODE SCIPfixVar(SCIP* scip, SCIP_VAR* var, SCIP_Real fixedval, SCIP_Bool* infeasible, SCIP_Bool* fixed) + SCIP_RETCODE SCIPdelVar(SCIP* scip, SCIP_VAR* var, SCIP_Bool* deleted) + SCIP_RETCODE SCIPchgVarType(SCIP* scip, SCIP_VAR* var, SCIP_VARTYPE vartype, SCIP_Bool* infeasible) SCIP_RETCODE SCIPcaptureVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPaddPricedVar(SCIP* scip, SCIP_VAR* var, SCIP_Real score) @@ -589,6 +617,8 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPgetLPBasisInd(SCIP* scip, int* basisind) SCIP_RETCODE SCIPgetLPBInvRow(SCIP* scip, int r, SCIP_Real* coefs, int* inds, int* ninds) SCIP_RETCODE SCIPgetLPBInvARow(SCIP* scip, int r, SCIP_Real* binvrow, SCIP_Real* coefs, int* inds, int* ninds) + SCIP_RETCODE SCIPconstructLP(SCIP* scip, SCIP_Bool* cutoff) + SCIP_Real SCIPgetLPObjval(SCIP* scip) SCIP_Bool SCIPisLPSolBasic(SCIP* scip) SCIP_LPSOLSTAT SCIPgetLPSolstat(SCIP* scip) int SCIPgetNLPRows(SCIP* scip) @@ -950,6 +980,19 @@ cdef extern from "scip/scip.h": SCIP_BENDERS** SCIPgetBenders(SCIP* scip) void SCIPbendersUpdateSubproblemLowerbound(SCIP_BENDERS* benders, int probnumber, SCIP_Real lowerbound) + SCIP_RETCODE SCIPbranchVar(SCIP* scip, + SCIP_VAR* var, + SCIP_NODE** downchild, + SCIP_NODE** eqchild, + SCIP_NODE** upchild) + + SCIP_RETCODE SCIPbranchVarVal(SCIP* scip, + SCIP_VAR* var, + SCIP_Real val, + SCIP_NODE** downchild, + SCIP_NODE** eqchild, + SCIP_NODE** upchild) + # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) SCIP_Real SCIPfrac(SCIP* scip, SCIP_Real val) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx old mode 100644 new mode 100755 index 503fdfe21..d6b0529dc --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -250,6 +250,20 @@ cdef class Event: def __repr__(self): return self.getType() + def getNewBound(self): + return SCIPeventGetNewbound(self.event) + + def getOldBound(self): + return SCIPeventGetOldbound(self.event) + + def getVar(self): + cdef SCIP_VAR* var = SCIPeventGetVar(self.event) + return Variable.create(var) + + def getNode(self): + cdef SCIP_NODE* node = SCIPeventGetNode(self.event) + return Node.create(node) + cdef class Column: """Base class holding a pointer to corresponding SCIP_COL""" cdef SCIP_COL* col @@ -766,6 +780,16 @@ cdef class Model: """returns current limit on objective function.""" return SCIPgetObjlimit(self._scip) + def delObjective(self): + """clear existing objective function""" + cdef SCIP_VAR** _vars + cdef int _nvars + + _vars = SCIPgetOrigVars(self._scip) + _nvars = SCIPgetNOrigVars(self._scip) + for i in range(_nvars): + PY_SCIP_CALL(SCIPchgVarObj(self._scip, _vars[i], 0.0)) + def setObjective(self, coeffs, sense = 'minimize', clear = 'true'): """Establish the objective function as a linear expression. @@ -969,6 +993,78 @@ cdef class Model: """ PY_SCIP_CALL(SCIPaddVarLocks(self._scip, var.var, nlocksdown, nlocksup)) + def fixVar(self, Variable var, val): + """Fixes the variable var to the value val if possible. + + :param Variable var: variable to fix + :param val: float, the fix value + :return tuple (infeasible, fixed) of booleans + """ + cdef SCIP_Bool infeasible + cdef SCIP_Bool fixed + PY_SCIP_CALL(SCIPfixVar(self._scip, var.var, val, &infeasible, &fixed)) + return infeasible, fixed + + def delVar(self, Variable var): + """Delete a variable. + + :param var: the variable which shall be deleted + :return bool, was deleting succesful + """ + cdef SCIP_Bool deleted + PY_SCIP_CALL(SCIPdelVar(self._scip, var.var, &deleted)) + return deleted + + def tightenVarLb(self, Variable var, lb, force=False): + """Tighten the lower bound in preprocessing or current node, if the bound is tighter. + :param var: SCIP variable + :param lb: possible new lower bound + :param force: force tightening even if below bound strengthening tolerance + :return: bool, if the bound was tightened + """ + cdef SCIP_Bool infeasible + cdef SCIP_Bool tightened + PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.var, lb, force, &infeasible, &tightened)) + return tightened + + + def tightenVarUb(self, Variable var, ub, force=False): + """Tighten the upper bound in preprocessing or current node, if the bound is tighter. + :param var: SCIP variable + :param ub: possible new upper bound + :param force: force tightening even if below bound strengthening tolerance + :return: bool, if the bound was tightened + """ + cdef SCIP_Bool infeasible + cdef SCIP_Bool tightened + PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.var, ub, force, &infeasible, &tightened)) + return tightened + + + def tightenVarUbGlobal(self, Variable var, ub, force=False): + """Tighten the global upper bound, if the bound is tighter. + :param var: SCIP variable + :param ub: possible new upper bound + :param force: force tightening even if below bound strengthening tolerance + :return: bool, if the bound was tightened + """ + cdef SCIP_Bool infeasible + cdef SCIP_Bool tightened + PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.var, ub, force, &infeasible, &tightened)) + return tightened + + def tightenVarLbGlobal(self, Variable var, lb, force=False): + """Tighten the global upper bound, if the bound is tighter. + :param var: SCIP variable + :param lb: possible new upper bound + :param force: force tightening even if below bound strengthening tolerance + :return: bool, if the bound was tightened + """ + cdef SCIP_Bool infeasible + cdef SCIP_Bool tightened + PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.var, lb, force, &infeasible, &tightened)) + return tightened + def chgVarLb(self, Variable var, lb): """Changes the lower bound of the specified variable. @@ -991,6 +1087,51 @@ cdef class Model: ub = SCIPinfinity(self._scip) PY_SCIP_CALL(SCIPchgVarUb(self._scip, var.var, ub)) + + def chgVarLbGlobal(self, Variable var, lb): + """Changes the global lower bound of the specified variable. + + :param Variable var: variable to change bound of + :param lb: new lower bound (set to None for -infinity) + + """ + if lb is None: + lb = -SCIPinfinity(self._scip) + PY_SCIP_CALL(SCIPchgVarLbGlobal(self._scip, var.var, lb)) + + def chgVarUbGlobal(self, Variable var, ub): + """Changes the global upper bound of the specified variable. + + :param Variable var: variable to change bound of + :param ub: new upper bound (set to None for +infinity) + + """ + if ub is None: + ub = SCIPinfinity(self._scip) + PY_SCIP_CALL(SCIPchgVarUbGlobal(self._scip, var.var, ub)) + + def chgVarLbNode(self, Node node, Variable var, lb): + """Changes the lower bound of the specified variable at the given node. + + :param Variable var: variable to change bound of + :param lb: new lower bound (set to None for -infinity) + """ + + if lb is None: + lb = -SCIPinfinity(self._scip) + PY_SCIP_CALL(SCIPchgVarLbNode(self._scip, node.node, var.var, lb)) + + def chgVarUbNode(self, Node node, Variable var, ub): + """Changes the upper bound of the specified variable at the given node. + + :param Variable var: variable to change bound of + :param ub: new upper bound (set to None for +infinity) + + """ + if ub is None: + ub = SCIPinfinity(self._scip) + PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.node, var.var, ub)) + def chgVarType(self, Variable var, vtype): """Changes the type of a variable @@ -1030,11 +1171,37 @@ cdef class Model: return [Variable.create(_vars[i]) for i in range(_nvars)] + def updateNodeLowerbound(self, Node node, lb): + """if given value is larger than the node's lower bound (in transformed problem), + sets the node's lower bound to the new value + Args: + node: Node, the node to update + newbound: float, new bound (if greater) for the node + """ + PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) + # LP Methods def getLPSolstat(self): """Gets solution status of current LP""" return SCIPgetLPSolstat(self._scip) + + def constructLP(self): + """makes sure that the LP of the current node is loaded and + may be accessed through the LP information methods + + Returns: + cutoff: bool, can the node be cut off? + """ + cdef SCIP_Bool cutoff + PY_SCIP_CALL(SCIPconstructLP(self._scip, &cutoff)) + return cutoff + + def getLPObjVal(self): + """gets objective value of current LP (which is the sum of column and loose objective value)""" + + return SCIPgetLPObjval(self._scip) + def getLPColsData(self): """Retrieve current LP columns""" cdef SCIP_COL** cols @@ -2002,7 +2169,7 @@ cdef class Model: def getConss(self): """Retrieve all constraints.""" cdef SCIP_CONS** _conss - cdef SCIP_CONS* _cons + # cdef SCIP_CONS* _cons # is not used cdef int _nconss conss = [] @@ -2488,6 +2655,39 @@ cdef class Model: benders.name = name Py_INCREF(benders) + def branchVar(self, variable): + """Branch on a non-continuous variable.""" + + cdef SCIP_NODE* downchild = malloc(sizeof(SCIP_NODE)) + cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) + cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) + + PY_SCIP_CALL(SCIPbranchVar(self._scip, (variable).var, &downchild, &eqchild, &upchild)) + + return Node.create(downchild), Node.create(eqchild), Node.create(upchild) + + + def branchVarVal(self, variable, value): + """Branches on variable using a value which separates the domain of the variable.""" + + cdef SCIP_NODE* downchild = malloc(sizeof(SCIP_NODE)) + cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) + cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) + + PY_SCIP_CALL(SCIPbranchVarVal(self._scip, (variable).var, value, &downchild, &eqchild, &upchild)) + + # TODO should the stuff be freed and how? + return Node.create(downchild), Node.create(eqchild), Node.create(upchild) + + + def createChild(self, nodeselprio, estimate): + """Create a child node of the focus node.""" + + cdef SCIP_NODE* child = malloc(sizeof(SCIP_NODE)) + PY_SCIP_CALL(SCIPcreateChild(self._scip, &child, nodeselprio, estimate)) + + return Node.create(child) + # Diving methods (Diving is LP related) def startDive(self): """Initiates LP diving @@ -2550,6 +2750,10 @@ cdef class Model: PY_SCIP_CALL(SCIPsolveDiveLP(self._scip, itlim, &lperror, &cutoff)) return lperror, cutoff + def inRepropagation(self): + """returns if the current node is already solved and only propagated again.""" + return SCIPinRepropagation(self._scip) + # Probing methods (Probing is tree based) def startProbing(self): """Initiates probing, making methods SCIPnewProbingNode(), SCIPbacktrackProbing(), SCIPchgVarLbProbing(), @@ -2565,6 +2769,16 @@ cdef class Model: """changes (column) variable's objective value during probing mode""" PY_SCIP_CALL(SCIPchgVarObjProbing(self._scip, var.var, newobj)) + def fixVarProbing(self, Variable var, fixedval): + """Fixes a variable at the current probing node.""" + PY_SCIP_CALL(SCIPfixVarProbing(self._scip, var.var, fixedval)) + + def isObjChangedProbing(self): + return SCIPisObjChangedProbing(self._scip) + + def inProbing(self): + return SCIPinProbing(self._scip) + def solveProbingLP(self, itlim = -1): """solves the LP at the current probing node (cannot be applied at preprocessing stage) no separation or pricing is applied @@ -2579,6 +2793,10 @@ cdef class Model: PY_SCIP_CALL(SCIPsolveProbingLP(self._scip, itlim, &lperror, &cutoff)) return lperror, cutoff + def interruptSolve(self): + """Interrupt the solving process as soon as possible.""" + PY_SCIP_CALL(SCIPinterruptSolve(self._scip)) + # Solution functions def createSol(self, Heur heur = None): From f74aa9e93bcbb47b0ee53e3b4aa9d609751e54f5 Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Fri, 19 Oct 2018 12:04:07 +0200 Subject: [PATCH 087/121] tighten returns also infeasible, documentation --- src/pyscipopt/scip.pyx | 52 ++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index d6b0529dc..60884a0f4 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -998,7 +998,8 @@ cdef class Model: :param Variable var: variable to fix :param val: float, the fix value - :return tuple (infeasible, fixed) of booleans + :return: tuple (infeasible, fixed) of booleans + """ cdef SCIP_Bool infeasible cdef SCIP_Bool fixed @@ -1009,7 +1010,8 @@ cdef class Model: """Delete a variable. :param var: the variable which shall be deleted - :return bool, was deleting succesful + :return: bool, was deleting succesful + """ cdef SCIP_Bool deleted PY_SCIP_CALL(SCIPdelVar(self._scip, var.var, &deleted)) @@ -1017,53 +1019,69 @@ cdef class Model: def tightenVarLb(self, Variable var, lb, force=False): """Tighten the lower bound in preprocessing or current node, if the bound is tighter. + :param var: SCIP variable :param lb: possible new lower bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.var, lb, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def tightenVarUb(self, Variable var, ub, force=False): """Tighten the upper bound in preprocessing or current node, if the bound is tighter. + :param var: SCIP variable :param ub: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.var, ub, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def tightenVarUbGlobal(self, Variable var, ub, force=False): """Tighten the global upper bound, if the bound is tighter. + :param var: SCIP variable :param ub: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.var, ub, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def tightenVarLbGlobal(self, Variable var, lb, force=False): """Tighten the global upper bound, if the bound is tighter. + :param var: SCIP variable :param lb: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.var, lb, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def chgVarLb(self, Variable var, lb): """Changes the lower bound of the specified variable. @@ -1174,9 +1192,10 @@ cdef class Model: def updateNodeLowerbound(self, Node node, lb): """if given value is larger than the node's lower bound (in transformed problem), sets the node's lower bound to the new value - Args: - node: Node, the node to update - newbound: float, new bound (if greater) for the node + + :param node: Node, the node to update + :param newbound: float, new bound (if greater) for the node + """ PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) @@ -1190,9 +1209,9 @@ cdef class Model: """makes sure that the LP of the current node is loaded and may be accessed through the LP information methods - Returns: - cutoff: bool, can the node be cut off? - """ + :return: bool cutoff, i.e. can the node be cut off? + + """ cdef SCIP_Bool cutoff PY_SCIP_CALL(SCIPconstructLP(self._scip, &cutoff)) return cutoff @@ -2169,7 +2188,6 @@ cdef class Model: def getConss(self): """Retrieve all constraints.""" cdef SCIP_CONS** _conss - # cdef SCIP_CONS* _cons # is not used cdef int _nconss conss = [] From 83442529ef19fb4cf0dadf50ec5d5b3a9c4bcff0 Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Fri, 19 Oct 2018 12:21:31 +0200 Subject: [PATCH 088/121] Test for variable methods --- tests/test_variablebounds.py | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/test_variablebounds.py diff --git a/tests/test_variablebounds.py b/tests/test_variablebounds.py new file mode 100644 index 000000000..e87f45aae --- /dev/null +++ b/tests/test_variablebounds.py @@ -0,0 +1,45 @@ +from pyscipopt import Model + + +m = Model() + +x0 = m.addVar(lb=-2, ub=4) +r1 = m.addVar() +r2 = m.addVar() +y0 = m.addVar(lb=3) +t = m.addVar(lb=None) +z = m.addVar() + +infeas, tightened = m.tightenVarLb(x0, -5) +assert not infeas +assert not tightened +infeas, tightened = m.tightenVarLbGlobal(x0, -1) +assert not infeas +assert tightened +infeas, tightened = m.tightenVarUb(x0, 3) +assert not infeas +assert tightened +infeas, tightened = m.tightenVarUbGlobal(x0, 9) +assert not infeas +assert not tightened +infeas, fixed = m.fixVar(z, 7) +assert not infeas +assert fixed +assert m.delVar(z) + +m.addCons(r1 >= x0) +m.addCons(r2 >= -x0) +m.addCons(y0 == r1 +r2) + +m.setObjective(t) +m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) + + +m.optimize() + +print("x0", m.getVal(x0)) +print("r1", m.getVal(r1)) +print("r2", m.getVal(r2)) +print("y0", m.getVal(y0)) +print("t", m.getVal(t)) + From f96c4e4eebc300cb6cc4202f33626dd55b0871b9 Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Fri, 19 Oct 2018 12:51:35 +0200 Subject: [PATCH 089/121] test branching and chgVarLbGlobal --- tests/test_branch_probing_lp.py | 78 +++++++++++++++++++++++++++++++++ tests/test_variablebounds.py | 5 ++- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/test_branch_probing_lp.py diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py new file mode 100644 index 000000000..156b4971b --- /dev/null +++ b/tests/test_branch_probing_lp.py @@ -0,0 +1,78 @@ +from pyscipopt import Model, Branchrule, SCIP_RESULT + + +class MyBranching(Branchrule): + + def __init__(self, model, cont, integral): + self.model = model + self.cont = cont + self.integral = integral + self.count = 0 + self.was_called_val = False + self.was_called_int = False + + def branchexeclp(self, allowaddcons): + print("in branchexelp") + self.count += 1 + if self.count >= 2: + return {"result": SCIP_RESULT.DIDNOTRUN} + assert allowaddcons + + assert not self.model.inRepropagation() + assert not self.model.inProbing() + self.model.startProbing() + assert not self.model.isObjChangedProbing() + self.model.fixVarProbing(self.cont, 2.0) + self.model.constructLP() + self.model.solveProbingLP() + self.model.getLPObjVal() + self.model.endProbing() + + if self.count == 1: + down, eq, up = self.model.branchVarVal(self.cont, 1.3) + self.model.chgVarLbNode(down, self.cont, -1.5) + self.model.chgVarUbNode(up, self.cont, 3.0) + self.was_called_val = True + down2, eq2, up2 = self.model.branchVar(self.integral) + self.was_called_int = True + self.model.createChild(6, 7) + return {"result": SCIP_RESULT.BRANCHED} + + + + +m = Model() + +x0 = m.addVar(lb=-2, ub=4) +r1 = m.addVar() +r2 = m.addVar() +y0 = m.addVar(lb=3) +t = m.addVar(lb=None) +l = m.addVar(vtype="I", lb=-9, ub=18) +u = m.addVar(vtype="I", lb=-3, ub=99) + + + +m.addCons(r1 >= x0) +m.addCons(r2 >= -x0) +m.addCons(y0 == r1 +r2) +m.addCons(t * l + l * u >= 4) + +m.setObjective(t) +m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) + +my_branchrule = MyBranching(m, x0, l) +m.includeBranchrule(my_branchrule, "test branch", "test branching and probing and lp functions", + priority=10000000, maxdepth=3, maxbounddist=1) + +m.optimize() + +print("x0", m.getVal(x0)) +print("r1", m.getVal(r1)) +print("r2", m.getVal(r2)) +print("y0", m.getVal(y0)) +print("t", m.getVal(t)) + +assert my_branchrule.was_called_val +assert my_branchrule.was_called_int + diff --git a/tests/test_variablebounds.py b/tests/test_variablebounds.py index e87f45aae..85ee87e1a 100644 --- a/tests/test_variablebounds.py +++ b/tests/test_variablebounds.py @@ -3,13 +3,16 @@ m = Model() -x0 = m.addVar(lb=-2, ub=4) +x0 = m.addVar(lb=-5, ub=8) r1 = m.addVar() r2 = m.addVar() y0 = m.addVar(lb=3) t = m.addVar(lb=None) z = m.addVar() +m.chgVarLbGlobal(x0, -2) +m.chgVarUbGlobal(x0, 4) + infeas, tightened = m.tightenVarLb(x0, -5) assert not infeas assert not tightened From a99c9ee5b6ea98148274242446ef11e998aae46e Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Fri, 19 Oct 2018 12:56:32 +0200 Subject: [PATCH 090/121] extended event test --- tests/test_event.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_event.py b/tests/test_event.py index 478a2e56a..cb050271b 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -16,6 +16,10 @@ def eventexit(self): def eventexec(self, event): calls.append('eventexec') + event.getNewBound() + event.getOldBound() + assert event.getNode().getNumber() == 1 + def test_event(): # create solver instance From b68281b4234a7e1466141f0b448c5b1a78e9313c Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Fri, 19 Oct 2018 15:22:46 +0200 Subject: [PATCH 091/121] allow constants in setObjective, esp. 0 to clear it --- src/pyscipopt/scip.pyx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 503fdfe21..293340ca1 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -776,7 +776,11 @@ cdef class Model: """ cdef SCIP_VAR** _vars cdef int _nvars - assert isinstance(coeffs, Expr) + + # turn the constant value into an Expr instance for further processing + if not isinstance(coeffs, Expr): + assert(_is_number(coeffs)) + coeffs = Expr() + coeffs if coeffs.degree() > 1: raise ValueError("Nonlinear objective functions are not supported!") From c0344a7e6244d71ae748d55376f7b74fd60d9d01 Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Sun, 28 Oct 2018 23:22:05 +0100 Subject: [PATCH 092/121] added functions for relaxing rows and fix vars --- src/pyscipopt/scip.pxd | 73 +++++++++ src/pyscipopt/scip.pyx | 334 +++++++++++++++++++++++++++++++++-------- 2 files changed, 347 insertions(+), 60 deletions(-) mode change 100755 => 100644 src/pyscipopt/scip.pxd mode change 100755 => 100644 src/pyscipopt/scip.pyx diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd old mode 100755 new mode 100644 index e3aa49cfc..b3107c27b --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -250,6 +250,10 @@ cdef extern from "scip/scip.h": SCIP_LPSOLSTAT_TIMELIMIT = 6 SCIP_LPSOLSTAT_ERROR = 7 + ctypedef enum SCIP_SIDETYPE: + SCIP_SIDETYPE_LEFT = 0 + SCIP_SIDETYPE_RIGHT = 1 + ctypedef bint SCIP_Bool ctypedef long long SCIP_Longint @@ -449,6 +453,15 @@ cdef extern from "scip/scip.h": SCIP_STAGE SCIPgetStage(SCIP* scip) SCIP_RETCODE SCIPsetProbName(SCIP* scip, char* name) const char* SCIPgetProbName(SCIP* scip) + SCIP_RETCODE SCIPcopy(SCIP* sourcescip, + SCIP* targetscip, + SCIP_HASHMAP* varmap, + SCIP_HASHMAP* consmap, + const char* suffix, + SCIP_Bool global_copy, + SCIP_Bool enablepricing, + SCIP_Bool passmessagehdlr, + SCIP_Bool* valid) # Diving methods SCIP_RETCODE SCIPstartDive(SCIP* scip) @@ -462,6 +475,8 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPchgRowRhsDive(SCIP* scip, SCIP_ROW* row, SCIP_Real newrhs) SCIP_RETCODE SCIPaddRowDive(SCIP* scip, SCIP_ROW* row) SCIP_RETCODE SCIPendDive(SCIP* scip) + SCIP_RETCODE SCIPgetLPRowChgsDive(SCIP* scip, int* ndivechgsides, SCIP_Real** divechgsides, + SCIP_SIDETYPE** divechgsidetypes, SCIP_ROW*** divechgrows) # Probing methods SCIP_RETCODE SCIPstartProbing(SCIP* scip) @@ -552,6 +567,7 @@ cdef extern from "scip/scip.h": SCIP_Longint SCIPnodeGetNumber(SCIP_NODE* node) int SCIPnodeGetDepth(SCIP_NODE* node) SCIP_Real SCIPnodeGetLowerbound(SCIP_NODE* node) + SCIP_RETCODE SCIPupdateNodeDualbound(SCIP* scip, SCIP_NODE* node, SCIP_Real newbound) SCIP_RETCODE SCIPupdateNodeLowerbound(SCIP* scip, SCIP_NODE* node, SCIP_Real newbound) SCIP_Real SCIPnodeGetEstimate(SCIP_NODE* node) SCIP_NODETYPE SCIPnodeGetType(SCIP_NODE* node) @@ -618,6 +634,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPgetLPBInvRow(SCIP* scip, int r, SCIP_Real* coefs, int* inds, int* ninds) SCIP_RETCODE SCIPgetLPBInvARow(SCIP* scip, int r, SCIP_Real* binvrow, SCIP_Real* coefs, int* inds, int* ninds) SCIP_RETCODE SCIPconstructLP(SCIP* scip, SCIP_Bool* cutoff) + SCIP_RETCODE SCIPflushLP(SCIP* scip) SCIP_Real SCIPgetLPObjval(SCIP* scip) SCIP_Bool SCIPisLPSolBasic(SCIP* scip) SCIP_LPSOLSTAT SCIPgetLPSolstat(SCIP* scip) @@ -629,6 +646,10 @@ cdef extern from "scip/scip.h": SCIP_Real SCIPgetCutEfficacy(SCIP* scip, SCIP_SOL* sol, SCIP_ROW* cut) SCIP_Bool SCIPisCutEfficacious(SCIP* scip, SCIP_SOL* sol, SCIP_ROW* cut) + SCIP_RETCODE SCIPmarkDoNotMultaggrVar(SCIP* scip, SCIP_VAR* var) + SCIP_VAR** SCIPgetFixedVars(SCIP* scip) + int SCIPgetNFixedVars(SCIP* scip) + # Constraint Methods SCIP_RETCODE SCIPcaptureCons(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPreleaseCons(SCIP* scip, SCIP_CONS** cons) @@ -645,6 +666,7 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPconsIsChecked(SCIP_CONS* cons) SCIP_Bool SCIPconsIsPropagated(SCIP_CONS* cons) SCIP_Bool SCIPconsIsLocal(SCIP_CONS* cons) + SCIP_Bool SCIPconsIsGlobal(SCIP_CONS* cons) SCIP_Bool SCIPconsIsModifiable(SCIP_CONS* cons) SCIP_Bool SCIPconsIsDynamic(SCIP_CONS* cons) SCIP_Bool SCIPconsIsRemovable(SCIP_CONS* cons) @@ -937,6 +959,10 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*branchruleexecps) (SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result), SCIP_BRANCHRULEDATA* branchruledata) SCIP_BRANCHRULEDATA* SCIPbranchruleGetData(SCIP_BRANCHRULE* branchrule) + void SCIPbranchruleSetData(SCIP_BRANCHRULE* branchrule, + SCIP_BRANCHRULEDATA* branchruledata + ) + # Benders' decomposition plugin SCIP_RETCODE SCIPincludeBenders(SCIP* scip, @@ -980,6 +1006,15 @@ cdef extern from "scip/scip.h": SCIP_BENDERS** SCIPgetBenders(SCIP* scip) void SCIPbendersUpdateSubproblemLowerbound(SCIP_BENDERS* benders, int probnumber, SCIP_Real lowerbound) + SCIP_RETCODE SCIPgetNLPBranchCands(SCIP* scip) + SCIP_RETCODE SCIPgetLPBranchCands(SCIP* scip, + SCIP_VAR*** lpcands, + SCIP_Real** lpcandssol, + SCIP_Real** lpcandsfrac, + int* nlpcands, + int* npriolpcands, + int* nfracimplvars) + SCIP_RETCODE SCIPbranchVar(SCIP* scip, SCIP_VAR* var, SCIP_NODE** downchild, @@ -993,6 +1028,15 @@ cdef extern from "scip/scip.h": SCIP_NODE** eqchild, SCIP_NODE** upchild) + SCIP_RETCODE SCIPaddConsNode(SCIP* scip, + SCIP_NODE* node, + SCIP_CONS* cons, + SCIP_NODE* validnode) + + SCIP_RETCODE SCIPaddConsLocal(SCIP* scip, + SCIP_CONS* cons, + SCIP_NODE* validnode) + # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) SCIP_Real SCIPfrac(SCIP* scip, SCIP_Real val) @@ -1066,11 +1110,17 @@ cdef extern from "scip/scip.h": #re-optimization routines SCIP_RETCODE SCIPfreeReoptSolve(SCIP* scip) SCIP_RETCODE SCIPchgReoptObjective(SCIP* scip, SCIP_OBJSENSE objsense, SCIP_VAR** vars, SCIP_Real* coefs, int nvars) + SCIP_RETCODE SCIPenableReoptimization(SCIP* scip, SCIP_Bool enable) + SCIP_RETCODE SCIPisReoptEnabled(SCIP* scip) BMS_BLKMEM* SCIPblkmem(SCIP* scip) cdef extern from "scip/tree.h": int SCIPnodeGetNAddedConss(SCIP_NODE* node) + void SCIPnodeGetAddedConss(SCIP_NODE* node, + SCIP_CONS** addedconss, + int* naddedconss, + int addedconsssize) cdef extern from "scip/scipdefplugins.h": SCIP_RETCODE SCIPincludeDefaultPlugins(SCIP* scip) @@ -1207,6 +1257,29 @@ cdef extern from "scip/cons_sos2.h": SCIP_CONS* cons, SCIP_VAR* var) +cdef extern from "scip/cons_bounddisjunction.h": + SCIP_RETCODE SCIPcreateConsBounddisjunction(SCIP* scip, + SCIP_CONS** cons, + const char* name, + int nvars, + SCIP_VAR** vars, + SCIP_BOUNDTYPE* boundtypes, + SCIP_Real* bounds, + SCIP_Bool initial, + SCIP_Bool separate, + SCIP_Bool enforce, + SCIP_Bool check, + SCIP_Bool propagate, + SCIP_Bool local, + SCIP_Bool modifiable, + SCIP_Bool dynamic, + SCIP_Bool removable, + SCIP_Bool stickingatnode) + +#cdef extern from "scip/branch_nndomain.h": + #SCIP_RETCODE SCIPincludeBranchruleNNDomain(SCIP* scip) + + cdef extern from "scip/cons_and.h": SCIP_RETCODE SCIPcreateConsAnd(SCIP* scip, SCIP_CONS** cons, diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx old mode 100755 new mode 100644 index 60884a0f4..d1e5aaad0 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -418,6 +418,17 @@ cdef class Node: """Retrieve number of added constraints at this node""" return SCIPnodeGetNAddedConss(self.node) + def getAddedConss(self): + """Retrieve all constraints added at this node.""" + cdef int _nconss + _nconss = SCIPnodeGetNAddedConss(self.node) + + cdef SCIP_CONS** _conss = malloc(_nconss * sizeof(SCIP_CONS*)) + + cdef int _nconss_new + SCIPnodeGetAddedConss(self.node, _conss, &_nconss_new, _nconss) + return [Constraint.create(_conss[i]) for i in range(_nconss_new)] + def isActive(self): """Is the node in the path to the current node?""" return SCIPnodeIsActive(self.node) @@ -426,7 +437,6 @@ cdef class Node: """Is the node marked to be propagated again?""" return SCIPnodeIsPropagatedAgain(self.node) - cdef class Variable(Expr): """Is a linear expression and has SCIP_VAR*""" cdef SCIP_VAR* var @@ -440,7 +450,7 @@ cdef class Variable(Expr): property name: def __get__(self): - cname = bytes( SCIPvarGetName(self.var) ) + cname = bytes(SCIPvarGetName(self.var)) return cname.decode('utf-8') def ptr(self): @@ -555,6 +565,10 @@ cdef class Constraint: """Retrieve True if constraint is only locally valid or not added to any (sub)problem""" return SCIPconsIsLocal(self.cons) + def isGlobal(self): + """returns TRUE iff constraint is globally valid""" + return SCIPconsIsGlobal(self.cons) + def isModifiable(self): """Retrieve True if constraint is modifiable (subject to column generation)""" return SCIPconsIsModifiable(self.cons) @@ -757,6 +771,13 @@ cdef class Model: return quality + # Enable Reoptimization + def enableReoptimization(self): + PY_SCIP_CALL(SCIPenableReoptimization(self._scip, True)) + + def isReoptEnabled(self): + return PY_SCIP_CALL(SCIPisReoptEnabled(self._scip)) + # Objective function def setMinimize(self): @@ -790,6 +811,7 @@ cdef class Model: for i in range(_nvars): PY_SCIP_CALL(SCIPchgVarObj(self._scip, _vars[i], 0.0)) + def setObjective(self, coeffs, sense = 'minimize', clear = 'true'): """Establish the objective function as a linear expression. @@ -921,6 +943,21 @@ cdef class Model: PY_SCIP_CALL(SCIPwriteOrigProblem(self._scip, fn, ext, False)) print('wrote problem to file ' + str(fn)) + + def copyModel(self, Model new_model, global_copy=True, enablepricing=False, passmessagehdlr=False): + """Create a copy of the model + + :param global_copy: + :param enablepricing: + :param passmessagehdlr: + :return: bool, indicating if copying was valid or not + """ + cdef SCIP_Bool valid + PY_SCIP_CALL(SCIPcopy(self._scip, new_model._scip, NULL, NULL, "suffix", global_copy, + enablepricing, passmessagehdlr, &valid)) + return valid + + # Variable Functions def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False): @@ -998,9 +1035,9 @@ cdef class Model: :param Variable var: variable to fix :param val: float, the fix value - :return: tuple (infeasible, fixed) of booleans - + :return tuple (infeasible, fixed) of booleans """ + cdef SCIP_Bool infeasible cdef SCIP_Bool fixed PY_SCIP_CALL(SCIPfixVar(self._scip, var.var, val, &infeasible, &fixed)) @@ -1010,78 +1047,62 @@ cdef class Model: """Delete a variable. :param var: the variable which shall be deleted - :return: bool, was deleting succesful - + :return bool, was deleting succesful """ + cdef SCIP_Bool deleted PY_SCIP_CALL(SCIPdelVar(self._scip, var.var, &deleted)) return deleted def tightenVarLb(self, Variable var, lb, force=False): """Tighten the lower bound in preprocessing or current node, if the bound is tighter. - :param var: SCIP variable :param lb: possible new lower bound :param force: force tightening even if below bound strengthening tolerance - :return: tuple of bools, (infeasible, tightened) - infeasible: whether new domain is empty - tightened: whether the bound was tightened - + :return: bool, if the bound was tightened """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.var, lb, force, &infeasible, &tightened)) - return infeasible, tightened + return tightened def tightenVarUb(self, Variable var, ub, force=False): """Tighten the upper bound in preprocessing or current node, if the bound is tighter. - :param var: SCIP variable :param ub: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: tuple of bools, (infeasible, tightened) - infeasible: whether new domain is empty - tightened: whether the bound was tightened - + :return: bool, if the bound was tightened """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.var, ub, force, &infeasible, &tightened)) - return infeasible, tightened + return tightened def tightenVarUbGlobal(self, Variable var, ub, force=False): """Tighten the global upper bound, if the bound is tighter. - :param var: SCIP variable :param ub: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: tuple of bools, (infeasible, tightened) - infeasible: whether new domain is empty - tightened: whether the bound was tightened - + :return: bool, if the bound was tightened """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.var, ub, force, &infeasible, &tightened)) - return infeasible, tightened + return tightened def tightenVarLbGlobal(self, Variable var, lb, force=False): """Tighten the global upper bound, if the bound is tighter. - :param var: SCIP variable :param lb: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: tuple of bools, (infeasible, tightened) - infeasible: whether new domain is empty - tightened: whether the bound was tightened - + :return: bool, if the bound was tightened """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.var, lb, force, &infeasible, &tightened)) - return infeasible, tightened + return tightened def chgVarLb(self, Variable var, lb): """Changes the lower bound of the specified variable. @@ -1150,6 +1171,27 @@ cdef class Model: ub = SCIPinfinity(self._scip) PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.node, var.var, ub)) + def updateNodeLowerbound(self, Node node, lb): + """if given value is larger than the node's lower bound (in transformed problem), + sets the node's lower bound to the new value + + Args: + node: Node, the node to update + newbound: float, new bound (if greater) for the node + """ + PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) + + def updateNodeDualbound(self, Node node, lb): + """if given value is larger than the node's dual bound (in transformed problem), + sets the node's lower bound to the new value + + Args: + node: Node, the node to update + newbound: float, new bound (if greater) for the node + """ + PY_SCIP_CALL(SCIPupdateNodeDualbound(self._scip, node.node, lb)) + + def chgVarType(self, Variable var, vtype): """Changes the type of a variable @@ -1169,53 +1211,52 @@ cdef class Model: if infeasible: print('could not change variable type of variable %s' % var) - def getVars(self, transformed=False): + def getVars(self, transformed=False, fixed=False): """Retrieve all variables. :param transformed: get transformed variables instead of original (Default value = False) + :param fixed: get all fixed and aggregated variables instead of original + Only one of transformed and fixed may be true. """ + assert not all((transformed, fixed)) cdef SCIP_VAR** _vars - cdef SCIP_VAR* _var cdef int _nvars vars = [] if transformed: _vars = SCIPgetVars(self._scip) _nvars = SCIPgetNVars(self._scip) + elif fixed: + _vars = SCIPgetFixedVars(self._scip) + _nvars = SCIPgetNFixedVars(self._scip) else: _vars = SCIPgetOrigVars(self._scip) _nvars = SCIPgetNOrigVars(self._scip) return [Variable.create(_vars[i]) for i in range(_nvars)] - def updateNodeLowerbound(self, Node node, lb): - """if given value is larger than the node's lower bound (in transformed problem), - sets the node's lower bound to the new value - - :param node: Node, the node to update - :param newbound: float, new bound (if greater) for the node - - """ - PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) - # LP Methods def getLPSolstat(self): """Gets solution status of current LP""" return SCIPgetLPSolstat(self._scip) - def constructLP(self): """makes sure that the LP of the current node is loaded and may be accessed through the LP information methods - :return: bool cutoff, i.e. can the node be cut off? - - """ + Returns: + cutoff: bool, can the node be cut off? + """ cdef SCIP_Bool cutoff PY_SCIP_CALL(SCIPconstructLP(self._scip, &cutoff)) return cutoff + def flushLP(self): + """makes sure that the LP of the current node is flushed""" + + PY_SCIP_CALL(SCIPflushLP(self._scip)) + def getLPObjVal(self): """gets objective value of current LP (which is the sum of column and loose objective value)""" @@ -1337,7 +1378,7 @@ cdef class Model: def addCons(self, cons, name='', initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, modifiable=False, dynamic=False, removable=False, - stickingatnode=False): + stickingatnode=False, Node node=None, Node valid_node=None): """Add a linear or quadratic constraint. :param cons: list of coefficients @@ -1365,7 +1406,7 @@ cdef class Model: propagate=propagate, local=local, modifiable=modifiable, dynamic=dynamic, removable=removable, - stickingatnode=stickingatnode) + stickingatnode=stickingatnode, node=node, valid_node=valid_node) kwargs['lhs'] = -SCIPinfinity(self._scip) if cons.lhs is None else cons.lhs kwargs['rhs'] = SCIPinfinity(self._scip) if cons.rhs is None else cons.rhs @@ -1386,20 +1427,48 @@ cdef class Model: terms = lincons.expr.terms cdef SCIP_CONS* scip_cons - PY_SCIP_CALL(SCIPcreateConsLinear( - self._scip, &scip_cons, str_conversion(kwargs['name']), 0, NULL, NULL, - kwargs['lhs'], kwargs['rhs'], kwargs['initial'], - kwargs['separate'], kwargs['enforce'], kwargs['check'], - kwargs['propagate'], kwargs['local'], kwargs['modifiable'], - kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) + cdef int nvars = len(terms.items()) - for key, coeff in terms.items(): - var = key[0] - PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, coeff)) + vars_array = malloc(nvars * sizeof(SCIP_VAR*)) + coeffs_array = malloc(nvars * sizeof(SCIP_Real)) + + for i, (key, coeff) in enumerate(terms.items()): + vars_array[i] = (key[0]).var + coeffs_array[i] = coeff + + PY_SCIP_CALL(SCIPcreateConsLinear( + self._scip, &scip_cons, str_conversion(kwargs['name']), nvars, vars_array, coeffs_array, + kwargs['lhs'], kwargs['rhs'], kwargs['initial'], + kwargs['separate'], kwargs['enforce'], kwargs['check'], + kwargs['propagate'], kwargs['local'], kwargs['modifiable'], + kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) + + # PY_SCIP_CALL(SCIPcreateConsLinear( + # self._scip, &scip_cons, str_conversion(kwargs['name']), 0, NULL, NULL, + # kwargs['lhs'], kwargs['rhs'], kwargs['initial'], + # kwargs['separate'], kwargs['enforce'], kwargs['check'], + # kwargs['propagate'], kwargs['local'], kwargs['modifiable'], + # kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) + # + # for key, coeff in terms.items(): + # var = key[0] + # PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, coeff)) + + if kwargs["node"] is None: + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) + #print("add cons globally") + #elif not kwargs["node_trick"]: + else: + # add cons at a node, valid_node is set to NULL + #print("add cons to node", kwargs["node"]) + PY_SCIP_CALL(SCIPaddConsNode(self._scip, (kwargs["node"]).node, scip_cons, NULL)) + # maybe should use this function for local constraints + #PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, NULL)) - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) PyCons = Constraint.create(scip_cons) PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + free(vars_array) + free(coeffs_array) return PyCons @@ -1676,6 +1745,54 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) return Constraint.create(scip_cons) + def markDoNotMultaggrVar(self, var): + + PY_SCIP_CALL(SCIPmarkDoNotMultaggrVar(self._scip, (var).var)) + + def addConsBoundDisjunction(self, vars, bound_types, bounds, name="BoundDisjunctionCons", initial=True, separate=True, enforce=True, check=True, + propagate=True, local=False, modifiable=False, dynamic=False, + removable=False, stickingatnode=False): + """Add a bound disjunction constraint. By Ansgar + + :param vars: list of variables that shall be included + :param bound_types: list of str, where "lb" for lower bound, "ub" for upperbound + :param bounds: list of float, the bound values, it must hold len(bounds) == len(bound_types) == len(vars) + """ + + assert len(vars) == len(bound_types) == len(bounds) + + cdef SCIP_CONS* scip_cons + nvars = len(vars) + vars_array = malloc(nvars * sizeof(SCIP_VAR*)) + bound_types_array = malloc(nvars * sizeof(SCIP_BOUNDTYPE)) + bounds_array = malloc(nvars * sizeof(SCIP_Real)) + + for i in range(nvars): + vars_array[i] = (vars[i]).var + if bound_types[i] == "lb": + bound_types_array[i] = SCIP_BOUNDTYPE_LOWER + if bound_types[i] == "ub": + bound_types_array[i] = SCIP_BOUNDTYPE_UPPER + bounds_array[i] = bounds[i] + + # SCIP_BOUNDTYPE is a typedef for SCIP_BoundType enum with entries SCIP_BOUNDTYPE_LOWER, SCIP_BOUNDTYPE_UPPER + PY_SCIP_CALL(SCIPcreateConsBounddisjunction(self._scip, &scip_cons, str_conversion(name), nvars, vars_array, + bound_types_array, bounds_array, initial, + separate, enforce, check, propagate, local, modifiable, dynamic, removable, + stickingatnode)) + + + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) + pyCons = Constraint.create(scip_cons) + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + + free(vars_array) + free(bound_types_array) + free(bounds_array) + + return pyCons + + def addConsAnd(self, vars, resvar, name="ANDcons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, modifiable=False, dynamic=False, @@ -2188,6 +2305,7 @@ cdef class Model: def getConss(self): """Retrieve all constraints.""" cdef SCIP_CONS** _conss + # cdef SCIP_CONS* _cons # is not used cdef int _nconss conss = [] @@ -2350,6 +2468,7 @@ cdef class Model: self.setBoolParam("constraints/benderslp/active", True) self.setBoolParam("constraints/benders/active", True) #self.setIntParam("limits/maxorigsol", 0) + free(subprobs) def computeBestSolSubproblems(self): """Solves the subproblems with the best solution to the master problem. @@ -2644,6 +2763,14 @@ cdef class Model: branchrule.model = weakref.proxy(self) Py_INCREF(branchrule) + #def includeBranchruleNNDomain(self): + + # PY_SCIP_CALL(SCIPincludeBranchruleNNDomain(self._scip)) + + def setNNDomainData(self): + pass + #cdef BRANCHRULE_DATA* + def includeBenders(self, Benders benders, name, desc, priority=1, cutlp=True, cutpseudo=True, cutrelax=True, shareaux=False): """Include a Benders' decomposition. @@ -2673,6 +2800,40 @@ cdef class Model: benders.name = name Py_INCREF(benders) + def getLPBranchCands(self): + """gets branching candidates for LP solution branching (fractional variables) along with solution values, + fractionalities, and number of branching candidates; The number of branching candidates does NOT account + for fractional implicit integer variables which should not be used for branching decisions. Fractional + implicit integer variables are stored at the positions *nlpcands to *nlpcands + *nfracimplvars - 1 + branching rules should always select the branching candidate among the first npriolpcands of the candidate list + + Returns: + lpcands pointer to store the array of LP branching candidates, or NULL + lpcandssol pointer to store the array of LP candidate solution values, or NULL + lpcandsfrac pointer to store the array of LP candidate fractionalities, or NULL + nlpcands pointer to store the number of LP branching candidates, or NULL + npriolpcands pointer to store the number of candidates with maximal priority, or NULL + nfracimplvars pointer to store the number of fractional implicit integer variables, or NULL + """ + + cdef int ncands + cdef int nlpcands + cdef int npriolpcands + cdef int nfracimplvars + + ncands = SCIPgetNLPBranchCands(self._scip) + + cdef SCIP_VAR** lpcands = malloc(ncands * sizeof(SCIP_VAR*)) + cdef SCIP_Real* lpcandssol = malloc(ncands * sizeof(SCIP_Real)) + cdef SCIP_Real* lpcandsfrac = malloc(ncands * sizeof(SCIP_Real)) + + PY_SCIP_CALL(SCIPgetLPBranchCands(self._scip, &lpcands, &lpcandssol, &lpcandsfrac, + &nlpcands, &npriolpcands, &nfracimplvars)) + + return ([Variable.create(lpcands[i]) for i in range(ncands)], [lpcandssol[i] for i in range(ncands)], + [lpcandsfrac[i] for i in range(ncands)], nlpcands, npriolpcands, nfracimplvars) + + def branchVar(self, variable): """Branch on a non-continuous variable.""" @@ -2697,7 +2858,6 @@ cdef class Model: # TODO should the stuff be freed and how? return Node.create(downchild), Node.create(eqchild), Node.create(upchild) - def createChild(self, nodeselprio, estimate): """Create a child node of the focus node.""" @@ -2772,6 +2932,58 @@ cdef class Model: """returns if the current node is already solved and only propagated again.""" return SCIPinRepropagation(self._scip) + def relaxAllConss(self): + """In diving mode, set the bounds to infinity for all constraints. Must only be called when SCIP is in + diving mode. + + """ + cdef SCIP_ROW** rows + cdef int nrows + + PY_SCIP_CALL(SCIPgetLPRowsData(self._scip, &rows, &nrows)) + cdef int i + for i in range(nrows): + PY_SCIP_CALL(SCIPchgRowLhsDive(self._scip, rows[i], -SCIPinfinity(self._scip))) + PY_SCIP_CALL(SCIPchgRowRhsDive(self._scip, rows[i], SCIPinfinity(self._scip))) + + def fixAllVariablesToZeroDive(self): + """Fixes all (transformed) variables to zero in diving mode. Must only be called when SCIP is in diving mode.""" + cdef SCIP_VAR** _vars = SCIPgetVars(self._scip) + cdef int _nvars = SCIPgetNVars(self._scip) + cdef int i + + for i in range(_nvars): + PY_SCIP_CALL(SCIPchgVarLbDive(self._scip, _vars[i], 0.0)) + PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, _vars[i], 0.0)) + + def enableVarAndConssDive(self): + + cdef int ndivechgsides + cdef SCIP_Real* divechgsides + cdef SCIP_ROW** divechgrows + cdef SCIP_SIDETYPE* divechgsidetypes + cdef SCIP* scip = self._scip # not sure if this is necessary + + + #PY_SCIP_CALl(SCIPchgVarLbDive(self._scip, var.var, SCIPvarGetLbLocal(var.var))) + #PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, var.var, SCIPvarGetUbLocal(var.var))) + PY_SCIP_CALL(SCIPgetLPRowChgsDive(self._scip, &ndivechgsides, &divechgsides, &divechgsidetypes, &divechgrows)) + + """cdef SCIP_COL* col + if SCIPvarIsInLP(var): + col = SCIPvarGetCol(var) + cdef SCIP_ROW** rows = SCIPcolGetRows(col)""" + + # we don't want Python stuff in the C loop, hence no PY_SCIP_CALL + cdef int i + for i in range(ndivechgsides): + if divechgsidetypes[i] == SCIP_SIDETYPE_LEFT: + SCIPchgRowLhsDive(scip, divechgrows[i], divechgsides[i]) + else: + SCIPchgRowRhsDive(scip, divechgrows[i], divechgsides[i]) + + + # Probing methods (Probing is tree based) def startProbing(self): """Initiates probing, making methods SCIPnewProbingNode(), SCIPbacktrackProbing(), SCIPchgVarLbProbing(), @@ -2797,6 +3009,7 @@ cdef class Model: def inProbing(self): return SCIPinProbing(self._scip) + def solveProbingLP(self, itlim = -1): """solves the LP at the current probing node (cannot be applied at preprocessing stage) no separation or pricing is applied @@ -3027,6 +3240,7 @@ cdef class Model: raise Warning("method cannot be called before problem is solved") return self.getSolVal(self._bestSol, var) + def getPrimalbound(self): """Retrieve the best primal bound.""" return SCIPgetPrimalbound(self._scip) @@ -3075,6 +3289,7 @@ cdef class Model: else: return "unknown" + def catchEvent(self, eventtype, Eventhdlr eventhdlr): cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): @@ -3161,7 +3376,6 @@ cdef class Model: SCIPsetMessagehdlrQuiet(self._scip, quiet) # Output Methods - def redirectOutput(self): """Send output to python instead of terminal.""" From a2bdb29991348e95944557fa1eb580b87587f04b Mon Sep 17 00:00:00 2001 From: roessig <27856832+roessig@users.noreply.github.com> Date: Mon, 29 Oct 2018 12:34:45 +0100 Subject: [PATCH 093/121] test to reduce priority of trivial heuristic --- tests/test_branch_probing_lp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index 156b4971b..7eaf0e71f 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -42,6 +42,7 @@ def branchexeclp(self, allowaddcons): m = Model() +m.setIntParam("heuristics/trivial/priority", -1000) x0 = m.addVar(lb=-2, ub=4) r1 = m.addVar() From 639a6f449b48c4acedb0e6fbdb682995e552b8e1 Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Mon, 29 Oct 2018 19:21:06 +0100 Subject: [PATCH 094/121] new test without quadratic --- tests/test_branch_probing_lp.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index 156b4971b..d5c65d8ec 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -1,12 +1,11 @@ -from pyscipopt import Model, Branchrule, SCIP_RESULT +from pyscipopt import Model, Branchrule, SCIP_RESULT, quicksum class MyBranching(Branchrule): - def __init__(self, model, cont, integral): + def __init__(self, model, cont): self.model = model self.cont = cont - self.integral = integral self.count = 0 self.was_called_val = False self.was_called_int = False @@ -28,6 +27,8 @@ def branchexeclp(self, allowaddcons): self.model.getLPObjVal() self.model.endProbing() + self.integral = self.model.getLPBranchCands()[0][0] + if self.count == 1: down, eq, up = self.model.branchVarVal(self.cont, 1.3) self.model.chgVarLbNode(down, self.cont, -1.5) @@ -42,6 +43,9 @@ def branchexeclp(self, allowaddcons): m = Model() +m.setIntParam("presolving/maxrounds", 0) +m.setLongintParam("lp/rootiterlim", 3) +m.setRealParam("limits/time", 10) x0 = m.addVar(lb=-2, ub=4) r1 = m.addVar() @@ -51,17 +55,28 @@ def branchexeclp(self, allowaddcons): l = m.addVar(vtype="I", lb=-9, ub=18) u = m.addVar(vtype="I", lb=-3, ub=99) +more_vars = [] +for i in range(1000): + more_vars.append(m.addVar(vtype="I", lb= -12, ub=40)) + m.addCons(quicksum(v for v in more_vars) <= (40 - i) * quicksum(v for v in more_vars[::2])) + m.addCons(r1 >= x0) m.addCons(r2 >= -x0) m.addCons(y0 == r1 +r2) -m.addCons(t * l + l * u >= 4) +#m.addCons(t * l + l * u >= 4) +m.addCons(t + l + 7* u <= 300) +m.addCons(t >= quicksum(v for v in more_vars[::3])) +m.addCons(more_vars[3] >= l + 2) +m.addCons(7 <= quicksum(v for v in more_vars[::4]) - x0) +m.addCons(quicksum(v for v in more_vars[::2]) + l<= quicksum(v for v in more_vars[::4])) + -m.setObjective(t) -m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) +m.setObjective(t - quicksum(j*v for j, v in enumerate(more_vars[20:-40]))) +#m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) -my_branchrule = MyBranching(m, x0, l) +my_branchrule = MyBranching(m, x0) m.includeBranchrule(my_branchrule, "test branch", "test branching and probing and lp functions", priority=10000000, maxdepth=3, maxbounddist=1) From 20edb272ecaf1adaa730235e432cf6d396d6e69b Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Mon, 29 Oct 2018 19:41:13 +0100 Subject: [PATCH 095/121] back to old status --- src/pyscipopt/scip.pxd | 73 ------- src/pyscipopt/scip.pyx | 334 ++++++-------------------------- tests/test_branch_probing_lp.py | 29 +-- 3 files changed, 67 insertions(+), 369 deletions(-) mode change 100644 => 100755 src/pyscipopt/scip.pxd mode change 100644 => 100755 src/pyscipopt/scip.pyx diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd old mode 100644 new mode 100755 index b3107c27b..e3aa49cfc --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -250,10 +250,6 @@ cdef extern from "scip/scip.h": SCIP_LPSOLSTAT_TIMELIMIT = 6 SCIP_LPSOLSTAT_ERROR = 7 - ctypedef enum SCIP_SIDETYPE: - SCIP_SIDETYPE_LEFT = 0 - SCIP_SIDETYPE_RIGHT = 1 - ctypedef bint SCIP_Bool ctypedef long long SCIP_Longint @@ -453,15 +449,6 @@ cdef extern from "scip/scip.h": SCIP_STAGE SCIPgetStage(SCIP* scip) SCIP_RETCODE SCIPsetProbName(SCIP* scip, char* name) const char* SCIPgetProbName(SCIP* scip) - SCIP_RETCODE SCIPcopy(SCIP* sourcescip, - SCIP* targetscip, - SCIP_HASHMAP* varmap, - SCIP_HASHMAP* consmap, - const char* suffix, - SCIP_Bool global_copy, - SCIP_Bool enablepricing, - SCIP_Bool passmessagehdlr, - SCIP_Bool* valid) # Diving methods SCIP_RETCODE SCIPstartDive(SCIP* scip) @@ -475,8 +462,6 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPchgRowRhsDive(SCIP* scip, SCIP_ROW* row, SCIP_Real newrhs) SCIP_RETCODE SCIPaddRowDive(SCIP* scip, SCIP_ROW* row) SCIP_RETCODE SCIPendDive(SCIP* scip) - SCIP_RETCODE SCIPgetLPRowChgsDive(SCIP* scip, int* ndivechgsides, SCIP_Real** divechgsides, - SCIP_SIDETYPE** divechgsidetypes, SCIP_ROW*** divechgrows) # Probing methods SCIP_RETCODE SCIPstartProbing(SCIP* scip) @@ -567,7 +552,6 @@ cdef extern from "scip/scip.h": SCIP_Longint SCIPnodeGetNumber(SCIP_NODE* node) int SCIPnodeGetDepth(SCIP_NODE* node) SCIP_Real SCIPnodeGetLowerbound(SCIP_NODE* node) - SCIP_RETCODE SCIPupdateNodeDualbound(SCIP* scip, SCIP_NODE* node, SCIP_Real newbound) SCIP_RETCODE SCIPupdateNodeLowerbound(SCIP* scip, SCIP_NODE* node, SCIP_Real newbound) SCIP_Real SCIPnodeGetEstimate(SCIP_NODE* node) SCIP_NODETYPE SCIPnodeGetType(SCIP_NODE* node) @@ -634,7 +618,6 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPgetLPBInvRow(SCIP* scip, int r, SCIP_Real* coefs, int* inds, int* ninds) SCIP_RETCODE SCIPgetLPBInvARow(SCIP* scip, int r, SCIP_Real* binvrow, SCIP_Real* coefs, int* inds, int* ninds) SCIP_RETCODE SCIPconstructLP(SCIP* scip, SCIP_Bool* cutoff) - SCIP_RETCODE SCIPflushLP(SCIP* scip) SCIP_Real SCIPgetLPObjval(SCIP* scip) SCIP_Bool SCIPisLPSolBasic(SCIP* scip) SCIP_LPSOLSTAT SCIPgetLPSolstat(SCIP* scip) @@ -646,10 +629,6 @@ cdef extern from "scip/scip.h": SCIP_Real SCIPgetCutEfficacy(SCIP* scip, SCIP_SOL* sol, SCIP_ROW* cut) SCIP_Bool SCIPisCutEfficacious(SCIP* scip, SCIP_SOL* sol, SCIP_ROW* cut) - SCIP_RETCODE SCIPmarkDoNotMultaggrVar(SCIP* scip, SCIP_VAR* var) - SCIP_VAR** SCIPgetFixedVars(SCIP* scip) - int SCIPgetNFixedVars(SCIP* scip) - # Constraint Methods SCIP_RETCODE SCIPcaptureCons(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPreleaseCons(SCIP* scip, SCIP_CONS** cons) @@ -666,7 +645,6 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPconsIsChecked(SCIP_CONS* cons) SCIP_Bool SCIPconsIsPropagated(SCIP_CONS* cons) SCIP_Bool SCIPconsIsLocal(SCIP_CONS* cons) - SCIP_Bool SCIPconsIsGlobal(SCIP_CONS* cons) SCIP_Bool SCIPconsIsModifiable(SCIP_CONS* cons) SCIP_Bool SCIPconsIsDynamic(SCIP_CONS* cons) SCIP_Bool SCIPconsIsRemovable(SCIP_CONS* cons) @@ -959,10 +937,6 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*branchruleexecps) (SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result), SCIP_BRANCHRULEDATA* branchruledata) SCIP_BRANCHRULEDATA* SCIPbranchruleGetData(SCIP_BRANCHRULE* branchrule) - void SCIPbranchruleSetData(SCIP_BRANCHRULE* branchrule, - SCIP_BRANCHRULEDATA* branchruledata - ) - # Benders' decomposition plugin SCIP_RETCODE SCIPincludeBenders(SCIP* scip, @@ -1006,15 +980,6 @@ cdef extern from "scip/scip.h": SCIP_BENDERS** SCIPgetBenders(SCIP* scip) void SCIPbendersUpdateSubproblemLowerbound(SCIP_BENDERS* benders, int probnumber, SCIP_Real lowerbound) - SCIP_RETCODE SCIPgetNLPBranchCands(SCIP* scip) - SCIP_RETCODE SCIPgetLPBranchCands(SCIP* scip, - SCIP_VAR*** lpcands, - SCIP_Real** lpcandssol, - SCIP_Real** lpcandsfrac, - int* nlpcands, - int* npriolpcands, - int* nfracimplvars) - SCIP_RETCODE SCIPbranchVar(SCIP* scip, SCIP_VAR* var, SCIP_NODE** downchild, @@ -1028,15 +993,6 @@ cdef extern from "scip/scip.h": SCIP_NODE** eqchild, SCIP_NODE** upchild) - SCIP_RETCODE SCIPaddConsNode(SCIP* scip, - SCIP_NODE* node, - SCIP_CONS* cons, - SCIP_NODE* validnode) - - SCIP_RETCODE SCIPaddConsLocal(SCIP* scip, - SCIP_CONS* cons, - SCIP_NODE* validnode) - # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) SCIP_Real SCIPfrac(SCIP* scip, SCIP_Real val) @@ -1110,17 +1066,11 @@ cdef extern from "scip/scip.h": #re-optimization routines SCIP_RETCODE SCIPfreeReoptSolve(SCIP* scip) SCIP_RETCODE SCIPchgReoptObjective(SCIP* scip, SCIP_OBJSENSE objsense, SCIP_VAR** vars, SCIP_Real* coefs, int nvars) - SCIP_RETCODE SCIPenableReoptimization(SCIP* scip, SCIP_Bool enable) - SCIP_RETCODE SCIPisReoptEnabled(SCIP* scip) BMS_BLKMEM* SCIPblkmem(SCIP* scip) cdef extern from "scip/tree.h": int SCIPnodeGetNAddedConss(SCIP_NODE* node) - void SCIPnodeGetAddedConss(SCIP_NODE* node, - SCIP_CONS** addedconss, - int* naddedconss, - int addedconsssize) cdef extern from "scip/scipdefplugins.h": SCIP_RETCODE SCIPincludeDefaultPlugins(SCIP* scip) @@ -1257,29 +1207,6 @@ cdef extern from "scip/cons_sos2.h": SCIP_CONS* cons, SCIP_VAR* var) -cdef extern from "scip/cons_bounddisjunction.h": - SCIP_RETCODE SCIPcreateConsBounddisjunction(SCIP* scip, - SCIP_CONS** cons, - const char* name, - int nvars, - SCIP_VAR** vars, - SCIP_BOUNDTYPE* boundtypes, - SCIP_Real* bounds, - SCIP_Bool initial, - SCIP_Bool separate, - SCIP_Bool enforce, - SCIP_Bool check, - SCIP_Bool propagate, - SCIP_Bool local, - SCIP_Bool modifiable, - SCIP_Bool dynamic, - SCIP_Bool removable, - SCIP_Bool stickingatnode) - -#cdef extern from "scip/branch_nndomain.h": - #SCIP_RETCODE SCIPincludeBranchruleNNDomain(SCIP* scip) - - cdef extern from "scip/cons_and.h": SCIP_RETCODE SCIPcreateConsAnd(SCIP* scip, SCIP_CONS** cons, diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx old mode 100644 new mode 100755 index d1e5aaad0..60884a0f4 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -418,17 +418,6 @@ cdef class Node: """Retrieve number of added constraints at this node""" return SCIPnodeGetNAddedConss(self.node) - def getAddedConss(self): - """Retrieve all constraints added at this node.""" - cdef int _nconss - _nconss = SCIPnodeGetNAddedConss(self.node) - - cdef SCIP_CONS** _conss = malloc(_nconss * sizeof(SCIP_CONS*)) - - cdef int _nconss_new - SCIPnodeGetAddedConss(self.node, _conss, &_nconss_new, _nconss) - return [Constraint.create(_conss[i]) for i in range(_nconss_new)] - def isActive(self): """Is the node in the path to the current node?""" return SCIPnodeIsActive(self.node) @@ -437,6 +426,7 @@ cdef class Node: """Is the node marked to be propagated again?""" return SCIPnodeIsPropagatedAgain(self.node) + cdef class Variable(Expr): """Is a linear expression and has SCIP_VAR*""" cdef SCIP_VAR* var @@ -450,7 +440,7 @@ cdef class Variable(Expr): property name: def __get__(self): - cname = bytes(SCIPvarGetName(self.var)) + cname = bytes( SCIPvarGetName(self.var) ) return cname.decode('utf-8') def ptr(self): @@ -565,10 +555,6 @@ cdef class Constraint: """Retrieve True if constraint is only locally valid or not added to any (sub)problem""" return SCIPconsIsLocal(self.cons) - def isGlobal(self): - """returns TRUE iff constraint is globally valid""" - return SCIPconsIsGlobal(self.cons) - def isModifiable(self): """Retrieve True if constraint is modifiable (subject to column generation)""" return SCIPconsIsModifiable(self.cons) @@ -771,13 +757,6 @@ cdef class Model: return quality - # Enable Reoptimization - def enableReoptimization(self): - PY_SCIP_CALL(SCIPenableReoptimization(self._scip, True)) - - def isReoptEnabled(self): - return PY_SCIP_CALL(SCIPisReoptEnabled(self._scip)) - # Objective function def setMinimize(self): @@ -811,7 +790,6 @@ cdef class Model: for i in range(_nvars): PY_SCIP_CALL(SCIPchgVarObj(self._scip, _vars[i], 0.0)) - def setObjective(self, coeffs, sense = 'minimize', clear = 'true'): """Establish the objective function as a linear expression. @@ -943,21 +921,6 @@ cdef class Model: PY_SCIP_CALL(SCIPwriteOrigProblem(self._scip, fn, ext, False)) print('wrote problem to file ' + str(fn)) - - def copyModel(self, Model new_model, global_copy=True, enablepricing=False, passmessagehdlr=False): - """Create a copy of the model - - :param global_copy: - :param enablepricing: - :param passmessagehdlr: - :return: bool, indicating if copying was valid or not - """ - cdef SCIP_Bool valid - PY_SCIP_CALL(SCIPcopy(self._scip, new_model._scip, NULL, NULL, "suffix", global_copy, - enablepricing, passmessagehdlr, &valid)) - return valid - - # Variable Functions def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False): @@ -1035,9 +998,9 @@ cdef class Model: :param Variable var: variable to fix :param val: float, the fix value - :return tuple (infeasible, fixed) of booleans - """ + :return: tuple (infeasible, fixed) of booleans + """ cdef SCIP_Bool infeasible cdef SCIP_Bool fixed PY_SCIP_CALL(SCIPfixVar(self._scip, var.var, val, &infeasible, &fixed)) @@ -1047,62 +1010,78 @@ cdef class Model: """Delete a variable. :param var: the variable which shall be deleted - :return bool, was deleting succesful - """ + :return: bool, was deleting succesful + """ cdef SCIP_Bool deleted PY_SCIP_CALL(SCIPdelVar(self._scip, var.var, &deleted)) return deleted def tightenVarLb(self, Variable var, lb, force=False): """Tighten the lower bound in preprocessing or current node, if the bound is tighter. + :param var: SCIP variable :param lb: possible new lower bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.var, lb, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def tightenVarUb(self, Variable var, ub, force=False): """Tighten the upper bound in preprocessing or current node, if the bound is tighter. + :param var: SCIP variable :param ub: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.var, ub, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def tightenVarUbGlobal(self, Variable var, ub, force=False): """Tighten the global upper bound, if the bound is tighter. + :param var: SCIP variable :param ub: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.var, ub, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def tightenVarLbGlobal(self, Variable var, lb, force=False): """Tighten the global upper bound, if the bound is tighter. + :param var: SCIP variable :param lb: possible new upper bound :param force: force tightening even if below bound strengthening tolerance - :return: bool, if the bound was tightened + :return: tuple of bools, (infeasible, tightened) + infeasible: whether new domain is empty + tightened: whether the bound was tightened + """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.var, lb, force, &infeasible, &tightened)) - return tightened + return infeasible, tightened def chgVarLb(self, Variable var, lb): """Changes the lower bound of the specified variable. @@ -1171,27 +1150,6 @@ cdef class Model: ub = SCIPinfinity(self._scip) PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.node, var.var, ub)) - def updateNodeLowerbound(self, Node node, lb): - """if given value is larger than the node's lower bound (in transformed problem), - sets the node's lower bound to the new value - - Args: - node: Node, the node to update - newbound: float, new bound (if greater) for the node - """ - PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) - - def updateNodeDualbound(self, Node node, lb): - """if given value is larger than the node's dual bound (in transformed problem), - sets the node's lower bound to the new value - - Args: - node: Node, the node to update - newbound: float, new bound (if greater) for the node - """ - PY_SCIP_CALL(SCIPupdateNodeDualbound(self._scip, node.node, lb)) - - def chgVarType(self, Variable var, vtype): """Changes the type of a variable @@ -1211,52 +1169,53 @@ cdef class Model: if infeasible: print('could not change variable type of variable %s' % var) - def getVars(self, transformed=False, fixed=False): + def getVars(self, transformed=False): """Retrieve all variables. :param transformed: get transformed variables instead of original (Default value = False) - :param fixed: get all fixed and aggregated variables instead of original - Only one of transformed and fixed may be true. """ - assert not all((transformed, fixed)) cdef SCIP_VAR** _vars + cdef SCIP_VAR* _var cdef int _nvars vars = [] if transformed: _vars = SCIPgetVars(self._scip) _nvars = SCIPgetNVars(self._scip) - elif fixed: - _vars = SCIPgetFixedVars(self._scip) - _nvars = SCIPgetNFixedVars(self._scip) else: _vars = SCIPgetOrigVars(self._scip) _nvars = SCIPgetNOrigVars(self._scip) return [Variable.create(_vars[i]) for i in range(_nvars)] + def updateNodeLowerbound(self, Node node, lb): + """if given value is larger than the node's lower bound (in transformed problem), + sets the node's lower bound to the new value + + :param node: Node, the node to update + :param newbound: float, new bound (if greater) for the node + + """ + PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) + # LP Methods def getLPSolstat(self): """Gets solution status of current LP""" return SCIPgetLPSolstat(self._scip) + def constructLP(self): """makes sure that the LP of the current node is loaded and may be accessed through the LP information methods - Returns: - cutoff: bool, can the node be cut off? - """ + :return: bool cutoff, i.e. can the node be cut off? + + """ cdef SCIP_Bool cutoff PY_SCIP_CALL(SCIPconstructLP(self._scip, &cutoff)) return cutoff - def flushLP(self): - """makes sure that the LP of the current node is flushed""" - - PY_SCIP_CALL(SCIPflushLP(self._scip)) - def getLPObjVal(self): """gets objective value of current LP (which is the sum of column and loose objective value)""" @@ -1378,7 +1337,7 @@ cdef class Model: def addCons(self, cons, name='', initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, modifiable=False, dynamic=False, removable=False, - stickingatnode=False, Node node=None, Node valid_node=None): + stickingatnode=False): """Add a linear or quadratic constraint. :param cons: list of coefficients @@ -1406,7 +1365,7 @@ cdef class Model: propagate=propagate, local=local, modifiable=modifiable, dynamic=dynamic, removable=removable, - stickingatnode=stickingatnode, node=node, valid_node=valid_node) + stickingatnode=stickingatnode) kwargs['lhs'] = -SCIPinfinity(self._scip) if cons.lhs is None else cons.lhs kwargs['rhs'] = SCIPinfinity(self._scip) if cons.rhs is None else cons.rhs @@ -1427,48 +1386,20 @@ cdef class Model: terms = lincons.expr.terms cdef SCIP_CONS* scip_cons - cdef int nvars = len(terms.items()) - - vars_array = malloc(nvars * sizeof(SCIP_VAR*)) - coeffs_array = malloc(nvars * sizeof(SCIP_Real)) - - for i, (key, coeff) in enumerate(terms.items()): - vars_array[i] = (key[0]).var - coeffs_array[i] = coeff - PY_SCIP_CALL(SCIPcreateConsLinear( - self._scip, &scip_cons, str_conversion(kwargs['name']), nvars, vars_array, coeffs_array, - kwargs['lhs'], kwargs['rhs'], kwargs['initial'], - kwargs['separate'], kwargs['enforce'], kwargs['check'], - kwargs['propagate'], kwargs['local'], kwargs['modifiable'], - kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) - - # PY_SCIP_CALL(SCIPcreateConsLinear( - # self._scip, &scip_cons, str_conversion(kwargs['name']), 0, NULL, NULL, - # kwargs['lhs'], kwargs['rhs'], kwargs['initial'], - # kwargs['separate'], kwargs['enforce'], kwargs['check'], - # kwargs['propagate'], kwargs['local'], kwargs['modifiable'], - # kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) - # - # for key, coeff in terms.items(): - # var = key[0] - # PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, coeff)) - - if kwargs["node"] is None: - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) - #print("add cons globally") - #elif not kwargs["node_trick"]: - else: - # add cons at a node, valid_node is set to NULL - #print("add cons to node", kwargs["node"]) - PY_SCIP_CALL(SCIPaddConsNode(self._scip, (kwargs["node"]).node, scip_cons, NULL)) - # maybe should use this function for local constraints - #PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, NULL)) + self._scip, &scip_cons, str_conversion(kwargs['name']), 0, NULL, NULL, + kwargs['lhs'], kwargs['rhs'], kwargs['initial'], + kwargs['separate'], kwargs['enforce'], kwargs['check'], + kwargs['propagate'], kwargs['local'], kwargs['modifiable'], + kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) + + for key, coeff in terms.items(): + var = key[0] + PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, coeff)) + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) PyCons = Constraint.create(scip_cons) PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) - free(vars_array) - free(coeffs_array) return PyCons @@ -1745,54 +1676,6 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) return Constraint.create(scip_cons) - def markDoNotMultaggrVar(self, var): - - PY_SCIP_CALL(SCIPmarkDoNotMultaggrVar(self._scip, (var).var)) - - def addConsBoundDisjunction(self, vars, bound_types, bounds, name="BoundDisjunctionCons", initial=True, separate=True, enforce=True, check=True, - propagate=True, local=False, modifiable=False, dynamic=False, - removable=False, stickingatnode=False): - """Add a bound disjunction constraint. By Ansgar - - :param vars: list of variables that shall be included - :param bound_types: list of str, where "lb" for lower bound, "ub" for upperbound - :param bounds: list of float, the bound values, it must hold len(bounds) == len(bound_types) == len(vars) - """ - - assert len(vars) == len(bound_types) == len(bounds) - - cdef SCIP_CONS* scip_cons - nvars = len(vars) - vars_array = malloc(nvars * sizeof(SCIP_VAR*)) - bound_types_array = malloc(nvars * sizeof(SCIP_BOUNDTYPE)) - bounds_array = malloc(nvars * sizeof(SCIP_Real)) - - for i in range(nvars): - vars_array[i] = (vars[i]).var - if bound_types[i] == "lb": - bound_types_array[i] = SCIP_BOUNDTYPE_LOWER - if bound_types[i] == "ub": - bound_types_array[i] = SCIP_BOUNDTYPE_UPPER - bounds_array[i] = bounds[i] - - # SCIP_BOUNDTYPE is a typedef for SCIP_BoundType enum with entries SCIP_BOUNDTYPE_LOWER, SCIP_BOUNDTYPE_UPPER - PY_SCIP_CALL(SCIPcreateConsBounddisjunction(self._scip, &scip_cons, str_conversion(name), nvars, vars_array, - bound_types_array, bounds_array, initial, - separate, enforce, check, propagate, local, modifiable, dynamic, removable, - stickingatnode)) - - - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) - pyCons = Constraint.create(scip_cons) - PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) - - free(vars_array) - free(bound_types_array) - free(bounds_array) - - return pyCons - - def addConsAnd(self, vars, resvar, name="ANDcons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, modifiable=False, dynamic=False, @@ -2305,7 +2188,6 @@ cdef class Model: def getConss(self): """Retrieve all constraints.""" cdef SCIP_CONS** _conss - # cdef SCIP_CONS* _cons # is not used cdef int _nconss conss = [] @@ -2468,7 +2350,6 @@ cdef class Model: self.setBoolParam("constraints/benderslp/active", True) self.setBoolParam("constraints/benders/active", True) #self.setIntParam("limits/maxorigsol", 0) - free(subprobs) def computeBestSolSubproblems(self): """Solves the subproblems with the best solution to the master problem. @@ -2763,14 +2644,6 @@ cdef class Model: branchrule.model = weakref.proxy(self) Py_INCREF(branchrule) - #def includeBranchruleNNDomain(self): - - # PY_SCIP_CALL(SCIPincludeBranchruleNNDomain(self._scip)) - - def setNNDomainData(self): - pass - #cdef BRANCHRULE_DATA* - def includeBenders(self, Benders benders, name, desc, priority=1, cutlp=True, cutpseudo=True, cutrelax=True, shareaux=False): """Include a Benders' decomposition. @@ -2800,40 +2673,6 @@ cdef class Model: benders.name = name Py_INCREF(benders) - def getLPBranchCands(self): - """gets branching candidates for LP solution branching (fractional variables) along with solution values, - fractionalities, and number of branching candidates; The number of branching candidates does NOT account - for fractional implicit integer variables which should not be used for branching decisions. Fractional - implicit integer variables are stored at the positions *nlpcands to *nlpcands + *nfracimplvars - 1 - branching rules should always select the branching candidate among the first npriolpcands of the candidate list - - Returns: - lpcands pointer to store the array of LP branching candidates, or NULL - lpcandssol pointer to store the array of LP candidate solution values, or NULL - lpcandsfrac pointer to store the array of LP candidate fractionalities, or NULL - nlpcands pointer to store the number of LP branching candidates, or NULL - npriolpcands pointer to store the number of candidates with maximal priority, or NULL - nfracimplvars pointer to store the number of fractional implicit integer variables, or NULL - """ - - cdef int ncands - cdef int nlpcands - cdef int npriolpcands - cdef int nfracimplvars - - ncands = SCIPgetNLPBranchCands(self._scip) - - cdef SCIP_VAR** lpcands = malloc(ncands * sizeof(SCIP_VAR*)) - cdef SCIP_Real* lpcandssol = malloc(ncands * sizeof(SCIP_Real)) - cdef SCIP_Real* lpcandsfrac = malloc(ncands * sizeof(SCIP_Real)) - - PY_SCIP_CALL(SCIPgetLPBranchCands(self._scip, &lpcands, &lpcandssol, &lpcandsfrac, - &nlpcands, &npriolpcands, &nfracimplvars)) - - return ([Variable.create(lpcands[i]) for i in range(ncands)], [lpcandssol[i] for i in range(ncands)], - [lpcandsfrac[i] for i in range(ncands)], nlpcands, npriolpcands, nfracimplvars) - - def branchVar(self, variable): """Branch on a non-continuous variable.""" @@ -2858,6 +2697,7 @@ cdef class Model: # TODO should the stuff be freed and how? return Node.create(downchild), Node.create(eqchild), Node.create(upchild) + def createChild(self, nodeselprio, estimate): """Create a child node of the focus node.""" @@ -2932,58 +2772,6 @@ cdef class Model: """returns if the current node is already solved and only propagated again.""" return SCIPinRepropagation(self._scip) - def relaxAllConss(self): - """In diving mode, set the bounds to infinity for all constraints. Must only be called when SCIP is in - diving mode. - - """ - cdef SCIP_ROW** rows - cdef int nrows - - PY_SCIP_CALL(SCIPgetLPRowsData(self._scip, &rows, &nrows)) - cdef int i - for i in range(nrows): - PY_SCIP_CALL(SCIPchgRowLhsDive(self._scip, rows[i], -SCIPinfinity(self._scip))) - PY_SCIP_CALL(SCIPchgRowRhsDive(self._scip, rows[i], SCIPinfinity(self._scip))) - - def fixAllVariablesToZeroDive(self): - """Fixes all (transformed) variables to zero in diving mode. Must only be called when SCIP is in diving mode.""" - cdef SCIP_VAR** _vars = SCIPgetVars(self._scip) - cdef int _nvars = SCIPgetNVars(self._scip) - cdef int i - - for i in range(_nvars): - PY_SCIP_CALL(SCIPchgVarLbDive(self._scip, _vars[i], 0.0)) - PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, _vars[i], 0.0)) - - def enableVarAndConssDive(self): - - cdef int ndivechgsides - cdef SCIP_Real* divechgsides - cdef SCIP_ROW** divechgrows - cdef SCIP_SIDETYPE* divechgsidetypes - cdef SCIP* scip = self._scip # not sure if this is necessary - - - #PY_SCIP_CALl(SCIPchgVarLbDive(self._scip, var.var, SCIPvarGetLbLocal(var.var))) - #PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, var.var, SCIPvarGetUbLocal(var.var))) - PY_SCIP_CALL(SCIPgetLPRowChgsDive(self._scip, &ndivechgsides, &divechgsides, &divechgsidetypes, &divechgrows)) - - """cdef SCIP_COL* col - if SCIPvarIsInLP(var): - col = SCIPvarGetCol(var) - cdef SCIP_ROW** rows = SCIPcolGetRows(col)""" - - # we don't want Python stuff in the C loop, hence no PY_SCIP_CALL - cdef int i - for i in range(ndivechgsides): - if divechgsidetypes[i] == SCIP_SIDETYPE_LEFT: - SCIPchgRowLhsDive(scip, divechgrows[i], divechgsides[i]) - else: - SCIPchgRowRhsDive(scip, divechgrows[i], divechgsides[i]) - - - # Probing methods (Probing is tree based) def startProbing(self): """Initiates probing, making methods SCIPnewProbingNode(), SCIPbacktrackProbing(), SCIPchgVarLbProbing(), @@ -3009,7 +2797,6 @@ cdef class Model: def inProbing(self): return SCIPinProbing(self._scip) - def solveProbingLP(self, itlim = -1): """solves the LP at the current probing node (cannot be applied at preprocessing stage) no separation or pricing is applied @@ -3240,7 +3027,6 @@ cdef class Model: raise Warning("method cannot be called before problem is solved") return self.getSolVal(self._bestSol, var) - def getPrimalbound(self): """Retrieve the best primal bound.""" return SCIPgetPrimalbound(self._scip) @@ -3289,7 +3075,6 @@ cdef class Model: else: return "unknown" - def catchEvent(self, eventtype, Eventhdlr eventhdlr): cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): @@ -3376,6 +3161,7 @@ cdef class Model: SCIPsetMessagehdlrQuiet(self._scip, quiet) # Output Methods + def redirectOutput(self): """Send output to python instead of terminal.""" diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index d5c65d8ec..156b4971b 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -1,11 +1,12 @@ -from pyscipopt import Model, Branchrule, SCIP_RESULT, quicksum +from pyscipopt import Model, Branchrule, SCIP_RESULT class MyBranching(Branchrule): - def __init__(self, model, cont): + def __init__(self, model, cont, integral): self.model = model self.cont = cont + self.integral = integral self.count = 0 self.was_called_val = False self.was_called_int = False @@ -27,8 +28,6 @@ def branchexeclp(self, allowaddcons): self.model.getLPObjVal() self.model.endProbing() - self.integral = self.model.getLPBranchCands()[0][0] - if self.count == 1: down, eq, up = self.model.branchVarVal(self.cont, 1.3) self.model.chgVarLbNode(down, self.cont, -1.5) @@ -43,9 +42,6 @@ def branchexeclp(self, allowaddcons): m = Model() -m.setIntParam("presolving/maxrounds", 0) -m.setLongintParam("lp/rootiterlim", 3) -m.setRealParam("limits/time", 10) x0 = m.addVar(lb=-2, ub=4) r1 = m.addVar() @@ -55,28 +51,17 @@ def branchexeclp(self, allowaddcons): l = m.addVar(vtype="I", lb=-9, ub=18) u = m.addVar(vtype="I", lb=-3, ub=99) -more_vars = [] -for i in range(1000): - more_vars.append(m.addVar(vtype="I", lb= -12, ub=40)) - m.addCons(quicksum(v for v in more_vars) <= (40 - i) * quicksum(v for v in more_vars[::2])) - m.addCons(r1 >= x0) m.addCons(r2 >= -x0) m.addCons(y0 == r1 +r2) -#m.addCons(t * l + l * u >= 4) -m.addCons(t + l + 7* u <= 300) -m.addCons(t >= quicksum(v for v in more_vars[::3])) -m.addCons(more_vars[3] >= l + 2) -m.addCons(7 <= quicksum(v for v in more_vars[::4]) - x0) -m.addCons(quicksum(v for v in more_vars[::2]) + l<= quicksum(v for v in more_vars[::4])) - +m.addCons(t * l + l * u >= 4) -m.setObjective(t - quicksum(j*v for j, v in enumerate(more_vars[20:-40]))) -#m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) +m.setObjective(t) +m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) -my_branchrule = MyBranching(m, x0) +my_branchrule = MyBranching(m, x0, l) m.includeBranchrule(my_branchrule, "test branch", "test branching and probing and lp functions", priority=10000000, maxdepth=3, maxbounddist=1) From 75099e8276c418c9ad3dcc157c003cc002b61279 Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Mon, 29 Oct 2018 19:41:57 +0100 Subject: [PATCH 096/121] new test added --- tests/test_branch_probing_lp.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index 156b4971b..d5c65d8ec 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -1,12 +1,11 @@ -from pyscipopt import Model, Branchrule, SCIP_RESULT +from pyscipopt import Model, Branchrule, SCIP_RESULT, quicksum class MyBranching(Branchrule): - def __init__(self, model, cont, integral): + def __init__(self, model, cont): self.model = model self.cont = cont - self.integral = integral self.count = 0 self.was_called_val = False self.was_called_int = False @@ -28,6 +27,8 @@ def branchexeclp(self, allowaddcons): self.model.getLPObjVal() self.model.endProbing() + self.integral = self.model.getLPBranchCands()[0][0] + if self.count == 1: down, eq, up = self.model.branchVarVal(self.cont, 1.3) self.model.chgVarLbNode(down, self.cont, -1.5) @@ -42,6 +43,9 @@ def branchexeclp(self, allowaddcons): m = Model() +m.setIntParam("presolving/maxrounds", 0) +m.setLongintParam("lp/rootiterlim", 3) +m.setRealParam("limits/time", 10) x0 = m.addVar(lb=-2, ub=4) r1 = m.addVar() @@ -51,17 +55,28 @@ def branchexeclp(self, allowaddcons): l = m.addVar(vtype="I", lb=-9, ub=18) u = m.addVar(vtype="I", lb=-3, ub=99) +more_vars = [] +for i in range(1000): + more_vars.append(m.addVar(vtype="I", lb= -12, ub=40)) + m.addCons(quicksum(v for v in more_vars) <= (40 - i) * quicksum(v for v in more_vars[::2])) + m.addCons(r1 >= x0) m.addCons(r2 >= -x0) m.addCons(y0 == r1 +r2) -m.addCons(t * l + l * u >= 4) +#m.addCons(t * l + l * u >= 4) +m.addCons(t + l + 7* u <= 300) +m.addCons(t >= quicksum(v for v in more_vars[::3])) +m.addCons(more_vars[3] >= l + 2) +m.addCons(7 <= quicksum(v for v in more_vars[::4]) - x0) +m.addCons(quicksum(v for v in more_vars[::2]) + l<= quicksum(v for v in more_vars[::4])) + -m.setObjective(t) -m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) +m.setObjective(t - quicksum(j*v for j, v in enumerate(more_vars[20:-40]))) +#m.addCons(t >= r1 * (r1 - x0) + r2 * (r2 + x0)) -my_branchrule = MyBranching(m, x0, l) +my_branchrule = MyBranching(m, x0) m.includeBranchrule(my_branchrule, "test branch", "test branching and probing and lp functions", priority=10000000, maxdepth=3, maxbounddist=1) From 124cee723db7292cc3d1bbe0c210b499760a40ba Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Mon, 29 Oct 2018 20:01:13 +0100 Subject: [PATCH 097/121] added getLPBranchcand --- src/pyscipopt/scip.pxd | 4 ++++ src/pyscipopt/scip.pyx | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index e3aa49cfc..cdac0eb44 100755 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -992,6 +992,10 @@ cdef extern from "scip/scip.h": SCIP_NODE** downchild, SCIP_NODE** eqchild, SCIP_NODE** upchild) + int SCIPgetNLPBranchCands(SCIP* scip) + SCIP_RETCODE SCIPgetLPBranchCands(SCIP* scip, SCIP_VAR*** lpcands, SCIP_Real** lpcandssol, + SCIP_Real** lpcandsfrac, int* nlpcands, int* npriolpcands, int* nfracimplvars) + # Numerical Methods SCIP_Real SCIPinfinity(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 60884a0f4..14b166f24 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -2673,6 +2673,42 @@ cdef class Model: benders.name = name Py_INCREF(benders) + + def getLPBranchCands(self): + """gets branching candidates for LP solution branching (fractional variables) along with solution values, + fractionalities, and number of branching candidates; The number of branching candidates does NOT account + for fractional implicit integer variables which should not be used for branching decisions. Fractional + implicit integer variables are stored at the positions *nlpcands to *nlpcands + *nfracimplvars - 1 + branching rules should always select the branching candidate among the first npriolpcands of the candidate list + + :return tuple (lpcands, lpcandssol, lpcadsfrac, nlpcands, npriolpcands, nfracimplvars) where + + lpcands: list of variables of LP branching candidates + lpcandssol: list of LP candidate solution values + lpcandsfrac list of LP candidate fractionalities + nlpcands: number of LP branching candidates + npriolpcands: number of candidates with maximal priority + nfracimplvars: number of fractional implicit integer variables + """ + + cdef int ncands + cdef int nlpcands + cdef int npriolpcands + cdef int nfracimplvars + + ncands = SCIPgetNLPBranchCands(self._scip) + + cdef SCIP_VAR** lpcands = malloc(ncands * sizeof(SCIP_VAR*)) + cdef SCIP_Real* lpcandssol = malloc(ncands * sizeof(SCIP_Real)) + cdef SCIP_Real* lpcandsfrac = malloc(ncands * sizeof(SCIP_Real)) + + PY_SCIP_CALL(SCIPgetLPBranchCands(self._scip, &lpcands, &lpcandssol, &lpcandsfrac, + &nlpcands, &npriolpcands, &nfracimplvars)) + + return ([Variable.create(lpcands[i]) for i in range(ncands)], [lpcandssol[i] for i in range(ncands)], + [lpcandsfrac[i] for i in range(ncands)], nlpcands, npriolpcands, nfracimplvars) + + def branchVar(self, variable): """Branch on a non-continuous variable.""" From 3f5d637f8f34ce12a58c91324f6cd155bc9184ce Mon Sep 17 00:00:00 2001 From: ansgar <27856832+roessig@users.noreply.github.com> Date: Mon, 29 Oct 2018 20:25:52 +0100 Subject: [PATCH 098/121] test without rootiterlimit --- tests/test_branch_probing_lp.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index d5c65d8ec..d1ce7bd87 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -44,7 +44,7 @@ def branchexeclp(self, allowaddcons): m = Model() m.setIntParam("presolving/maxrounds", 0) -m.setLongintParam("lp/rootiterlim", 3) +#m.setLongintParam("lp/rootiterlim", 3) m.setRealParam("limits/time", 10) x0 = m.addVar(lb=-2, ub=4) @@ -60,6 +60,10 @@ def branchexeclp(self, allowaddcons): more_vars.append(m.addVar(vtype="I", lb= -12, ub=40)) m.addCons(quicksum(v for v in more_vars) <= (40 - i) * quicksum(v for v in more_vars[::2])) +for i in range(1000): + more_vars.append(m.addVar(vtype="I", lb= -52, ub=10)) + m.addCons(quicksum(v for v in more_vars[50::2]) <= (40 - i) * quicksum(v for v in more_vars[405::2])) + m.addCons(r1 >= x0) @@ -67,10 +71,10 @@ def branchexeclp(self, allowaddcons): m.addCons(y0 == r1 +r2) #m.addCons(t * l + l * u >= 4) m.addCons(t + l + 7* u <= 300) -m.addCons(t >= quicksum(v for v in more_vars[::3])) +m.addCons(t >= quicksum(v for v in more_vars[::3]) - 10 * more_vars[5] + 5* more_vars[9]) m.addCons(more_vars[3] >= l + 2) m.addCons(7 <= quicksum(v for v in more_vars[::4]) - x0) -m.addCons(quicksum(v for v in more_vars[::2]) + l<= quicksum(v for v in more_vars[::4])) +m.addCons(quicksum(v for v in more_vars[::2]) + l <= quicksum(v for v in more_vars[::4])) m.setObjective(t - quicksum(j*v for j, v in enumerate(more_vars[20:-40]))) From 6f82eca42ac0ee56972a18168df7d17dd2a7770b Mon Sep 17 00:00:00 2001 From: roessig <27856832+roessig@users.noreply.github.com> Date: Tue, 30 Oct 2018 11:17:53 +0100 Subject: [PATCH 099/121] Updated docu, removed delObjective --- src/pyscipopt/scip.pyx | 45 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 14b166f24..512bb2cb3 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -780,16 +780,6 @@ cdef class Model: """returns current limit on objective function.""" return SCIPgetObjlimit(self._scip) - def delObjective(self): - """clear existing objective function""" - cdef SCIP_VAR** _vars - cdef int _nvars - - _vars = SCIPgetOrigVars(self._scip) - _nvars = SCIPgetNOrigVars(self._scip) - for i in range(_nvars): - PY_SCIP_CALL(SCIPchgVarObj(self._scip, _vars[i], 0.0)) - def setObjective(self, coeffs, sense = 'minimize', clear = 'true'): """Establish the objective function as a linear expression. @@ -2689,15 +2679,14 @@ cdef class Model: nlpcands: number of LP branching candidates npriolpcands: number of candidates with maximal priority nfracimplvars: number of fractional implicit integer variables + """ - cdef int ncands cdef int nlpcands cdef int npriolpcands cdef int nfracimplvars ncands = SCIPgetNLPBranchCands(self._scip) - cdef SCIP_VAR** lpcands = malloc(ncands * sizeof(SCIP_VAR*)) cdef SCIP_Real* lpcandssol = malloc(ncands * sizeof(SCIP_Real)) cdef SCIP_Real* lpcandsfrac = malloc(ncands * sizeof(SCIP_Real)) @@ -2710,36 +2699,48 @@ cdef class Model: def branchVar(self, variable): - """Branch on a non-continuous variable.""" - + """Branch on a non-continuous variable. + + :param variable: Variable to branch on + :return: tuple(downchild, eqchild, upchild) of Nodes of the left, middle and right child. + + """ cdef SCIP_NODE* downchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) PY_SCIP_CALL(SCIPbranchVar(self._scip, (variable).var, &downchild, &eqchild, &upchild)) - return Node.create(downchild), Node.create(eqchild), Node.create(upchild) def branchVarVal(self, variable, value): - """Branches on variable using a value which separates the domain of the variable.""" - + """Branches on variable using a value which separates the domain of the variable. + + :param variable: Variable to branch on + :param value: float, value to branch on + :return: tuple(downchild, eqchild, upchild) of Nodes of the left, middle and right child. Middle child only exists + if branch variable is integer + + """ cdef SCIP_NODE* downchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) - + PY_SCIP_CALL(SCIPbranchVarVal(self._scip, (variable).var, value, &downchild, &eqchild, &upchild)) - # TODO should the stuff be freed and how? return Node.create(downchild), Node.create(eqchild), Node.create(upchild) def createChild(self, nodeselprio, estimate): - """Create a child node of the focus node.""" - + """Create a child node of the focus node. + + :param nodeselprio: float, node selection priority of new node + :param estimate: float, estimate for(transformed) objective value of best feasible solution in subtree + :return: Node, the child which was created + + """ cdef SCIP_NODE* child = malloc(sizeof(SCIP_NODE)) PY_SCIP_CALL(SCIPcreateChild(self._scip, &child, nodeselprio, estimate)) - return Node.create(child) # Diving methods (Diving is LP related) From e001bd8ce3e7df22d701daae7fd984b88838d09b Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Tue, 30 Oct 2018 11:18:15 +0100 Subject: [PATCH 100/121] debug output removed in test Co-Authored-By: roessig <27856832+roessig@users.noreply.github.com> --- tests/test_branch_probing_lp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index d1ce7bd87..c410eed46 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -11,7 +11,6 @@ def __init__(self, model, cont): self.was_called_int = False def branchexeclp(self, allowaddcons): - print("in branchexelp") self.count += 1 if self.count >= 2: return {"result": SCIP_RESULT.DIDNOTRUN} From a8c750ce3737b5f8a5c41c19b246fb6386b14c65 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Wed, 31 Oct 2018 13:39:58 +0100 Subject: [PATCH 101/121] update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e3a3dc5fe..6fd3dce32 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,8 @@ venv.bak/ # model (for tests) model +model.cip +model.lp # VSCode -.vscode/ \ No newline at end of file +.vscode/ From 453482a1752d87fe4b21276eb2ab3ef388526a5b Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Wed, 31 Oct 2018 13:43:13 +0100 Subject: [PATCH 102/121] clearer assertions, cosmetics --- src/pyscipopt/scip.pyx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 293340ca1..693b08b4f 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -779,7 +779,7 @@ cdef class Model: # turn the constant value into an Expr instance for further processing if not isinstance(coeffs, Expr): - assert(_is_number(coeffs)) + assert(_is_number(coeffs)), "given coefficient is neither ExprCons or number but %s" % coeffs.__class__.__name__ coeffs = Expr() + coeffs if coeffs.degree() > 1: @@ -925,6 +925,7 @@ cdef class Model: if lb is None: lb = -SCIPinfinity(self._scip) cdef SCIP_VAR* scip_var + vtype = vtype.upper() if vtype in ['C', 'CONTINUOUS']: PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_CONTINUOUS)) elif vtype in ['B', 'BINARY']: @@ -1172,7 +1173,7 @@ cdef class Model: :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) """ - assert isinstance(cons, ExprCons) + assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__ # replace empty name with generic one if name == '': @@ -1198,9 +1199,9 @@ cdef class Model: return self._addNonlinearCons(cons, **kwargs) def _addLinCons(self, ExprCons lincons, **kwargs): - assert isinstance(lincons, ExprCons) + assert isinstance(lincons, ExprCons), "given constraint is not ExprCons but %s" % lincons.__class__.__name__ - assert lincons.expr.degree() <= 1 + assert lincons.expr.degree() <= 1, "given constraint is not linear, degree == %d" % lincons.expr.degree() terms = lincons.expr.terms cdef SCIP_CONS* scip_cons @@ -1223,7 +1224,7 @@ cdef class Model: def _addQuadCons(self, ExprCons quadcons, **kwargs): terms = quadcons.expr.terms - assert quadcons.expr.degree() <= 2 + assert quadcons.expr.degree() <= 2, "given constraint is not quadratic, degree == %d" % quadcons.expr.degree() cdef SCIP_CONS* scip_cons PY_SCIP_CALL(SCIPcreateConsQuadratic( @@ -1240,7 +1241,7 @@ cdef class Model: var = v[0] PY_SCIP_CALL(SCIPaddLinearVarQuadratic(self._scip, scip_cons, var.var, c)) else: # quadratic - assert len(v) == 2, 'term: %s' % v + assert len(v) == 2, 'term length must be 1 or 2 but it is %s' % len(v) var1, var2 = v[0], v[1] PY_SCIP_CALL(SCIPaddBilinTermQuadratic(self._scip, scip_cons, var1.var, var2.var, c)) @@ -1662,7 +1663,7 @@ cdef class Model: return pyCons - def addConsIndicator(self, cons, binvar=None, name="CardinalityCons", + def addConsIndicator(self, cons, binvar=None, name="IndicatorCons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, dynamic=False, removable=False, stickingatnode=False): @@ -1673,7 +1674,7 @@ cdef class Model: :param cons: a linear inequality of the form "<=" :param binvar: binary indicator variable, or None if it should be created (Default value = None) - :param name: name of the constraint (Default value = "CardinalityCons") + :param name: name of the constraint (Default value = "IndicatorCons") :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) :param separate: should the constraint be separated during LP processing? (Default value = True) :param enforce: should the constraint be enforced during node processing? (Default value = True) @@ -1685,7 +1686,7 @@ cdef class Model: :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) """ - assert isinstance(cons, ExprCons) + assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__ cdef SCIP_CONS* scip_cons cdef SCIP_VAR* _binVar if cons.lhs is not None and cons.rhs is not None: @@ -1694,7 +1695,7 @@ cdef class Model: if cons.expr.degree() > 1: raise ValueError("expected linear inequality, expression has degree %d" % cons.expr.degree()) - assert cons.expr.degree() <= 1 + assert cons.expr.degree() <= 1 # redundant with previous if-statement? if cons.rhs is not None: rhs = cons.rhs @@ -3160,7 +3161,7 @@ cdef class Model: else: raise Warning("unrecognized optimization sense: %s" % sense) - assert isinstance(coeffs, Expr) + assert isinstance(coeffs, Expr), "given coefficients are not Expr but %s" % coeffs.__class__.__name__ if coeffs.degree() > 1: raise ValueError("Nonlinear objective functions are not supported!") From bdf0c2d72836a66155b942ab5181c7181aeb99a2 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Wed, 31 Oct 2018 13:46:20 +0100 Subject: [PATCH 103/121] .gitignore typo --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6fd3dce32..8486967c8 100644 --- a/.gitignore +++ b/.gitignore @@ -117,7 +117,7 @@ venv.bak/ .mypy_cache/ # pytest -.pytest_chache/ +.pytest_cache/ # model (for tests) model From f3bb0931cf9bc133f6599662b98057fd615a19c7 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Wed, 31 Oct 2018 15:02:45 +0100 Subject: [PATCH 104/121] Update src/pyscipopt/scip.pyx Co-Authored-By: ramabile --- src/pyscipopt/scip.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 693b08b4f..07be48768 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1695,7 +1695,6 @@ cdef class Model: if cons.expr.degree() > 1: raise ValueError("expected linear inequality, expression has degree %d" % cons.expr.degree()) - assert cons.expr.degree() <= 1 # redundant with previous if-statement? if cons.rhs is not None: rhs = cons.rhs From 16dae9a1a739c9bd673ec981188d862781133a1f Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Wed, 31 Oct 2018 16:33:18 +0100 Subject: [PATCH 105/121] cosmetics --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 07be48768..1125f86d8 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1969,7 +1969,7 @@ cdef class Model: cdef int _nquadterms cdef int _nlinvars - assert cons.isQuadratic() + assert cons.isQuadratic(), "constraint is not quadratic" bilinterms = [] quadterms = [] From 659ebb7a32e387335dcc7f7adfdb1ea695c46802 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Thu, 1 Nov 2018 14:15:11 +0100 Subject: [PATCH 106/121] cosmetic fix cf. #212: right assertion error message --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index c281e11c8..30e553db9 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -793,7 +793,7 @@ cdef class Model: # turn the constant value into an Expr instance for further processing if not isinstance(coeffs, Expr): - assert(_is_number(coeffs)), "given coefficient is neither ExprCons or number but %s" % coeffs.__class__.__name__ + assert(_is_number(coeffs)), "given coefficients are neither Expr or number but %s" % coeffs.__class__.__name__ coeffs = Expr() + coeffs if coeffs.degree() > 1: From b3c95f0eca8f295339575161d8607a69b3c5087e Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Fri, 2 Nov 2018 00:56:38 +0100 Subject: [PATCH 107/121] upgrade to general expression only when necessary --- src/pyscipopt/expr.pxi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 9014f1c4e..91ecc2c07 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -216,20 +216,32 @@ cdef class Expr: def __div__(self, other): ''' transforms Expr into GenExpr''' + if _is_number(other): + f = 1.0/float(other) + return f * self selfexpr = buildGenExprObj(self) return selfexpr.__div__(other) def __rdiv__(self, other): ''' other / self ''' + if _is_number(self): + f = 1.0/float(self) + return f * other otherexpr = buildGenExprObj(other) return otherexpr.__div__(self) def __truediv__(self,other): + if _is_number(other): + f = 1.0/float(other) + return f * self selfexpr = buildGenExprObj(self) return selfexpr.__truediv__(other) def __rtruediv__(self, other): ''' other / self ''' + if _is_number(self): + f = 1.0/float(self) + return f * other otherexpr = buildGenExprObj(other) return otherexpr.__truediv__(self) From c400e9f3d47aa96f029e83842dc12e6cb7d16aa1 Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Fri, 2 Nov 2018 10:02:19 +0100 Subject: [PATCH 108/121] examples: cleaner and tutorials (#211) --- examples/finished/eoq_en.py | 2 +- examples/finished/gcp_fixed_k.py | 1 - examples/finished/lotsizing_lazy.py | 2 +- examples/finished/mctransp.py | 2 +- examples/finished/pfs.py | 2 +- examples/finished/transp_nofn.py | 2 +- examples/tutorial/even.py | 116 ++++++++++++++++++++++ examples/tutorial/logical.py | 85 ++++++++++++++++ examples/{finished => tutorial}/puzzle.py | 0 9 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 examples/tutorial/even.py create mode 100644 examples/tutorial/logical.py rename examples/{finished => tutorial}/puzzle.py (100%) diff --git a/examples/finished/eoq_en.py b/examples/finished/eoq_en.py index 448ff3857..b540ca829 100644 --- a/examples/finished/eoq_en.py +++ b/examples/finished/eoq_en.py @@ -1,5 +1,5 @@ """ -eoq.py: piecewise linear model to the multi-item economic ordering quantity problem. +eoq_en.py: piecewise linear model to the multi-item economic ordering quantity problem. Approach: use a convex combination formulation. diff --git a/examples/finished/gcp_fixed_k.py b/examples/finished/gcp_fixed_k.py index 172035a2d..321ec57e2 100644 --- a/examples/finished/gcp_fixed_k.py +++ b/examples/finished/gcp_fixed_k.py @@ -1,4 +1,3 @@ -#todo """ gcp_fixed_k.py: solve the graph coloring problem with fixed-k model diff --git a/examples/finished/lotsizing_lazy.py b/examples/finished/lotsizing_lazy.py index 9e2b9f2c5..5fdbedc33 100644 --- a/examples/finished/lotsizing_lazy.py +++ b/examples/finished/lotsizing_lazy.py @@ -1,5 +1,5 @@ """ -lotsizing_cut.py: solve the single-item lot-sizing problem. +lotsizing_lazy.py: solve the single-item lot-sizing problem. Approaches: - sils: solve the problem using the standard formulation diff --git a/examples/finished/mctransp.py b/examples/finished/mctransp.py index b72dd3a17..c670e79df 100644 --- a/examples/finished/mctransp.py +++ b/examples/finished/mctransp.py @@ -1,5 +1,5 @@ """ -transp.py: a model for the multi-commodity transportation problem +mctransp.py: a model for the multi-commodity transportation problem Model for solving the multi-commodity transportation problem: minimize the total transportation cost for satisfying demand at diff --git a/examples/finished/pfs.py b/examples/finished/pfs.py index f25bb1315..fc178d307 100644 --- a/examples/finished/pfs.py +++ b/examples/finished/pfs.py @@ -1,5 +1,5 @@ """ -permutationflowshop.py: model for the permutation flow shop problem +pfs.py: model for the permutation flow shop problem Use a position index formulation for modeling the permutation flow shop problem, with the objective of minimizing the makespan (maximum diff --git a/examples/finished/transp_nofn.py b/examples/finished/transp_nofn.py index fd366db7c..f15f4b850 100644 --- a/examples/finished/transp_nofn.py +++ b/examples/finished/transp_nofn.py @@ -1,5 +1,5 @@ """ -transp.py: a model for the transportation problem +transp_nofn.py: a model for the transportation problem Model for solving a transportation problem: minimize the total transportation cost for satisfying demand at diff --git a/examples/tutorial/even.py b/examples/tutorial/even.py new file mode 100644 index 000000000..495a997d0 --- /dev/null +++ b/examples/tutorial/even.py @@ -0,0 +1,116 @@ +""" +even.py: Tutorial example to check whether values are even or odd + +Public Domain, WTFNMFPL Public Licence +""" +from pyscipopt import Model +from pprint import pformat as pfmt + +example_values = [ + 0, + 1, + 1.5, + "helloworld", + 20, + 25, + -101, + -15., + -10, + -2**31, + -int(2**31), + "2**31-1", + int(2**31-1), + int(2**63)-1 +] + +verbose = False +#verbose = True # uncomment for additional info on variables! +sdic = {0: "even", 1: "odd"} # remainder to 2 + +def parity(number): + """ + Prints if a value is even/odd/neither per each value in a example list + + This example is made for newcomers and motivated by: + - modulus is unsupported for pyscipopt.scip.Variable and int + - variables are non-integer by default + Based on this: #172#issuecomment-394644046 + + Args: + number: value which parity is checked + + Returns: + sval: 1 if number is odd, 0 if number is even, -1 if neither + """ + sval = -1 + if verbose: + print(80*"*") + try: + assert number == int(round(number)) + m = Model() + m.hideOutput() + + # x and n are integer, s is binary + # Irrespective to their type, variables are non-negative by default + # since 0 is the default lb. To allow for negative values, give None + # as lower bound. + # (None means -infinity as lower bound and +infinity as upper bound) + x = m.addVar("x", vtype="I", lb=None, ub=None) #ub=None is default + n = m.addVar("n", vtype="I", lb=None) + s = m.addVar("s", vtype="B") + # CAVEAT: if number is negative, x's lower bound must be None + # if x is set by default as non-negative and number is negative: + # there is no feasible solution (trivial) but the program + # does not highlight which constraints conflict. + + m.addCons(x==number) + + # minimize the difference between the number and twice a natural number + m.addCons(s == x-2*n) + m.setObjective(s) + m.optimize() + + assert m.getStatus() == "optimal" + boolmod = m.getVal(s) == m.getVal(x)%2 + if verbose: + for v in m.getVars(): + print("%*s: %d" % (fmtlen, v,m.getVal(v))) + print("%*d%%2 == %d?" % (fmtlen, m.getVal(x), m.getVal(s))) + print("%*s" % (fmtlen, boolmod)) + + xval = m.getVal(x) + sval = m.getVal(s) + sstr = sdic[sval] + print("%*d is %s" % (fmtlen, xval, sstr)) + except (AssertionError, TypeError): + print("%*s is neither even nor odd!" % (fmtlen, number.__repr__())) + finally: + if verbose: + print(80*"*") + print("") + return sval + +if __name__ == "__main__": + """ + If positional arguments are given: + the parity check is performed on each of them + Else: + the parity check is performed on each of the default example values + """ + import sys + from ast import literal_eval as leval + try: + # check parity for each positional arguments + sys.argv[1] + values = sys.argv[1:] + except IndexError: + # check parity for each default example value + values = example_values + # format lenght, cosmetics + fmtlen = max([len(fmt) for fmt in pfmt(values,width=1).split('\n')]) + for value in values: + try: + n = leval(value) + except (ValueError, SyntaxError): # for numbers or str w/ spaces + n = value + parity(n) diff --git a/examples/tutorial/logical.py b/examples/tutorial/logical.py new file mode 100644 index 000000000..14e40f233 --- /dev/null +++ b/examples/tutorial/logical.py @@ -0,0 +1,85 @@ +""" +logical.py: Tutorial example on how to use AND/OR/XOR constraints. + +N.B.: standard SCIP XOR constraint works differently from AND/OR by design. +The constraint is set with a boolean rhs instead of an integer resultant. +cf. http://listserv.zib.de/pipermail/scip/2018-May/003392.html +A workaround to get the resultant as variable is here proposed. + +Public Domain, WTFNMFPL Public Licence +""" +from pyscipopt import Model +from pyscipopt import quicksum + +def _init(): + model = Model() + model.hideOutput() + x = model.addVar("x","B") + y = model.addVar("y","B") + z = model.addVar("z","B") + return model, x, y, z + +def _optimize(name, m): + m.optimize() + print("* %s constraint *" % name) + objSet = bool(m.getObjective().terms.keys()) + print("* Is objective set? %s" % objSet) + if objSet: + print("* Sense: %s" % m.getObjectiveSense()) + status = m.getStatus() + print("* Model status: %s" % status) + if status == 'optimal': + for v in m.getVars(): + if v.name != "n": + print("%s: %d" % (v, round(m.getVal(v)))) + else: + print("* No variable is printed if model status is not optimal") + print("") + +def and_constraint(v=1, sense="minimize"): + # AND constraint + assert v in [0,1], "v must be 0 or 1 instead of %s" % v.__repr__() + model, x, y, z = _init() + r = model.addVar("r", "B") + model.addConsAnd([x,y,z], r) + model.addCons(x==v) + model.setObjective(r, sense=sense) + _optimize("AND", model) + + +def or_constraint(v=0, sense="maximize"): + # OR constraint + assert v in [0,1], "v must be 0 or 1 instead of %s" % v.__repr__() + model, x, y, z = _init() + r = model.addVar("r", "B") + model.addConsOr([x,y,z], r) + model.addCons(x==v) + model.setObjective(r, sense=sense) + _optimize("OR", model) + +def xors_constraint(v=1): + # XOR (r as boolean) standard constraint + assert v in [0,1], "v must be 0 or 1 instead of %s" % v.__repr__() + model, x, y, z = _init() + r = True + model.addConsXor([x,y,z], r) + model.addCons(x==v) + _optimize("Standard XOR (as boolean)", model) + +def xorc_constraint(v=0, sense="maximize"): + # XOR (r as variable) custom constraint + assert v in [0,1], "v must be 0 or 1 instead of %s" % v.__repr__() + model, x, y, z = _init() + r = model.addVar("r", "B") + n = model.addVar("n", "I") # auxiliary + model.addCons(r+quicksum([x,y,z]) == 2*n) + model.addCons(x==v) + model.setObjective(r, sense=sense) + _optimize("Custom XOR (as variable)", model) + +if __name__ == "__main__": + and_constraint() + or_constraint() + xors_constraint() + xorc_constraint() + diff --git a/examples/finished/puzzle.py b/examples/tutorial/puzzle.py similarity index 100% rename from examples/finished/puzzle.py rename to examples/tutorial/puzzle.py From 3861be7bf099ba040e5ea15d81e731f9ff94702f Mon Sep 17 00:00:00 2001 From: Roberto Amabile Date: Fri, 2 Nov 2018 10:32:39 +0100 Subject: [PATCH 109/121] fix ZeroDivisionError (#215) --- src/pyscipopt/expr.pxi | 4 ++-- tests/test_expr.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 91ecc2c07..9b27506cc 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -545,7 +545,7 @@ cdef class GenExpr: divisor = buildGenExprObj(other) # we can't divide by 0 if divisor.getOp() == Operator.const and divisor.number == 0.0: - raise ValueError("cannot divide by 0") + raise ZeroDivisionError("cannot divide by 0") return self * divisor**(-1) def __rdiv__(self, other): @@ -557,7 +557,7 @@ cdef class GenExpr: divisor = buildGenExprObj(other) # we can't divide by 0 if divisor.getOp() == Operator.const and divisor.number == 0.0: - raise ValueError("cannot divide by 0") + raise ZeroDivisionError("cannot divide by 0") return self * divisor**(-1) def __rtruediv__(self, other): diff --git a/tests/test_expr.py b/tests/test_expr.py index a9dca9698..81fef8a48 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -51,7 +51,7 @@ def test_upgrade(model): assert isinstance(log(expr), GenExpr) assert isinstance(exp(expr), GenExpr) - with pytest.raises(ValueError): + with pytest.raises(ZeroDivisionError): expr /= 0.0 def test_genexpr_op_expr(model): From 10c88f67be4d6ab3d79d39eeb10b104adeb5dd10 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Sun, 4 Nov 2018 21:57:41 +0100 Subject: [PATCH 110/121] increase version to 2.0.2 --- src/pyscipopt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 9d7be575a..d89c72143 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.0.1' +__version__ = '2.0.2' # export user-relevant objects: from pyscipopt.Multidict import multidict From 1be12399a27be4b6138a1d163f866323aab507e4 Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Sun, 11 Nov 2018 16:27:02 +0100 Subject: [PATCH 111/121] fix lotisizing_lazy example --- examples/finished/lotsizing_lazy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/finished/lotsizing_lazy.py b/examples/finished/lotsizing_lazy.py index 5fdbedc33..1ef581ce4 100644 --- a/examples/finished/lotsizing_lazy.py +++ b/examples/finished/lotsizing_lazy.py @@ -39,7 +39,7 @@ def addcut(self, checkonly, sol): cutsadded = True return cutsadded - def conscheck(self, constraints, solution, checkintegrality, checklprows, printreason): + def conscheck(self, constraints, solution, checkintegrality, checklprows, printreason, completely): if not self.addcut(checkonly = True, sol = solution): return {"result": SCIP_RESULT.INFEASIBLE} else: From d84ff4bce01725865b36a76e6fb38e87e01ef6a4 Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Mon, 12 Nov 2018 22:52:51 +0100 Subject: [PATCH 112/121] fix set/get char param --- src/pyscipopt/scip.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 30e553db9..dcf0fa197 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -3262,7 +3262,7 @@ cdef class Model: """ n = str_conversion(name) - PY_SCIP_CALL(SCIPsetCharParam(self._scip, n, value)) + PY_SCIP_CALL(SCIPsetCharParam(self._scip, n, ord(value))) def setStringParam(self, name, value): """Set a string-valued parameter. @@ -3330,7 +3330,7 @@ cdef class Model: elif paramtype == SCIP_PARAMTYPE_REAL: return SCIPparamGetReal(param) elif paramtype == SCIP_PARAMTYPE_CHAR: - return SCIPparamGetChar(param) + return chr(SCIPparamGetChar(param)) elif paramtype == SCIP_PARAMTYPE_STRING: return SCIPparamGetString(param) From acdbee785d3ad451f6456469981b160d26622e10 Mon Sep 17 00:00:00 2001 From: Maxime Gasse Date: Thu, 15 Nov 2018 11:44:11 -0500 Subject: [PATCH 113/121] Bugfix: setObjLimit -> getObjLimit typo --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index dcf0fa197..f8c83349d 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -776,7 +776,7 @@ cdef class Model: """ PY_SCIP_CALL(SCIPsetObjlimit(self._scip, objlimit)) - def setObjlimit(self, objlimit): + def getObjlimit(self): """returns current limit on objective function.""" return SCIPgetObjlimit(self._scip) From 38c565c5ff6192b2538ae732cf717b07556e76d2 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Thu, 22 Nov 2018 18:16:24 +0100 Subject: [PATCH 114/121] update networkx call to get connected components --- tests/test_alldiff.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_alldiff.py b/tests/test_alldiff.py index fdc0083d8..9fc091134 100644 --- a/tests/test_alldiff.py +++ b/tests/test_alldiff.py @@ -129,8 +129,7 @@ def propagate_cons(self, cons): G.remove_edges_from(visited_edges) # compute strongly connected components of D and mark edges on the cc as useful - gscc = networkx.strongly_connected_component_subgraphs(D, copy=False) - for g in gscc: + for g in networkx.strongly_connected_components(D): for e in g.edges(): if G.has_edge(*e): G.remove_edge(*e) From 0a07f17d87b6b9eb3ba6950446d8f3b98eabeea1 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Thu, 22 Nov 2018 20:48:30 +0100 Subject: [PATCH 115/121] fix new connected components test --- tests/test_alldiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_alldiff.py b/tests/test_alldiff.py index 9fc091134..ad5c2d946 100644 --- a/tests/test_alldiff.py +++ b/tests/test_alldiff.py @@ -130,7 +130,7 @@ def propagate_cons(self, cons): # compute strongly connected components of D and mark edges on the cc as useful for g in networkx.strongly_connected_components(D): - for e in g.edges(): + for e in D.subgraph(g).edges(): if G.has_edge(*e): G.remove_edge(*e) From 069912a7c2970b033ed6c693a5ca3b96ba513f51 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Thu, 22 Nov 2018 20:57:58 +0100 Subject: [PATCH 116/121] enlarge time limit in tests --- tests/test_branch_probing_lp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_branch_probing_lp.py b/tests/test_branch_probing_lp.py index c410eed46..c7a493cdb 100644 --- a/tests/test_branch_probing_lp.py +++ b/tests/test_branch_probing_lp.py @@ -44,7 +44,7 @@ def branchexeclp(self, allowaddcons): m = Model() m.setIntParam("presolving/maxrounds", 0) #m.setLongintParam("lp/rootiterlim", 3) -m.setRealParam("limits/time", 10) +m.setRealParam("limits/time", 60) x0 = m.addVar(lb=-2, ub=4) r1 = m.addVar() From 9326fce9694b0c6aacad22abcf11c41a377d2a9b Mon Sep 17 00:00:00 2001 From: Felipe Serrano Date: Wed, 28 Nov 2018 15:56:27 +0100 Subject: [PATCH 117/121] conslock changed --- examples/finished/lotsizing_lazy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/finished/lotsizing_lazy.py b/examples/finished/lotsizing_lazy.py index 1ef581ce4..c3db8f505 100644 --- a/examples/finished/lotsizing_lazy.py +++ b/examples/finished/lotsizing_lazy.py @@ -51,7 +51,7 @@ def consenfolp(self, constraints, nusefulconss, solinfeasible): else: return {"result": SCIP_RESULT.FEASIBLE} - def conslock(self, constraint, nlockspos, nlocksneg): + def conslock(self, constraint, locktype, nlockspos, nlocksneg): pass def sils(T,f,c,d,h): From b03a2ca0a47f6acd58aa214531d46ee4fd20f36a Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Sun, 2 Dec 2018 14:36:49 +0100 Subject: [PATCH 118/121] add more methods for branching --- src/pyscipopt/__init__.py | 1 + src/pyscipopt/scip.pxd | 8 +++++++ src/pyscipopt/scip.pyx | 47 +++++++++++++++++++++++++++++++-------- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index d89c72143..ca0a6d089 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -28,3 +28,4 @@ from pyscipopt.scip import PY_SCIP_HEURTIMING as SCIP_HEURTIMING from pyscipopt.scip import PY_SCIP_EVENTTYPE as SCIP_EVENTTYPE from pyscipopt.scip import PY_SCIP_LPSOLSTAT as SCIP_LPSOLSTAT +from pyscipopt.scip import PY_SCIP_BRANCHDIR as SCIP_BRANCHDIR diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index cdac0eb44..95dc6739d 100755 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -250,6 +250,12 @@ cdef extern from "scip/scip.h": SCIP_LPSOLSTAT_TIMELIMIT = 6 SCIP_LPSOLSTAT_ERROR = 7 + ctypedef enum SCIP_BRANCHDIR: + SCIP_BRANCHDIR_DOWNWARDS = 0 + SCIP_BRANCHDIR_UPWARDS = 1 + SCIP_BRANCHDIR_FIXED = 2 + SCIP_BRANCHDIR_AUTO = 3 + ctypedef bint SCIP_Bool ctypedef long long SCIP_Longint @@ -557,6 +563,8 @@ cdef extern from "scip/scip.h": SCIP_NODETYPE SCIPnodeGetType(SCIP_NODE* node) SCIP_Bool SCIPnodeIsActive(SCIP_NODE* node) SCIP_Bool SCIPnodeIsPropagatedAgain(SCIP_NODE* node) + SCIP_Real SCIPcalcNodeselPriority(SCIP* scip, SCIP_VAR* var, SCIP_BRANCHDIR branchdir, SCIP_Real targetvalue) + SCIP_Real SCIPcalcChildEstimate(SCIP* scip, SCIP_VAR* var, SCIP_Real targetvalue) SCIP_RETCODE SCIPcreateChild(SCIP* scip, SCIP_NODE** node, SCIP_Real nodeselprio, SCIP_Real estimate) SCIP_Bool SCIPinRepropagation(SCIP* scip) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index f8c83349d..462f4d6a0 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -193,6 +193,11 @@ cdef class PY_SCIP_LPSOLSTAT: TIMELIMIT = SCIP_LPSOLSTAT_TIMELIMIT ERROR = SCIP_LPSOLSTAT_ERROR +cdef class PY_SCIP_BRANCH_DIR: + DOWNWARDS = SCIP_BRANCHDIR_DOWNWARDS + UPWARDS = SCIP_BRANCHDIR_UPWARDS + FIXED = SCIP_BRANCHDIR_FIXED + AUTO = SCIP_BRANCHDIR_AUTO def PY_SCIP_CALL(SCIP_RETCODE rc): if rc == SCIP_OKAY: @@ -2683,7 +2688,7 @@ cdef class Model: nlpcands: number of LP branching candidates npriolpcands: number of candidates with maximal priority nfracimplvars: number of fractional implicit integer variables - + """ cdef int ncands cdef int nlpcands @@ -2704,10 +2709,10 @@ cdef class Model: def branchVar(self, variable): """Branch on a non-continuous variable. - + :param variable: Variable to branch on :return: tuple(downchild, eqchild, upchild) of Nodes of the left, middle and right child. - + """ cdef SCIP_NODE* downchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) @@ -2719,29 +2724,53 @@ cdef class Model: def branchVarVal(self, variable, value): """Branches on variable using a value which separates the domain of the variable. - + :param variable: Variable to branch on :param value: float, value to branch on :return: tuple(downchild, eqchild, upchild) of Nodes of the left, middle and right child. Middle child only exists if branch variable is integer - + """ cdef SCIP_NODE* downchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) - + PY_SCIP_CALL(SCIPbranchVarVal(self._scip, (variable).var, value, &downchild, &eqchild, &upchild)) # TODO should the stuff be freed and how? return Node.create(downchild), Node.create(eqchild), Node.create(upchild) + def calcNodeselPriority(self, Variable variable, branchdir, targetvalue): + """calculates the node selection priority for moving the given variable's LP value + to the given target value; + this node selection priority can be given to the SCIPcreateChild() call + + :param variable: variable on which the branching is applied + :param branchdir: type of branching that was performed + :param targetvalue: new value of the variable in the child node + :return: node selection priority for moving the given variable's LP value to the given target value + + """ + return SCIPcalcNodeselPriority(self._scip, variable.var, branchdir, targetvalue) + + def calcChildEstimate(self, Variable variable, targetvalue): + """Calculates an estimate for the objective of the best feasible solution + contained in the subtree after applying the given branching; + this estimate can be given to the SCIPcreateChild() call + + :param variable: Variable to compute the estimate for + :param targetvalue: new value of the variable in the child node + :return: objective estimate of the best solution in the subtree after applying the given branching + + """ + return SCIPcalcChildEstimate(self._scip, variable.var, targetvalue) def createChild(self, nodeselprio, estimate): """Create a child node of the focus node. - + :param nodeselprio: float, node selection priority of new node - :param estimate: float, estimate for(transformed) objective value of best feasible solution in subtree + :param estimate: float, estimate for(transformed) objective value of best feasible solution in subtree :return: Node, the child which was created - + """ cdef SCIP_NODE* child = malloc(sizeof(SCIP_NODE)) PY_SCIP_CALL(SCIPcreateChild(self._scip, &child, nodeselprio, estimate)) From 710e0146fb961f361304afa57294d2a0f3021ab8 Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Sun, 2 Dec 2018 14:42:15 +0100 Subject: [PATCH 119/121] fix typo --- src/pyscipopt/scip.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 462f4d6a0..cb7d25ba1 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -193,7 +193,7 @@ cdef class PY_SCIP_LPSOLSTAT: TIMELIMIT = SCIP_LPSOLSTAT_TIMELIMIT ERROR = SCIP_LPSOLSTAT_ERROR -cdef class PY_SCIP_BRANCH_DIR: +cdef class PY_SCIP_BRANCHDIR: DOWNWARDS = SCIP_BRANCHDIR_DOWNWARDS UPWARDS = SCIP_BRANCHDIR_UPWARDS FIXED = SCIP_BRANCHDIR_FIXED From f73d812916c39ed768344386e2b472a80405a47e Mon Sep 17 00:00:00 2001 From: Matthias Miltenberger Date: Mon, 3 Dec 2018 07:50:57 +0100 Subject: [PATCH 120/121] version 2.1.0 and explain versioning in CONTRIBUTING --- CONTRIBUTING.rst | 6 ++++-- src/pyscipopt/__init__.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d9947709e..e1b1fc35b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,6 +20,8 @@ Code contributions are very welcome and should comply to a few rules: 6. Before implementing a new PySCIPOpt feature, check whether the feature exists in SCIP. If so, implement it as a pure wrapper, mimicking SCIP whenever possible. If the new feature does not exist in SCIP but it is close to an existing one, consider if implementing that way is substantially convenient (e.g. Pythonic). If it does something completely different, you are welcome to pull your request and discuss the implementation. +7. PySCIPOpt uses `semantic versioning `__. Version number increase only happens on master and must be tagged to build a new PyPI release. + For general reference, we suggest: - `PySCIPOpt README `__; @@ -43,8 +45,8 @@ PySCIPOpt is meant to be a fast-prototyping interface of the pure SCIP C API. By - with an expected behavior - and parameters, returns, attributes, ... - as close to SCIP as possible - without *"breaking"* Python and the purpose for what the language it is meant. -Ideally speaking, we want every SCIP function to be wrapped in PySCIPOpt. +Ideally speaking, we want every SCIP function to be wrapped in PySCIPOpt. -**Convenience functions** are additional, non-detrimental features meant to help prototyping the Python way. Since these functions are not in SCIP, we wish to limit them to prevent difference in features between SCIP and PySCIPOPT, which are always difficult to maintain. A few convenience functions survive in PySCIPOpt when keeping them is doubtless beneficial. +**Convenience functions** are additional, non-detrimental features meant to help prototyping the Python way. Since these functions are not in SCIP, we wish to limit them to prevent difference in features between SCIP and PySCIPOPT, which are always difficult to maintain. A few convenience functions survive in PySCIPOpt when keeping them is doubtless beneficial. Admittedly, *there is a middle ground where functions are not completely wrappers or just convenient*. That is the case, for instance, of fundamental :code:`Model` methods like :code:`addCons` or :code:`writeProblem`. We want to leave their development to negotiation. diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index ca0a6d089..aeeaef79a 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.0.2' +__version__ = '2.1.0' # export user-relevant objects: from pyscipopt.Multidict import multidict From 040e5d87779235c9aaf8a283974bab9551a913f2 Mon Sep 17 00:00:00 2001 From: Yifan <31308930+andynuaa@users.noreply.github.com> Date: Thu, 4 Apr 2019 13:50:34 +0800 Subject: [PATCH 121/121] Update scip.pyx Based on Maher's version, few typos are fixed --- src/pyscipopt/scip.pyx | 775 +++++++++++++++++++++++++++++------------ 1 file changed, 558 insertions(+), 217 deletions(-) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index cb7d25ba1..32c8cf183 100755 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1,3 +1,5 @@ +##@file scip.pyx +#@brief holding functions in python that reference the SCIP public functions included in scip.pxd import weakref from os.path import abspath from os.path import splitext @@ -11,6 +13,7 @@ from libc.stdio cimport fdopen include "expr.pxi" include "lp.pxi" include "benders.pxi" +include "benderscut.pxi" include "branchrule.pxi" include "conshdlr.pxi" include "event.pxi" @@ -19,6 +22,7 @@ include "presol.pxi" include "pricer.pxi" include "propagator.pxi" include "sepa.pxi" +include "relax.pxi" # recommended SCIP version; major version is required MAJOR = 6 @@ -242,6 +246,8 @@ def PY_SCIP_CALL(SCIP_RETCODE rc): cdef class Event: cdef SCIP_EVENT* event + # can be used to store problem data + cdef public object data @staticmethod cdef create(SCIP_EVENT* scip_event): @@ -250,41 +256,49 @@ cdef class Event: return event def getType(self): + """gets type of event""" return SCIPeventGetType(self.event) def __repr__(self): return self.getType() def getNewBound(self): + """gets new bound for a bound change event""" return SCIPeventGetNewbound(self.event) def getOldBound(self): + """gets old bound for a bound change event""" return SCIPeventGetOldbound(self.event) def getVar(self): + """gets variable for a variable event (var added, var deleted, var fixed, objective value or domain change, domain hole added or removed)""" cdef SCIP_VAR* var = SCIPeventGetVar(self.event) return Variable.create(var) def getNode(self): + """gets node for a node or LP event""" cdef SCIP_NODE* node = SCIPeventGetNode(self.event) return Node.create(node) cdef class Column: """Base class holding a pointer to corresponding SCIP_COL""" - cdef SCIP_COL* col + cdef SCIP_COL* scip_col + # can be used to store problem data + cdef public object data @staticmethod - cdef create(SCIP_COL* scip_col): + cdef create(SCIP_COL* scipcol): col = Column() - col.col = scip_col + col.scip_col = scipcol return col def getLPPos(self): - return SCIPcolGetLPPos(self.col) + """gets position of column in current LP, or -1 if it is not in LP""" + return SCIPcolGetLPPos(self.scip_col) def getBasisStatus(self): - """Note: returns basis status `zero` for columns not in the current SCIP LP""" - cdef SCIP_BASESTAT stat = SCIPcolGetBasisStatus(self.col) + """gets the basis status of a column in the LP solution, Note: returns basis status `zero` for columns not in the current SCIP LP""" + cdef SCIP_BASESTAT stat = SCIPcolGetBasisStatus(self.scip_col) if stat == SCIP_BASESTAT_LOWER: return "lower" elif stat == SCIP_BASESTAT_BASIC: @@ -297,48 +311,57 @@ cdef class Column: raise Exception('SCIP returned unknown base status!') def isIntegral(self): - return SCIPcolIsIntegral(self.col) + """returns whether the associated variable is of integral type (binary, integer, implicit integer)""" + return SCIPcolIsIntegral(self.scip_col) def getVar(self): """gets variable this column represents""" - cdef SCIP_VAR* var = SCIPcolGetVar(self.col) + cdef SCIP_VAR* var = SCIPcolGetVar(self.scip_col) return Variable.create(var) def getPrimsol(self): """gets the primal LP solution of a column""" - return SCIPcolGetPrimsol(self.col) + return SCIPcolGetPrimsol(self.scip_col) def getLb(self): - return SCIPcolGetLb(self.col) + """gets lower bound of column""" + return SCIPcolGetLb(self.scip_col) def getUb(self): - return SCIPcolGetUb(self.col) + """gets upper bound of column""" + return SCIPcolGetUb(self.scip_col) cdef class Row: """Base class holding a pointer to corresponding SCIP_ROW""" - cdef SCIP_ROW* row + cdef SCIP_ROW* scip_row + # can be used to store problem data + cdef public object data @staticmethod - cdef create(SCIP_ROW* scip_row): + cdef create(SCIP_ROW* sciprow): row = Row() - row.row = scip_row + row.scip_row = sciprow return row def getLhs(self): - return SCIProwGetLhs(self.row) + """returns the left hand side of row""" + return SCIProwGetLhs(self.scip_row) def getRhs(self): - return SCIProwGetRhs(self.row) + """returns the right hand side of row""" + return SCIProwGetRhs(self.scip_row) def getConstant(self): - return SCIProwGetConstant(self.row) + """gets constant shift of row""" + return SCIProwGetConstant(self.scip_row) def getLPPos(self): - return SCIProwGetLPPos(self.row) + """gets position of row in current LP, or -1 if it is not in LP""" + return SCIProwGetLPPos(self.scip_row) def getBasisStatus(self): - """Note: returns basis status `basic` for rows not in the current SCIP LP""" - cdef SCIP_BASESTAT stat = SCIProwGetBasisStatus(self.row) + """gets the basis status of a row in the LP solution, Note: returns basis status `basic` for rows not in the current SCIP LP""" + cdef SCIP_BASESTAT stat = SCIProwGetBasisStatus(self.scip_row) if stat == SCIP_BASESTAT_LOWER: return "lower" elif stat == SCIP_BASESTAT_BASIC: @@ -352,32 +375,36 @@ cdef class Row: raise Exception('SCIP returned unknown base status!') def isIntegral(self): - return SCIProwIsIntegral(self.row) + """returns TRUE iff the activity of the row (without the row's constant) is always integral in a feasible solution """ + return SCIProwIsIntegral(self.scip_row) def isModifiable(self): - return SCIProwIsModifiable(self.row) + """returns TRUE iff row is modifiable during node processing (subject to column generation) """ + return SCIProwIsModifiable(self.scip_row) def getNNonz(self): """get number of nonzero entries in row vector""" - return SCIProwGetNNonz(self.row) + return SCIProwGetNNonz(self.scip_row) def getNLPNonz(self): """get number of nonzero entries in row vector that correspond to columns currently in the SCIP LP""" - return SCIProwGetNLPNonz(self.row) + return SCIProwGetNLPNonz(self.scip_row) def getCols(self): """gets list with columns of nonzero entries""" - cdef SCIP_COL** cols = SCIProwGetCols(self.row) + cdef SCIP_COL** cols = SCIProwGetCols(self.scip_row) return [Column.create(cols[i]) for i in range(self.getNNonz())] def getVals(self): """gets list with coefficients of nonzero entries""" - cdef SCIP_Real* vals = SCIProwGetVals(self.row) + cdef SCIP_Real* vals = SCIProwGetVals(self.scip_row) return [vals[i] for i in range(self.getNNonz())] cdef class Solution: """Base class holding a pointer to corresponding SCIP_SOL""" cdef SCIP_SOL* sol + # can be used to store problem data + cdef public object data @staticmethod cdef create(SCIP_SOL* scip_sol): @@ -387,77 +414,81 @@ cdef class Solution: cdef class Node: """Base class holding a pointer to corresponding SCIP_NODE""" - cdef SCIP_NODE* node + cdef SCIP_NODE* scip_node + # can be used to store problem data + cdef public object data @staticmethod - cdef create(SCIP_NODE* scip_node): + cdef create(SCIP_NODE* scipnode): node = Node() - node.node = scip_node + node.scip_node = scipnode return node def getParent(self): """Retrieve parent node.""" - return Node.create(SCIPnodeGetParent(self.node)) + return Node.create(SCIPnodeGetParent(self.scip_node)) def getNumber(self): """Retrieve number of node.""" - return SCIPnodeGetNumber(self.node) + return SCIPnodeGetNumber(self.scip_node) def getDepth(self): """Retrieve depth of node.""" - return SCIPnodeGetDepth(self.node) + return SCIPnodeGetDepth(self.scip_node) def getType(self): """Retrieve type of node.""" - return SCIPnodeGetType(self.node) + return SCIPnodeGetType(self.scip_node) def getLowerbound(self): """Retrieve lower bound of node.""" - return SCIPnodeGetLowerbound(self.node) + return SCIPnodeGetLowerbound(self.scip_node) def getEstimate(self): """Retrieve the estimated value of the best feasible solution in subtree of the node""" - return SCIPnodeGetEstimate(self.node) + return SCIPnodeGetEstimate(self.scip_node) def getNAddedConss(self): """Retrieve number of added constraints at this node""" - return SCIPnodeGetNAddedConss(self.node) + return SCIPnodeGetNAddedConss(self.scip_node) def isActive(self): """Is the node in the path to the current node?""" - return SCIPnodeIsActive(self.node) + return SCIPnodeIsActive(self.scip_node) def isPropagatedAgain(self): """Is the node marked to be propagated again?""" - return SCIPnodeIsPropagatedAgain(self.node) + return SCIPnodeIsPropagatedAgain(self.scip_node) cdef class Variable(Expr): """Is a linear expression and has SCIP_VAR*""" - cdef SCIP_VAR* var + cdef SCIP_VAR* scip_var + # can be used to store problem data + cdef public object data @staticmethod cdef create(SCIP_VAR* scipvar): var = Variable() - var.var = scipvar + var.scip_var = scipvar Expr.__init__(var, {Term(var) : 1.0}) return var property name: def __get__(self): - cname = bytes( SCIPvarGetName(self.var) ) + cname = bytes( SCIPvarGetName(self.scip_var) ) return cname.decode('utf-8') def ptr(self): """ """ - return (self.var) + return (self.scip_var) def __repr__(self): return self.name def vtype(self): """Retrieve the variables type (BINARY, INTEGER or CONTINUOUS)""" - vartype = SCIPvarGetType(self.var) + vartype = SCIPvarGetType(self.scip_var) if vartype == SCIP_VARTYPE_BINARY: return "BINARY" elif vartype == SCIP_VARTYPE_INTEGER: @@ -467,66 +498,67 @@ cdef class Variable(Expr): def isOriginal(self): """Retrieve whether the variable belongs to the original problem""" - return SCIPvarIsOriginal(self.var) + return SCIPvarIsOriginal(self.scip_var) def isInLP(self): """Retrieve whether the variable is a COLUMN variable that is member of the current LP""" - return SCIPvarIsInLP(self.var) + return SCIPvarIsInLP(self.scip_var) def getCol(self): """Retrieve column of COLUMN variable""" cdef SCIP_COL* scip_col - scip_col = SCIPvarGetCol(self.var) + scip_col = SCIPvarGetCol(self.scip_var) return Column.create(scip_col) def getLbOriginal(self): """Retrieve original lower bound of variable""" - return SCIPvarGetLbOriginal(self.var) + return SCIPvarGetLbOriginal(self.scip_var) def getUbOriginal(self): """Retrieve original upper bound of variable""" - return SCIPvarGetUbOriginal(self.var) + return SCIPvarGetUbOriginal(self.scip_var) def getLbGlobal(self): """Retrieve global lower bound of variable""" - return SCIPvarGetLbGlobal(self.var) + return SCIPvarGetLbGlobal(self.scip_var) def getUbGlobal(self): """Retrieve global upper bound of variable""" - return SCIPvarGetUbGlobal(self.var) + return SCIPvarGetUbGlobal(self.scip_var) def getLbLocal(self): """Retrieve current lower bound of variable""" - return SCIPvarGetLbLocal(self.var) + return SCIPvarGetLbLocal(self.scip_var) def getUbLocal(self): """Retrieve current upper bound of variable""" - return SCIPvarGetUbLocal(self.var) + return SCIPvarGetUbLocal(self.scip_var) def getObj(self): """Retrieve current objective value of variable""" - return SCIPvarGetObj(self.var) + return SCIPvarGetObj(self.scip_var) def getLPSol(self): """Retrieve the current LP solution value of variable""" - return SCIPvarGetLPSol(self.var) + return SCIPvarGetLPSol(self.scip_var) cdef class Constraint: - cdef SCIP_CONS* cons - cdef public object data #storage for python user + cdef SCIP_CONS* scip_cons + # can be used to store problem data + cdef public object data @staticmethod cdef create(SCIP_CONS* scipcons): if scipcons == NULL: raise Warning("cannot create Constraint with SCIP_CONS* == NULL") cons = Constraint() - cons.cons = scipcons + cons.scip_cons = scipcons return cons property name: def __get__(self): - cname = bytes( SCIPconsGetName(self.cons) ) + cname = bytes( SCIPconsGetName(self.scip_cons) ) return cname.decode('utf-8') def __repr__(self): @@ -534,56 +566,56 @@ cdef class Constraint: def isOriginal(self): """Retrieve whether the constraint belongs to the original problem""" - return SCIPconsIsOriginal(self.cons) + return SCIPconsIsOriginal(self.scip_cons) def isInitial(self): """Retrieve True if the relaxation of the constraint should be in the initial LP""" - return SCIPconsIsInitial(self.cons) + return SCIPconsIsInitial(self.scip_cons) def isSeparated(self): """Retrieve True if constraint should be separated during LP processing""" - return SCIPconsIsSeparated(self.cons) + return SCIPconsIsSeparated(self.scip_cons) def isEnforced(self): """Retrieve True if constraint should be enforced during node processing""" - return SCIPconsIsEnforced(self.cons) + return SCIPconsIsEnforced(self.scip_cons) def isChecked(self): """Retrieve True if constraint should be checked for feasibility""" - return SCIPconsIsChecked(self.cons) + return SCIPconsIsChecked(self.scip_cons) def isPropagated(self): """Retrieve True if constraint should be propagated during node processing""" - return SCIPconsIsPropagated(self.cons) + return SCIPconsIsPropagated(self.scip_cons) def isLocal(self): """Retrieve True if constraint is only locally valid or not added to any (sub)problem""" - return SCIPconsIsLocal(self.cons) + return SCIPconsIsLocal(self.scip_cons) def isModifiable(self): """Retrieve True if constraint is modifiable (subject to column generation)""" - return SCIPconsIsModifiable(self.cons) + return SCIPconsIsModifiable(self.scip_cons) def isDynamic(self): """Retrieve True if constraint is subject to aging""" - return SCIPconsIsDynamic(self.cons) + return SCIPconsIsDynamic(self.scip_cons) def isRemovable(self): """Retrieve True if constraint's relaxation should be removed from the LP due to aging or cleanup""" - return SCIPconsIsRemovable(self.cons) + return SCIPconsIsRemovable(self.scip_cons) def isStickingAtNode(self): """Retrieve True if constraint is only locally valid or not added to any (sub)problem""" - return SCIPconsIsStickingAtNode(self.cons) + return SCIPconsIsStickingAtNode(self.scip_cons) def isLinear(self): """Retrieve True if constraint is linear""" - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') return constype == 'linear' def isQuadratic(self): """Retrieve True if constraint is quadratic""" - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') return constype == 'quadratic' @@ -596,8 +628,12 @@ cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): # - remove create(), includeDefaultPlugins(), createProbBasic() methods # - replace free() by "destructor" # - interface SCIPfreeProb() +## +#@anchor Model +## cdef class Model: cdef SCIP* _scip + cdef SCIP_Bool* _valid # store best solution to get the solution values easier cdef Solution _bestSol # can be used to store problem data @@ -605,20 +641,33 @@ cdef class Model: # make Model weak referentiable cdef object __weakref__ - def __init__(self, problemName='model', defaultPlugins=True): + def __init__(self, problemName='model', defaultPlugins=True, Model sourceModel=None, origcopy=False, globalcopy=True, enablepricing=False): """ :param problemName: name of the problem (default 'model') :param defaultPlugins: use default plugins? (default True) + :param sourceModel: create a copy of the given Model instance (default None) + :param origcopy: whether to call copy or copyOrig (default False) + :param globalcopy: whether to create a global or a local copy (default True) + :param enablepricing: whether to enable pricing in copy (default False) """ if self.version() < MAJOR: raise Exception("linked SCIP is not compatible to this version of PySCIPOpt - use at least version", MAJOR) if self.version() < MAJOR + MINOR/10.0 + PATCH/100.0: warnings.warn("linked SCIP {} is not recommended for this version of PySCIPOpt - use version {}.{}.{}".format(self.version(), MAJOR, MINOR, PATCH)) - self.create() - self._bestSol = None - if defaultPlugins: - self.includeDefaultPlugins() - self.createProbBasic(problemName) + if sourceModel is None: + self.create() + self._bestSol = None + if defaultPlugins: + self.includeDefaultPlugins() + self.createProbBasic(problemName) + else: + self.create() + self._bestSol = sourceModel._bestSol + n = str_conversion(problemName) + if origcopy: + PY_SCIP_CALL(SCIPcopyOrig(sourceModel._scip, self._scip, NULL, NULL, n, enablepricing, True, self._valid)) + else: + PY_SCIP_CALL(SCIPcopy(sourceModel._scip, self._scip, NULL, NULL, n, globalcopy, enablepricing, True, self._valid)) def __dealloc__(self): # call C function directly, because we can no longer call this object's methods, according to @@ -818,7 +867,7 @@ cdef class Model: if term != CONST: assert len(term) == 1 var = term[0] - PY_SCIP_CALL(SCIPchgVarObj(self._scip, var.var, coef)) + PY_SCIP_CALL(SCIPchgVarObj(self._scip, var.scip_var, coef)) if sense == "minimize": self.setMinimize() @@ -962,6 +1011,9 @@ cdef class Model: PY_SCIP_CALL(SCIPaddVar(self._scip, scip_var)) pyVar = Variable.create(scip_var) + + #setting the variable data + SCIPvarSetData(scip_var, pyVar) PY_SCIP_CALL(SCIPreleaseVar(self._scip, &scip_var)) return pyVar @@ -971,7 +1023,7 @@ cdef class Model: :param Variable var: variable to be released """ - PY_SCIP_CALL(SCIPreleaseVar(self._scip, &var.var)) + PY_SCIP_CALL(SCIPreleaseVar(self._scip, &var.scip_var)) def getTransformedVar(self, Variable var): """Retrieve the transformed variable. @@ -980,7 +1032,7 @@ cdef class Model: """ cdef SCIP_VAR* _tvar - PY_SCIP_CALL(SCIPtransformVar(self._scip, var.var, &_tvar)) + PY_SCIP_CALL(SCIPtransformVar(self._scip, var.scip_var, &_tvar)) return Variable.create(_tvar) def addVarLocks(self, Variable var, nlocksdown, nlocksup): @@ -991,7 +1043,7 @@ cdef class Model: :param nlocksup: new number of up locks """ - PY_SCIP_CALL(SCIPaddVarLocks(self._scip, var.var, nlocksdown, nlocksup)) + PY_SCIP_CALL(SCIPaddVarLocks(self._scip, var.scip_var, nlocksdown, nlocksup)) def fixVar(self, Variable var, val): """Fixes the variable var to the value val if possible. @@ -1003,7 +1055,7 @@ cdef class Model: """ cdef SCIP_Bool infeasible cdef SCIP_Bool fixed - PY_SCIP_CALL(SCIPfixVar(self._scip, var.var, val, &infeasible, &fixed)) + PY_SCIP_CALL(SCIPfixVar(self._scip, var.scip_var, val, &infeasible, &fixed)) return infeasible, fixed def delVar(self, Variable var): @@ -1014,7 +1066,7 @@ cdef class Model: """ cdef SCIP_Bool deleted - PY_SCIP_CALL(SCIPdelVar(self._scip, var.var, &deleted)) + PY_SCIP_CALL(SCIPdelVar(self._scip, var.scip_var, &deleted)) return deleted def tightenVarLb(self, Variable var, lb, force=False): @@ -1030,7 +1082,7 @@ cdef class Model: """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened - PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.var, lb, force, &infeasible, &tightened)) + PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.scip_var, lb, force, &infeasible, &tightened)) return infeasible, tightened @@ -1047,10 +1099,10 @@ cdef class Model: """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened - PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.var, ub, force, &infeasible, &tightened)) + PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.scip_var, ub, force, &infeasible, &tightened)) return infeasible, tightened - - + + def tightenVarUbGlobal(self, Variable var, ub, force=False): """Tighten the global upper bound, if the bound is tighter. @@ -1064,9 +1116,9 @@ cdef class Model: """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened - PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.var, ub, force, &infeasible, &tightened)) + PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.scip_var, ub, force, &infeasible, &tightened)) return infeasible, tightened - + def tightenVarLbGlobal(self, Variable var, lb, force=False): """Tighten the global upper bound, if the bound is tighter. @@ -1080,7 +1132,7 @@ cdef class Model: """ cdef SCIP_Bool infeasible cdef SCIP_Bool tightened - PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.var, lb, force, &infeasible, &tightened)) + PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.scip_var, lb, force, &infeasible, &tightened)) return infeasible, tightened def chgVarLb(self, Variable var, lb): @@ -1092,7 +1144,7 @@ cdef class Model: """ if lb is None: lb = -SCIPinfinity(self._scip) - PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.var, lb)) + PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.scip_var, lb)) def chgVarUb(self, Variable var, ub): """Changes the upper bound of the specified variable. @@ -1103,7 +1155,7 @@ cdef class Model: """ if ub is None: ub = SCIPinfinity(self._scip) - PY_SCIP_CALL(SCIPchgVarUb(self._scip, var.var, ub)) + PY_SCIP_CALL(SCIPchgVarUb(self._scip, var.scip_var, ub)) def chgVarLbGlobal(self, Variable var, lb): @@ -1115,7 +1167,7 @@ cdef class Model: """ if lb is None: lb = -SCIPinfinity(self._scip) - PY_SCIP_CALL(SCIPchgVarLbGlobal(self._scip, var.var, lb)) + PY_SCIP_CALL(SCIPchgVarLbGlobal(self._scip, var.scip_var, lb)) def chgVarUbGlobal(self, Variable var, ub): """Changes the global upper bound of the specified variable. @@ -1126,7 +1178,7 @@ cdef class Model: """ if ub is None: ub = SCIPinfinity(self._scip) - PY_SCIP_CALL(SCIPchgVarUbGlobal(self._scip, var.var, ub)) + PY_SCIP_CALL(SCIPchgVarUbGlobal(self._scip, var.scip_var, ub)) def chgVarLbNode(self, Node node, Variable var, lb): """Changes the lower bound of the specified variable at the given node. @@ -1137,7 +1189,7 @@ cdef class Model: if lb is None: lb = -SCIPinfinity(self._scip) - PY_SCIP_CALL(SCIPchgVarLbNode(self._scip, node.node, var.var, lb)) + PY_SCIP_CALL(SCIPchgVarLbNode(self._scip, node.scip_node, var.scip_var, lb)) def chgVarUbNode(self, Node node, Variable var, ub): """Changes the upper bound of the specified variable at the given node. @@ -1148,7 +1200,7 @@ cdef class Model: """ if ub is None: ub = SCIPinfinity(self._scip) - PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.node, var.var, ub)) + PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.scip_node, var.scip_var, ub)) def chgVarType(self, Variable var, vtype): """Changes the type of a variable @@ -1159,11 +1211,11 @@ cdef class Model: """ cdef SCIP_Bool infeasible if vtype in ['C', 'CONTINUOUS']: - PY_SCIP_CALL(SCIPchgVarType(self._scip, var.var, SCIP_VARTYPE_CONTINUOUS, &infeasible)) + PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_CONTINUOUS, &infeasible)) elif vtype in ['B', 'BINARY']: - PY_SCIP_CALL(SCIPchgVarType(self._scip, var.var, SCIP_VARTYPE_BINARY, &infeasible)) + PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_BINARY, &infeasible)) elif vtype in ['I', 'INTEGER']: - PY_SCIP_CALL(SCIPchgVarType(self._scip, var.var, SCIP_VARTYPE_INTEGER, &infeasible)) + PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_INTEGER, &infeasible)) else: raise Warning("unrecognized variable type") if infeasible: @@ -1189,6 +1241,14 @@ cdef class Model: return [Variable.create(_vars[i]) for i in range(_nvars)] + def getNVars(self): + """Retrieve number of variables in the problems""" + return SCIPgetNVars(self._scip) + + def getNConss(self): + """Retrieve the number of constraints.""" + return SCIPgetNConss(self._scip) + def updateNodeLowerbound(self, Node node, lb): """if given value is larger than the node's lower bound (in transformed problem), sets the node's lower bound to the new value @@ -1197,7 +1257,7 @@ cdef class Model: :param newbound: float, new bound (if greater) for the node """ - PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.node, lb)) + PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.scip_node, lb)) # LP Methods def getLPSolstat(self): @@ -1237,6 +1297,14 @@ cdef class Model: PY_SCIP_CALL(SCIPgetLPRowsData(self._scip, &rows, &nrows)) return [Row.create(rows[i]) for i in range(nrows)] + def getNLPRows(self): + """Retrieve the number of rows currently in the LP""" + return SCIPgetNLPRows(self._scip) + + def getNLPCols(self): + """Retrieve the number of cols currently in the LP""" + return SCIPgetNLPCols(self._scip) + def getLPBasisInd(self): """Gets all indices of basic columns and rows: index i >= 0 corresponds to column i, index i < 0 to row -i-1""" cdef int nrows = SCIPgetNLPRows(self._scip) @@ -1276,6 +1344,16 @@ cdef class Model: #TODO: documentation!! # LP Row Methods def createEmptyRowSepa(self, Sepa sepa, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True): + """creates and captures an LP row without any coefficients from a separator + + :param sepa: separator that creates the row + :param name: name of row (Default value = "row") + :param lhs: left hand side of row (Default value = 0) + :param rhs: right hand side of row (Default value = None) + :param local: is row only valid locally? (Default value = True) + :param modifiable: is row modifiable during node processing (subject to column generation)? (Default value = False) + :param removable: should the row be removed from the LP due to aging or cleanup? (Default value = True) + """ cdef SCIP_ROW* row lhs = -SCIPinfinity(self._scip) if lhs is None else lhs rhs = SCIPinfinity(self._scip) if rhs is None else rhs @@ -1285,7 +1363,15 @@ cdef class Model: return PyRow def createEmptyRowUnspec(self, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True): - """creates and captures an LP row without any coefficients from an unspecified source""" + """creates and captures an LP row without any coefficients from an unspecified source + + :param name: name of row (Default value = "row") + :param lhs: left hand side of row (Default value = 0) + :param rhs: right hand side of row (Default value = None) + :param local: is row only valid locally? (Default value = True) + :param modifiable: is row modifiable during node processing (subject to column generation)? (Default value = False) + :param removable: should the row be removed from the LP due to aging or cleanup? (Default value = True) + """ cdef SCIP_ROW* row lhs = -SCIPinfinity(self._scip) if lhs is None else lhs rhs = SCIPinfinity(self._scip) if rhs is None else rhs @@ -1294,45 +1380,64 @@ cdef class Model: return PyRow def getRowActivity(self, Row row): - return SCIPgetRowActivity(self._scip, row.row) + """returns the activity of a row in the last LP or pseudo solution""" + return SCIPgetRowActivity(self._scip, row.scip_row) def getRowLPActivity(self, Row row): - return SCIPgetRowLPActivity(self._scip, row.row) + """returns the activity of a row in the last LP solution""" + return SCIPgetRowLPActivity(self._scip, row.scip_row) # TODO: do we need this? (also do we need release var??) def releaseRow(self, Row row not None): - PY_SCIP_CALL(SCIPreleaseRow(self._scip, &row.row)) + """decreases usage counter of LP row, and frees memory if necessary""" + PY_SCIP_CALL(SCIPreleaseRow(self._scip, &row.scip_row)) def cacheRowExtensions(self, Row row not None): - PY_SCIP_CALL(SCIPcacheRowExtensions(self._scip, row.row)) + """informs row, that all subsequent additions of variables to the row should be cached and not directly applied; + after all additions were applied, flushRowExtensions() must be called; + while the caching of row extensions is activated, information methods of the row give invalid results; + caching should be used, if a row is build with addVarToRow() calls variable by variable to increase the performance""" + PY_SCIP_CALL(SCIPcacheRowExtensions(self._scip, row.scip_row)) def flushRowExtensions(self, Row row not None): - PY_SCIP_CALL(SCIPflushRowExtensions(self._scip, row.row)) + """flushes all cached row extensions after a call of cacheRowExtensions() and merges coefficients with equal columns into a single coefficient""" + PY_SCIP_CALL(SCIPflushRowExtensions(self._scip, row.scip_row)) def addVarToRow(self, Row row not None, Variable var not None, value): - PY_SCIP_CALL(SCIPaddVarToRow(self._scip, row.row, var.var, value)) + """resolves variable to columns and adds them with the coefficient to the row""" + PY_SCIP_CALL(SCIPaddVarToRow(self._scip, row.scip_row, var.scip_var, value)) def printRow(self, Row row not None): """Prints row.""" - PY_SCIP_CALL(SCIPprintRow(self._scip, row.row, NULL)) + PY_SCIP_CALL(SCIPprintRow(self._scip, row.scip_row, NULL)) # Cutting Plane Methods def addPoolCut(self, Row row not None): - PY_SCIP_CALL(SCIPaddPoolCut(self._scip, row.row)) + """if not already existing, adds row to global cut pool""" + PY_SCIP_CALL(SCIPaddPoolCut(self._scip, row.scip_row)) def getCutEfficacy(self, Row cut not None, Solution sol = None): - return SCIPgetCutEfficacy(self._scip, NULL if sol is None else sol.sol, cut.row) + """returns efficacy of the cut with respect to the given primal solution or the current LP solution: e = -feasibility/norm""" + return SCIPgetCutEfficacy(self._scip, NULL if sol is None else sol.sol, cut.scip_row) def isCutEfficacious(self, Row cut not None, Solution sol = None): """ returns whether the cut's efficacy with respect to the given primal solution or the current LP solution is greater than the minimal cut efficacy""" - return SCIPisCutEfficacious(self._scip, NULL if sol is None else sol.sol, cut.row) + return SCIPisCutEfficacious(self._scip, NULL if sol is None else sol.sol, cut.scip_row) def addCut(self, Row cut not None, forcecut = False): """adds cut to separation storage and returns whether cut has been detected to be infeasible for local bounds""" cdef SCIP_Bool infeasible - PY_SCIP_CALL(SCIPaddRow(self._scip, cut.row, forcecut, &infeasible)) + PY_SCIP_CALL(SCIPaddRow(self._scip, cut.scip_row, forcecut, &infeasible)) return infeasible + def getNCuts(self): + """Retrieve total number of cuts in storage""" + return SCIPgetNCuts(self._scip) + + def getNCutsApplied(self): + """Retrieve number of currently applied cuts""" + return SCIPgetNCutsApplied(self._scip) + # Constraint functions def addCons(self, cons, name='', initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, @@ -1366,8 +1471,8 @@ cdef class Model: modifiable=modifiable, dynamic=dynamic, removable=removable, stickingatnode=stickingatnode) - kwargs['lhs'] = -SCIPinfinity(self._scip) if cons.lhs is None else cons.lhs - kwargs['rhs'] = SCIPinfinity(self._scip) if cons.rhs is None else cons.rhs + kwargs['lhs'] = -SCIPinfinity(self._scip) if cons._lhs is None else cons._lhs + kwargs['rhs'] = SCIPinfinity(self._scip) if cons._rhs is None else cons._rhs deg = cons.expr.degree() if deg <= 1: @@ -1395,7 +1500,7 @@ cdef class Model: for key, coeff in terms.items(): var = key[0] - PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, coeff)) + PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.scip_var, coeff)) PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) PyCons = Constraint.create(scip_cons) @@ -1420,11 +1525,11 @@ cdef class Model: for v, c in terms.items(): if len(v) == 1: # linear var = v[0] - PY_SCIP_CALL(SCIPaddLinearVarQuadratic(self._scip, scip_cons, var.var, c)) + PY_SCIP_CALL(SCIPaddLinearVarQuadratic(self._scip, scip_cons, var.scip_var, c)) else: # quadratic assert len(v) == 2, 'term length must be 1 or 2 but it is %s' % len(v) var1, var2 = v[0], v[1] - PY_SCIP_CALL(SCIPaddBilinTermQuadratic(self._scip, scip_cons, var1.var, var2.var, c)) + PY_SCIP_CALL(SCIPaddBilinTermQuadratic(self._scip, scip_cons, var1.scip_var, var2.scip_var, c)) PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) PyCons = Constraint.create(scip_cons) @@ -1471,7 +1576,7 @@ cdef class Model: PY_SCIP_CALL( SCIPexprtreeCreate(SCIPblkmem(self._scip), &exprtree, expr, len(variables), 0, NULL) ) vars = malloc(len(variables) * sizeof(SCIP_VAR*)) for idx, var in enumerate(variables): # same as varindex - vars[idx] = (var).var + vars[idx] = (var).scip_var PY_SCIP_CALL( SCIPexprtreeSetVars(exprtree, len(variables), vars) ) # create nonlinear constraint for exprtree @@ -1528,7 +1633,7 @@ cdef class Model: assert len(node[1]) == 1 pyvar = node[1][0] # for vars we store the actual var! PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], opidx, varpos) ) - vars[varpos] = (pyvar).var + vars[varpos] = (pyvar).scip_var varpos += 1 continue if opidx == SCIP_EXPR_CONST: @@ -1596,7 +1701,32 @@ cdef class Model: :param coeff: coefficient of new variable """ - PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, cons.cons, var.var, coeff)) + PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, cons.scip_cons, var.scip_var, coeff)) + + def addConsNode(self, Node node, Constraint cons, Node validnode=None): + """Add a constraint to the given node + + :param Node node: node to add the constraint to + :param Constraint cons: constraint to add + :param Node validnode: more global node where cons is also valid + + """ + if isinstance(validnode, Node): + PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, validnode.scip_node)) + else: + PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, NULL)) + + def addConsLocal(self, Constraint cons, Node validnode=None): + """Add a constraint to the current node + + :param Constraint cons: constraint to add + :param Node validnode: more global node where cons is also valid + + """ + if isinstance(validnode, Node): + PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, validnode.scip_node)) + else: + PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, NULL)) def addConsSOS1(self, vars, weights=None, name="SOS1cons", initial=True, separate=True, enforce=True, check=True, @@ -1627,12 +1757,12 @@ cdef class Model: if weights is None: for v in vars: var = v - PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, scip_cons, var.var)) + PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, scip_cons, var.scip_var)) else: nvars = len(vars) for i in range(nvars): var = vars[i] - PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, scip_cons, var.var, weights[i])) + PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, scip_cons, var.scip_var, weights[i])) PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) return Constraint.create(scip_cons) @@ -1666,12 +1796,12 @@ cdef class Model: if weights is None: for v in vars: var = v - PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, scip_cons, var.var)) + PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, scip_cons, var.scip_var)) else: nvars = len(vars) for i in range(nvars): var = vars[i] - PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, scip_cons, var.var, weights[i])) + 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) @@ -1701,8 +1831,8 @@ cdef class Model: _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) for idx, var in enumerate(vars): - _vars[idx] = (var).var - _resVar = (resvar).var + _vars[idx] = (var).scip_var + _resVar = (resvar).scip_var PY_SCIP_CALL(SCIPcreateConsAnd(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) @@ -1740,8 +1870,8 @@ cdef class Model: _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) for idx, var in enumerate(vars): - _vars[idx] = (var).var - _resVar = (resvar).var + _vars[idx] = (var).scip_var + _resVar = (resvar).scip_var PY_SCIP_CALL(SCIPcreateConsOr(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) @@ -1780,7 +1910,7 @@ cdef class Model: assert type(rhsvar) is type(bool()), "Provide BOOLEAN value as rhsvar, you gave %s." % type(rhsvar) _vars = malloc(len(vars) * sizeof(SCIP_VAR*)) for idx, var in enumerate(vars): - _vars[idx] = (var).var + _vars[idx] = (var).scip_var PY_SCIP_CALL(SCIPcreateConsXor(self._scip, &scip_cons, str_conversion(name), rhsvar, nvars, _vars, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) @@ -1828,13 +1958,13 @@ cdef class Model: for i, v in enumerate(consvars): var = v if indvars: - indvar = (indvars[i]).var + indvar = (indvars[i]).scip_var else: indvar = NULL if weights is None: - PY_SCIP_CALL(SCIPappendVarCardinality(self._scip, scip_cons, var.var, indvar)) + PY_SCIP_CALL(SCIPappendVarCardinality(self._scip, scip_cons, var.scip_var, indvar)) else: - PY_SCIP_CALL(SCIPaddVarCardinality(self._scip, scip_cons, var.var, indvar, weights[i])) + PY_SCIP_CALL(SCIPaddVarCardinality(self._scip, scip_cons, var.scip_var, indvar, weights[i])) PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) pyCons = Constraint.create(scip_cons) @@ -1870,21 +2000,21 @@ cdef class Model: assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__ cdef SCIP_CONS* scip_cons cdef SCIP_VAR* _binVar - if cons.lhs is not None and cons.rhs is not None: + if cons._lhs is not None and cons._rhs is not None: raise ValueError("expected inequality that has either only a left or right hand side") if cons.expr.degree() > 1: raise ValueError("expected linear inequality, expression has degree %d" % cons.expr.degree()) - if cons.rhs is not None: - rhs = cons.rhs + if cons._rhs is not None: + rhs = cons._rhs negate = False else: - rhs = -cons.lhs + rhs = -cons._lhs negate = True - _binVar = (binvar).var if binvar is not None else NULL + _binVar = (binvar).scip_var if binvar is not None else NULL PY_SCIP_CALL(SCIPcreateConsIndicator(self._scip, &scip_cons, str_conversion(name), _binVar, 0, NULL, NULL, rhs, initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode)) @@ -1894,7 +2024,7 @@ cdef class Model: var = key[0] if negate: coeff = -coeff - PY_SCIP_CALL(SCIPaddVarIndicator(self._scip, scip_cons, var.var, coeff)) + PY_SCIP_CALL(SCIPaddVarIndicator(self._scip, scip_cons, var.scip_var, coeff)) PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) pyCons = Constraint.create(scip_cons) @@ -1909,7 +2039,7 @@ cdef class Model: :param Constraint cons: constraint to add """ - PY_SCIP_CALL(SCIPaddCons(self._scip, cons.cons)) + PY_SCIP_CALL(SCIPaddCons(self._scip, cons.scip_cons)) Py_INCREF(cons) def addVarSOS1(self, Constraint cons, Variable var, weight): @@ -1920,7 +2050,7 @@ cdef class Model: :param weight: weight of new variable """ - PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, cons.cons, var.var, weight)) + PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, cons.scip_cons, var.scip_var, weight)) def appendVarSOS1(self, Constraint cons, Variable var): """Append variable to SOS1 constraint. @@ -1929,7 +2059,7 @@ cdef class Model: :param Variable var: variable to append """ - PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, cons.cons, var.var)) + PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, cons.scip_cons, var.scip_var)) def addVarSOS2(self, Constraint cons, Variable var, weight): """Add variable to SOS2 constraint. @@ -1939,7 +2069,7 @@ cdef class Model: :param weight: weight of new variable """ - PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, cons.cons, var.var, weight)) + PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, cons.scip_cons, var.scip_var, weight)) def appendVarSOS2(self, Constraint cons, Variable var): """Append variable to SOS2 constraint. @@ -1948,7 +2078,7 @@ cdef class Model: :param Variable var: variable to append """ - PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, cons.cons, var.var)) + PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, cons.scip_cons, var.scip_var)) def setInitial(self, Constraint cons, newInit): """Set "initial" flag of a constraint. @@ -1957,7 +2087,7 @@ cdef class Model: cons -- constraint newInit -- new initial value """ - PY_SCIP_CALL(SCIPsetConsInitial(self._scip, cons.cons, newInit)) + PY_SCIP_CALL(SCIPsetConsInitial(self._scip, cons.scip_cons, newInit)) def setRemovable(self, Constraint cons, newRem): """Set "removable" flag of a constraint. @@ -1966,7 +2096,7 @@ cdef class Model: cons -- constraint newRem -- new removable value """ - PY_SCIP_CALL(SCIPsetConsRemovable(self._scip, cons.cons, newRem)) + PY_SCIP_CALL(SCIPsetConsRemovable(self._scip, cons.scip_cons, newRem)) def setEnforced(self, Constraint cons, newEnf): """Set "enforced" flag of a constraint. @@ -1975,7 +2105,7 @@ cdef class Model: cons -- constraint newEnf -- new enforced value """ - PY_SCIP_CALL(SCIPsetConsEnforced(self._scip, cons.cons, newEnf)) + PY_SCIP_CALL(SCIPsetConsEnforced(self._scip, cons.scip_cons, newEnf)) def setCheck(self, Constraint cons, newCheck): """Set "check" flag of a constraint. @@ -1984,7 +2114,7 @@ cdef class Model: cons -- constraint newCheck -- new check value """ - PY_SCIP_CALL(SCIPsetConsChecked(self._scip, cons.cons, newCheck)) + PY_SCIP_CALL(SCIPsetConsChecked(self._scip, cons.scip_cons, newCheck)) def chgRhs(self, Constraint cons, rhs): """Change right hand side value of a constraint. @@ -1997,11 +2127,11 @@ cdef class Model: if rhs is None: rhs = SCIPinfinity(self._scip) - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if constype == 'linear': - PY_SCIP_CALL(SCIPchgRhsLinear(self._scip, cons.cons, rhs)) + PY_SCIP_CALL(SCIPchgRhsLinear(self._scip, cons.scip_cons, rhs)) elif constype == 'quadratic': - PY_SCIP_CALL(SCIPchgRhsQuadratic(self._scip, cons.cons, rhs)) + PY_SCIP_CALL(SCIPchgRhsQuadratic(self._scip, cons.scip_cons, rhs)) else: raise Warning("method cannot be called for constraints of type " + constype) @@ -2016,11 +2146,11 @@ cdef class Model: if lhs is None: lhs = -SCIPinfinity(self._scip) - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if constype == 'linear': - PY_SCIP_CALL(SCIPchgLhsLinear(self._scip, cons.cons, lhs)) + PY_SCIP_CALL(SCIPchgLhsLinear(self._scip, cons.scip_cons, lhs)) elif constype == 'quadratic': - PY_SCIP_CALL(SCIPchgLhsQuadratic(self._scip, cons.cons, lhs)) + PY_SCIP_CALL(SCIPchgLhsQuadratic(self._scip, cons.scip_cons, lhs)) else: raise Warning("method cannot be called for constraints of type " + constype) @@ -2030,11 +2160,11 @@ cdef class Model: :param Constraint cons: linear or quadratic constraint """ - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if constype == 'linear': - return SCIPgetRhsLinear(self._scip, cons.cons) + return SCIPgetRhsLinear(self._scip, cons.scip_cons) elif constype == 'quadratic': - return SCIPgetRhsQuadratic(self._scip, cons.cons) + return SCIPgetRhsQuadratic(self._scip, cons.scip_cons) else: raise Warning("method cannot be called for constraints of type " + constype) @@ -2044,11 +2174,11 @@ cdef class Model: :param Constraint cons: linear or quadratic constraint """ - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if constype == 'linear': - return SCIPgetLhsLinear(self._scip, cons.cons) + return SCIPgetLhsLinear(self._scip, cons.scip_cons) elif constype == 'quadratic': - return SCIPgetLhsQuadratic(self._scip, cons.cons) + return SCIPgetLhsQuadratic(self._scip, cons.scip_cons) else: raise Warning("method cannot be called for constraints of type " + constype) @@ -2071,11 +2201,11 @@ cdef class Model: else: scip_sol = NULL - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if constype == 'linear': - activity = SCIPgetActivityLinear(self._scip, cons.cons, scip_sol) + activity = SCIPgetActivityLinear(self._scip, cons.scip_cons, scip_sol) elif constype == 'quadratic': - PY_SCIP_CALL(SCIPgetActivityQuadratic(self._scip, cons.cons, scip_sol, &activity)) + PY_SCIP_CALL(SCIPgetActivityQuadratic(self._scip, cons.scip_cons, scip_sol, &activity)) else: raise Warning("method cannot be called for constraints of type " + constype) @@ -2104,15 +2234,15 @@ cdef class Model: else: scip_sol = NULL - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if constype == 'linear': - lhs = SCIPgetLhsLinear(self._scip, cons.cons) - rhs = SCIPgetRhsLinear(self._scip, cons.cons) - activity = SCIPgetActivityLinear(self._scip, cons.cons, scip_sol) + lhs = SCIPgetLhsLinear(self._scip, cons.scip_cons) + rhs = SCIPgetRhsLinear(self._scip, cons.scip_cons) + activity = SCIPgetActivityLinear(self._scip, cons.scip_cons, scip_sol) elif constype == 'quadratic': - lhs = SCIPgetLhsQuadratic(self._scip, cons.cons) - rhs = SCIPgetRhsQuadratic(self._scip, cons.cons) - PY_SCIP_CALL(SCIPgetActivityQuadratic(self._scip, cons.cons, scip_sol, &activity)) + lhs = SCIPgetLhsQuadratic(self._scip, cons.scip_cons) + rhs = SCIPgetRhsQuadratic(self._scip, cons.scip_cons) + PY_SCIP_CALL(SCIPgetActivityQuadratic(self._scip, cons.scip_cons, scip_sol, &activity)) else: raise Warning("method cannot be called for constraints of type " + constype) @@ -2133,7 +2263,7 @@ cdef class Model: """ cdef SCIP_CONS* transcons - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.cons, &transcons)) + PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) return Constraint.create(transcons) def getTermsQuadratic(self, Constraint cons): @@ -2157,8 +2287,8 @@ cdef class Model: linterms = [] # bilinear terms - _bilinterms = SCIPgetBilinTermsQuadratic(self._scip, cons.cons) - _nbilinterms = SCIPgetNBilinTermsQuadratic(self._scip, cons.cons) + _bilinterms = SCIPgetBilinTermsQuadratic(self._scip, cons.scip_cons) + _nbilinterms = SCIPgetNBilinTermsQuadratic(self._scip, cons.scip_cons) for i in range(_nbilinterms): var1 = Variable.create(_bilinterms[i].var1) @@ -2166,17 +2296,17 @@ cdef class Model: bilinterms.append((var1,var2,_bilinterms[i].coef)) # quadratic terms - _quadterms = SCIPgetQuadVarTermsQuadratic(self._scip, cons.cons) - _nquadterms = SCIPgetNQuadVarTermsQuadratic(self._scip, cons.cons) + _quadterms = SCIPgetQuadVarTermsQuadratic(self._scip, cons.scip_cons) + _nquadterms = SCIPgetNQuadVarTermsQuadratic(self._scip, cons.scip_cons) for i in range(_nquadterms): var = Variable.create(_quadterms[i].var) quadterms.append((var,_quadterms[i].sqrcoef,_quadterms[i].lincoef)) # linear terms - _linvars = SCIPgetLinearVarsQuadratic(self._scip, cons.cons) - _lincoefs = SCIPgetCoefsLinearVarsQuadratic(self._scip, cons.cons) - _nlinvars = SCIPgetNLinearVarsQuadratic(self._scip, cons.cons) + _linvars = SCIPgetLinearVarsQuadratic(self._scip, cons.scip_cons) + _lincoefs = SCIPgetCoefsLinearVarsQuadratic(self._scip, cons.scip_cons) + _nlinvars = SCIPgetNLinearVarsQuadratic(self._scip, cons.scip_cons) for i in range(_nlinvars): var = Variable.create(_linvars[i]) @@ -2184,6 +2314,10 @@ cdef class Model: return (bilinterms, quadterms, linterms) + def setRelaxSolVal(self, Variable var, val): + """sets the value of the given variable in the global relaxation solution""" + PY_SCIP_CALL(SCIPsetRelaxSolVal(self._scip, var.scip_var, val)) + def getConss(self): """Retrieve all constraints.""" cdef SCIP_CONS** _conss @@ -2194,13 +2328,17 @@ cdef class Model: _nconss = SCIPgetNConss(self._scip) return [Constraint.create(_conss[i]) for i in range(_nconss)] + def getNConss(self): + """Retrieve number of all constraints""" + return SCIPgetNConss(self._scip) + def delCons(self, Constraint cons): """Delete constraint from the model :param Constraint cons: constraint to be deleted """ - PY_SCIP_CALL(SCIPdelCons(self._scip, cons.cons)) + PY_SCIP_CALL(SCIPdelCons(self._scip, cons.scip_cons)) def delConsLocal(self, Constraint cons): """Delete constraint from the current node and it's children @@ -2208,7 +2346,7 @@ cdef class Model: :param Constraint cons: constraint to be deleted """ - PY_SCIP_CALL(SCIPdelConsLocal(self._scip, cons.cons)) + PY_SCIP_CALL(SCIPdelConsLocal(self._scip, cons.scip_cons)) def getValsLinear(self, Constraint cons): """Retrieve the coefficients of a linear constraint @@ -2219,18 +2357,33 @@ cdef class Model: cdef SCIP_Real* _vals cdef SCIP_VAR** _vars - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if not constype == 'linear': raise Warning("coefficients not available for constraints of type ", constype) - _vals = SCIPgetValsLinear(self._scip, cons.cons) - _vars = SCIPgetVarsLinear(self._scip, cons.cons) + _vals = SCIPgetValsLinear(self._scip, cons.scip_cons) + _vars = SCIPgetVarsLinear(self._scip, cons.scip_cons) valsdict = {} - for i in range(SCIPgetNVarsLinear(self._scip, cons.cons)): + for i in range(SCIPgetNVarsLinear(self._scip, cons.scip_cons)): valsdict[bytes(SCIPvarGetName(_vars[i])).decode('utf-8')] = _vals[i] return valsdict + def getDualMultiplier(self, Constraint cons): + """Retrieve the dual multiplier to a linear constraint. + + :param Constraint cons: linear constraint + + """ + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + if not constype == 'linear': + raise Warning("dual solution values not available for constraints of type ", constype) + if cons.isOriginal(): + transcons = self.getTransformedCons(cons) + else: + transcons = cons + return SCIPgetDualsolLinear(self._scip, transcons.scip_cons) + def getDualsolLinear(self, Constraint cons): """Retrieve the dual solution to a linear constraint. @@ -2245,28 +2398,28 @@ cdef class Model: cdef SCIP_Bool _success if self.version() > 6.0: - PY_SCIP_CALL( SCIPgetDualSolVal(self._scip, cons.cons, &dualsolval, &boundconstraint) ) + PY_SCIP_CALL( SCIPgetDualSolVal(self._scip, cons.scip_cons, &dualsolval, &boundconstraint) ) return dualsolval else: dual = 0.0 - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.cons))).decode('UTF-8') + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') if not constype == 'linear': raise Warning("dual solution values not available for constraints of type ", constype) try: - _nvars = SCIPgetNVarsLinear(self._scip, cons.cons) + _nvars = SCIPgetNVarsLinear(self._scip, cons.scip_cons) if cons.isOriginal(): transcons = self.getTransformedCons(cons) else: transcons = cons - dual = SCIPgetDualsolLinear(self._scip, transcons.cons) + dual = SCIPgetDualsolLinear(self._scip, transcons.scip_cons) if dual == 0.0 and _nvars == 1: - _vars = SCIPgetVarsLinear(self._scip, transcons.cons) - _vals = SCIPgetValsLinear(self._scip, transcons.cons) + _vars = SCIPgetVarsLinear(self._scip, transcons.scip_cons) + _vals = SCIPgetValsLinear(self._scip, transcons.scip_cons) activity = SCIPvarGetLPSol(_vars[0]) * _vals[0] - rhs = SCIPgetRhsLinear(self._scip, transcons.cons) - lhs = SCIPgetLhsLinear(self._scip, transcons.cons) + rhs = SCIPgetRhsLinear(self._scip, transcons.scip_cons) + lhs = SCIPgetLhsLinear(self._scip, transcons.scip_cons) if (activity == rhs) or (activity == lhs): dual = SCIPgetVarRedcost(self._scip, _vars[0]) @@ -2285,9 +2438,9 @@ cdef class Model: # TODO this should ideally be handled on the SCIP side if cons.isOriginal(): transcons = self.getTransformedCons(cons) - return SCIPgetDualfarkasLinear(self._scip, transcons.cons) + return SCIPgetDualfarkasLinear(self._scip, transcons.scip_cons) else: - return SCIPgetDualfarkasLinear(self._scip, cons.cons) + return SCIPgetDualfarkasLinear(self._scip, cons.scip_cons) def getVarRedcost(self, Variable var): """Retrieve the reduced cost of a variable. @@ -2297,7 +2450,7 @@ cdef class Model: """ redcost = None try: - redcost = SCIPgetVarRedcost(self._scip, var.var) + redcost = SCIPgetVarRedcost(self._scip, var.scip_var) if self.getObjectiveSense() == "maximize": redcost = -redcost except: @@ -2414,6 +2567,116 @@ cdef class Model: for d in lowerbounds.keys(): SCIPbendersUpdateSubproblemLowerbound(_benders, d, lowerbounds[d]) + def activateBenders(self, str name, int nsubproblems): + """Activates the Benders' decomposition plugin with the input name + + Keyword arguments: + name -- the name of the Benders' decomposition plugin + nsubproblems -- the number of subproblems in the Benders' decomposition + """ + n = str_conversion(name) + cdef SCIP_BENDERS* benders + benders = SCIPfindBenders(self._scip, n) + PY_SCIP_CALL(SCIPactivateBenders(self._scip, benders, nsubproblems)) + + def addBendersSubproblem(self, str name, subproblem): + """adds a subproblem to the Benders' decomposition given by the input + name. + + Keyword arguments: + name -- the Benders' decomposition that the subproblem is added to + subproblem -- the subproblem to add to the decomposition + """ + cdef SCIP_BENDERS* benders + n = str_conversion(name) + benders = SCIPfindBenders(self._scip, n) + PY_SCIP_CALL(SCIPaddBendersSubproblem(self._scip, benders, (subproblem)._scip)) + + def getBendersVar(self, Variable var, Benders benders = None, probnumber = -1): + """Returns the variable for the subproblem or master problem + depending on the input probnumber + + Keyword arguments: + var -- the source variable for which the target variable is requested + benders -- the Benders' decomposition to which the subproblem variables belong to + probnumber -- the problem number for which the target variable belongs, -1 for master problem + """ + cdef SCIP_BENDERS* _benders + cdef SCIP_VAR* _mappedvar + + if benders is None: + _benders = SCIPfindBenders(self._scip, "default") + else: + n = str_conversion(benders.name) + _benders = SCIPfindBenders(self._scip, n) + + if probnumber == -1: + PY_SCIP_CALL(SCIPgetBendersMasterVar(self._scip, _benders, var.scip_var, &_mappedvar)) + else: + PY_SCIP_CALL(SCIPgetBendersSubproblemVar(self._scip, _benders, var.scip_var, &_mappedvar, probnumber)) + + if _mappedvar == NULL: + mappedvar = None + else: + mappedvar = Variable.create(_mappedvar) + + return mappedvar + + def setupBendersSubproblem(self, probnumber, Benders benders = None, Solution solution = None): + """ sets up the Benders' subproblem given the master problem solution + Keyword arguments: + probnumber -- the index of the problem that is to be set up + benders -- the Benders' decomposition to which the subproblem belongs to + solution -- the master problem solution that is used for the set up, if None, then the LP solution is used + """ + cdef SCIP_BENDERS* scip_benders + cdef SCIP_SOL* scip_sol + if isinstance(solution, Solution): + scip_sol = solution.sol + else: + scip_sol = NULL + + if benders is None: + scip_benders = SCIPfindBenders(self._scip, "default") + else: + n = str_conversion(benders.name) + scip_benders = SCIPfindBenders(self._scip, n) + + PY_SCIP_CALL(SCIPsetupBendersSubproblem(self._scip, scip_benders, scip_sol, probnumber)) + + def solveBendersSubproblem(self, probnumber, enfotype, solvecip, Benders benders = None, Solution solution = None): + """ solves the Benders' decomposition subproblem. The convex relaxation will be solved unless + the parameter solvecip is set to True. + + Keyword arguments: + probnumber -- the index of the problem that is to be set up + enfotype -- the enforcement type used for solving the subproblem. Either LP, RELAX, PSEUDO or CHECK + solvecip -- should the CIP of the subproblem be solved, if False, then only the convex relaxation is solved + benders -- the Benders' decomposition to which the subproblem belongs to + solution -- the master problem solution that is used for the set up, if None, then the LP solution is used + """ + + cdef SCIP_BENDERS* scip_benders + cdef SCIP_Real objective + cdef SCIP_Bool infeasible + cdef SCIP_SOL* scip_sol + + if isinstance(solution, Solution): + scip_sol = solution.sol + else: + scip_sol = NULL + + if benders is None: + scip_benders = SCIPfindBenders(self._scip, "default") + else: + n = str_conversion(benders.name) + scip_benders = SCIPfindBenders(self._scip, n) + + PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip, scip_benders, scip_sol, + probnumber, &infeasible, enfotype, solvecip, &objective)) + + return infeasible, objective + def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): """Include an event handler. @@ -2522,7 +2785,7 @@ cdef class Model: cdef SCIP_CONSHDLR* scip_conshdlr scip_conshdlr = SCIPfindConshdlr(self._scip, str_conversion(conshdlr.name)) constraint = Constraint() - PY_SCIP_CALL(SCIPcreateCons(self._scip, &(constraint.cons), n, scip_conshdlr, constraint, + PY_SCIP_CALL(SCIPcreateCons(self._scip, &(constraint.scip_cons), n, scip_conshdlr, constraint, initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) return constraint @@ -2622,6 +2885,25 @@ cdef class Model: heur.name = name Py_INCREF(heur) + def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): + """Include a relaxation handler. + + :param Relax relax: relaxation handler + :param name: name of relaxation handler + :param desc: description of relaxation handler + :param priority: priority of the relaxation handler (negative: after LP, non-negative: before LP, Default value = 10000) + :param freq: frequency for calling relaxation handler + + """ + nam = str_conversion(name) + des = str_conversion(desc) + PY_SCIP_CALL(SCIPincludeRelax(self._scip, nam, des, priority, freq, PyRelaxCopy, PyRelaxFree, PyRelaxInit, PyRelaxExit, + PyRelaxInitsol, PyRelaxExitsol, PyRelaxExec, relax)) + relax.model = weakref.proxy(self) + relax.name = name + + Py_INCREF(relax) + def includeBranchrule(self, Branchrule branchrule, name, desc, priority, maxdepth, maxbounddist): """Include a branching rule. @@ -2672,6 +2954,37 @@ cdef class Model: benders.name = name Py_INCREF(benders) + def includeBenderscut(self, Benders benders, Benderscut benderscut, name, desc, priority=1, islpcut=True): + """ Include a Benders' decomposition cutting method + + Keyword arguments: + benders -- the Benders' decomposition that this cutting method is attached to + benderscut --- the Benders' decomposition cutting method + name -- the name + desc -- the description + priority -- priority of the Benders' decomposition + islpcut -- is this cutting method suitable for generating cuts for convex relaxations? + """ + cdef SCIP_BENDERS* _benders + + bendersname = str_conversion(benders.name) + _benders = SCIPfindBenders(self._scip, bendersname) + + n = str_conversion(name) + d = str_conversion(desc) + PY_SCIP_CALL(SCIPincludeBenderscut(self._scip, _benders, n, d, priority, islpcut, + PyBenderscutCopy, PyBenderscutFree, PyBenderscutInit, PyBenderscutExit, + PyBenderscutInitsol, PyBenderscutExitsol, PyBenderscutExec, + benderscut)) + + cdef SCIP_BENDERSCUT* scip_benderscut + scip_benderscut = SCIPfindBenderscut(_benders, n) + benderscut.model = weakref.proxy(self) + benderscut.benders = weakref.proxy(benders) + benderscut.name = name + # TODO: It might be necessary in increment the reference to benders i.e Py_INCREF(benders) + Py_INCREF(benderscut) + def getLPBranchCands(self): """gets branching candidates for LP solution branching (fractional variables) along with solution values, @@ -2718,7 +3031,7 @@ cdef class Model: cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) - PY_SCIP_CALL(SCIPbranchVar(self._scip, (variable).var, &downchild, &eqchild, &upchild)) + PY_SCIP_CALL(SCIPbranchVar(self._scip, (variable).scip_var, &downchild, &eqchild, &upchild)) return Node.create(downchild), Node.create(eqchild), Node.create(upchild) @@ -2735,7 +3048,7 @@ cdef class Model: cdef SCIP_NODE* eqchild = malloc(sizeof(SCIP_NODE)) cdef SCIP_NODE* upchild = malloc(sizeof(SCIP_NODE)) - PY_SCIP_CALL(SCIPbranchVarVal(self._scip, (variable).var, value, &downchild, &eqchild, &upchild)) + PY_SCIP_CALL(SCIPbranchVarVal(self._scip, (variable).scip_var, value, &downchild, &eqchild, &upchild)) # TODO should the stuff be freed and how? return Node.create(downchild), Node.create(eqchild), Node.create(upchild) @@ -2750,7 +3063,7 @@ cdef class Model: :return: node selection priority for moving the given variable's LP value to the given target value """ - return SCIPcalcNodeselPriority(self._scip, variable.var, branchdir, targetvalue) + return SCIPcalcNodeselPriority(self._scip, variable.scip_var, branchdir, targetvalue) def calcChildEstimate(self, Variable variable, targetvalue): """Calculates an estimate for the objective of the best feasible solution @@ -2762,7 +3075,7 @@ cdef class Model: :return: objective estimate of the best solution in the subtree after applying the given branching """ - return SCIPcalcChildEstimate(self._scip, variable.var, targetvalue) + return SCIPcalcChildEstimate(self._scip, variable.scip_var, targetvalue) def createChild(self, nodeselprio, estimate): """Create a child node of the focus node. @@ -2790,39 +3103,39 @@ cdef class Model: def chgVarObjDive(self, Variable var, newobj): """changes (column) variable's objective value in current dive""" - PY_SCIP_CALL(SCIPchgVarObjDive(self._scip, var.var, newobj)) + PY_SCIP_CALL(SCIPchgVarObjDive(self._scip, var.scip_var, newobj)) def chgVarLbDive(self, Variable var, newbound): """changes variable's current lb in current dive""" - PY_SCIP_CALL(SCIPchgVarLbDive(self._scip, var.var, newbound)) + PY_SCIP_CALL(SCIPchgVarLbDive(self._scip, var.scip_var, newbound)) def chgVarUbDive(self, Variable var, newbound): """changes variable's current ub in current dive""" - PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, var.var, newbound)) + PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, var.scip_var, newbound)) def getVarLbDive(self, Variable var): """returns variable's current lb in current dive""" - return SCIPgetVarLbDive(self._scip, var.var) + return SCIPgetVarLbDive(self._scip, var.scip_var) def getVarUbDive(self, Variable var): """returns variable's current ub in current dive""" - return SCIPgetVarUbDive(self._scip, var.var) + return SCIPgetVarUbDive(self._scip, var.scip_var) def chgRowLhsDive(self, Row row, newlhs): """changes row lhs in current dive, change will be undone after diving ends, for permanent changes use SCIPchgRowLhs() """ - PY_SCIP_CALL(SCIPchgRowLhsDive(self._scip, row.row, newlhs)) + PY_SCIP_CALL(SCIPchgRowLhsDive(self._scip, row.scip_row, newlhs)) def chgRowRhsDive(self, Row row, newrhs): """changes row rhs in current dive, change will be undone after diving ends, for permanent changes use SCIPchgRowLhs() """ - PY_SCIP_CALL(SCIPchgRowRhsDive(self._scip, row.row, newrhs)) + PY_SCIP_CALL(SCIPchgRowRhsDive(self._scip, row.scip_row, newrhs)) def addRowDive(self, Row row): """adds a row to the LP in current dive""" - PY_SCIP_CALL(SCIPaddRowDive(self._scip, row.row)) + PY_SCIP_CALL(SCIPaddRowDive(self._scip, row.scip_row)) def solveDiveLP(self, itlim = -1): """solves the LP of the current dive no separation or pricing is applied @@ -2855,16 +3168,18 @@ cdef class Model: def chgVarObjProbing(self, Variable var, newobj): """changes (column) variable's objective value during probing mode""" - PY_SCIP_CALL(SCIPchgVarObjProbing(self._scip, var.var, newobj)) + PY_SCIP_CALL(SCIPchgVarObjProbing(self._scip, var.scip_var, newobj)) def fixVarProbing(self, Variable var, fixedval): """Fixes a variable at the current probing node.""" - PY_SCIP_CALL(SCIPfixVarProbing(self._scip, var.var, fixedval)) + PY_SCIP_CALL(SCIPfixVarProbing(self._scip, var.scip_var, fixedval)) def isObjChangedProbing(self): + """returns whether the objective function has changed during probing mode""" return SCIPisObjChangedProbing(self._scip) def inProbing(self): + """returns whether we are in probing mode; probing mode is activated via startProbing() and stopped via endProbing()""" return SCIPinProbing(self._scip) def solveProbingLP(self, itlim = -1): @@ -2987,7 +3302,7 @@ cdef class Model: """ cdef SCIP_SOL* _sol _sol = solution.sol - PY_SCIP_CALL(SCIPsetSolVal(self._scip, _sol, var.var, val)) + PY_SCIP_CALL(SCIPsetSolVal(self._scip, _sol, var.scip_var, val)) def trySol(self, Solution solution, printreason=True, completely=False, checkbounds=True, checkintegrality=True, checklprows=True, free=True): """Check given primal solution for feasibility and try to add it to the storage. @@ -3008,6 +3323,25 @@ cdef class Model: PY_SCIP_CALL(SCIPtrySol(self._scip, solution.sol, printreason, completely, checkbounds, checkintegrality, checklprows, &stored)) return stored + def checkSol(self, Solution solution, printreason=True, completely=False, checkbounds=True, checkintegrality=True, checklprows=True, original=False): + """Check given primal solution for feasibility without adding it to the storage. + + :param Solution solution: solution to store + :param printreason: should all reasons of violations be printed? (Default value = True) + :param completely: should all violation be checked? (Default value = False) + :param checkbounds: should the bounds of the variables be checked? (Default value = True) + :param checkintegrality: has integrality to be checked? (Default value = True) + :param checklprows: have current LP rows (both local and global) to be checked? (Default value = True) + :param original: must the solution be checked against the original problem (Default value = False) + + """ + cdef SCIP_Bool feasible + if original: + PY_SCIP_CALL(SCIPcheckSolOrig(self._scip, solution.sol, &feasible, printreason, completely)) + else: + PY_SCIP_CALL(SCIPcheckSol(self._scip, solution.sol, printreason, completely, checkbounds, checkintegrality, checklprows, &feasible)) + return feasible + def addSol(self, Solution solution, free=True): """Try to add a solution to the storage. @@ -3084,7 +3418,7 @@ cdef class Model: """ if sol == None: sol = Solution.create(NULL) - return SCIPgetSolVal(self._scip, sol.sol, var.var) + return SCIPgetSolVal(self._scip, sol.sol, var.scip_var) def getVal(self, Variable var): """Retrieve the value of the best known solution. @@ -3115,7 +3449,7 @@ cdef class Model: :param Variable var: variable """ - PY_SCIP_CALL(SCIPwriteVarName(self._scip, NULL, var.var, False)) + PY_SCIP_CALL(SCIPwriteVarName(self._scip, NULL, var.scip_var, False)) def getStage(self): """Retrieve current SCIP stage""" @@ -3146,6 +3480,7 @@ cdef class Model: return "unknown" def catchEvent(self, eventtype, Eventhdlr eventhdlr): + """catches a global (not variable or row dependent) event""" cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) @@ -3155,6 +3490,7 @@ cdef class Model: PY_SCIP_CALL(SCIPcatchEvent(self._scip, eventtype, _eventhdlr, NULL, NULL)) def dropEvent(self, eventtype, Eventhdlr eventhdlr): + """drops a global event (stops to track event)""" cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) @@ -3164,40 +3500,44 @@ cdef class Model: PY_SCIP_CALL(SCIPdropEvent(self._scip, eventtype, _eventhdlr, NULL, -1)) def catchVarEvent(self, Variable var, eventtype, Eventhdlr eventhdlr): + """catches an objective value or domain change event on the given transformed variable""" cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) _eventhdlr = SCIPfindEventhdlr(self._scip, n) else: raise Warning("event handler not found") - PY_SCIP_CALL(SCIPcatchVarEvent(self._scip, var.var, eventtype, _eventhdlr, NULL, NULL)) + PY_SCIP_CALL(SCIPcatchVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, NULL)) def dropVarEvent(self, Variable var, eventtype, Eventhdlr eventhdlr): + """drops an objective value or domain change event (stops to track event) on the given transformed variable""" cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) _eventhdlr = SCIPfindEventhdlr(self._scip, n) else: raise Warning("event handler not found") - PY_SCIP_CALL(SCIPdropVarEvent(self._scip, var.var, eventtype, _eventhdlr, NULL, -1)) + PY_SCIP_CALL(SCIPdropVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, -1)) def catchRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): + """catches a row coefficient, constant, or side change event on the given row""" cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) _eventhdlr = SCIPfindEventhdlr(self._scip, n) else: raise Warning("event handler not found") - PY_SCIP_CALL(SCIPcatchRowEvent(self._scip, row.row, eventtype, _eventhdlr, NULL, NULL)) + PY_SCIP_CALL(SCIPcatchRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, NULL)) def dropRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): + """drops a row coefficient, constant, or side change event (stops to track event) on the given row""" cdef SCIP_EVENTHDLR* _eventhdlr if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) _eventhdlr = SCIPfindEventhdlr(self._scip, n) else: raise Warning("event handler not found") - PY_SCIP_CALL(SCIPdropRowEvent(self._scip, row.row, eventtype, _eventhdlr, NULL, -1)) + PY_SCIP_CALL(SCIPdropRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, -1)) # Statistic Methods @@ -3218,6 +3558,7 @@ cdef class Model: PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) def getNLPs(self): + """gets total number of LPs solved so far""" return SCIPgetNLPs(self._scip) # Verbosity Methods @@ -3484,7 +3825,7 @@ cdef class Model: assert len(term) == 1 var = term[0] for i in range(_nvars): - if _vars[i] == var.var: + if _vars[i] == var.scip_var: _coeffs[i] = coef PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, _vars, &_coeffs[0], _nvars))