Skip to content

Commit 749c87c

Browse files
committed
add tests for disjunction constraint, fix constraint free placement
1 parent 5b94da1 commit 749c87c

File tree

3 files changed

+59
-24
lines changed

3 files changed

+59
-24
lines changed

src/pyscipopt/scip.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,7 @@ cdef extern from "scip/cons_disjunction.h":
15071507
SCIP_Bool local,
15081508
SCIP_Bool modifiable,
15091509
SCIP_Bool dynamic)
1510+
15101511
SCIP_RETCODE SCIPaddConsElemDisjunction(SCIP *scip,
15111512
SCIP_CONS *cons,
15121513
SCIP_CONS *addcons)

src/pyscipopt/scip.pxi

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2372,19 +2372,27 @@ cdef class Model:
23722372
"""
23732373
assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__
23742374

2375+
cdef SCIP_CONS* scip_cons
2376+
23752377
kwargs = dict(name=name, initial=initial, separate=separate,
23762378
enforce=enforce, check=check,
23772379
propagate=propagate, local=local,
23782380
modifiable=modifiable, dynamic=dynamic,
23792381
removable=removable,
23802382
stickingatnode=stickingatnode)
2381-
# replace empty name with generic one
2382-
pycons = self.createExprCons(cons, **kwargs)
2383-
PY_SCIP_CALL(SCIPaddCons(self._scip, (<Constraint>pycons).scip_cons))
2384-
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &((<Constraint>pycons).scip_cons)))
2383+
# we have to pass this back to a SCIP_CONS*
2384+
# object to create a new python constraint & handle constraint release
2385+
# correctly. Otherwise, segfaults when trying to query information
2386+
# about the created constraint later.
2387+
pycons_initial = self.createExprCons(cons, **kwargs)
2388+
scip_cons = (<Constraint>pycons_initial).scip_cons
2389+
2390+
PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
2391+
pycons = Constraint.create(scip_cons)
2392+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
2393+
23852394
return pycons
23862395

2387-
23882396
def addConss(self, conss, name='', initial=True, separate=True,
23892397
enforce=True, check=True, propagate=True, local=False,
23902398
modifiable=False, dynamic=False, removable=False,
@@ -2638,7 +2646,6 @@ cdef class Model:
26382646

26392647
PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
26402648
return Constraint.create(scip_cons)
2641-
26422649
def addConsSOS2(self, vars, weights=None, name="SOS2cons",
26432650
initial=True, separate=True, enforce=True, check=True,
26442651
propagate=True, local=False, dynamic=False,
@@ -3458,6 +3465,7 @@ cdef class Model:
34583465
self.optimize()
34593466
else:
34603467
PY_SCIP_CALL(SCIPsolveConcurrent(self._scip))
3468+
print("solveconcurrent")
34613469
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))
34623470

34633471
def presolve(self):

tests/test_cons.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def test_getConsNVars():
1111
x[i] = m.addVar("%i" % i)
1212

1313
c = m.addCons(quicksum(x[i] for i in x) <= 10)
14+
print(c.__hash__())
1415
assert m.getConsNVars(c) == n_vars
1516

1617
m.optimize()
@@ -117,7 +118,9 @@ def test_cons_indicator():
117118
assert c.getConshdlrName() == "indicator"
118119

119120

120-
@pytest.mark.xfail(reason="addConsIndicator doesn't behave as expected when binary variable is False. See Issue #717.")
121+
@pytest.mark.xfail(
122+
reason="addConsIndicator doesn't behave as expected when binary variable is False. See Issue #717."
123+
)
121124
def test_cons_indicator_fail():
122125
m = Model()
123126
binvar = m.addVar(vtype="B")
@@ -131,23 +134,6 @@ def test_cons_indicator_fail():
131134
m.setSolVal(sol, binvar, 0)
132135
assert m.checkSol(sol) # solution should be feasible
133136

134-
def test_addConsDisjunction():
135-
m = Model()
136-
137-
x1 = m.addVar(vtype="C", lb=-1, ub=1)
138-
x2 = m.addVar(vtype="C", lb=-3, ub=3)
139-
o = m.addVar(vtype="C")
140-
141-
c = m.addConsDisjunction([x1 <= 0, x2 <= 0])
142-
m.addCons(o <= x1 + x2)
143-
144-
m.setObjective(o, "maximize")
145-
m.optimize()
146-
147-
assert m.isEQ(m.getVal(x1), 0.0)
148-
assert m.isEQ(m.getVal(x2), 3.0)
149-
assert m.isEQ(m.getVal(o), 3.0)
150-
#assert c.getConshdlrName() == "disjunction"
151137

152138
def test_addConsCardinality():
153139
m = Model()
@@ -170,6 +156,42 @@ def test_printCons():
170156
m.printCons(c)
171157

172158

159+
def test_addConsElemDisjunction():
160+
m = Model()
161+
x = m.addVar(vtype="c", lb=-10, ub=2)
162+
y = m.addVar(vtype="c", lb=-10, ub=5)
163+
o = m.addVar(vtype="c")
164+
165+
m.addCons(o <= (x + y))
166+
disj_cons = m.addConsDisjunction([])
167+
c1 = m.createExprCons(x <= 1)
168+
c2 = m.createExprCons(x <= 0)
169+
c3 = m.createExprCons(y <= 0)
170+
m.addConsElemDisjunction(disj_cons, c1)
171+
disj_cons = m.addConsElemDisjunction(disj_cons, c2)
172+
disj_cons = m.addConsElemDisjunction(disj_cons, c3)
173+
m.setObjective(o, "maximize")
174+
m.optimize()
175+
assert m.isEQ(m.getVal(x), 1)
176+
assert m.isEQ(m.getVal(y), 5)
177+
assert m.isEQ(m.getVal(o), 6)
178+
179+
180+
def test_addConsDisjunction_expr_init():
181+
m = Model()
182+
x = m.addVar(vtype="c", lb=-10, ub=2)
183+
y = m.addVar(vtype="c", lb=-10, ub=5)
184+
o = m.addVar(vtype="c")
185+
186+
m.addCons(o <= (x + y))
187+
m.addConsDisjunction([x <= 1, x <= 0, y <= 0])
188+
m.setObjective(o, "maximize")
189+
m.optimize()
190+
assert m.isEQ(m.getVal(x), 1)
191+
assert m.isEQ(m.getVal(y), 5)
192+
assert m.isEQ(m.getVal(o), 6)
193+
194+
173195
@pytest.mark.skip(reason="TODO: test getValsLinear()")
174196
def test_getValsLinear():
175197
assert True
@@ -178,3 +200,7 @@ def test_getValsLinear():
178200
@pytest.mark.skip(reason="TODO: test getRowLinear()")
179201
def test_getRowLinear():
180202
assert True
203+
204+
205+
if __name__ == "__main__":
206+
test_getConsNVars()

0 commit comments

Comments
 (0)