From 8beb978d2a834c0d38d87bfae9cf7de1b5fb6d25 Mon Sep 17 00:00:00 2001 From: Connor Duncan Date: Thu, 11 Apr 2024 18:07:37 -0500 Subject: [PATCH 1/3] commit relative to upstream master --- src/pyscipopt/scip.pxd | 100 +++++++++++++++++++++++++++++++++ src/pyscipopt/scip.pxi | 125 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 23537569f..da15bd5de 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -333,6 +333,9 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_CONS: pass + ctypedef struct SCIP_DECOMP: + pass + ctypedef struct SCIP_ROW: pass @@ -1552,6 +1555,95 @@ cdef extern from "scip/scip_cons.h": SCIP_CONS* cons, FILE* file) +cdef extern from "scip/pub_dcmp.h": + SCIP_RETCODE SCIPdecompCreate(SCIP_DECOMP** decomp, + BMS_BLKMEM* blkmem, + int nblocks, + SCIP_Bool original, + SCIP_Bool benderslabels) + + SCIP_Bool SCIPdecompIsOriginal(SCIP_DECOMP *decomp) + + void SCIPdecompSetUseBendersLabels(SCIP_DECOMP *decomp, SCIP_Bool benderslabels) + + SCIP_Bool SCIPdecompUseBendersLabels(SCIP_DECOMP *decomp) + + int SCIPdecompGetNBlocks(SCIP_DECOMP *decomp) + + SCIP_Real SCIPdecompGetAreaScore(SCIP_DECOMP *decomp) + + SCIP_Real SCIPdecompGetModularity(SCIP_DECOMP *decomp) + + SCIP_RETCODE SCIPdecompGetVarsSize(SCIP_DECOMP *decomp, int *varssize, int nblocks) + + SCIP_RETCODE SCIPdecompGetConssSize(SCIP_DECOMP *decomp, int *consssize, int nblocks) + + int SCIPdecompGetNBorderVars(SCIP_DECOMP *decomp) + + int SCIPdecompGetNBorderConss(SCIP_DECOMP *decomp) + + int SCIPdecompGetNBlockGraphEdges(SCIP_DECOMP *decomp) + + int SCIPdecompGetNBlockGraphComponents(SCIP_DECOMP *decomp) + + int SCIPdecompGetNBlockGraphArticulations(SCIP_DECOMP *decomp) + + int SCIPdecompGetBlockGraphMaxDegree(SCIP_DECOMP *decomp) + + int SCIPdecompGetBlockGraphMinDegree(SCIP_DECOMP *decomp) + + SCIP_RETCODE SCIPdecompSetVarsLabels(SCIP_DECOMP *decomp, SCIP_VAR **vrs, int *labels, int nvars) + + void SCIPdecompGetVarsLabels(SCIP_DECOMP *decomp, SCIP_VAR **vrs, int *labels, int nvars) + + SCIP_RETCODE SCIPdecompSetConsLabels(SCIP_DECOMP *decomp, SCIP_CONS **conss, int *labels, int nconss) + + void SCIPdecompGetConsLabels(SCIP_DECOMP *decomp, SCIP_CONS **conss, int *labels, int nconss) + + SCIP_RETCODE SCIPdecompClear(SCIP_DECOMP *decomp, SCIP_Bool clearvarlabels, SCIP_Bool clearconslabels) + + char* SCIPdecompPrintStats(SCIP_DECOMP *decomp, char *strbuf) + +cdef extern from "scip/scip_dcmp.h": + SCIP_RETCODE SCIPcreateDecomp(SCIP* scip, + SCIP_DECOMP** decomp, + int nblocks, + SCIP_Bool original, + SCIP_Bool benderslabels) + + SCIP_RETCODE SCIPaddDecomp(SCIP* scip, SCIP_DECOMP* decomp) + + void SCIPfreeDecomp(SCIP* scip, SCIP_DECOMP** decomp) + + void SCIPgetDecomps(SCIP* scip, + SCIP_DECOMP*** decomp, + int* ndecomps, + SCIP_Bool original) + + SCIP_RETCODE SCIPhasConsOnlyLinkVars(SCIP* scip, + SCIP_DECOMP* decomp, + SCIP_CONS *cons, + SCIP_Bool* hasonlylinkvars) + + SCIP_RETCODE SCIPcomputeDecompConsLabels(SCIP* scip, + SCIP_DECOMP* decomp, + SCIP_CONS** conss, + int nconss) + + SCIP_RETCODE SCIPcomputeDecompVarsLabels(SCIP* scip, + SCIP_DECOMP* decomp, + SCIP_CONS** conss, + int nconss) + SCIP_RETCODE SCIPassignDecompLinkConss(SCIP* scip, + SCIP_DECOMP* decomp, + SCIP_CONS** conss, + int* nskipconss) + + SCIP_RETCODE SCIPcomputeDecompStats(SCIP* scip, + SCIP_DECOMP *decomp, + SCIP_CONS** conss, + int nconss, + int* nskipconss) cdef extern from "blockmemshell/memory.h": void BMScheckEmptyMemory() long long BMSgetMemoryUsed() @@ -1916,6 +2008,14 @@ cdef class Constraint: @staticmethod cdef create(SCIP_CONS* scipcons) +cdef class Decomposition: + cdef SCIP_DECOMP* scip_decomp + + cdef public object data + + @staticmethod + cdef create(SCIP_DECOMP* scip_decomp) + cdef class Model: cdef SCIP* _scip cdef SCIP_Bool* _valid diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index cb48195d1..26b48d8ab 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -975,6 +975,128 @@ cdef class Constraint: return (self.__class__ == other.__class__ and self.scip_cons == (other).scip_cons) +cdef class Decomposition: + """Base class holding a pointer to SCIP decomposition""" + + @staticmethod + cdef create(SCIP_DECOMP* scip_decomp): + if scip_decomp == NULL: + raise Warning("cannot create Constraint with SCIP_CONS* == NULL") + decomp = Decomposition() + decomp.scip_decomp = scip_decomp + return decomp + + def isOriginal(self): + return SCIPdecompIsOriginal(self.scip_decomp) + + def getAreaScore(self): + return SCIPdecompGetAreaScore(self.scip_decomp) + + def getModularity(self): + return SCIPdecompGetModularity(self.scip_decomp) + + def getNBorderVars(self): + return SCIPdecompGetNBorderVars(self.scip_decomp) + + def getNBorderConss(self): + return SCIPdecompGetNBorderConss(self.scip_decomp) + + def getNBlockGraphEdges(self): + return SCIPdecompGetNBlockGraphEdges(self.scip_decomp) + + def getNBlockGraphComponents(self): + return SCIPdecompGetNBlockGraphComponents(self.scip_decomp) + + def getNblockGraphArticulations(self): + return SCIPdecompGetNBlockGraphArticulations(self.scip_decomp) + + def getBlockGraphMaxDegree(self): + return SCIPdecompGetBlockGraphMaxDegree(self.scip_decomp) + + def getBlockGraphMinDegree(self): + return SCIPdecompGetBlockGraphMinDegree(self.scip_decomp) + + def getConsLabels(self, conss): + """get {cons: label} pair for python constraints conss""" + cdef int nconss = len(conss) + cdef int* labels = malloc(nconss * sizeof(int)) + cdef SCIP_CONS** scip_conss = malloc(nconss * sizeof(SCIP_CONS*)) + + for i in range(nconss): + scip_conss[i] = (conss[i]).scip_cons + + PY_SCIP_CALL(SCIPdecompGetConsLabels(self.scip_decomp, scip_conss, labels, nconss)) + + cons_labels = {} + for i in range(nconss): + cons_labels[conss[i]] = labels[i] + free(labels) + free(scip_conss) + return cons_labels + + def setConsLabels(self, cons_labels): + """ applies labels to constraints in decomposition. + :param cons_labels dict of {constraint: label} pairs to be applied + """ + cons_labels = cons_labels.items() + + cdef int nconss = len(cons_labels) + cdef SCIP_CONS** scip_conss = malloc(nconss* sizeof(SCIP_CONS*)) + cdef int* labels = malloc(nconss * sizeof(int)) + + for i in range(nconss): + scip_conss[i] = (cons_labels[i][0]).scip_cons + labels[i] = cons_labels[i][1] + + PY_SCIP_CALL(SCIPdecompSetConsLabels(self.scip_decomp, scip_conss, labels, nconss)) + + free(scip_conss) + free(labels) + + def getVarsLabels(self, vrs): + """get {var: label} pairs for python variables vrs""" + + cdef int nvars = len(vrs) + cdef int* labels = malloc(nvars * sizeof(int)) + cdef SCIP_VAR** scip_vars = malloc(nvars * sizeof(SCIP_VAR*)) + + for i in range(nvars): + scip_vars[i] = (vrs[i]).scip_var + + PY_SCIP_CALL(SCIPdecompGetVarsLabels(self.scip_decomp, scip_vars, labels, nvars)) + + var_labels = {} + for i in range(nvars): + var_labels[vrs[i]] = labels[i] + + free(labels) + free(scip_vars) + return var_labels + + def setVarLabels(self, var_labels): + """set {var: label} pairs in decomposition""" + var_labels = var_labels.items() + + cdef int nvars= len(var_labels) + cdef SCIP_VAR** scip_vars = malloc(nvars * sizeof(SCIP_VAR*)) + cdef int* labels = malloc(nvars * sizeof(int)) + + for i in range(nvars): + scip_vars[i] = (var_labels[i][0]).scip_var + labels[i] = var_labels[i][1] + + PY_SCIP_CALL(SCIPdecompSetVarsLabels(self.scip_decomp, scip_vars, labels, nvars)) + + free(scip_vars) + free(labels) + + def clear(self, varlabels =True, conslabels = True): + """clears variable and/or constraint labels from decomposition""" + + if not (varlabels or conslabels): + ... # TODO does decomp clear do anything if both options are false? + else: + PY_SCIP_CALL(SCIPdecompClear(self.scip_decomp, varlabels, conslabels)) cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) noexcept: sys.stdout.write(msg.decode('UTF-8')) @@ -3422,6 +3544,9 @@ cdef class Model: """Presolve the problem.""" PY_SCIP_CALL(SCIPpresolve(self._scip)) + def addDecomposition(self, Decomposition decomp): + PY_SCIP_CALL(SCIPaddDecomp(self._scip, decomp.scip_decomp)) + # Benders' decomposition methods def initBendersDefault(self, subproblems): """initialises the default Benders' decomposition with a dictionary of subproblems From 53aa357a4fa9eae8f912015e55c7edc7423f98e3 Mon Sep 17 00:00:00 2001 From: Connor Duncan Date: Mon, 15 Apr 2024 07:28:35 -0500 Subject: [PATCH 2/3] begin migration of creation methods to Model --- src/pyscipopt/__init__.py | 1 + src/pyscipopt/scip.pxi | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index a370c60b7..95572aac4 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -9,6 +9,7 @@ # export user-relevant objects: from pyscipopt.Multidict import multidict from pyscipopt.scip import Model +from pyscipopt.scip import Decomposition from pyscipopt.scip import Variable from pyscipopt.scip import Constraint from pyscipopt.scip import Benders diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 26b48d8ab..2c4acb3aa 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -977,7 +977,8 @@ cdef class Constraint: cdef class Decomposition: """Base class holding a pointer to SCIP decomposition""" - + def __init__(self): + self.create() @staticmethod cdef create(SCIP_DECOMP* scip_decomp): if scip_decomp == NULL: @@ -1044,9 +1045,10 @@ cdef class Decomposition: cdef SCIP_CONS** scip_conss = malloc(nconss* sizeof(SCIP_CONS*)) cdef int* labels = malloc(nconss * sizeof(int)) - for i in range(nconss): - scip_conss[i] = (cons_labels[i][0]).scip_cons - labels[i] = cons_labels[i][1] + for i, pair in enumerate(cons_labels): + cons, lab = pair + scip_conss[i] = (cons).scip_cons + labels[i] = lab PY_SCIP_CALL(SCIPdecompSetConsLabels(self.scip_decomp, scip_conss, labels, nconss)) From 3b6b372ba2b415e871a63dcb874a879336e1d49d Mon Sep 17 00:00:00 2001 From: Connor Duncan Date: Mon, 15 Apr 2024 07:29:26 -0500 Subject: [PATCH 3/3] add test --- tests/test_decomp.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_decomp.py diff --git a/tests/test_decomp.py b/tests/test_decomp.py new file mode 100644 index 000000000..662c03078 --- /dev/null +++ b/tests/test_decomp.py @@ -0,0 +1,24 @@ +from pyscipopt import Model, Decomposition, quicksum +import pytest + + +def test_consLabels(): + m = Model() + a = m.addVar("C", lb = 0) + b = m.addVar("C", lb = 0) + c = m.addVar("M", lb = 0) + d = m.addVar("C", lb = 0) + + cons_1 = m.addCons( a + b <= 5) + cons_2a = m.addCons( c**2 + d**2 <= 8) + cons_2b = m.addCons( c == d ) + decomp = Decomposition() + decomp.setConsLabels({cons_1: 1, cons_2a: 2, cons_2b: 2}) + + m.addDecomposition(decomp) + o = m.addVar("C") + m.addCons( o <= a + b + c + d) + m.setObjective("o", "minimize") + m.optimize() + +test_consLabels()