Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for user-provided decompositions #842

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/pyscipopt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
127 changes: 127 additions & 0 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,130 @@ cdef class Constraint:
return (self.__class__ == other.__class__
and self.scip_cons == (<Constraint>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):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're trying to make sure new functionality isn't added without having tests. So for all available functions you're adding please put them into a simple test (one test can cover multiple functions)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add more tests & docs when I get a moment, sorry. This one got put on the backburner due to other stuff at work.

For our use case, we have a modeling layer on top of PySCIPOpt that basically uses dataframe-like syntax to create groups of constraints using a table that characterizes our variables. So the goal is to break .groupby statements into subproblems where each of the groups can be solved ~in parallel. We have some combinatorial constraints as well, so decompositions seem like a natural place to reduce the number of combinations to check.

return SCIPdecompIsOriginal(self.scip_decomp)

def getAreaScore(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this isn't standard knowledge (or at least I don't know it), if you can add a one line comment for what the function does then please do. This really helps out users and means they don't need to go through SCIP itself for answers.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extends to most functions below

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 = <int> len(conss)
cdef int* labels = <int *> malloc(nconss * sizeof(int))
cdef SCIP_CONS** scip_conss = <SCIP_CONS**> malloc(nconss * sizeof(SCIP_CONS*))

for i in range(nconss):
scip_conss[i] = (<Constraint>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 = <SCIP_CONS**> malloc(nconss* sizeof(SCIP_CONS*))
cdef int* labels = <int*> malloc(nconss * sizeof(int))

for i, pair in enumerate(cons_labels):
cons, lab = pair
scip_conss[i] = (<Constraint>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 = <int> len(vrs)
cdef int* labels = <int*> malloc(nvars * sizeof(int))
cdef SCIP_VAR** scip_vars = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*))

for i in range(nvars):
scip_vars[i] = (<Variable>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 = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*))
cdef int* labels = <int*> malloc(nvars * sizeof(int))

for i in range(nvars):
scip_vars[i] = (<Variable>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:
Expand Down Expand Up @@ -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):
"""
Expand Down
24 changes: 24 additions & 0 deletions tests/test_decomp.py
Original file line number Diff line number Diff line change
@@ -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()
Loading