-
Notifications
You must be signed in to change notification settings - Fork 258
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
return SCIPdecompIsOriginal(self.scip_decomp) | ||
|
||
def getAreaScore(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
@@ -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): | ||
""" | ||
|
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() |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.