Skip to content

Conversation

Zeroto521
Copy link
Contributor

to fix #1061

Updated type hints and isinstance checks in MatrixExpr comparison methods to use Expr instead of Variable. This change improves compatibility with broader expression types in matrix operations.
Updated type hints and isinstance checks in MatrixExprCons.__le__ and __ge__ methods to use Expr instead of Variable. This change improves consistency with the expected types for matrix expression constraints.
Introduces test_ranged_matrix_cons to verify correct behavior when adding a ranged matrix constraint to the model. Ensures that the matrix variable x is set to ones as expected.
Introduced a shared _matrixexpr_richcmp helper to handle rich comparison logic for MatrixExpr and MatrixExprCons, reducing code duplication and improving maintainability. Updated __le__, __ge__, and __eq__ methods to use this helper, and removed redundant code.
The __eq__ method of MatrixExprCons now raises NotImplementedError with a descriptive message instead of TypeError, clarifying that '==' comparison is not supported.
Added tests for '<=', '>=', and '==' operators in matrix constraints. Verified correct exception is raised for unsupported '==' operator.
Relocated the _is_number utility from expr.pxi to matrix.pxi for better modularity. Updated _matrixexpr_richcmp to use a local _richcmp helper for comparison operations.
Replaces usage of undefined 'shape' variable with 'self.shape' when creating the result array in _matrixexpr_richcmp, ensuring correct array dimensions.
Comment on lines -47 to -55
def _is_number(e):
try:
f = float(e)
return True
except ValueError: # for malformed strings
return False
except TypeError: # for other types (Variable, Expr)
return False

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Remove duplicated parts. It also appears in matrix.pxi

Comment on lines +21 to +29
def _richcmp(self, other, op):
if op == 1: # <=
return self.__le__(other)
elif op == 5: # >=
return self.__ge__(other)
elif op == 2: # ==
return self.__eq__(other)
else:
raise NotImplementedError("Can only support constraints with '<=', '>=', or '=='.")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can't use expr.pxi/_expr_richcmp. It will cause circular imports

Removed unnecessary double .all() calls in assertions for matrix variable tests, simplifying the checks for equality with np.ones(3).
Updated assertions in test_matrix_variable.py to use m.getVal(x) and m.getVal(y) instead of direct variable comparison. This ensures the tests check the evaluated values from the model rather than the symbolic variables.
Comment on lines -127 to -128
if not _is_number(other) or not isinstance(other, MatrixExpr):
raise TypeError('Ranged MatrixExprCons is not well defined!')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This checking is duplicated to _expr_richcmp

expr_cons_matrix[idx] = self[idx] >= other[idx]
else:
raise TypeError(f"Unsupported type {type(other)}")
def __le__(self, other: Union[float, int, np.ndarray]) -> MatrixExprCons:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ranged ExprCons can only support numbers.
So MatrixExprCons.__ge__ can only receive a number type other.

def __richcmp__(self, other, op):
'''turn it into a constraint'''
if op == 1: # <=
if not self._rhs is None:
raise TypeError('ExprCons already has upper bound')
assert not self._lhs is None
if not _is_number(other):
raise TypeError('Ranged ExprCons is not well defined!')
return ExprCons(self.expr, lhs=self._lhs, rhs=float(other))
elif op == 5: # >=
if not self._lhs is None:
raise TypeError('ExprCons already has lower bound')
assert self._lhs is None
assert not self._rhs is None
if not _is_number(other):
raise TypeError('Ranged ExprCons is not well defined!')
return ExprCons(self.expr, lhs=float(other), rhs=self._rhs)
else:
raise TypeError

A toy demo to show that

from pyscipopt import Model

m = Model()
x = m.addVar(vtype="B", ub=0)
y = m.addVar(vtype="B", ub=0)
# (x <= 1) >= y  # left is (x <= 1) (ExprCons), right is y (Variable)
# Traceback (most recent call last):
#   line 6, in <module>
#     (x <= 1) >= y
#   File "src/pyscipopt/expr.pxi", line 352, in pyscipopt.scip.ExprCons.__richcmp__
# TypeError: Ranged ExprCons is not well defined!
y <= (x <= 1)  # left is y (Variable), right is (x <= 1) (ExprCons)
# Traceback (most recent call last):
#   line 12, in <module>
#     y <= (x <= 1)
#   File "src/pyscipopt/expr.pxi", line 287, in pyscipopt.scip.Expr.__richcmp__
#   File "src/pyscipopt/expr.pxi", line 65, in pyscipopt.scip._expr_richcmp
# NotImplementedError

if not self._rhs is None:
raise TypeError('ExprCons already has upper bound')
assert not self._lhs is None
if not self._rhs is None:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use 4 spaces as the indent

@jonathanberthias
Copy link

This is great @Zeroto521, I just hit the same issue so thanks a lot for working on this!
Just a quick question, would any more changes be needed to make comparison with GenExprs work?

@Zeroto521
Copy link
Contributor Author

Zeroto521 commented Sep 17, 2025

This is great @Zeroto521, I just hit the same issue so thanks a lot for working on this! Just a quick question, would any more changes be needed to make comparison with GenExprs work?

Expr and GenExpr are two different classes. The relationships between Variable, Term, Expr, and GenExpr are kind of complex. I want to merge Expr and GenExpr and simplify the inheritance.
This pr only helps for Expr now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: matrix variable can't be compared with expr directly
2 participants