From 452ca36c0442ddec97a96712fe6563bba5416c98 Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 21 Jan 2026 18:29:06 +0800 Subject: [PATCH 01/22] Add numpy ufunc support for expression classes 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. --- src/pyscipopt/expr.pxi | 108 ++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index e0d35e8b0..96b5e69d2 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -42,6 +42,8 @@ # which should, in princple, modify the expr. However, since we do not implement __isub__, __sub__ # gets called (I guess) and so a copy is returned. # Modifying the expression directly would be a bug, given that the expression might be re-used by the user. +from typing import Literal + import numpy as np @@ -119,6 +121,7 @@ class Term: CONST = Term() + # helper function def buildGenExprObj(expr): """helper function to generate an object of type GenExpr""" @@ -154,9 +157,44 @@ def buildGenExprObj(expr): assert isinstance(expr, GenExpr) return expr + +cdef class ExprLike: + + def __array_ufunc__( + self, + ufunc: np.ufunc, + method: Literal["__call__", "reduce", "reduceat", "accumulate", "outer", "at"], + *args, + **kwargs, + ): + if method == "__call__": + if ufunc in UNARY_MAPPER: + return getattr(self, UNARY_MAPPER[ufunc])() + + return NotImplemented + + def __abs__(self): + return UnaryExpr(Operator.fabs, buildGenExprObj(self)) + + def exp(self): + return UnaryExpr(Operator.exp, buildGenExprObj(self)) + + def log(self): + return UnaryExpr(Operator.log, buildGenExprObj(self)) + + def sqrt(self): + return UnaryExpr(Operator.sqrt, buildGenExprObj(self)) + + def sin(self): + return UnaryExpr(Operator.sin, buildGenExprObj(self)) + + def cos(self): + return UnaryExpr(Operator.cos, buildGenExprObj(self)) + + ##@details Polynomial expressions of variables with operator overloading. \n #See also the @ref ExprDetails "description" in the expr.pxi. -cdef class Expr: +cdef class Expr(ExprLike): def __init__(self, terms=None): '''terms is a dict of variables to coefficients. @@ -179,9 +217,6 @@ cdef class Expr: try: return next(self.terms) except: raise StopIteration - def __abs__(self): - return abs(buildGenExprObj(self)) - def __add__(self, other): left = self right = other @@ -426,7 +461,7 @@ Operator = Op() # so expr[x] will generate an error instead of returning the coefficient of x # #See also the @ref ExprDetails "description" in the expr.pxi. -cdef class GenExpr: +cdef class GenExpr(ExprLike): cdef public _op cdef public children @@ -434,9 +469,6 @@ cdef class GenExpr: def __init__(self): # do we need it ''' ''' - def __abs__(self): - return UnaryExpr(Operator.fabs, self) - def __add__(self, other): if isinstance(other, np.ndarray): return other + self @@ -673,55 +705,21 @@ cdef class Constant(GenExpr): def __repr__(self): return str(self.number) -def exp(expr): - """returns expression with exp-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.exp, buildGenExprObj(expr[idx])) - return unary_exprs.view(MatrixGenExpr) - else: - return UnaryExpr(Operator.exp, buildGenExprObj(expr)) -def log(expr): - """returns expression with log-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.log, buildGenExprObj(expr[idx])) - return unary_exprs.view(MatrixGenExpr) - else: - return UnaryExpr(Operator.log, buildGenExprObj(expr)) +exp = np.exp +log = np.log +sqrt = np.sqrt +sin = np.sin +cos = np.cos +UNARY_MAPPER = { + np.absolute: "__abs__", + np.exp: "exp", + np.log: "log", + np.sqrt: "sqrt", + np.sin: "sin", + np.cos: "cos", +} -def sqrt(expr): - """returns expression with sqrt-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.sqrt, buildGenExprObj(expr[idx])) - return unary_exprs.view(MatrixGenExpr) - else: - return UnaryExpr(Operator.sqrt, buildGenExprObj(expr)) - -def sin(expr): - """returns expression with sin-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.sin, buildGenExprObj(expr[idx])) - return unary_exprs.view(MatrixGenExpr) - else: - return UnaryExpr(Operator.sin, buildGenExprObj(expr)) - -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)) def expr_to_nodes(expr): '''transforms tree to an array of nodes. each node is an operator and the position of the From 4d911c1ef47ccc8c03a6e3e398dd55133682f088 Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 21 Jan 2026 18:30:32 +0800 Subject: [PATCH 02/22] Introduce ExprLike base class for expressions 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. --- src/pyscipopt/scip.pxd | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index b9bffc1d6..ebd9762d4 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -2107,7 +2107,10 @@ cdef extern from "scip/scip_var.h": cdef extern from "tpi/tpi.h": int SCIPtpiGetNumThreads() -cdef class Expr: +cdef class ExprLike + pass + +cdef class Expr(ExprLike): cdef public terms cdef class Event: From b1192243b6d73d934dcce189dd3a977b6d4ec7e4 Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 21 Jan 2026 18:31:54 +0800 Subject: [PATCH 03/22] Fix unary ufunc mapping in ExprLike class 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. --- src/pyscipopt/expr.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 96b5e69d2..1c1df453f 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -169,7 +169,7 @@ cdef class ExprLike: ): if method == "__call__": if ufunc in UNARY_MAPPER: - return getattr(self, UNARY_MAPPER[ufunc])() + return getattr(args[0], UNARY_MAPPER[ufunc])() return NotImplemented From 4a65d98ba08f9989d1e8b371cd8376b51f27fcda Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 21 Jan 2026 18:39:04 +0800 Subject: [PATCH 04/22] Add tests for unary operations and numpy compatibility 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. --- tests/test_expr.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index ce79b7cc5..4f40c65d4 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -1,7 +1,9 @@ +import numpy as np import pytest -from pyscipopt import Model, sqrt, log, exp, sin, cos -from pyscipopt.scip import Expr, GenExpr, ExprCons, Term, quicksum +from pyscipopt import Model, cos, exp, log, sin, sqrt +from pyscipopt.scip import Expr, ExprCons, GenExpr, Term + @pytest.fixture(scope="module") def model(): @@ -188,3 +190,26 @@ def test_rpow_constant_base(model): with pytest.raises(ValueError): c = (-2)**x + + +def test_unary(model): + m, x, y, z = model + + assert str(abs(x)) == "abs(sum(0.0,prod(1.0,x1)))" + assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x1)))" + assert ( + str(sin([x, y, z])) + == "[abs(sum(0.0,prod(1.0,x1))), abs(sum(0.0,prod(1.0,x1))), abs(sum(0.0,prod(1.0,x1)))]" + ) + assert ( + str(np.sin([x, y, z])) + == "[sin(sum(0.0,prod(1.0,x1))) sin(sum(0.0,prod(1.0,x1))) sin(sum(0.0,prod(1.0,x1)))]" + ) + assert ( + str(sqrt([x, y, z])) + == "[sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1)))]" + ) + assert ( + str(np.sqrt([x, y, z])) + == "[sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1)))]" + ) From 01658f465d34b852080a9c5ff7790fae2c58c1e2 Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 22 Jan 2026 18:38:36 +0800 Subject: [PATCH 05/22] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc7dd4b59..c133d7c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Include parameter names in type stubs - Speed up MatrixExpr.sum(axis=...) via quicksum - Added structured_optimization_trace recipe for structured optimization progress tracking +- Expr and GenExpr support numpy unary func (`np.sin`, `np.cos`, `np.sqrt`, `np.exp`, `np.log`, `np.absolute`) ### Fixed - getBestSol() now returns None for infeasible problems instead of a Solution with NULL pointer - all fundamental callbacks now raise an error if not implemented From f0aacb67adc6dba15b6300fe44d1cbe408cc7541 Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 22 Jan 2026 18:52:29 +0800 Subject: [PATCH 06/22] Add colon to ExprLike class definition Added a missing colon to the ExprLike class definition in scip.pxd to conform with Python/Cython syntax. --- src/pyscipopt/scip.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 6e2dff12b..3ee3311d3 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -2107,7 +2107,7 @@ cdef extern from "scip/scip_var.h": cdef extern from "tpi/tpi.h": int SCIPtpiGetNumThreads() -cdef class ExprLike +cdef class ExprLike: pass cdef class Expr(ExprLike): From 7e78cacae0ba641d7e4e0c13b28e470b487eb59e Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 22 Jan 2026 18:59:57 +0800 Subject: [PATCH 07/22] Fix test expectations for variable names in unary ops 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. --- tests/test_expr.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 1031a66fd..e86e295bb 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -196,23 +196,23 @@ def test_rpow_constant_base(model): def test_unary(model): m, x, y, z = model - assert str(abs(x)) == "abs(sum(0.0,prod(1.0,x1)))" - assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x1)))" + assert str(abs(x)) == "abs(sum(0.0,prod(1.0,x)))" + assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x)))" assert ( str(sin([x, y, z])) - == "[abs(sum(0.0,prod(1.0,x1))), abs(sum(0.0,prod(1.0,x1))), abs(sum(0.0,prod(1.0,x1)))]" + == "[abs(sum(0.0,prod(1.0,x))), abs(sum(0.0,prod(1.0,y))), abs(sum(0.0,prod(1.0,z)))]" ) assert ( str(np.sin([x, y, z])) - == "[sin(sum(0.0,prod(1.0,x1))) sin(sum(0.0,prod(1.0,x1))) sin(sum(0.0,prod(1.0,x1)))]" + == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y))) sin(sum(0.0,prod(1.0,z)))]" ) assert ( str(sqrt([x, y, z])) - == "[sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1)))]" + == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y))) sqrt(sum(0.0,prod(1.0,z)))]" ) assert ( str(np.sqrt([x, y, z])) - == "[sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1))) sqrt(sum(0.0,prod(1.0,x1)))]" + == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y))) sqrt(sum(0.0,prod(1.0,z)))]" ) From 196f43a5e32b0572b4e8cfc7530979d20d3b599a Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 22 Jan 2026 19:00:30 +0800 Subject: [PATCH 08/22] Fix expected output format in test_unary Updated the expected string in test_unary to match the correct output format by removing commas between list elements. --- tests/test_expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index e86e295bb..49720e167 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -200,7 +200,7 @@ def test_unary(model): assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x)))" assert ( str(sin([x, y, z])) - == "[abs(sum(0.0,prod(1.0,x))), abs(sum(0.0,prod(1.0,y))), abs(sum(0.0,prod(1.0,z)))]" + == "[abs(sum(0.0,prod(1.0,x))) abs(sum(0.0,prod(1.0,y))) abs(sum(0.0,prod(1.0,z)))]" ) assert ( str(np.sin([x, y, z])) From 6dd2c9344dba80c73fc2f877c89ca1a4a661a033 Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 22 Jan 2026 19:01:00 +0800 Subject: [PATCH 09/22] Fix expected output for sin function in test_unary Corrected the expected string in test_unary to use 'sin' instead of 'abs' for the sin([x, y, z]) test case. --- tests/test_expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 49720e167..663465153 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -200,7 +200,7 @@ def test_unary(model): assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x)))" assert ( str(sin([x, y, z])) - == "[abs(sum(0.0,prod(1.0,x))) abs(sum(0.0,prod(1.0,y))) abs(sum(0.0,prod(1.0,z)))]" + == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y))) sin(sum(0.0,prod(1.0,z)))]" ) assert ( str(np.sin([x, y, z])) From e76b0d6bbc3b19b6421e9874d5af4fa6c53744f0 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:04:25 +0800 Subject: [PATCH 10/22] Add _evaluate method to Constant class Introduces a cpdef _evaluate method to the Constant class, returning the constant's value. This provides a consistent evaluation interface for expressions. --- src/pyscipopt/expr.pxi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index e3ba287d0..0bbea1ef9 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -787,6 +787,9 @@ cdef class Constant(GenExpr): def __repr__(self): return str(self.number) + cpdef double _evaluate(self, Solution sol) except *: + return self.number + exp = np.exp log = np.log From 06b4df4a59a772df4f74e90108c87ccf14022da9 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:08:01 +0800 Subject: [PATCH 11/22] Update genexpr power tests for sqrt handling 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'). --- tests/test_expr.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 663465153..70de3c79d 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -118,10 +118,12 @@ def test_genexpr_op_genexpr(model): 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 - # we can only power to constant expressions! - with pytest.raises(NotImplementedError): - genexpr **= sqrt(2) + + genexpr **= sqrt(2) + assert isinstance(genexpr, GenExpr) + + with pytest.raises(TypeError): + genexpr **= sqrt("2") def test_degree(model): m, x, y, z = model From a76ef442c5106a2435ab67b64691575dc5caffc2 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:09:50 +0800 Subject: [PATCH 12/22] Update test_unary to use two variables instead of three 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. --- tests/test_expr.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 70de3c79d..24923e03c 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -195,29 +195,6 @@ def test_rpow_constant_base(model): c = (-2)**x -def test_unary(model): - m, x, y, z = model - - assert str(abs(x)) == "abs(sum(0.0,prod(1.0,x)))" - assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x)))" - assert ( - str(sin([x, y, z])) - == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y))) sin(sum(0.0,prod(1.0,z)))]" - ) - assert ( - str(np.sin([x, y, z])) - == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y))) sin(sum(0.0,prod(1.0,z)))]" - ) - assert ( - str(sqrt([x, y, z])) - == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y))) sqrt(sum(0.0,prod(1.0,z)))]" - ) - assert ( - str(np.sqrt([x, y, z])) - == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y))) sqrt(sum(0.0,prod(1.0,z)))]" - ) - - def test_getVal_with_GenExpr(): m = Model() x = m.addVar(lb=1, ub=1, name="x") @@ -243,3 +220,21 @@ def test_getVal_with_GenExpr(): with pytest.raises(ZeroDivisionError): m.getVal(1 / z) + + +def test_unary(model): + m, x, y, z = model + + assert str(abs(x)) == "abs(sum(0.0,prod(1.0,x)))" + assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x)))" + assert str(sin([x, y])) == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y)))]" + assert ( + str(np.sin([x, y])) == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y)))]" + ) + assert ( + str(sqrt([x, y])) == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y)))]" + ) + assert ( + str(np.sqrt([x, y])) + == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y)))]" + ) From 5611d5210050801298fc1ac9bd58ed9de2b091cc Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:12:43 +0800 Subject: [PATCH 13/22] Refactor expression classes with ExprLike base 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. --- src/pyscipopt/scip.pyi | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 534b90420..cd85241ef 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -324,12 +324,20 @@ class Eventhdlr: def eventinitsol(self) -> Incomplete: ... @disjoint_base -class Expr: +class ExprLike: + def __abs__(self) -> Incomplete: ... + def exp(self) -> Incomplete: ... + def log(self) -> Incomplete: ... + def sqrt(self) -> Incomplete: ... + def sin(self) -> Incomplete: ... + def cos(self) -> Incomplete: ... + +@disjoint_base +class Expr(ExprLike): terms: Incomplete def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... def degree(self) -> Incomplete: ... def normalize(self) -> Incomplete: ... - def __abs__(self) -> Incomplete: ... def __add__(self, other: Incomplete) -> Incomplete: ... def __eq__(self, other: Incomplete)-> Incomplete: ... def __ge__(self, other: Incomplete)-> Incomplete: ... @@ -368,13 +376,12 @@ class ExprCons: def __ne__(self, other: Incomplete)-> Incomplete: ... @disjoint_base -class GenExpr: +class GenExpr(ExprLike): _op: Incomplete children: Incomplete def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... def degree(self) -> Incomplete: ... def getOp(self) -> Incomplete: ... - def __abs__(self) -> Incomplete: ... def __add__(self, other: Incomplete) -> Incomplete: ... def __eq__(self, other: Incomplete)-> Incomplete: ... def __ge__(self, other: Incomplete)-> Incomplete: ... From 925cb43d232fe51983f45d0ec93060d364651046 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:19:06 +0800 Subject: [PATCH 14/22] Add __array_ufunc__ to ExprLike type stub Introduces the __array_ufunc__ method to the ExprLike class in the type stubs, enabling better compatibility with NumPy ufuncs. --- src/pyscipopt/scip.pyi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index cd85241ef..94c6e99a8 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -325,6 +325,13 @@ class Eventhdlr: @disjoint_base class ExprLike: + def __array_ufunc__( + self, + ufunc: Incomplete, + method: str, + *args: Incomplete, + **kwargs: Incomplete, + ) -> Incomplete: ... def __abs__(self) -> Incomplete: ... def exp(self) -> Incomplete: ... def log(self) -> Incomplete: ... From c94177cf64a7f1f22742d44d2b5bfcdb38f85e9d Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:27:33 +0800 Subject: [PATCH 15/22] Update numpy import and type annotations in stubs 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]. --- src/pyscipopt/scip.pyi | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 11b88d65a..038107c52 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1,10 +1,11 @@ -from typing import ClassVar +from typing import ClassVar, Dict -import numpy +import numpy as np from _typeshed import Incomplete from typing_extensions import disjoint_base CONST: Term +UNARY_MAPPER: Dict[np.ufunc, str] EventNames: dict MAJOR: int MINOR: int @@ -510,7 +511,7 @@ class LP: def solve(self, dual: Incomplete = ...) -> Incomplete: ... def writeLP(self, filename: Incomplete) -> Incomplete: ... -class MatrixConstraint(numpy.ndarray): +class MatrixConstraint(np.ndarray): def getConshdlrName(self) -> Incomplete: ... def isActive(self) -> Incomplete: ... def isChecked(self) -> Incomplete: ... @@ -526,7 +527,7 @@ class MatrixConstraint(numpy.ndarray): def isSeparated(self) -> Incomplete: ... def isStickingAtNode(self) -> Incomplete: ... -class MatrixExpr(numpy.ndarray): +class MatrixExpr(np.ndarray): def _evaluate(self, sol: Incomplete) -> Incomplete: ... def __array_ufunc__( self, @@ -536,7 +537,7 @@ class MatrixExpr(numpy.ndarray): **kwargs: Incomplete, ) -> Incomplete: ... -class MatrixExprCons(numpy.ndarray): +class MatrixExprCons(np.ndarray): def __array_ufunc__( self, ufunc: Incomplete, From 588cba454dccfa74cae644d2bfb672856ec5d6a9 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:28:06 +0800 Subject: [PATCH 16/22] Update __array_ufunc__ type hints to use np.ufunc and 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. --- src/pyscipopt/scip.pyi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 038107c52..35114dabe 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -330,7 +330,7 @@ class Eventhdlr: class ExprLike: def __array_ufunc__( self, - ufunc: Incomplete, + ufunc: np.ufunc, method: str, *args: Incomplete, **kwargs: Incomplete, @@ -531,8 +531,8 @@ class MatrixExpr(np.ndarray): def _evaluate(self, sol: Incomplete) -> Incomplete: ... def __array_ufunc__( self, - ufunc: Incomplete, - method: Incomplete, + ufunc: np.ufunc, + method: str, *args: Incomplete, **kwargs: Incomplete, ) -> Incomplete: ... @@ -540,8 +540,8 @@ class MatrixExpr(np.ndarray): class MatrixExprCons(np.ndarray): def __array_ufunc__( self, - ufunc: Incomplete, - method: Incomplete, + ufunc: np.ufunc, + method: str, *args: Incomplete, **kwargs: Incomplete, ) -> Incomplete: ... From cd642f98673e2393cffb8ca1517bba7f5641d5e9 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:29:02 +0800 Subject: [PATCH 17/22] Reorder and add math functions in scip.pyi 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. --- src/pyscipopt/scip.pyi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 35114dabe..0cc64d5cb 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -21,18 +21,18 @@ _core_sum: Incomplete _expr_richcmp: Incomplete _is_number: Incomplete buildGenExprObj: Incomplete -cos: Incomplete exp: Incomplete +log: Incomplete +sin: Incomplete +cos: Incomplete +sqrt: Incomplete expr_to_array: Incomplete expr_to_nodes: Incomplete is_memory_freed: Incomplete -log: Incomplete print_memory_in_use: Incomplete quickprod: Incomplete quicksum: Incomplete readStatistics: Incomplete -sin: Incomplete -sqrt: Incomplete str_conversion: Incomplete value_to_array: Incomplete From f8e61322e9a82a73fa341b09fede2e42b0a88757 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:30:18 +0800 Subject: [PATCH 18/22] Reorder Operator and PATCH declarations in scip.pyi Moved the Operator type annotation below PATCH to maintain consistent ordering of variable declarations in the scip.pyi file. --- src/pyscipopt/scip.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 0cc64d5cb..e4ba86c90 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -9,8 +9,8 @@ UNARY_MAPPER: Dict[np.ufunc, str] EventNames: dict MAJOR: int MINOR: int -Operator: Op PATCH: int +Operator: Op PY_SCIP_CALL: Incomplete StageNames: dict TYPE_CHECKING: bool From 940fd931095ad3b8db2e410ce5953b467669bc59 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 13:31:30 +0800 Subject: [PATCH 19/22] Remove @disjoint_base decorator from ExprLike 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. --- src/pyscipopt/scip.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index e4ba86c90..c0f0254c0 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -326,7 +326,6 @@ class Eventhdlr: def eventinit(self) -> Incomplete: ... def eventinitsol(self) -> Incomplete: ... -@disjoint_base class ExprLike: def __array_ufunc__( self, From 900fdc3745042800894c8fc568b02d4a73ce6d20 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 15:05:04 +0800 Subject: [PATCH 20/22] Fix UNARY_MAPPER to use local math function references 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. --- src/pyscipopt/expr.pxi | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 909e6a39c..f6645db6b 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -792,13 +792,13 @@ log = np.log sqrt = np.sqrt sin = np.sin cos = np.cos -UNARY_MAPPER = { +cdef dict UNARY_MAPPER = { np.absolute: "__abs__", - np.exp: "exp", - np.log: "log", - np.sqrt: "sqrt", - np.sin: "sin", - np.cos: "cos", + exp: "exp", + log: "log", + sqrt: "sqrt", + sin: "sin", + cos: "cos", } From c85be94a9dd38e31fd46b08aa8e63926a02ff432 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 15:05:15 +0800 Subject: [PATCH 21/22] Update typing for UNARY_MAPPER in scip.pyi 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. --- src/pyscipopt/scip.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index c0f0254c0..bf5b8e6da 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1,11 +1,11 @@ -from typing import ClassVar, Dict +from typing import ClassVar import numpy as np from _typeshed import Incomplete from typing_extensions import disjoint_base CONST: Term -UNARY_MAPPER: Dict[np.ufunc, str] +UNARY_MAPPER: dict[np.ufunc, str] EventNames: dict MAJOR: int MINOR: int From 4f6058a0d52c08542efa1cff5e35a6659c46df5b Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 23 Jan 2026 15:09:10 +0800 Subject: [PATCH 22/22] Remove unused UNARY_MAPPER from type stub Deleted the UNARY_MAPPER dictionary from scip.pyi as it is no longer needed or used in the type stub. --- src/pyscipopt/scip.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index bf5b8e6da..c76ba659e 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -5,7 +5,6 @@ from _typeshed import Incomplete from typing_extensions import disjoint_base CONST: Term -UNARY_MAPPER: dict[np.ufunc, str] EventNames: dict MAJOR: int MINOR: int