Return NotImplemented for Expr and GenExpr operators to simplify#1182
Return NotImplemented for Expr and GenExpr operators to simplify#1182Zeroto521 wants to merge 29 commits intoscipopt:masterfrom
NotImplemented for Expr and GenExpr operators to simplify#1182Conversation
Improves the _expr_richcmp function by using explicit type checks, handling numpy arrays and numbers more robustly, and leveraging Python C API comparison constants. This refactor enhances error handling and code readability, and ensures unsupported types raise clear exceptions.
Replaces repeated isinstance checks for numeric types with a shared NUMBER_TYPES tuple. This improves maintainability and consistency in type checking within _expr_richcmp and related code.
Modified __mul__ and __add__ methods in Expr and GenExpr classes to return NotImplemented when the operand is not an instance of EXPR_OP_TYPES, improving type safety and operator behavior.
Replaces the explicit type tuple with EXPR_OP_TYPES in the type check for 'other' in _expr_richcmp, raising TypeError for unsupported types. This improves maintainability and consistency in type validation.
Enhanced type validation in expression operator overloads by returning NotImplemented for unsupported types and raising TypeError in buildGenExprObj for invalid inputs. Removed special handling for numpy arrays and simplified code paths for better maintainability and clearer error reporting.
Simplified the implementation of __truediv__ and __rtruediv__ by directly using the division operator instead of calling __truediv__ explicitly on generated expression objects.
float(Expr) can't return True
Replaces manual timing with the timeit module for more accurate performance measurement in matrix operation tests. Updates assertions to require the optimized implementation to be at least 25% faster, and reduces test parameterization to n=100 for consistency.
Replaces random matrix generation with a stacked matrix of zeros and ones in test_matrix_dot_performance to provide more controlled test data.
Replaces the custom _is_number function with isinstance checks against NUMBER_TYPES for improved clarity and consistency in type checking throughout expression operations.
Simplifies type checking and arithmetic operations in Expr and GenExpr classes by removing unnecessary float conversions and reordering type checks. Also improves error handling for exponentiation and constraint comparisons.
Documented that Expr and GenExpr now return NotImplemented when they cannot handle other types in calculations.
Replaces EXPR_OP_TYPES with GENEXPR_OP_TYPES in GenExpr and related functions to distinguish between Expr and GenExpr operations. Removes special handling for GenExpr in Expr arithmetic methods, simplifying type logic and improving consistency.
Clarified the changelog note to specify that NotImplemented is returned for Expr and GenExpr operators when they can't handle input types in calculations.
Corrects error messages in Expr and GenExpr exponentiation to display the correct variable. Removes an unnecessary assertion in Model and replaces a call to _is_number with isinstance for type checking in readStatistics.
Replaces EXPR_OP_TYPES with GENEXPR_OP_TYPES in the type check to ensure correct type validation in the _expr_richcmp method.
| elif isinstance(other, np.ndarray): | ||
| return _expr_richcmp(other, self, 2) |
There was a problem hiding this comment.
MatrixExpr can handle a vector and a scalar well.
| elif isinstance(expr, np.ndarray): | ||
| GenExprs = np.empty(expr.shape, dtype=object) | ||
| for idx in np.ndindex(expr.shape): | ||
| GenExprs[idx] = buildGenExprObj(expr[idx]) | ||
| return GenExprs.view(MatrixExpr) |
There was a problem hiding this comment.
MatrixExpr is a vector container. It could take care of vector and scalar.
Expr and GenExpr should only care about scalar types.
Casts the exponent base to float in GenExpr's __pow__ method to prevent type errors when reformulating expressions using log and exp.
NotImplemented for Expr and GenExpr operatorsNotImplemented for Expr and GenExpr operators to simplify
Explicitly converts 'other' to float in the exponentiation method to avoid type errors when using non-float numeric types.
The _is_number symbol was removed from the list of incomplete stubs in scip.pyi, likely because it is no longer needed or has been implemented.
Ensures that multiplication of Expr by numeric types consistently uses float conversion, preventing potential type errors when multiplying terms.
There was a problem hiding this comment.
Pull request overview
This pull request refactors operator overloading in Expr and GenExpr classes to follow Python's standard protocol by returning NotImplemented for unsupported operand types instead of explicitly checking types and raising errors. This allows Python's reflection mechanism to properly handle mixed-type operations.
Changes:
- Removed the
_is_number()helper function and replaced it with explicitisinstance()checks against new type tuples (NUMBER_TYPES,EXPR_OP_TYPES,GENEXPR_OP_TYPES) - Modified operators (
__add__,__iadd__,__mul__,__truediv__,__rtruediv__,__rpow__) to returnNotImplementedfor unsupported types - Updated
_expr_richcmp()to returnNotImplementedfor numpy arrays - Simplified
buildGenExprObj()by removing numpy array handling and adding explicit type validation - Updated type annotations to clarify expected input types
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/pyscipopt/scip.pyi | Removed _is_number import from type stub |
| src/pyscipopt/scip.pxi | Removed assertion using _is_number in setObjective(), replaced _is_number() call with isinstance() check in readStatistics() (but incorrectly) |
| src/pyscipopt/expr.pxi | Main refactoring: removed _is_number() function, added type tuples, updated all operator methods to return NotImplemented, simplified type handling logic |
| CHANGELOG.md | Documented the change |
Comments suppressed due to low confidence (1)
src/pyscipopt/expr.pxi:245
- The
__pow__method should returnNotImplementedfor unsupported types before attempting to callfloat(other). Currently, if an invalid type is passed, it will raise a TypeError fromfloat()instead of allowing Python to try the reflected operation on the other operand. Add a type check at the beginning of the method similar to other operators.
def __pow__(self, other, modulo):
if float(other).is_integer() and other >= 0:
exp = int(other)
else: # need to transform to GenExpr
return buildGenExprObj(self)**other
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Refactored the value assignment in readStatistics to use a try-except block for float conversion, ensuring non-numeric values are handled gracefully. This simplifies the logic and improves robustness when parsing statistics.
Fix a bug where the method returned the original 'terms' variable instead of the newly built 'res' dictionary. The change ensures Expr is constructed from the computed aggregation (res), preventing stale/incorrect expression data after the operation.
Python prefers to use
NotImplementedto handle the unknown input types. It's clear.Here is an example.
A.__add__can only receiveinttype. Buta + bcan work. When Python callsA.__add__(B), it returnsNotImplemented. Then Python will callB.__radd__(A). If both of them areNotImplemented, Python will raise aTypeError.So we use
NotImplementedto simplifyExpr,GenExpr, andMatrixExpr.