From 35991590f684fa1eaf222697e045b535e645a432 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 26 Apr 2025 18:04:22 +0100 Subject: [PATCH 1/5] Support for AND-constraints --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 5 ++ src/pyscipopt/scip.pxi | 118 +++++++++++++++++++++++++++++++++++++++++ tests/test_cons.py | 20 +++++++ 4 files changed, 144 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dadd7965..5b48cb751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- More support for AND-Constraints - Added getLinearConsIndicator - Added SCIP_LPPARAM, setIntParam, setRealParam, getIntParam, getRealParam, isOptimal, getObjVal, getRedcost for lpi - Added isFeasPositive diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index f53421164..16c9a5488 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1612,6 +1612,11 @@ cdef extern from "scip/cons_and.h": SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode) + int SCIPgetNVarsAnd(SCIP* scip, SCIP_CONS* cons) + SCIP_VAR** SCIPgetVarsAnd(SCIP* scip, SCIP_CONS* cons) + SCIP_VAR* SCIPgetResultantAnd(SCIP* scip, SCIP_CONS* cons) + SCIP_Bool SCIPisAndConsSorted(SCIP* scip, SCIP_CONS* cons) + SCIP_RETCODE SCIPsortAndCons(SCIP* scip, SCIP_CONS* cons) cdef extern from "scip/cons_or.h": SCIP_RETCODE SCIPcreateConsOr(SCIP* scip, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 3b847ef60..32909bcaf 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -5689,6 +5689,124 @@ cdef class Model: return vars + def getNVarsAnd(self, Constraint constraint): + """ + Gets number of variables in and constraint. + + Parameters + ---------- + constraint : Constraint + Constraint to get the number of variables from. + + Returns + ------- + int + + """ + cdef int nvars + cdef SCIP_Bool success + + return SCIPgetConsNVarsAnd(self._scip, constraint.scip_cons) + + def getConsVarsAnd(self, Constraint constraint): + """ + Gets variables in and constraint. + + Parameters + ---------- + constraint : Constraint + Constraint to get the variables from. + + Returns + ------- + list of Variable + + """ + cdef SCIP_VAR** _vars + cdef int nvars + cdef SCIP_Bool success + cdef int i + + SCIPgetConsNVarsAnd(self._scip, constraint.scip_cons, &nvars, &success) + _vars = malloc(nvars * sizeof(SCIP_VAR*)) + _vars = SCIPgetConsVarsAnd(self._scip, constraint.scip_cons) + + vars = [] + for i in range(nvars): + ptr = (_vars[i]) + # check whether the corresponding variable exists already + if ptr in self._modelvars: + vars.append(self._modelvars[ptr]) + else: + # create a new variable + var = Variable.create(_vars[i]) + assert var.ptr() == ptr + self._modelvars[ptr] = var + vars.append(var) + + return vars + + def getResultantAnd(self, Constraint constraint): + """ + Gets the resultant variable in And constraint. + + Parameters + ---------- + constraint : Constraint + Constraint to get the resultant variable from. + + Returns + ------- + Variable + + """ + cdef SCIP_VAR* _resultant + cdef SCIP_Bool success + + _resultant = SCIPgetResultantAnd(self._scip, constraint.scip_cons) + + ptr = (_resultant) + # check whether the corresponding variable exists already + if ptr not in self._modelvars: + # create a new variable + var = Variable.create(_resultant) + assert var.ptr() == ptr + self._modelvars[ptr] = var + + return resultant + + def isAndConsSorted(self, Constraint constraint): + """ + Returns if the variables of the AND-constraint are sorted with respect to their indices. + + Parameters + ---------- + constraint : Constraint + Constraint to check. + + Returns + ------- + bool + + """ + cdef SCIP_Bool success + + return SCIPisAndConsSorted(self._scip, constraint.scip_cons) + + def sortAndCons(self, Constraint constraint): + """ + Sorts the variables of the AND-constraint with respect to their indices. + + Parameters + ---------- + constraint : Constraint + Constraint to sort. + + """ + cdef SCIP_Bool success + + PY_SCIP_CALL(SCIPsortAndCons(self._scip, constraint.scip_cons)) + def printCons(self, Constraint constraint): """ Print the constraint diff --git a/tests/test_cons.py b/tests/test_cons.py index ab53e9a24..d9e026608 100644 --- a/tests/test_cons.py +++ b/tests/test_cons.py @@ -72,6 +72,26 @@ def test_cons_logical(): assert m.isEQ(m.getVal(result1), 1) assert m.isEQ(m.getVal(result2), 0) +def test_cons_and(): + m = Model() + x1 = m.addVar(vtype="B") + x2 = m.addVar(vtype="B") + result = m.addVar(vtype="B") + + and_cons = m.addConsAnd([x1, x2], result) + + assert m.getNConsAnd(and_cons) == 1 + vars = m.getVarsAnd(and_cons) + assert len(vars) == 2 + assert vars[0] == x1 + assert vars[1] == x2 + resultant_var = m.getResultAnd(and_cons) + assert resultant_var == result + m.optimize() + + m.sortAndCons(and_cons) + assert m.isAndConsSorted(and_cons) + def test_SOScons(): m = Model() x = {} From 155d786a7ce20b83020d72bc73679dfc225c6980 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 26 Apr 2025 19:23:36 +0100 Subject: [PATCH 2/5] better spacing in pxd --- src/pyscipopt/scip.pxd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 16c9a5488..c42cb6ae6 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1612,10 +1612,15 @@ cdef extern from "scip/cons_and.h": SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode) + int SCIPgetNVarsAnd(SCIP* scip, SCIP_CONS* cons) + SCIP_VAR** SCIPgetVarsAnd(SCIP* scip, SCIP_CONS* cons) + SCIP_VAR* SCIPgetResultantAnd(SCIP* scip, SCIP_CONS* cons) + SCIP_Bool SCIPisAndConsSorted(SCIP* scip, SCIP_CONS* cons) + SCIP_RETCODE SCIPsortAndCons(SCIP* scip, SCIP_CONS* cons) cdef extern from "scip/cons_or.h": From 178a850c60d5732aac92754de9adc5fd3f6e9b8e Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Mon, 12 May 2025 20:12:58 +0100 Subject: [PATCH 3/5] some debugging --- src/pyscipopt/scip.pxi | 49 +++++++++++++++++++++++------------------- tests/test_cons.py | 11 ++++------ 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 512a13fd2..f38deec4d 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -5778,14 +5778,14 @@ cdef class Model: return vars - def getNVarsAnd(self, Constraint constraint): + def getNVarsAnd(self, Constraint and_cons): """ Gets number of variables in and constraint. Parameters ---------- - constraint : Constraint - Constraint to get the number of variables from. + and_cons : Constraint + AND constraint to get the number of variables from. Returns ------- @@ -5795,16 +5795,16 @@ cdef class Model: cdef int nvars cdef SCIP_Bool success - return SCIPgetConsNVarsAnd(self._scip, constraint.scip_cons) + return SCIPgetNVarsAnd(self._scip, and_cons.scip_cons) - def getConsVarsAnd(self, Constraint constraint): + def getVarsAnd(self, Constraint and_cons): """ - Gets variables in and constraint. + Gets variables in AND constraint. Parameters ---------- - constraint : Constraint - Constraint to get the variables from. + and_cons : Constraint + AND Constraint to get the variables from. Returns ------- @@ -5816,9 +5816,12 @@ cdef class Model: cdef SCIP_Bool success cdef int i - SCIPgetConsNVarsAnd(self._scip, constraint.scip_cons, &nvars, &success) + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(and_cons.scip_cons))).decode('UTF-8') + assert(constype == 'and', "The constraint handler %s does not have this functionality." % constype) + + nvars = SCIPgetNVarsAnd(self._scip, and_cons.scip_cons) _vars = malloc(nvars * sizeof(SCIP_VAR*)) - _vars = SCIPgetConsVarsAnd(self._scip, constraint.scip_cons) + _vars = SCIPgetVarsAnd(self._scip, and_cons.scip_cons) vars = [] for i in range(nvars): @@ -5835,13 +5838,13 @@ cdef class Model: return vars - def getResultantAnd(self, Constraint constraint): + def getResultantAnd(self, Constraint and_cons): """ Gets the resultant variable in And constraint. Parameters ---------- - constraint : Constraint + and_cons : Constraint Constraint to get the resultant variable from. Returns @@ -5852,25 +5855,27 @@ cdef class Model: cdef SCIP_VAR* _resultant cdef SCIP_Bool success - _resultant = SCIPgetResultantAnd(self._scip, constraint.scip_cons) + _resultant = SCIPgetResultantAnd(self._scip, and_cons.scip_cons) ptr = (_resultant) # check whether the corresponding variable exists already if ptr not in self._modelvars: # create a new variable - var = Variable.create(_resultant) - assert var.ptr() == ptr - self._modelvars[ptr] = var + resultant = Variable.create(_resultant) + assert resultant.ptr() == ptr + self._modelvars[ptr] = resultant + else: + resultant = self._modelvars[ptr] return resultant - def isAndConsSorted(self, Constraint constraint): + def isAndConsSorted(self, Constraint and_cons): """ Returns if the variables of the AND-constraint are sorted with respect to their indices. Parameters ---------- - constraint : Constraint + and_cons : Constraint Constraint to check. Returns @@ -5880,21 +5885,21 @@ cdef class Model: """ cdef SCIP_Bool success - return SCIPisAndConsSorted(self._scip, constraint.scip_cons) + return SCIPisAndConsSorted(self._scip, and_cons.scip_cons) - def sortAndCons(self, Constraint constraint): + def sortAndCons(self, Constraint and_cons): """ Sorts the variables of the AND-constraint with respect to their indices. Parameters ---------- - constraint : Constraint + and_cons : Constraint Constraint to sort. """ cdef SCIP_Bool success - PY_SCIP_CALL(SCIPsortAndCons(self._scip, constraint.scip_cons)) + PY_SCIP_CALL(SCIPsortAndCons(self._scip, and_cons.scip_cons)) def printCons(self, Constraint constraint): """ diff --git a/tests/test_cons.py b/tests/test_cons.py index 85f6b03cf..2c5503af9 100644 --- a/tests/test_cons.py +++ b/tests/test_cons.py @@ -80,13 +80,10 @@ def test_cons_and(): and_cons = m.addConsAnd([x1, x2], result) - assert m.getNConsAnd(and_cons) == 1 - vars = m.getVarsAnd(and_cons) - assert len(vars) == 2 - assert vars[0] == x1 - assert vars[1] == x2 - resultant_var = m.getResultAnd(and_cons) - assert resultant_var == result + assert m.getNVarsAnd(and_cons) == 2 + assert m.getVarsAnd(and_cons) == [x1, x2] + resultant_var = m.getResultantAnd(and_cons) + assert resultant_var is result m.optimize() m.sortAndCons(and_cons) From 1099b2f0a5d9ebb42da2e78f177b6e8c2b3e6645 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 17 Jun 2025 17:46:13 +0100 Subject: [PATCH 4/5] Add two missing methods. Will not test --- src/pyscipopt/scip.pxd | 7 ++----- src/pyscipopt/scip.pxi | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 6d845a732..b198b221d 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1653,16 +1653,13 @@ cdef extern from "scip/cons_and.h": SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode) - int SCIPgetNVarsAnd(SCIP* scip, SCIP_CONS* cons) - SCIP_VAR** SCIPgetVarsAnd(SCIP* scip, SCIP_CONS* cons) - SCIP_VAR* SCIPgetResultantAnd(SCIP* scip, SCIP_CONS* cons) - SCIP_Bool SCIPisAndConsSorted(SCIP* scip, SCIP_CONS* cons) - SCIP_RETCODE SCIPsortAndCons(SCIP* scip, SCIP_CONS* cons) + SCIP_RETCODE SCIPchgAndConsCheckFlagWhenUpgr(SCIP* scip, SCIP_CONS* cons, SCIP_Bool flag) + SCIP_RETCODE SCIPchgAndConsRemovableFlagWhenUpgr(SCIP* scip, SCIP_CONS* cons, SCIP_Bool flag) cdef extern from "scip/cons_or.h": SCIP_RETCODE SCIPcreateConsOr(SCIP* scip, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index b28ebfec8..fa8df08fc 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -6112,6 +6112,38 @@ cdef class Model: cdef SCIP_Bool success PY_SCIP_CALL(SCIPsortAndCons(self._scip, and_cons.scip_cons)) + + def chgAndConsCheckFlagWhenUpgr(self, Constraint cons, flag): + """ + when 'upgrading' the given AND-constraint, should the check flag for the upgraded + constraint be set to TRUE, even if the check flag of this AND-constraint is set to FALSE? + + Parameters + ---------- + cons : Constraint + The AND constraint to change. + flag : bool + The new value for the check flag. + + """ + + PY_SCIP_CALL(SCIPchgAndConsCheckFlagWhenUpgr(self._scip, cons.scip_cons, flag)) + + def chgAndConsRemovableFlagWhenUpgr(self, Constraint cons, flag): + """ + when 'upgrading' the given AND-constraint, should the removable flag for the upgraded + constraint be set to TRUE, even if the removable flag of this AND-constraint is set to FALSE? + + Parameters + ---------- + cons : Constraint + The AND constraint to change. + flag : bool + The new value for the removable flag. + + """ + + PY_SCIP_CALL(SCIPchgAndConsRemovableFlagWhenUpgr(self._scip, cons.scip_cons, flag)) def printCons(self, Constraint constraint): """ From de44b27f98538932abfe48e2569cecab698eb3af Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 17 Jun 2025 17:51:00 +0100 Subject: [PATCH 5/5] fix memory leak --- src/pyscipopt/scip.pxi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index fa8df08fc..7c7216c83 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -6032,7 +6032,6 @@ cdef class Model: assert(constype == 'and', "The constraint handler %s does not have this functionality." % constype) nvars = SCIPgetNVarsAnd(self._scip, and_cons.scip_cons) - _vars = malloc(nvars * sizeof(SCIP_VAR*)) _vars = SCIPgetVarsAnd(self._scip, and_cons.scip_cons) vars = []