diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dadd7965..83daa0b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Added getLinearConsIndicator - Added SCIP_LPPARAM, setIntParam, setRealParam, getIntParam, getRealParam, isOptimal, getObjVal, getRedcost for lpi - Added isFeasPositive +- Added SCIP function SCIProwGetDualsol and wrapper getDualsol +- Added SCIP function SCIProwGetDualfarkas and wrapper getDualfarkas ### Fixed - Fixed bug when accessing matrix variable attributes ### Changed diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index f53421164..aaf38a77c 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1895,6 +1895,8 @@ cdef extern from "scip/pub_lp.h": SCIP_Real SCIProwGetLhs(SCIP_ROW* row) SCIP_Real SCIProwGetRhs(SCIP_ROW* row) SCIP_Real SCIProwGetConstant(SCIP_ROW* row) + SCIP_Real SCIProwGetDualsol(SCIP_ROW* row) + SCIP_Real SCIProwGetDualfarkas(SCIP_ROW* row) int SCIProwGetLPPos(SCIP_ROW* row) SCIP_BASESTAT SCIProwGetBasisStatus(SCIP_ROW* row) SCIP_Bool SCIProwIsIntegral(SCIP_ROW* row) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 3b847ef60..e8f39a63c 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -679,6 +679,28 @@ cdef class Row: """ return SCIProwGetConstant(self.scip_row) + def getDualsol(self): + """ + Returns the dual solution of row. + + Returns + ------- + float + + """ + return SCIProwGetDualsol(self.scip_row) + + def getDualfarkas(self): + """ + Returns the dual Farkas solution of row. + + Returns + ------- + float + + """ + return SCIProwGetDualfarkas(self.scip_row) + def getLPPos(self): """ Gets position of row in current LP, or -1 if it is not in LP. diff --git a/tests/test_row_dual.py b/tests/test_row_dual.py new file mode 100644 index 000000000..32dbd8d8f --- /dev/null +++ b/tests/test_row_dual.py @@ -0,0 +1,96 @@ +from pyscipopt import Model, Sepa, SCIP_RESULT, SCIP_PARAMSETTING + + +class SimpleSepa(Sepa): + + def __init__(self, x, y): + self.cut = None + self.x = x + self.y = y + self.has_checked = False + + def sepainit(self): + scip = self.model + self.trans_x = scip.getTransformedVar(self.x) + self.trans_y = scip.getTransformedVar(self.y) + + def sepaexeclp(self): + result = SCIP_RESULT.SEPARATED + scip = self.model + + if self.cut is not None and not self.has_checked: + # rhs * dual should be equal to optimal objective (= -1) + assert scip.isFeasEQ(self.cut.getDualsol(), -1.0) + self.has_checked = True + + cut = scip.createEmptyRowSepa(self, + lhs=-scip.infinity(), + rhs=1.0) + + scip.cacheRowExtensions(cut) + + scip.addVarToRow(cut, self.trans_x, 1.) + scip.addVarToRow(cut, self.trans_y, 1.) + + scip.flushRowExtensions(cut) + + scip.addCut(cut, forcecut=True) + + self.cut = cut + + return {"result": result} + + def sepaexit(self): + assert self.has_checked, "Separator called < 2 times" + + +def model(): + # create solver instance + s = Model() + + # turn off presolve + s.setPresolve(SCIP_PARAMSETTING.OFF) + # turn off heuristics + s.setHeuristics(SCIP_PARAMSETTING.OFF) + # turn off propagation + s.setIntParam("propagating/maxrounds", 0) + s.setIntParam("propagating/maxroundsroot", 0) + + # turn off all other separators + s.setIntParam("separating/strongcg/freq", -1) + s.setIntParam("separating/gomory/freq", -1) + s.setIntParam("separating/aggregation/freq", -1) + s.setIntParam("separating/mcf/freq", -1) + s.setIntParam("separating/closecuts/freq", -1) + s.setIntParam("separating/clique/freq", -1) + s.setIntParam("separating/zerohalf/freq", -1) + s.setIntParam("separating/mixing/freq", -1) + s.setIntParam("separating/rapidlearning/freq", -1) + s.setIntParam("separating/rlt/freq", -1) + + # only two rounds of cuts + # s.setIntParam("separating/maxroundsroot", 10) + + return s + + +def test_row_dual(): + s = model() + # add variable + x = s.addVar("x", vtype='I', obj=-1, lb=0.) + y = s.addVar("y", vtype='I', obj=-1, lb=0.) + + # add constraint + s.addCons(x <= 1.5) + s.addCons(y <= 1.5) + + # include separator + sepa = SimpleSepa(x, y) + s.includeSepa(sepa, "python_simple", "generates a simple cut", + priority=1000, + freq=1) + + s.addCons(x + y <= 1.75) + + # solve problem + s.optimize()