Feature: Expr and GenExpr support numpy unary func like np.sin#1170
Feature: Expr and GenExpr support numpy unary func like np.sin#1170Joao-Dionisio merged 59 commits intoscipopt:masterfrom
np.sin#1170Conversation
Introduced the ExprLike base class with __array_ufunc__ to enable numpy universal function (ufunc) support for Expr and GenExpr. Replaced standalone exp, log, sqrt, sin, and cos functions with numpy equivalents and mapped them for ufunc dispatch. This change improves interoperability with numpy and simplifies the codebase.
Added a new ExprLike base class and made Expr inherit from it. This refactoring prepares the codebase for future extensions or polymorphism involving expression-like objects.
Corrects the method call to use the first argument in the unary ufunc mapping, ensuring the correct object is used when applying numpy universal functions.
Introduces tests for unary operations such as abs, sin, and sqrt, including their numpy equivalents, to ensure correct string representations and compatibility with numpy functions.
Added a missing colon to the ExprLike class definition in scip.pxd to conform with Python/Cython syntax.
There was a problem hiding this comment.
Pull request overview
This PR adds support for NumPy unary functions (np.sin, np.cos, np.sqrt, np.exp, np.log, np.absolute) to work with Expr and GenExpr objects by implementing the __array_ufunc__ protocol.
Changes:
- Introduces a new
ExprLikebase class that implements__array_ufunc__and unary operation methods - Makes
ExprandGenExprinherit fromExprLiketo support NumPy ufuncs - Replaces custom function implementations with NumPy function aliases
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/pyscipopt/scip.pxd | Adds ExprLike base class declaration and updates Expr inheritance hierarchy |
| src/pyscipopt/expr.pxi | Implements ExprLike with __array_ufunc__ support, consolidates unary methods, aliases functions to NumPy equivalents |
| tests/test_expr.py | Adds test coverage for both custom functions and NumPy ufuncs with single expressions and arrays |
| CHANGELOG.md | Documents the new NumPy unary function support feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Updated test assertions in test_unary to expect variable names 'x', 'y', and 'z' instead of 'x1'. This aligns the tests with the current string representation of expressions.
Updated the expected string in test_unary to match the correct output format by removing commas between list elements.
Corrected the expected string in test_unary to use 'sin' instead of 'abs' for the sin([x, y, z]) test case.
Introduces a cpdef _evaluate method to the Constant class, returning the constant's value. This provides a consistent evaluation interface for expressions.
Removes the NotImplementedError expectation when raising genexpr to sqrt(2) and instead asserts the result is a GenExpr. Adds a new test to expect TypeError when raising to sqrt('2').
The test_unary function was moved and modified to test unary operations on lists containing two variables (x, y) instead of three (x, y, z). This streamlines the tests and aligns them with the current requirements.
Introduced a new ExprLike base class to encapsulate common mathematical methods such as __abs__, exp, log, sqrt, sin, and cos. Updated Expr and GenExpr to inherit from ExprLike, reducing code duplication and improving maintainability.
Introduces the __array_ufunc__ method to the ExprLike class in the type stubs, enabling better compatibility with NumPy ufuncs.
…nto expr/unary
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1170 +/- ##
==========================================
- Coverage 56.64% 56.53% -0.11%
==========================================
Files 26 26
Lines 5602 5628 +26
==========================================
+ Hits 3173 3182 +9
- Misses 2429 2446 +17 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Changed 'import numpy' to 'import numpy as np' and updated type annotations to use 'np.ndarray' instead of 'numpy.ndarray' in the scip.pyi stub file. Also added UNARY_MAPPER as a Dict[np.ufunc, str].
Replaces 'Incomplete' type hints with more specific types (np.ufunc and str) for the __array_ufunc__ methods in ExprLike, MatrixExpr, and MatrixExprCons classes to improve type accuracy.
Reordered the declarations of math functions and added missing entries for log, sin, and sqrt in the scip.pyi stub file to improve completeness and maintain consistency.
Moved the Operator type annotation below PATCH to maintain consistent ordering of variable declarations in the scip.pyi file.
The @disjoint_base decorator was removed from the ExprLike class in the type stub. This may reflect a change in the class hierarchy or decorator usage.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Add a guard to ExprLike.__array_ufunc__ that raises a TypeError if the numpy ufunc 'out' keyword argument is provided. Expression objects do not support writing into preallocated output arrays, so this explicit error prevents silent misuse or incorrect behavior when numpy ufuncs pass an 'out' parameter.
|
@Zeroto521 can you please add a couple more tests? It feels uncomfortable to merge something with 34% coverage. |
Add a unit test in tests/test_expr.py to assert that calling np.sin with an out= parameter on a Variable/Expr/GenExpr raises TypeError. This prevents in-place modification of expression objects via NumPy ufunc out arguments and documents the intended behavior.
The codes are different between codecov and the head commit of this PR. The head commit is the following exp = lambda x: _wrap_ufunc(x, np.exp)
log = lambda x: _wrap_ufunc(x, np.log)
sqrt = lambda x: _wrap_ufunc(x, np.sqrt)
sin = lambda x: _wrap_ufunc(x, np.sin)
cos = lambda x: _wrap_ufunc(x, np.cos)But the codecov is still the old version. Can the codecov CI be triggered manually and run again? |
Add an explanatory docstring to the _wrap_ufunc helper describing how it handles scalars and collections: converting numeric inputs to Constant expressions, applying the ufunc element-wise via np.frompyfunc for arrays/lists/tuples, and returning a MatrixGenExpr for ndarrays or a list/tuple of GenExprs otherwise. Documentation-only change; no functional behavior modified.
directly use inner method
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/pyscipopt/scip.pyi:361
Exprnow contains duplicate dunder method declarations: non-positional-only versions were added (e.g.,def __add__(self, other: Incomplete)) but the positional-only versions still exist below. This duplication is likely to fail the stub linting (ruff) and is inconsistent with the earlier change to mark dunder params as positional-only; please remove the redundant non-positional-only declarations (or convert to proper@overloadif they are intended to differ).
def __add__(self, other: Incomplete) -> Incomplete: ...
def __eq__(self, other: object) -> bool: ...
def __ge__(self, other: object) -> bool: ...
def __getitem__(self, index: Incomplete) -> Incomplete: ...
def __gt__(self, other: object) -> bool: ...
def __iadd__(self, other: Incomplete) -> Incomplete: ... # noqa: PYI034
def __abs__(self) -> Incomplete: ...
def __add__(self, other: Incomplete, /) -> Incomplete: ...
def __eq__(self, other: object, /) -> bool: ...
def __ge__(self, other: object, /) -> bool: ...
def __getitem__(self, index: Incomplete, /) -> Incomplete: ...
def __gt__(self, other: object, /) -> bool: ...
def __iadd__(self, other: Incomplete, /) -> Incomplete: ... # noqa: PYI034
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
After addressing Copilot's comments, this can be merged, I think. |
Expand and reformat the _wrap_ufunc docstring to include a dedicated Returns section. The new text clarifies the function's return types for scalar vs. vector inputs (GenExpr/MatrixGenExpr) and explains that vectors yield an array of the same shape with the ufunc applied element-wise. Also minor formatting/line-wrap cleanup.
Replace terse lambda aliases for exp, log, sqrt, sin, and cos with full function definitions that call _wrap_ufunc. Each function now includes a detailed docstring describing parameters, behavior for scalars and vectors, and return types. This improves readability and documents expected input/outputs without changing functionality (still delegates to _wrap_ufunc and numpy ufuncs).
|
My only concern is that now numpy will be used even if you're not using arrays, the dependency coupling is much stricter now. But, it ultimately doesn't seem too problematic. Thank you once more, @Zeroto521 ! |



Expr and GenExpr support numpy unary functions like
np.sin.Now we can do this
Before this, we couldn't