Skip to content

Commit e9909a8

Browse files
committed
initial support for conjunction constraints
1 parent 6bcaf9d commit e9909a8

File tree

3 files changed

+206
-4
lines changed

3 files changed

+206
-4
lines changed

src/pyscipopt/scip.pxd

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,22 @@ cdef extern from "scip/cons_disjunction.h":
15121512
SCIP_CONS *cons,
15131513
SCIP_CONS *addcons)
15141514

1515+
cdef extern from "scip/cons_conjunction.h":
1516+
SCIP_RETCODE SCIPcreateConsConjunction(SCIP *scip,
1517+
SCIP_CONS **cons,
1518+
const char *name,
1519+
int nconss,
1520+
SCIP_CONS **conss,
1521+
SCIP_Bool enforce,
1522+
SCIP_Bool check,
1523+
SCIP_Bool local,
1524+
SCIP_Bool modifiable,
1525+
SCIP_Bool dynamic)
1526+
1527+
SCIP_RETCODE SCIPaddConsElemConjunction(SCIP *scip,
1528+
SCIP_CONS *cons,
1529+
SCIP_CONS *addcons)
1530+
15151531
cdef extern from "scip/cons_and.h":
15161532
SCIP_RETCODE SCIPcreateConsAnd(SCIP* scip,
15171533
SCIP_CONS** cons,

src/pyscipopt/scip.pxi

Lines changed: 166 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,6 +2489,118 @@ cdef class Model:
24892489

24902490
return constraints
24912491

2492+
def addConsConjunction(self, conss, name = '',
2493+
relaxcons = None, enforce=True, check =True,
2494+
local=False, modifiable = False, dynamic = False):
2495+
"""Add a conjunction constraint.
2496+
2497+
:param Iterable[Constraint] conss: An iterable of constraint objects to be included initially in the conjunction. Currently, these must be expressions.
2498+
:param name: the name of the conjunction constraint.
2499+
:param relaxcons: a conjunction constraint containing the linear relaxation of the conjunction constraint, or None. (Default value = None)
2500+
:param enforce: should the constraint be enforced during node processing? (Default value = True)
2501+
:param check: should the constraint be checked for feasibility? (Default value = True)
2502+
:param local: is the constraint only valid locally? (Default value = False)
2503+
:param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False)
2504+
:param dynamic: is the constraint subject to aging? (Default value = False)
2505+
:return The added @ref scip#Constraint "Constraint" object.
2506+
"""
2507+
def ensure_iterable(elem, length):
2508+
if isinstance(elem, Iterable):
2509+
return elem
2510+
else:
2511+
return list(repeat(elem, length))
2512+
assert isinstance(conss, Iterable), "Given constraint list is not iterable"
2513+
2514+
conss = list(conss)
2515+
n_conss = len(conss)
2516+
2517+
cdef SCIP_CONS* conj_cons
2518+
2519+
cdef SCIP_CONS* scip_cons
2520+
2521+
cdef SCIP_EXPR* scip_expr
2522+
2523+
PY_SCIP_CALL(SCIPcreateConsConjunction(
2524+
self._scip, &conj_cons, str_conversion(name), 0, &scip_cons,
2525+
enforce, check, local, modifiable, dynamic
2526+
))
2527+
2528+
2529+
# TODO add constraints to disjunction
2530+
for i, cons in enumerate(conss):
2531+
pycons = self.createExprCons(cons, name=name, initial = True, # TODO is there ever a case these shouldn't be default?
2532+
enforce=enforce, check=check,
2533+
local=local, modifiable=modifiable, dynamic=dynamic
2534+
)
2535+
PY_SCIP_CALL(SCIPaddConsElemConjunction(self._scip,conj_cons, (<Constraint>pycons).scip_cons))
2536+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &(<Constraint>pycons).scip_cons))
2537+
PY_SCIP_CALL(SCIPaddCons(self._scip, conj_cons))
2538+
PyCons = Constraint.create(conj_cons)
2539+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &conj_cons))
2540+
return PyCons
2541+
2542+
def addConsElemConjunction(self, Constraint conj_cons, Constraint cons):
2543+
"""Appends a constraint to a conjunction.
2544+
2545+
:param Constraint conj_cons: the conjunction constraint to append to.
2546+
:param Constraint cons: the Constraint to append
2547+
:return The disjunction constraint with added @ref scip#Constraint object.
2548+
"""
2549+
assert conj_cons.getConshdlrName() == "conjunction", "conj_cons is not conjunction but %s" % conj_cons.getConshdlrName()
2550+
2551+
PY_SCIP_CALL(SCIPaddConsElemConjunction(self._scip, (<Constraint>conj_cons).scip_cons, (<Constraint>cons).scip_cons))
2552+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &((<Constraint>cons).scip_cons)))
2553+
return conj_cons
2554+
2555+
def createConsConjunction(self, conss, name = '',
2556+
relaxcons = None, enforce=True, check =True,
2557+
local=False, modifiable = False, dynamic = False):
2558+
"""Create a conjunction constraint without adding it to the problem.
2559+
2560+
:param Iterable[Constraint] conss: An iterable of constraint objects to be included initially in the conjunction. Currently, these must be expressions.
2561+
:param name: the name of the conjunction constraint.
2562+
:param relaxcons: a conjunction constraint containing the linear relaxation of the conjunction constraint, or None. (Default value = None)
2563+
:param enforce: should the constraint be enforced during node processing? (Default value = True)
2564+
:param check: should the constraint be checked for feasibility? (Default value = True)
2565+
:param local: is the constraint only valid locally? (Default value = False)
2566+
:param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False)
2567+
:param dynamic: is the constraint subject to aging? (Default value = False)
2568+
:return The added @ref scip#Constraint "Constraint" object.
2569+
"""
2570+
def ensure_iterable(elem, length):
2571+
if isinstance(elem, Iterable):
2572+
return elem
2573+
else:
2574+
return list(repeat(elem, length))
2575+
assert isinstance(conss, Iterable), "Given constraint list is not iterable"
2576+
2577+
conss = list(conss)
2578+
n_conss = len(conss)
2579+
2580+
cdef SCIP_CONS* conj_cons
2581+
2582+
cdef SCIP_CONS* scip_cons
2583+
2584+
cdef SCIP_EXPR* scip_expr
2585+
2586+
PY_SCIP_CALL(SCIPcreateConsConjunction(
2587+
self._scip, &conj_cons, str_conversion(name), 0, &scip_cons,
2588+
enforce, check, local, modifiable, dynamic
2589+
))
2590+
2591+
2592+
# TODO add constraints to conujunction
2593+
for i, cons in enumerate(conss):
2594+
pycons = self.createExprCons(cons, name=name, initial = True, # TODO is there ever a case these shouldn't be default?
2595+
enforce=enforce, check=check,
2596+
local=local, modifiable=modifiable, dynamic=dynamic
2597+
)
2598+
PY_SCIP_CALL(SCIPaddConsElemConjunction(self._scip,conj_cons, (<Constraint>pycons).scip_cons))
2599+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &(<Constraint>pycons).scip_cons))
2600+
2601+
PyCons = Constraint.create(conj_cons)
2602+
return PyCons
2603+
24922604
def addConsDisjunction(self, conss, name = '', initial = True,
24932605
relaxcons = None, enforce=True, check =True,
24942606
local=False, modifiable = False, dynamic = False):
@@ -2533,22 +2645,72 @@ cdef class Model:
25332645
enforce=enforce, check=check,
25342646
local=local, modifiable=modifiable, dynamic=dynamic
25352647
)
2536-
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip,disj_cons, (<Constraint>pycons).scip_cons))
2648+
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip, disj_cons, (<Constraint>pycons).scip_cons))
25372649
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &(<Constraint>pycons).scip_cons))
25382650
PY_SCIP_CALL(SCIPaddCons(self._scip, disj_cons))
25392651
PyCons = Constraint.create(disj_cons)
25402652
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &disj_cons))
25412653
return PyCons
2654+
2655+
def createConsDisjunction(self, conss, name = '', initial = True,
2656+
relaxcons = None, enforce=True, check =True,
2657+
local=False, modifiable = False, dynamic = False):
2658+
"""Add a disjunction constraint.
2659+
2660+
:param Iterable[Constraint] conss: An iterable of constraint objects to be included initially in the disjunction. Currently, these must be expressions.
2661+
:param name: the name of the disjunction constraint.
2662+
:param initial: should the LP relaxation of disjunction constraint be in the initial LP? (Default value = True)
2663+
:param relaxcons: a conjunction constraint containing the linear relaxation of the disjunction constraint, or None. (Default value = None)
2664+
:param enforce: should the constraint be enforced during node processing? (Default value = True)
2665+
:param check: should the constraint be checked for feasibility? (Default value = True)
2666+
:param local: is the constraint only valid locally? (Default value = False)
2667+
:param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False)
2668+
:param dynamic: is the constraint subject to aging? (Default value = False)
2669+
:return The added @ref scip#Constraint "Constraint" object.
2670+
"""
2671+
def ensure_iterable(elem, length):
2672+
if isinstance(elem, Iterable):
2673+
return elem
2674+
else:
2675+
return list(repeat(elem, length))
2676+
assert isinstance(conss, Iterable), "Given constraint list is not iterable"
2677+
2678+
conss = list(conss)
2679+
n_conss = len(conss)
2680+
2681+
cdef SCIP_CONS* disj_cons
2682+
2683+
cdef SCIP_CONS* scip_cons
2684+
2685+
cdef SCIP_EXPR* scip_expr
2686+
2687+
PY_SCIP_CALL(SCIPcreateConsDisjunction(
2688+
self._scip, &disj_cons, str_conversion(name), 0, &scip_cons, NULL,
2689+
initial, enforce, check, local, modifiable, dynamic
2690+
))
2691+
25422692

2693+
# TODO add constraints to disjunction
2694+
for i, cons in enumerate(conss):
2695+
pycons = self.createExprCons(cons, name=name, initial = initial,
2696+
enforce=enforce, check=check,
2697+
local=local, modifiable=modifiable, dynamic=dynamic
2698+
)
2699+
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip, disj_cons, (<Constraint>pycons).scip_cons))
2700+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &(<Constraint>pycons).scip_cons))
2701+
PyCons = Constraint.create(disj_cons)
2702+
return PyCons
25432703
def addConsElemDisjunction(self, Constraint disj_cons, Constraint cons):
2544-
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip, (<Constraint>disj_cons).scip_cons, (<Constraint>cons).scip_cons))
2545-
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &((<Constraint>cons).scip_cons)))
25462704
"""Appends a constraint to a disjunction.
2547-
2705+
25482706
:param Constraint disj_cons: the disjunction constraint to append to.
25492707
:param Constraint cons: the Constraint to append
25502708
:return The disjunction constraint with added @ref scip#Constraint object.
25512709
"""
2710+
assert disj_cons.getConshdlrName() == "disjunction", "disj_cons is not disjunction but %s" % disj_cons.getConshdlrName()
2711+
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip, (<Constraint>disj_cons).scip_cons, (<Constraint>cons).scip_cons))
2712+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &((<Constraint>cons).scip_cons)))
2713+
25522714
return disj_cons
25532715

25542716

tests/test_cons.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,26 @@ def test_addConsDisjunction_expr_init():
192192
assert m.isEQ(m.getVal(o), 6)
193193

194194

195+
def test_addConsElemConjunction():
196+
m = Model()
197+
x = m.addVar(vtype="c", lb=-10, ub=2)
198+
y = m.addVar(vtype="c", lb=-10, ub=5)
199+
o = m.addVar(vtype="c")
200+
201+
m.addCons(o <= (x + y))
202+
conj_cons = m.addConsConjunction([])
203+
print(conj_cons.getConshdlrName())
204+
c1 = m.createExprCons(x <= 1)
205+
c3 = m.createExprCons(y <= 0)
206+
m.addConsElemConjunction(conj_cons, c1)
207+
m.addConsElemConjunction(conj_cons, c3)
208+
m.setObjective(o, "maximize")
209+
m.optimize()
210+
assert m.isEQ(m.getVal(x), 1)
211+
assert m.isEQ(m.getVal(y), 0)
212+
assert m.isEQ(m.getVal(o), 1)
213+
214+
195215
@pytest.mark.skip(reason="TODO: test getValsLinear()")
196216
def test_getValsLinear():
197217
assert True
@@ -200,3 +220,7 @@ def test_getValsLinear():
200220
@pytest.mark.skip(reason="TODO: test getRowLinear()")
201221
def test_getRowLinear():
202222
assert True
223+
224+
225+
if __name__ == "__main__":
226+
test_addConsElemConjunction()

0 commit comments

Comments
 (0)