Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
885865a
Refactor _VarArray initialization and type checks
Zeroto521 Jul 31, 2025
9451cc6
Refactor _VarArray to support MatrixVariable input
Zeroto521 Jul 31, 2025
7ee3a6f
Add test for indicator constraint with matrix binary var
Zeroto521 Jul 31, 2025
2cde7a6
Update CHANGELOG.md
Zeroto521 Jul 31, 2025
50bb995
Declare ptr variable in create_ptr function
Zeroto521 Jul 31, 2025
d72f951
Refactor create_ptr to remove redundant size argument
Zeroto521 Jul 31, 2025
110679f
Refactor _VarArray initialization logic
Zeroto521 Jul 31, 2025
f181cf4
Update CHANGELOG
Zeroto521 Jul 31, 2025
0346c52
Update test for addConsIndicator with activeone flag
Zeroto521 Jul 31, 2025
68f2e44
ENH: flatten list
Zeroto521 Aug 1, 2025
5580e12
BUG: use `np.ravel` avoid to run out of vars range
Zeroto521 Aug 1, 2025
8fc900c
Merge branch 'master' into fix/1043
Joao-Dionisio Aug 2, 2025
d52eb54
test binvar with (1, 1, 1) matrix
Zeroto521 Aug 3, 2025
8424c6d
test binvar with (2, 3) matrix
Zeroto521 Aug 3, 2025
710a836
test binvar with (2, 1) list of lists
Zeroto521 Aug 3, 2025
3b64e79
correct index
Zeroto521 Aug 3, 2025
2be1117
Add TypeError test for addConsIndicator with binvar
Zeroto521 Aug 4, 2025
7277e55
Merge branch 'master' into fix/1043
Joao-Dionisio Aug 16, 2025
09e3a0f
Only requiring a 1D array
Zeroto521 Aug 18, 2025
c749183
TST: raise an error while get not 1D array
Zeroto521 Aug 18, 2025
cba76c2
MAINT: Update error message
Zeroto521 Aug 18, 2025
44de7b7
TST: update error type
Zeroto521 Aug 18, 2025
1fd24a9
Update CHANGELOG.md
Zeroto521 Aug 18, 2025
1a3d2e1
Fix _VarArray initialization for empty input
Zeroto521 Aug 18, 2025
e17f68b
Remove _VarArray changing from changelog
Zeroto521 Aug 18, 2025
d280764
TST: update the comparing method
Zeroto521 Aug 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
### Fixed
- Raised an error when an expression is used when a variable is required
- Fixed some compile warnings
- _VarArray raised an error when the inputting wasn't 1D array.
### Changed
- MatrixExpr.sum() now supports axis arguments and can return either a scalar or MatrixExpr, depending on the result dimensions.
- AddMatrixCons() also accepts ExprCons.
Expand Down
26 changes: 12 additions & 14 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 311 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / test-coverage (3.11)

SCIP: unspecified error!
elif rc == SCIP_NOMEMORY:
raise MemoryError('SCIP: insufficient memory error!')
elif rc == SCIP_READERROR:
Expand Down Expand Up @@ -2499,21 +2499,19 @@

def __cinit__(self, object vars):
if isinstance(vars, Variable):
self.size = 1
self.ptr = <SCIP_VAR**> malloc(sizeof(SCIP_VAR*))
self.ptr[0] = (<Variable>vars).scip_var
vars = [vars]
elif isinstance(vars, (list, tuple, MatrixVariable)):
if (ndim := np.ndim(vars)) != 1:
raise ValueError(f"Expected a 1D array, but got a {ndim}D array.")
else:
if not isinstance(vars, (list, tuple)):
raise TypeError("Expected Variable or list of Variable, got %s." % type(vars))
self.size = len(vars)
if self.size == 0:
self.ptr = NULL
else:
self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*))
for i, var in enumerate(vars):
if not isinstance(var, Variable):
raise TypeError("Expected Variable, got %s." % type(var))
self.ptr[i] = (<Variable>var).scip_var
raise TypeError(f"Expected Variable or list of Variable, got {type(vars)}.")

self.size = len(vars)
self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*)) if self.size else NULL
for i, var in enumerate(vars):
if not isinstance(var, Variable):
raise TypeError(f"Expected Variable, got {type(var)}.")
self.ptr[i] = (<Variable>var).scip_var
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.size = len(vars)
self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*)) if self.size else NULL
for i, var in enumerate(vars):
if not isinstance(var, Variable):
raise TypeError(f"Expected Variable, got {type(var)}.")
self.ptr[i] = (<Variable>var).scip_var
if vars:
self.size = len(vars)
self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*))
for i, var in enumerate(vars):
if not isinstance(var, Variable):
raise TypeError(f"Expected Variable, got {type(var)}.")
self.ptr[i] = (<Variable>var).scip_var

Together with https://github.com/scipopt/PySCIPOpt/pull/1044/files#r2281629510.

Copy link
Contributor Author

@Zeroto521 Zeroto521 Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot that this line has a problem. @DominikKamp
numpy.ndarray will raise an error if the size is larger than 2.

        if vars:

In my local model, it raised an error with the head of the commit building package.

from pyscipopt import Model

model = Model()
vars = model.addMatrixVar(3, vtype="B")
model.addConsKnapsack(vars, [1, 2, 3], 5)
model.setObjective(vars.sum(), "maximize")
model.optimize()
# Traceback (most recent call last):
#   line 5, in <module>
#     model.addConsKnapsack(vars, [1, 2, 3], 5)
#   File "src/pyscipopt/scip.pxi", line 6463, in pyscipopt. scip.Model.addConsKnapsack
#   File "src/pyscipopt/scip.pxi", line 2512, in pyscipopt.scip._VarArray.__cinit__
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

It should be kept original, in my mind. Since self.size and self.ptr are both initiated at the top.

        self.size = len(vars)
        self.ptr = <SCIP_VAR**> malloc(self.size * sizeof(SCIP_VAR*)) if self.size else NULL
        for i, var in enumerate(vars):
            if not isinstance(var, Variable):
                raise TypeError(f"Expected Variable, got {type(var)}.")
            self.ptr[i] = (<Variable>var).scip_var

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a matrix variable is an ndarray and not a list. I would suggest pulling the initialization of self.size by len(vars) down here and update the condition to self.size. We do not need to enumerate an empty something.


def __dealloc__(self):
if self.ptr != NULL:
Expand Down
31 changes: 31 additions & 0 deletions tests/test_cons.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,37 @@ def test_cons_indicator():
assert m.isEQ(m.getVal(x), 1)
assert c1.getConshdlrName() == "indicator"

def test_cons_indicator_with_matrix_binvar():
# test matrix variable binvar #1043
m = Model()
x = m.addVar(vtype="B")

# test binvar with int
with pytest.raises(TypeError):
m.addConsIndicator(x <= 0, 1)

# test binvar with (1, 1, 1) shape of matrix variable
with pytest.raises(ValueError):
m.addConsIndicator(x <= 0, m.addMatrixVar(((1, 1, 1)), vtype="B"))

# test binvar with (2, 3) shape of matrix variable
with pytest.raises(ValueError):
m.addConsIndicator(x <= 0, m.addMatrixVar(((2, 3)), vtype="B"))

# test binvar with (2, 1) shape of list of lists
with pytest.raises(ValueError):
m.addConsIndicator(x <= 0, [[m.addVar(vtype="B")], [m.addVar(vtype="B")]])

# test binvar with requiring type and dimension
binvar = m.addMatrixVar(1, vtype="B")
m.addConsIndicator(x >= 1, binvar, activeone=True)
m.addConsIndicator(x <= 0, binvar, activeone=False)

m.setObjective(binvar.sum(), "maximize")
m.optimize()

assert m.getVal(x) == 1

@pytest.mark.xfail(
reason="addConsIndicator doesn't behave as expected when binary variable is False. See Issue #717."
)
Expand Down