diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index eece6c939..010cc3bb5 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.pxd b/src/pyscipopt/scip.pxd index 2b629053c..ec1f00431 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -346,6 +346,9 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_CONS: pass + ctypedef struct SCIP_DECOMP: + pass + ctypedef struct SCIP_ROW: pass @@ -1617,6 +1620,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() @@ -1984,6 +2076,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 6a5513cd1..9337459b4 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1849,6 +1849,130 @@ 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""" + def __init__(self): + self.create() + @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, 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)) + + 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: if file is stdout: @@ -6290,6 +6414,9 @@ cdef class Model: PY_SCIP_CALL(SCIPpresolve(self._scip)) self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip)) + def addDecomposition(self, Decomposition decomp): + PY_SCIP_CALL(SCIPaddDecomp(self._scip, decomp.scip_decomp)) + # Benders' decomposition methods def initBendersDefault(self, subproblems): """ 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()