Feature: Expr and GenExpr support NumPy binary functions like np.add#1203
Feature: Expr and GenExpr support NumPy binary functions like np.add#1203Zeroto521 wants to merge 9 commits intoscipopt:masterfrom
Expr and GenExpr support NumPy binary functions like np.add#1203Conversation
Handle numpy.ndarray arguments in ExprLike.__call__ by converting numeric ndarrays to MatrixGenExpr via a new _to_matrix helper and applying the ufunc. Non-numeric dtypes return NotImplemented. Also map common ufuncs (add, subtract, multiply, divide/true_divide, power, negative, comparisons, equal, absolute) to corresponding Expr/operator semantics to enable elementwise and operator-based behavior.
Annotate _to_matrix arg as object and change its behavior to return MatrixExpr for numpy ndarray inputs and for values that are Expr instances; otherwise return MatrixGenExpr. This ensures Expr-backed arrays get a MatrixExpr view while keeping generic inputs as MatrixGenExpr.
Rename the test function in tests/test_expr.py from test_unary to test_unary_ufunc to make its purpose clearer — it specifically targets unary ufunc behavior. No functional changes to the test logic.
Add test_binary_ufunc to tests/test_expr.py. The new tests exercise np.add, np.subtract, np.multiply, np.divide, np.negative and np.power against Expr objects and numpy arrays, including scalar/array operands and swapped operand orders, asserting expected string representations to catch regressions in binary ufunc handling.
Add a clarifying comment in src/pyscipopt/expr.pxi explaining that when a numeric numpy ndarray is present among arguments, all arguments are converted to MatrixExpr or MatrixGenExpr before applying the ufunc. This improves code readability for future maintainers and documents the intended behavior in the ExprLike ufunc handling.
Relocate the np.negative assertion from the binary ufunc test block into the unary_ufunc test to group unary function checks together and eliminate the duplicate assertion. No functional changes—just test reorganization for clarity.
Expr and GenExpr support NumPy binary functions (like np.add)Expr and GenExpr support NumPy binary functions like np.add
There was a problem hiding this comment.
Pull request overview
This PR extends PySCIPOpt’s expression types (Expr/GenExpr) to interoperate with NumPy ufuncs for common binary arithmetic (and np.negative), enabling expressions like x + np.array([...]) to produce arrays of PySCIPOpt expressions instead of raising TypeError.
Changes:
- Extend
ExprLike.__array_ufunc__to handle NumPy binary ufuncs (add/subtract/multiply/divide/true_divide/power) andnp.negative, including a numeric-ndarray conversion path. - Add
_to_matrixhelper to convert scalar Expr/GenExpr/number arguments intoMatrixExpr/MatrixGenExprfor ufunc application. - Add/adjust tests validating unary and binary ufunc behavior and update the changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/pyscipopt/expr.pxi |
Implements NumPy ufunc dispatch for scalar Expr/GenExpr, including numeric-ndarray handling via matrix wrappers. |
tests/test_expr.py |
Adds coverage for np.negative and several binary ufuncs with scalar and np.ndarray inputs. |
CHANGELOG.md |
Documents newly supported NumPy ufunc interoperability. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/pyscipopt/expr.pxi
Outdated
| if arrays := [a for a in args if type(a) is np.ndarray]: | ||
| if any(a.dtype.kind not in "fiub" for a in arrays): | ||
| return NotImplemented | ||
| # If the np.ndarray is of numeric type, all arguments are converted to | ||
| # MatrixExpr or MatrixGenExpr and then the ufunc is applied. | ||
| return ufunc(*[_to_matrix(a) for a in args], **kwargs) |
There was a problem hiding this comment.
type(a) is np.ndarray only catches base ndarray instances; ndarray subclasses (e.g., np.matrix, masked arrays, or third-party subclasses) will bypass the numeric-array conversion path and may still fail for ufuncs like np.add/np.subtract. Consider switching to isinstance(a, np.ndarray) while explicitly excluding MatrixExpr/MatrixGenExpr (or checking a.__class__ is np.ndarray) so numeric subclasses get the same behavior without breaking existing Matrix* handling.
There was a problem hiding this comment.
Some subclasses will pass via isinstance. So here I only check np.ndarray with numeric dtype.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Rename helper function _to_matrix to _ensure_matrix and update its call site in ExprLike ufunc handling. The change preserves behavior (numpy arrays are viewed as MatrixExpr/MatrixGenExpr and other Expr instances map to MatrixExpr/MatrixGenExpr) while clarifying the function's intention.
With this feature, we can do this now.