Skip to content

Conversation

@Zeroto521
Copy link
Contributor

@Zeroto521 Zeroto521 commented Jan 22, 2026

Expr and GenExpr support numpy unary functions like np.sin.

Now we can do this

In [8]: x = m.addVar(name='x')

In [9]: np.sin(x)
Out[9]: sin(sum(0.0,prod(1.0,x)))

In [10]: np.cos([x, y])
Out[10]: [cos(sum(0.0,prod(1.0,x))) sin(cos(0.0,prod(1.0,y)))]

Before this, we couldn't

In [8]: x = m.addVar(name='x')

In [9]: np.sin(x)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: 'pyscipopt.scip.Variable' object has no attribute 'sin'

The above exception was the direct cause of the following exception:

TypeError                                 Traceback (most recent call last)
Cell In[9], line 1
----> 1 np.sin(x)

TypeError: loop of ufunc does not support argument 0 of type pyscipopt.scip.Variable which has no callable sin method

In [12]: np.cos([x, y])
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: 'pyscipopt.scip.Variable' object has no attribute 'cos'

The above exception was the direct cause of the following exception:

TypeError                                 Traceback (most recent call last)
Cell In[12], line 1
----> 1 np.cos([x, y])

TypeError: loop of ufunc does not support argument 0 of type pyscipopt.scip.Variable which has no callable cos method

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.
Copilot AI review requested due to automatic review settings January 22, 2026 10:45
return expr


cdef class ExprLike:
Copy link
Contributor Author

@Zeroto521 Zeroto521 Jan 22, 2026

Choose a reason for hiding this comment

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

ExprLike is the base class and also a duck type.
It defines the behavior, and its subclass defines the data.
I will use ExprLike to split Variable and Expr in the future.

Added a missing colon to the ExprLike class definition in scip.pxd to conform with Python/Cython syntax.
Copy link
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 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 ExprLike base class that implements __array_ufunc__ and unary operation methods
  • Makes Expr and GenExpr inherit from ExprLike to 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.
assert isinstance(1/x + genexpr, GenExpr)
assert isinstance(1/x**1.5 - genexpr, GenExpr)
assert isinstance(y/x - exp(genexpr), GenExpr)
# sqrt(2) is not a constant expression and
Copy link
Contributor Author

Choose a reason for hiding this comment

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

pyscipopt.sqrt(2) = np.sqrt(2). It's a constant now, not a Constant(GenExpr)

Introduces the __array_ufunc__ method to the ExprLike class in the type stubs, enabling better compatibility with NumPy ufuncs.
@codecov
Copy link

codecov bot commented Jan 23, 2026

Codecov Report

❌ Patch coverage is 31.03448% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.50%. Comparing base (a82bf15) to head (857c969).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
src/pyscipopt/expr.pxi 31.03% 20 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1170      +/-   ##
==========================================
- Coverage   55.87%   55.50%   -0.37%     
==========================================
  Files          25       25              
  Lines        5502     5495       -7     
==========================================
- Hits         3074     3050      -24     
- Misses       2428     2445      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.
Comment on lines +25 to +28
log: Incomplete
sin: Incomplete
cos: Incomplete
sqrt: Incomplete
Copy link
Contributor Author

Choose a reason for hiding this comment

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

put them together

Comment on lines -801 to -809
def cos(expr):
"""returns expression with cos-function"""
if isinstance(expr, MatrixExpr):
unary_exprs = np.empty(shape=expr.shape, dtype=object)
for idx in np.ndindex(expr.shape):
unary_exprs[idx] = UnaryExpr(Operator.cos, buildGenExprObj(expr[idx]))
return unary_exprs.view(MatrixGenExpr)
else:
return UnaryExpr(Operator.cos, buildGenExprObj(expr))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • Before:
    • number: cos(2) will return a UnaryExpr to wrap 2.
    • np.ndarray(..., dtype=np.number): can't handle this.
  • Now:
    • number: cos(2) will return constant cos(2)=-0.416. Both Expr and GenExpr can handle numbers.
    • np.ndarray(..., dtype=np.number): return np.ndarray(..., dtype=np.number).

Replaces numpy function references in UNARY_MAPPER with local aliases (exp, log, sqrt, sin, cos) to ensure correct mapping and avoid issues with numpy function identity.
Replaces the use of Dict from typing with the built-in dict for the UNARY_MAPPER type annotation. This modernizes the type hint and removes an unused import.
Deleted the UNARY_MAPPER dictionary from scip.pyi as it is no longer needed or used in the type stub.
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.

1 participant