Skip to content

Commit

Permalink
Merge pull request #173 from ramabile/master
Browse files Browse the repository at this point in the history
XOR constraint + misc
  • Loading branch information
fserra committed Jun 8, 2018
2 parents 240a09c + 9665b2e commit c317270
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 27 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,6 @@ venv.bak/

# pytest
.pytest_chache/

# model (for tests)
model
72 changes: 72 additions & 0 deletions examples/finished/even.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from pyscipopt import Model

################################################################################
#
# EVEN OR ODD?
#
# If a positional argument is given:
# prints if the argument is even/odd/neither
# else:
# prints if a value is even/odd/neither per each value in a example list
#
# This example is made for newcomers and motivated by:
# - modulus is unsupported for pyscipopt.scip.Variable and int
# - variables are non-integer by default
# Based on this:
# https://github.com/SCIP-Interfaces/PySCIPOpt/issues/172#issuecomment-394644046
#
################################################################################

verbose = False
sdic = {0:"even",1:"odd"}

def parity(number):
try:
assert number == int(round(number))
m = Model()
m.hideOutput()

### variables are non-negative by default since 0 is the default lb.
### To allow for negative values, give None as lower bound
### (None means -infinity as lower bound and +infinity as upper bound)
x = m.addVar("x", vtype="I", lb=None, ub=None) #ub=None is default
n = m.addVar("n", vtype="I", lb=None)
s = m.addVar("s", vtype="B")

### CAVEAT: if number is negative, x's lb must be None
### if x is set by default as non-negative and number is negative:
### there is no feasible solution (trivial) but the program
### does not highlight which constraints conflict.
m.addCons(x==number)

m.addCons(s == x-2*n)
m.setObjective(s)
m.optimize()

assert m.getStatus() == "optimal"
if verbose:
for v in m.getVars():
print("%s %d" % (v,m.getVal(v)))
print("%d%%2 == %d?" % (m.getVal(x), m.getVal(s)))
print(m.getVal(s) == m.getVal(x)%2)

xval = m.getVal(x)
sval = m.getVal(s)
sstr = sdic[sval]
print("%d is %s" % (xval, sstr))
except (AssertionError, TypeError):
print("%s is neither even nor odd!" % number.__repr__())

if __name__ == "__main__":
import sys
from ast import literal_eval as leval
example_values = [0, 1, 1.5, "hallo welt", 20, 25, -101, -15., -10, -int(2**31), int(2**31-1), int(2**63)-1]
try:
try:
n = leval(sys.argv[1])
except ValueError:
n = sys.argv[1]
parity(n)
except IndexError:
for n in example_values:
parity(n)
78 changes: 78 additions & 0 deletions examples/finished/logical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from pyscipopt import Model
from pyscipopt import quicksum

################################################################################
#
# AND/OR/XOR CONSTRAINTS
#
# Tutorial example on how to use AND/OR/XOR constraints.
#
# N.B.: standard SCIP XOR constraint works differently from AND/OR by design.
# The constraint is set with a boolean rhs instead of an integer resultant.
# cf. http://listserv.zib.de/pipermail/scip/2018-May/003392.html
# A workaround to get the resultant as variable is here proposed.
#
################################################################################

def printFunc(name,m):
print("* %s *" % name)
objSet = bool(m.getObjective().terms.keys())
print("* Is objective set? %s" % objSet)
if objSet:
print("* Sense: %s" % m.getObjectiveSense())
for v in m.getVars():
if v.name != "n":
print("%s: %d" % (v, round(m.getVal(v))))
print("\n")

# AND #
model = Model()
model.hideOutput()
x = model.addVar("x","B")
y = model.addVar("y","B")
z = model.addVar("z","B")
r = model.addVar("r","B")
model.addConsAnd([x,y,z],r)
model.addCons(x==1)
model.setObjective(r,sense="minimize")
model.optimize()
printFunc("AND",model)

# OR #
model = Model()
model.hideOutput()
x = model.addVar("x","B")
y = model.addVar("y","B")
z = model.addVar("z","B")
r = model.addVar("r","B")
model.addConsOr([x,y,z],r)
model.addCons(x==0)
model.setObjective(r,sense="maximize")
model.optimize()
printFunc("OR",model)

# XOR (r as boolean, standard) #
model = Model()
model.hideOutput()
x = model.addVar("x","B")
y = model.addVar("y","B")
z = model.addVar("z","B")
r = True
model.addConsXor([x,y,z],r)
model.addCons(x==1)
model.optimize()
printFunc("Standard XOR (as boolean)",model)

# XOR (r as variable, custom) #
model = Model()
model.hideOutput()
x = model.addVar("x","B")
y = model.addVar("y","B")
z = model.addVar("z","B")
r = model.addVar("r","B")
n = model.addVar("n","I") #auxiliary
model.addCons(r+quicksum([x,y,z]) == 2*n)
model.addCons(x==0)
model.setObjective(r,sense="maximize")
model.optimize()
printFunc("Custom XOR (as variable)",model)
18 changes: 18 additions & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,24 @@ cdef extern from "scip/cons_or.h":
SCIP_Bool removable,
SCIP_Bool stickingatnode)

cdef extern from "scip/cons_xor.h":
SCIP_RETCODE SCIPcreateConsXor(SCIP* scip,
SCIP_CONS** cons,
const char* name,
SCIP_Bool rhs,
int nvars,
SCIP_VAR** vars,
SCIP_Bool initial,
SCIP_Bool separate,
SCIP_Bool enforce,
SCIP_Bool check,
SCIP_Bool propagate,
SCIP_Bool local,
SCIP_Bool modifiable,
SCIP_Bool dynamic,
SCIP_Bool removable,
SCIP_Bool stickingatnode)

cdef extern from "blockmemshell/memory.h":
void BMScheckEmptyMemory()
long long BMSgetMemoryUsed()
Expand Down
41 changes: 40 additions & 1 deletion src/pyscipopt/scip.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ cdef class Model:
# Variable Functions

def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False):
"""Create a new variable.
"""Create a new variable. Default variable is non-negative and continuous.
:param name: name of the variable, generic if empty (Default value = '')
:param vtype: type of the variable (Default value = 'C')
Expand Down Expand Up @@ -1521,6 +1521,45 @@ cdef class Model:

return pyCons

def addConsXor(self, vars, rhsvar, name="XORcons",
initial=True, separate=True, enforce=True, check=True,
propagate=True, local=False, modifiable=False, dynamic=False,
removable=False, stickingatnode=False):
"""Add a XOR-constraint.
:param vars: list of BINARY variables to be included (operators)
:param rhsvar: BOOLEAN value, explicit True, False or bool(obj) is needed (right-hand side)
:param name: name of the constraint (Default value = "XORcons")
:param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True)
:param separate: should the constraint be separated during LP processing? (Default value = True)
:param enforce: should the constraint be enforced during node processing? (Default value = True)
:param check: should the constraint be checked for feasibility? (Default value = True)
:param propagate: should the constraint be propagated during node processing? (Default value = True)
:param local: is the constraint only valid locally? (Default value = False)
:param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False)
:param dynamic: is the constraint subject to aging? (Default value = False)
:param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False)
:param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False)
"""
cdef SCIP_CONS* scip_cons

nvars = len(vars)

assert type(rhsvar) is type(bool()), "Provide BOOLEAN value as rhsvar, you gave %s." % type(rhsvar)
_vars = <SCIP_VAR**> malloc(len(vars) * sizeof(SCIP_VAR*))
for idx, var in enumerate(vars):
_vars[idx] = (<Variable>var).var

PY_SCIP_CALL(SCIPcreateConsXor(self._scip, &scip_cons, str_conversion(name), rhsvar, nvars, _vars,
initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))

PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
pyCons = Constraint.create(scip_cons)
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))

free(_vars)

return pyCons

def addConsCardinality(self, consvars, cardval, indvars=None, weights=None, name="CardinalityCons",
initial=True, separate=True, enforce=True, check=True,
propagate=True, local=False, dynamic=False,
Expand Down
Loading

0 comments on commit c317270

Please sign in to comment.