Skip to content

Commit c681f94

Browse files
Zeroto521Joao-DionisioDominikKamp
authored
BUG: _VarArray can't handle MatrixVar (#1044)
* Refactor _VarArray initialization and type checks Simplifies and clarifies the logic for initializing _VarArray by restructuring type checks and error handling. Now uses f-strings for error messages and ensures consistent handling of empty lists and invalid types. * Refactor _VarArray to support MatrixVariable input Updated the _VarArray class constructor to handle MatrixVariable inputs in addition to Variable, list, and tuple. Refactored pointer allocation logic into a helper function for improved code clarity and maintainability. * Add test for indicator constraint with matrix binary var Introduces a new test to verify that addConsIndicator works correctly when the binary variable is a matrix variable, addressing issue #1043. * Update CHANGELOG.md * Declare ptr variable in create_ptr function To fix "Storing unsafe C derivative of temporary Python reference" * Refactor create_ptr to remove redundant size argument The create_ptr function in _VarArray.__cinit__ no longer takes a separate size argument and instead uses len(vars) directly. This simplifies the function signature and reduces redundancy. * Refactor _VarArray initialization logic Inlined the pointer creation logic in _VarArray's __cinit__ method, removing the inner create_ptr function. This simplifies the code and improves readability while maintaining the same functionality. * Update CHANGELOG * Update test for addConsIndicator with activeone flag The test now checks addConsIndicator with both activeone=True and activeone=False, and updates the objective to use binvar.sum(). * ENH: flatten list * BUG: use `np.ravel` avoid to run out of vars range * test binvar with (1, 1, 1) matrix * test binvar with (2, 3) matrix * test binvar with (2, 1) list of lists * correct index * Add TypeError test for addConsIndicator with binvar * Only requiring a 1D array * TST: raise an error while get not 1D array * MAINT: Update error message * TST: update error type * Update CHANGELOG.md * Fix _VarArray initialization for empty input Ensures _VarArray initializes ptr and size to NULL and 0, respectively, before processing input. Prevents allocation and assignment errors when an empty or invalid input is provided. * Remove _VarArray changing from changelog * TST: update the comparing method Co-authored-by: DominikKamp <130753997+DominikKamp@users.noreply.github.com> --------- Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com> Co-authored-by: DominikKamp <130753997+DominikKamp@users.noreply.github.com>
1 parent a3c2a48 commit c681f94

File tree

2 files changed

+46
-13
lines changed

2 files changed

+46
-13
lines changed

src/pyscipopt/scip.pxi

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2498,22 +2498,24 @@ cdef class _VarArray:
24982498
cdef int size
24992499

25002500
def __cinit__(self, object vars):
2501+
self.ptr = NULL
2502+
self.size = 0
2503+
25012504
if isinstance(vars, Variable):
2502-
self.size = 1
2503-
self.ptr = <SCIP_VAR**> malloc(sizeof(SCIP_VAR*))
2504-
self.ptr[0] = (<Variable>vars).scip_var
2505+
vars = [vars]
2506+
elif isinstance(vars, (list, tuple, MatrixVariable)):
2507+
if (ndim := np.ndim(vars)) != 1:
2508+
raise ValueError(f"Expected a 1D array, but got a {ndim}D array.")
25052509
else:
2506-
if not isinstance(vars, (list, tuple)):
2507-
raise TypeError("Expected Variable or list of Variable, got %s." % type(vars))
2510+
raise TypeError(f"Expected Variable or list of Variable, got {type(vars)}.")
2511+
2512+
if vars:
25082513
self.size = len(vars)
2509-
if self.size == 0:
2510-
self.ptr = NULL
2511-
else:
2512-
self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*))
2513-
for i, var in enumerate(vars):
2514-
if not isinstance(var, Variable):
2515-
raise TypeError("Expected Variable, got %s." % type(var))
2516-
self.ptr[i] = (<Variable>var).scip_var
2514+
self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*))
2515+
for i, var in enumerate(vars):
2516+
if not isinstance(var, Variable):
2517+
raise TypeError(f"Expected Variable, got {type(var)}.")
2518+
self.ptr[i] = (<Variable>var).scip_var
25172519

25182520
def __dealloc__(self):
25192521
if self.ptr != NULL:

tests/test_cons.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,37 @@ def test_cons_indicator():
179179
assert m.isEQ(m.getVal(x), 1)
180180
assert c1.getConshdlrName() == "indicator"
181181

182+
def test_cons_indicator_with_matrix_binvar():
183+
# test matrix variable binvar #1043
184+
m = Model()
185+
x = m.addVar(vtype="B")
186+
187+
# test binvar with int
188+
with pytest.raises(TypeError):
189+
m.addConsIndicator(x <= 0, 1)
190+
191+
# test binvar with (1, 1, 1) shape of matrix variable
192+
with pytest.raises(ValueError):
193+
m.addConsIndicator(x <= 0, m.addMatrixVar(((1, 1, 1)), vtype="B"))
194+
195+
# test binvar with (2, 3) shape of matrix variable
196+
with pytest.raises(ValueError):
197+
m.addConsIndicator(x <= 0, m.addMatrixVar(((2, 3)), vtype="B"))
198+
199+
# test binvar with (2, 1) shape of list of lists
200+
with pytest.raises(ValueError):
201+
m.addConsIndicator(x <= 0, [[m.addVar(vtype="B")], [m.addVar(vtype="B")]])
202+
203+
# test binvar with requiring type and dimension
204+
binvar = m.addMatrixVar(1, vtype="B")
205+
m.addConsIndicator(x >= 1, binvar, activeone=True)
206+
m.addConsIndicator(x <= 0, binvar, activeone=False)
207+
208+
m.setObjective(binvar.sum(), "maximize")
209+
m.optimize()
210+
211+
assert m.isEQ(m.getVal(x), 1)
212+
182213
@pytest.mark.xfail(
183214
reason="addConsIndicator doesn't behave as expected when binary variable is False. See Issue #717."
184215
)

0 commit comments

Comments
 (0)