Return NotImplemented for Expr and GenExpr operators to simplify#1182
Open
Zeroto521 wants to merge 55 commits intoscipopt:masterfrom
Open
Return NotImplemented for Expr and GenExpr operators to simplify#1182Zeroto521 wants to merge 55 commits intoscipopt:masterfrom
NotImplemented for Expr and GenExpr operators to simplify#1182Zeroto521 wants to merge 55 commits intoscipopt:masterfrom
Conversation
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.
Zeroto521
commented
Jan 31, 2026
Zeroto521
commented
Jan 31, 2026
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.
Contributor
Author
|
mypy-1.20.0 causes CI to fail. mypy-1.19.0 could pass. |
Replace Python-level float(other) calls with Cython <double>other casts in _expr_richcmp when `other` is numeric. This updates the lhs/rhs arguments for ExprCons in the <=, >=, and == branches to use direct C double values, reducing Python overhead while preserving behavior.
Update _expr_richcmp in src/pyscipopt/expr.pxi to explicitly return NotImplemented when the other operand is a numpy.ndarray, and to raise a TypeError for operands that are not genexpr-compatible instead of silently returning NotImplemented. This makes comparisons with NumPy arrays behave predictably and surfaces clear errors for unsupported types.
Add test_NotImplemented to tests/test_expr.py. The new test verifies that mixing strings with Model variables and generator expressions (created as 1/x) raises TypeError for arithmetic (+, *, /), augmented assignments (+=), and comparisons (<=, >=, ==), ensuring unsupported operand combinations are rejected.
Annotate the 'op' parameter as an int in __richcmp__ for both Expr and GenExpr in src/pyscipopt/expr.pxi. This adds Cython-level typing for the rich comparison operator argument when turning expressions into constraints, reducing Python object overhead and ensuring correct C-level behavior.
Add np.number to Union type annotations for buildGenExprObj and _expr_richcmp so numpy scalar types are treated as compatible GenExpr/Expr inputs. Also reformat the _expr_richcmp signature across multiple lines for readability. No behavior changes beyond broader type compatibility with numpy scalar types.
Introduce numpy import and expand test coverage for interactions between Expr and GenExpr: replace 1/x with sqrt(x) and add assertions for Expr+GenExpr, Expr*GenExpr, in-place +=, and Expr + numpy array. Also tidy import ordering from pyscipopt and pyscipopt.scip. These changes validate operator overloading and interoperability with numpy arrays.
Extend tests in tests/test_expr.py to cover interactions between Expr/GenExpr and numpy arrays and matrix variables. Added assertions for Expr + array and GenExpr + array string representations, and for comparison operators (>=, <=, ==) against a matrix variable (m.addMatrixVar(1)), verifying expected ExprCons representations. Removed a now-redundant x + np.array([1]) test.
Zeroto521
commented
Apr 2, 2026
Contributor
Author
All test cases are added. |
tests/test_expr.py
Outdated
| with pytest.raises(TypeError): | ||
| x == "1" | ||
|
|
||
| genexpr = 1 /x |
Contributor
Author
Member
There was a problem hiding this comment.
I don't understand why that matters
Contributor
Author
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Member
|
After addressing Copilot's comments, this can be merged. |
Modify tests/test_expr.py to better cover comparison directions: change a genexpr >= '1' assertion to '1' >= genexpr to test the reflected comparison, and add a new assertion that genexpr <= '1' raises TypeError. Ensures both operand orders for <=/>= with a genexpr and a string are validated to raise TypeError.
Replace the previous inline PyNumber_Check heuristic with explicit runtime type inspection: obtain the object's Py_TYPE, treat builtin int/float as numbers, explicitly reject numpy arrays, Expr/GenExpr and Python lists/tuples, and fall back to PyNumber_Check otherwise. Also remove the inline qualifier. This tightens number detection to avoid misclassifying arrays or expression objects as numeric values.
In src/pyscipopt/expr.pxi, replace the explicit Cython cast `1.0 / <double>other * self` with `1.0 / other * self`. This removes the redundant C-level cast when dividing by a numeric Python object, simplifying the code and avoiding potential type/casting issues while preserving the intended behavior.
auto-merge was automatically disabled
April 5, 2026 02:07
Head branch was pushed to by a user without write access
Add an explicit type check in Model to ensure the provided expr is either an Expr or a numeric value before coercing it via Expr() + expr. If the value is neither, raise a TypeError with an informative message. This prevents silent or invalid coercions and improves error diagnostics when callers pass unsupported types.
Replace a bare except in src/pyscipopt/scip.pxi (inside readStatistics) with except (ValueError, TypeError) to avoid unintentionally catching unrelated exceptions (e.g. KeyboardInterrupt/SystemExit) while still handling float conversion failures for statistic values.
Replace Python-level type comparisons with C API checks: use PyLong_Check and PyFloat_Check to detect ints/floats instead of comparing Py_TYPE(o) to int/float. Also switch the numpy cimport to 'cimport numpy as cnp' and use cnp.PyArray_Check. Add the necessary cimports for PyFloat_Check and PyLong_Check and simplify the _is_number implementation for correctness and performance.
Zeroto521
commented
Apr 6, 2026
Comment on lines
+337
to
+340
| a = np.array([1]) | ||
| assert str(x + a) == "[Expr({Term(x): 1.0, Term(): 1.0})]" | ||
| # test GenExpr + array | ||
| assert str(genexpr + a) == "[sum(1.0,sqrt(sum(0.0,prod(1.0,x))))]" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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.