Skip to content

Commit d7a85ef

Browse files
committed
Merge and small test
2 parents e7026d3 + ed56984 commit d7a85ef

5 files changed

Lines changed: 210 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
- Added support for knapsack constraints
77
- Added isPositive(), isNegative(), isFeasLE(), isFeasLT(), isFeasGE(), isFeasGT(), isHugeValue(), and tests
88
- Added SCIP_LOCKTYPE, addVarLocksType(), getNLocksDown(), getNLocksUp(), getNLocksDownType(), getNLocksUpType(), and tests
9-
- Added SCIPvarMarkRelaxationOnly, SCIPvarIsRelaxationOnly
9+
- Added addMatrixConsIndicator(), and tests
10+
- Added SCIPvarMarkRelaxationOnly, SCIPvarIsRelaxationOnly, and tests
1011
### Fixed
1112
- Raised an error when an expression is used when a variable is required
1213
### Changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# look for environment variable that specifies path to SCIP
55
scipoptdir = os.environ.get("SCIPOPTDIR", "").strip('"')
66

7-
extra_compile_args = []
7+
extra_compile_args = ["-UNDEBUG"]
88
extra_link_args = []
99

1010
# if SCIPOPTDIR is not set, we assume that SCIP is installed globally

src/pyscipopt/scip.pxi

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1676,7 +1676,7 @@ cdef class Variable(Expr):
16761676
16771677
"""
16781678
return SCIPvarGetAvgSol(self.scip_var)
1679-
1679+
16801680
def markRelaxationOnly(self):
16811681
"""
16821682
marks that this variable has only been introduced to define a relaxation
@@ -6841,6 +6841,169 @@ cdef class Model:
68416841

68426842
return pyCons
68436843

6844+
6845+
def addMatrixConsIndicator(self, cons: MatrixExprCons, binvar: Union[Variable, MatrixVariable] = None,
6846+
activeone: Union[bool, np.ndarray] = True, name: Union[str, np.ndarray] = "",
6847+
initial: Union[bool, np.ndarray] = True, separate: Union[bool, np.ndarray] = True,
6848+
enforce: Union[bool, np.ndarray] = True, check: Union[bool, np.ndarray] = True,
6849+
propagate: Union[bool, np.ndarray] = True, local: Union[bool, np.ndarray] = False,
6850+
dynamic: Union[bool, np.ndarray] = False, removable: Union[bool, np.ndarray] = False,
6851+
stickingatnode: Union[bool, np.ndarray] = False) -> MatrixConstraint:
6852+
"""Add an indicator matrix constraint for the linear inequality `cons`.
6853+
6854+
The `binvar` argument models the redundancy of the linear constraint. A solution
6855+
for which `binvar` is 1 must satisfy the constraint.
6856+
6857+
Parameters
6858+
----------
6859+
cons : MatrixExprCons
6860+
a linear inequality of the form "<=".
6861+
binvar : Variable or MatrixVariable, optional
6862+
binary indicator variable / matrix variable, or None if it should be created. (Default value = None)
6863+
activeone : bool or np.ndarray, optional
6864+
the matrix constraint should be active if binvar is 1 (0 if activeone = False).
6865+
name : str or np.ndarray, optional
6866+
name of the matrix constraint. (Default value = "")
6867+
initial : bool or np.ndarray, optional
6868+
should the LP relaxation of matrix constraint be in the initial LP? (Default value = True)
6869+
separate : bool or np.ndarray, optional
6870+
should the matrix constraint be separated during LP processing? (Default value = True)
6871+
enforce : bool or np.ndarray, optional
6872+
should the matrix constraint be enforced during node processing? (Default value = True)
6873+
check : bool or np.ndarray, optional
6874+
should the matrix constraint be checked for feasibility? (Default value = True)
6875+
propagate : bool or np.ndarray, optional
6876+
should the matrix constraint be propagated during node processing? (Default value = True)
6877+
local : bool or np.ndarray, optional
6878+
is the matrix constraint only valid locally? (Default value = False)
6879+
dynamic : bool or np.ndarray, optional
6880+
is the matrix constraint subject to aging? (Default value = False)
6881+
removable : bool or np.ndarray, optional
6882+
should the relaxation be removed from the LP due to aging or cleanup? (Default value = False)
6883+
stickingatnode : bool or np.ndarray, optional
6884+
should the matrix constraint always be kept at the node where it was added,
6885+
even if it may be moved to a more global node? (Default value = False)
6886+
6887+
Returns
6888+
-------
6889+
MatrixConstraint
6890+
The newly created Indicator MatrixConstraint object.
6891+
"""
6892+
6893+
assert isinstance(cons, MatrixExprCons), (
6894+
f"given constraint is not MatrixExprCons but {cons.__class__.__name__}"
6895+
)
6896+
6897+
shape = cons.shape
6898+
6899+
if isinstance(binvar, MatrixVariable):
6900+
assert binvar.shape == shape
6901+
if isinstance(activeone, np.ndarray):
6902+
assert activeone.shape == shape
6903+
if isinstance(name, np.ndarray):
6904+
assert name.shape == shape
6905+
if isinstance(initial, np.ndarray):
6906+
assert initial.shape == shape
6907+
if isinstance(separate, np.ndarray):
6908+
assert separate.shape == shape
6909+
if isinstance(enforce, np.ndarray):
6910+
assert enforce.shape == shape
6911+
if isinstance(check, np.ndarray):
6912+
assert check.shape == shape
6913+
if isinstance(propagate, np.ndarray):
6914+
assert propagate.shape == shape
6915+
if isinstance(local, np.ndarray):
6916+
assert local.shape == shape
6917+
if isinstance(dynamic, np.ndarray):
6918+
assert dynamic.shape == shape
6919+
if isinstance(removable, np.ndarray):
6920+
assert removable.shape == shape
6921+
if isinstance(stickingatnode, np.ndarray):
6922+
assert stickingatnode.shape == shape
6923+
6924+
if not isinstance(binvar, MatrixVariable):
6925+
matrix_binvar = np.full(shape, binvar, dtype=Variable)
6926+
else:
6927+
matrix_binvar = binvar
6928+
6929+
if not isinstance(activeone, np.ndarray):
6930+
matrix_activeone = np.full(shape, activeone, dtype=bool)
6931+
else:
6932+
matrix_activeone = activeone
6933+
6934+
if isinstance(name, str):
6935+
matrix_names = np.full(shape, name, dtype=object)
6936+
if name != "":
6937+
for idx in np.ndindex(shape):
6938+
matrix_names[idx] = f"{name}_{'_'.join(map(str, idx))}"
6939+
else:
6940+
matrix_names = name
6941+
6942+
if not isinstance(initial, np.ndarray):
6943+
matrix_initial = np.full(shape, initial, dtype=bool)
6944+
else:
6945+
matrix_initial = initial
6946+
6947+
if not isinstance(enforce, np.ndarray):
6948+
matrix_enforce = np.full(shape, enforce, dtype=bool)
6949+
else:
6950+
matrix_enforce = enforce
6951+
6952+
if not isinstance(separate, np.ndarray):
6953+
matrix_separate = np.full(shape, separate, dtype=bool)
6954+
else:
6955+
matrix_separate = separate
6956+
6957+
if not isinstance(check, np.ndarray):
6958+
matrix_check = np.full(shape, check, dtype=bool)
6959+
else:
6960+
matrix_check = check
6961+
6962+
if not isinstance(propagate, np.ndarray):
6963+
matrix_propagate = np.full(shape, propagate, dtype=bool)
6964+
else:
6965+
matrix_propagate = propagate
6966+
6967+
if not isinstance(local, np.ndarray):
6968+
matrix_local = np.full(shape, local, dtype=bool)
6969+
else:
6970+
matrix_local = local
6971+
6972+
if not isinstance(dynamic, np.ndarray):
6973+
matrix_dynamic = np.full(shape, dynamic, dtype=bool)
6974+
else:
6975+
matrix_dynamic = dynamic
6976+
6977+
if not isinstance(removable, np.ndarray):
6978+
matrix_removable = np.full(shape, removable, dtype=bool)
6979+
else:
6980+
matrix_removable = removable
6981+
6982+
if not isinstance(stickingatnode, np.ndarray):
6983+
matrix_stickingatnode = np.full(shape, stickingatnode, dtype=bool)
6984+
else:
6985+
matrix_stickingatnode = stickingatnode
6986+
6987+
matrix_cons = np.empty(shape, dtype=object)
6988+
for idx in np.ndindex(shape):
6989+
matrix_cons[idx] = self.addConsIndicator(
6990+
cons[idx],
6991+
binvar=matrix_binvar[idx],
6992+
activeone=matrix_activeone[idx],
6993+
name=matrix_names[idx],
6994+
initial=matrix_initial[idx],
6995+
separate=matrix_separate[idx],
6996+
enforce=matrix_enforce[idx],
6997+
check=matrix_check[idx],
6998+
propagate=matrix_propagate[idx],
6999+
local=matrix_local[idx],
7000+
dynamic=matrix_dynamic[idx],
7001+
removable=matrix_removable[idx],
7002+
stickingatnode=matrix_stickingatnode[idx],
7003+
)
7004+
7005+
return matrix_cons.view(MatrixConstraint)
7006+
68447007
def getLinearConsIndicator(self, Constraint cons):
68457008
"""
68467009
Get the linear constraint corresponding to the indicator constraint.

tests/test_matrix_variable.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,32 @@ def test_performance():
336336
orig_time = end_orig - start_orig
337337

338338
assert m.isGT(orig_time, matrix_time)
339+
340+
341+
def test_matrix_cons_indicator():
342+
m = Model()
343+
x = m.addMatrixVar((2, 3), vtype="I", ub=10)
344+
y = m.addMatrixVar(x.shape, vtype="I", ub=10)
345+
is_equal = m.addMatrixVar((1, 2), vtype="B")
346+
347+
# shape of cons is not equal to shape of is_equal
348+
with pytest.raises(Exception):
349+
m.addMatrixConsIndicator(x >= y, is_equal)
350+
351+
for i in range(2):
352+
m.addMatrixConsIndicator(x[i] >= y[i], is_equal[0, i])
353+
m.addMatrixConsIndicator(x[i] <= y[i], is_equal[0, i])
354+
355+
m.addMatrixConsIndicator(x[i] >= 5, is_equal[0, i])
356+
m.addMatrixConsIndicator(y[i] <= 5, is_equal[0, i])
357+
358+
for i in range(3):
359+
m.addMatrixConsIndicator(x[:, i] >= y[:, i], is_equal[0])
360+
m.addMatrixConsIndicator(x[:, i] <= y[:, i], is_equal[0])
361+
362+
m.setObjective(is_equal.sum(), "maximize")
363+
m.optimize()
364+
365+
assert m.getVal(is_equal).sum() == 2
366+
assert (m.getVal(x) == m.getVal(y)).all().all()
367+
assert (m.getVal(x) == np.array([[5, 5, 5], [5, 5, 5]])).all().all()

tests/test_vars.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,17 @@ def test_vtype():
6464
assert x.vtype() == "INTEGER"
6565

6666
m.chgVarType(y, 'M')
67-
assert y.vtype() == "IMPLINT"
67+
assert y.vtype() == "IMPLINT"
68+
69+
def test_markRelaxationOnly():
70+
m = Model()
71+
72+
x = m.addVar(vtype='C', lb=-5.5, ub=8)
73+
y = m.addVar(vtype='I', lb=-5.2, ub=8)
74+
75+
assert not x.isRelaxationOnly()
76+
assert not y.isRelaxationOnly()
77+
78+
x.markRelaxationOnly()
79+
assert x.isRelaxationOnly()
80+
assert not y.isRelaxationOnly()

0 commit comments

Comments
 (0)