Skip to content

Commit a8daa5c

Browse files
Add access functions for row duals (#986)
* Add access functions for row duals * Add cut dual test function * Update Changelog * Rename test_cut_dual.py to test_row_dual.py --------- Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com>
1 parent 14ba422 commit a8daa5c

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Added getLinearConsIndicator
66
- Added SCIP_LPPARAM, setIntParam, setRealParam, getIntParam, getRealParam, isOptimal, getObjVal, getRedcost for lpi
77
- Added isFeasPositive
8+
- Added SCIP function SCIProwGetDualsol and wrapper getDualsol
9+
- Added SCIP function SCIProwGetDualfarkas and wrapper getDualfarkas
810
### Fixed
911
- Fixed bug when accessing matrix variable attributes
1012
### Changed

src/pyscipopt/scip.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,6 +1895,8 @@ cdef extern from "scip/pub_lp.h":
18951895
SCIP_Real SCIProwGetLhs(SCIP_ROW* row)
18961896
SCIP_Real SCIProwGetRhs(SCIP_ROW* row)
18971897
SCIP_Real SCIProwGetConstant(SCIP_ROW* row)
1898+
SCIP_Real SCIProwGetDualsol(SCIP_ROW* row)
1899+
SCIP_Real SCIProwGetDualfarkas(SCIP_ROW* row)
18981900
int SCIProwGetLPPos(SCIP_ROW* row)
18991901
SCIP_BASESTAT SCIProwGetBasisStatus(SCIP_ROW* row)
19001902
SCIP_Bool SCIProwIsIntegral(SCIP_ROW* row)

src/pyscipopt/scip.pxi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,28 @@ cdef class Row:
679679
"""
680680
return SCIProwGetConstant(self.scip_row)
681681

682+
def getDualsol(self):
683+
"""
684+
Returns the dual solution of row.
685+
686+
Returns
687+
-------
688+
float
689+
690+
"""
691+
return SCIProwGetDualsol(self.scip_row)
692+
693+
def getDualfarkas(self):
694+
"""
695+
Returns the dual Farkas solution of row.
696+
697+
Returns
698+
-------
699+
float
700+
701+
"""
702+
return SCIProwGetDualfarkas(self.scip_row)
703+
682704
def getLPPos(self):
683705
"""
684706
Gets position of row in current LP, or -1 if it is not in LP.

tests/test_row_dual.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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

Comments
 (0)