|
| 1 | +from pyscipopt import Model, Sepa, SCIP_RESULT, SCIP_PARAMSETTING |
| 2 | + |
| 3 | + |
| 4 | +class SimpleSepa(Sepa): |
| 5 | + |
| 6 | + def __init__(self, x, y): |
| 7 | + self.cut = None |
| 8 | + self.x = x |
| 9 | + self.y = y |
| 10 | + self.has_checked = False |
| 11 | + |
| 12 | + def sepainit(self): |
| 13 | + scip = self.model |
| 14 | + self.trans_x = scip.getTransformedVar(self.x) |
| 15 | + self.trans_y = scip.getTransformedVar(self.y) |
| 16 | + |
| 17 | + def sepaexeclp(self): |
| 18 | + result = SCIP_RESULT.SEPARATED |
| 19 | + scip = self.model |
| 20 | + |
| 21 | + if self.cut is not None and not self.has_checked: |
| 22 | + # rhs * dual should be equal to optimal objective (= -1) |
| 23 | + assert scip.isFeasEQ(self.cut.getDualsol(), -1.0) |
| 24 | + self.has_checked = True |
| 25 | + |
| 26 | + cut = scip.createEmptyRowSepa(self, |
| 27 | + lhs=-scip.infinity(), |
| 28 | + rhs=1.0) |
| 29 | + |
| 30 | + scip.cacheRowExtensions(cut) |
| 31 | + |
| 32 | + scip.addVarToRow(cut, self.trans_x, 1.) |
| 33 | + scip.addVarToRow(cut, self.trans_y, 1.) |
| 34 | + |
| 35 | + scip.flushRowExtensions(cut) |
| 36 | + |
| 37 | + scip.addCut(cut, forcecut=True) |
| 38 | + |
| 39 | + self.cut = cut |
| 40 | + |
| 41 | + return {"result": result} |
| 42 | + |
| 43 | + def sepaexit(self): |
| 44 | + assert self.has_checked, "Separator called < 2 times" |
| 45 | + |
| 46 | + |
| 47 | +def model(): |
| 48 | + # create solver instance |
| 49 | + s = Model() |
| 50 | + |
| 51 | + # turn off presolve |
| 52 | + s.setPresolve(SCIP_PARAMSETTING.OFF) |
| 53 | + # turn off heuristics |
| 54 | + s.setHeuristics(SCIP_PARAMSETTING.OFF) |
| 55 | + # turn off propagation |
| 56 | + s.setIntParam("propagating/maxrounds", 0) |
| 57 | + s.setIntParam("propagating/maxroundsroot", 0) |
| 58 | + |
| 59 | + # turn off all other separators |
| 60 | + s.setIntParam("separating/strongcg/freq", -1) |
| 61 | + s.setIntParam("separating/gomory/freq", -1) |
| 62 | + s.setIntParam("separating/aggregation/freq", -1) |
| 63 | + s.setIntParam("separating/mcf/freq", -1) |
| 64 | + s.setIntParam("separating/closecuts/freq", -1) |
| 65 | + s.setIntParam("separating/clique/freq", -1) |
| 66 | + s.setIntParam("separating/zerohalf/freq", -1) |
| 67 | + s.setIntParam("separating/mixing/freq", -1) |
| 68 | + s.setIntParam("separating/rapidlearning/freq", -1) |
| 69 | + s.setIntParam("separating/rlt/freq", -1) |
| 70 | + |
| 71 | + # only two rounds of cuts |
| 72 | + # s.setIntParam("separating/maxroundsroot", 10) |
| 73 | + |
| 74 | + return s |
| 75 | + |
| 76 | + |
| 77 | +def test_row_dual(): |
| 78 | + s = model() |
| 79 | + # add variable |
| 80 | + x = s.addVar("x", vtype='I', obj=-1, lb=0.) |
| 81 | + y = s.addVar("y", vtype='I', obj=-1, lb=0.) |
| 82 | + |
| 83 | + # add constraint |
| 84 | + s.addCons(x <= 1.5) |
| 85 | + s.addCons(y <= 1.5) |
| 86 | + |
| 87 | + # include separator |
| 88 | + sepa = SimpleSepa(x, y) |
| 89 | + s.includeSepa(sepa, "python_simple", "generates a simple cut", |
| 90 | + priority=1000, |
| 91 | + freq=1) |
| 92 | + |
| 93 | + s.addCons(x + y <= 1.75) |
| 94 | + |
| 95 | + # solve problem |
| 96 | + s.optimize() |
0 commit comments