Skip to content

Feature: Expr and GenExpr support NumPy binary functions like np.add#1203

Open
Zeroto521 wants to merge 9 commits intoscipopt:masterfrom
Zeroto521:expr/unarys
Open

Feature: Expr and GenExpr support NumPy binary functions like np.add#1203
Zeroto521 wants to merge 9 commits intoscipopt:masterfrom
Zeroto521:expr/unarys

Conversation

@Zeroto521
Copy link
Copy Markdown
Contributor

With this feature, we can do this now.

import numpy as np

from pyscipopt import Model

m = Model()
x = m.addVar("x")
print(x + np.array([1, 2]))
# Now
# [Expr({Term(x): 1.0, Term(): 1.0}) Expr({Term(x): 1.0, Term(): 2.0})]

# Before
# TypeError                                 Traceback (most recent call last)
# Cell In[3], line 6
#       4 m = Model()
#       5 x = m.addVar("x")
# ----> 6 x + np.array([1, 2])

# File src/pyscipopt/expr.pxi:206, in pyscipopt.scip.Expr.__add__()

# TypeError: Unsupported type <class 'numpy.ndarray'>

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.
Copilot AI review requested due to automatic review settings April 6, 2026 11:43
@Zeroto521 Zeroto521 changed the title Feature: Expr and GenExpr support NumPy binary functions (like np.add) Feature: Expr and GenExpr support NumPy binary functions like np.add Apr 6, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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) and np.negative, including a numeric-ndarray conversion path.
  • Add _to_matrix helper to convert scalar Expr/GenExpr/number arguments into MatrixExpr/MatrixGenExpr for 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.

Comment on lines +249 to +254
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)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@Zeroto521 Zeroto521 Apr 6, 2026

Choose a reason for hiding this comment

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

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.
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.

2 participants