Skip to content

Commit 80a97c2

Browse files
committed
approximation of linear, quadratic disjunction constraints
1 parent 8d1062b commit 80a97c2

File tree

4 files changed

+137
-1
lines changed

4 files changed

+137
-1
lines changed

src/pyscipopt/scip.pxd

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,23 @@ cdef extern from "scip/cons_sos2.h":
14931493
SCIP_CONS* cons,
14941494
SCIP_VAR* var)
14951495

1496+
cdef extern from "scip/cons_disjunction.h":
1497+
SCIP_RETCODE SCIPcreateConsDisjunction(SCIP *scip,
1498+
SCIP_CONS **cons,
1499+
const char *name,
1500+
int nconss,
1501+
SCIP_CONS **conss,
1502+
SCIP_CONS *relaxcons,
1503+
SCIP_Bool initial,
1504+
SCIP_Bool enforce,
1505+
SCIP_Bool check,
1506+
SCIP_Bool local,
1507+
SCIP_Bool modifiable,
1508+
SCIP_Bool dynamic)
1509+
SCIP_RETCODE SCIPaddConsElemDisjunction(SCIP *scip,
1510+
SCIP_CONS *cons,
1511+
SCIP_CONS *addcons)
1512+
14961513
cdef extern from "scip/cons_and.h":
14971514
SCIP_RETCODE SCIPcreateConsAnd(SCIP* scip,
14981515
SCIP_CONS** cons,

src/pyscipopt/scip.pxi

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2122,7 +2122,7 @@ cdef class Model:
21222122
return self._addGenNonlinearCons(cons, **kwargs)
21232123
else:
21242124
return self._addNonlinearCons(cons, **kwargs)
2125-
2125+
21262126
def addConss(self, conss, name='', initial=True, separate=True,
21272127
enforce=True, check=True, propagate=True, local=False,
21282128
modifiable=False, dynamic=False, removable=False,
@@ -2188,6 +2188,106 @@ cdef class Model:
21882188

21892189
return constraints
21902190

2191+
def addConsDisjunction(self, conss, name = '', initial = True,
2192+
relaxcons = None, enforce=True, check =True,
2193+
local=False, modifiable = False, dynamic = False):
2194+
2195+
def ensure_iterable(elem, length):
2196+
if isinstance(elem, Iterable):
2197+
return elem
2198+
else:
2199+
return list(repeat(elem, length))
2200+
assert isinstance(conss, Iterable), "Given constraint list is not iterable"
2201+
2202+
conss = list(conss)
2203+
n_conss = len(conss)
2204+
assert n_conss >= 2, "Given constraint list contains fewer than 2 items!"
2205+
2206+
cdef SCIP_CONS* disj_cons
2207+
2208+
cdef SCIP_CONS* scip_cons
2209+
2210+
cdef SCIP_EXPR* scip_expr
2211+
2212+
PY_SCIP_CALL(SCIPcreateConsDisjunction(
2213+
self._scip, &disj_cons, str_conversion(name), 0, &scip_cons, NULL,
2214+
initial, enforce, check, local, modifiable, dynamic
2215+
))
2216+
2217+
#PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
2218+
2219+
for i, cons in enumerate(conss):
2220+
deg = cons.expr.degree()
2221+
assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__
2222+
2223+
kwargs = dict(name='c'+str(SCIPgetNConss(self._scip)+1),initial=True,separate=True,
2224+
enforce=True, check=True, propagate=True, local=False,
2225+
modifiable=False, dynamic=False, removable=False,
2226+
stickingatnode=False)
2227+
2228+
kwargs['lhs'] = -SCIPinfinity(self._scip) if cons._lhs is None else cons._lhs
2229+
kwargs['rhs'] = SCIPinfinity(self._scip) if cons._rhs is None else cons._rhs
2230+
terms = cons.expr.terms
2231+
2232+
if deg <= 1:
2233+
nvars = len(terms.items())
2234+
vars_array = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*))
2235+
coeffs_array = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real))
2236+
2237+
for i, (key, coeff) in enumerate(terms.items()):
2238+
vars_array[i] = <SCIP_VAR*>(<Variable>key[0]).scip_var
2239+
coeffs_array[i] = <SCIP_Real>coeff
2240+
2241+
PY_SCIP_CALL(SCIPcreateConsLinear(
2242+
self._scip, &scip_cons, str_conversion(kwargs['name']), nvars, vars_array, coeffs_array,
2243+
kwargs['lhs'], kwargs['rhs'], kwargs['initial'],
2244+
kwargs['separate'], kwargs['enforce'], kwargs['check'],
2245+
kwargs['propagate'], kwargs['local'], kwargs['modifiable'],
2246+
kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode']))
2247+
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip, disj_cons, scip_cons))
2248+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
2249+
free(vars_array)
2250+
free(coeffs_array)
2251+
elif deg <=2:
2252+
PY_SCIP_CALL(SCIPcreateConsQuadraticNonlinear(
2253+
self._scip, &scip_cons, str_conversion(kwargs['name']),
2254+
0, NULL, NULL, # linear
2255+
0, NULL, NULL, NULL, # quadratc
2256+
kwargs['lhs'], kwargs['rhs'],
2257+
kwargs['initial'], kwargs['separate'], kwargs['enforce'],
2258+
kwargs['check'], kwargs['propagate'], kwargs['local'],
2259+
kwargs['modifiable'], kwargs['dynamic'], kwargs['removable']))
2260+
for v, c in terms.items():
2261+
if len(v) == 1: # linear
2262+
var = <Variable>v[0]
2263+
PY_SCIP_CALL(SCIPaddLinearVarNonlinear(self._scip, scip_cons, var.scip_var, c))
2264+
else: # quadratic
2265+
assert len(v) == 2, 'term length must be 1 or 2 but it is %s' % len(v)
2266+
2267+
varexprs = <SCIP_EXPR**> malloc(2 * sizeof(SCIP_EXPR*))
2268+
var1, var2 = <Variable>v[0], <Variable>v[1]
2269+
PY_SCIP_CALL( SCIPcreateExprVar(self._scip, &varexprs[0], var1.scip_var, NULL, NULL) )
2270+
PY_SCIP_CALL( SCIPcreateExprVar(self._scip, &varexprs[1], var2.scip_var, NULL, NULL) )
2271+
PY_SCIP_CALL( SCIPcreateExprProduct(self._scip, &scip_expr, 2, varexprs, 1.0, NULL, NULL) )
2272+
2273+
PY_SCIP_CALL( SCIPaddExprNonlinear(self._scip, scip_cons, scip_expr, c) )
2274+
2275+
PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip, disj_cons, scip_cons))
2276+
PY_SCIP_CALL( SCIPreleaseExpr(self._scip, &scip_expr) )
2277+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
2278+
PY_SCIP_CALL( SCIPreleaseExpr(self._scip, &varexprs[1]) )
2279+
PY_SCIP_CALL( SCIPreleaseExpr(self._scip, &varexprs[0]) )
2280+
free(varexprs)
2281+
else:
2282+
raise NotImplementedError("Only Linear Expressions are currently supported")
2283+
2284+
2285+
PY_SCIP_CALL(SCIPaddCons(self._scip, disj_cons))
2286+
PyCons = Constraint.create(disj_cons)
2287+
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &disj_cons))
2288+
return PyCons
2289+
2290+
21912291
def getConsNVars(self, Constraint constraint):
21922292
"""
21932293
Gets number of variables in a constraint.

tests/test_cons.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,23 @@ def test_cons_indicator_fail():
131131
m.setSolVal(sol, binvar, 0)
132132
assert m.checkSol(sol) # solution should be feasible
133133

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"
134151

135152
def test_addConsCardinality():
136153
m = Model()

tests/test_disjunction.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+

0 commit comments

Comments
 (0)