From b8648511f4d10dbd27a658dbfeb02b971e6f2eb3 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:41:40 +0800 Subject: [PATCH 01/11] Add copy method and abs support to expression classes Introduces a copy method to GenExpr and its subclasses for duplicating expression objects, with optional deep copy of children and coefficients. Also adds __abs__ support to UnaryExpr, enabling correct handling of absolute value expressions. --- src/pyscipopt/expr.pxi | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 07d6ab031..c68940f52 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -647,6 +647,20 @@ cdef class GenExpr: '''returns operator of GenExpr''' return self._op + cdef GenExpr copy(self, bool copy = True): + cls = type(self) + cdef GenExpr res = cls.__new__(cls) + res._op = self._op + res.children = self.children.copy() if copy else self.children + if cls is SumExpr: + res.constant = self.constant + res.coefs = self.coefs.copy() if copy else self.coefs + if cls is ProdExpr: + res.constant = self.constant + elif cls is PowExpr: + res.expo = self.expo + return res + # Sum Expressions cdef class SumExpr(GenExpr): @@ -736,6 +750,11 @@ cdef class UnaryExpr(GenExpr): self.children.append(expr) self._op = op + def __abs__(self): + if self._op == "abs": + return self.copy() + return UnaryExpr(Operator.fabs, self) + def __repr__(self): return self._op + "(" + self.children[0].__repr__() + ")" From a8d916038cfa2e8e7723bf18a2cb2b382adbcac0 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:46:55 +0800 Subject: [PATCH 02/11] Add test to ensure abs(abs(x)) simplifies to abs(x) Introduces a test to verify that applying abs() twice to a variable results in the same string representation as applying it once, ensuring correct simplification in expression handling. --- tests/test_expr.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_expr.py b/tests/test_expr.py index c9135d2fa..fc6c9c193 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -218,3 +218,11 @@ def test_getVal_with_GenExpr(): with pytest.raises(ZeroDivisionError): m.getVal(1 / z) + + +def test_abs_abs_expr(): + m = Model() + x = m.addVar(name="x") + + # should print abs(x) not abs(abs(x)) + assert str(abs(abs(x))) == str(abs(x)) From ca3339eaa82dc199bebe435d0814dd453d9105b4 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:47:47 +0800 Subject: [PATCH 03/11] Update CHANGELOG for abs to UnaryExpr(Operator.fabs) Documented that abs now returns itself as UnaryExpr(Operator.fabs) in the CHANGELOG. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfed00f5..d5fc978d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Speed up MatrixExpr.add.reduce via quicksum - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs +- Return itself for abs to UnaryExpr(Operator.fabs) ### Removed ## 6.0.0 - 2025.xx.yy From 13ed542b17daaa94ee1f7555100cd30783d2f5cc Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:49:09 +0800 Subject: [PATCH 04/11] Update __abs__ return type in GenExpr and add to UnaryExpr Changed the return type of __abs__ in GenExpr from Incomplete to GenExpr and added the __abs__ method to UnaryExpr. This improves type hinting and consistency in the class hierarchy. --- src/pyscipopt/scip.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 61c4ba773..ef45fab74 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -377,7 +377,7 @@ class GenExpr: def __init__(self) -> None: ... def degree(self) -> Incomplete: ... def getOp(self) -> Incomplete: ... - def __abs__(self) -> Incomplete: ... + def __abs__(self) -> GenExpr: ... def __add__(self, other: Incomplete) -> Incomplete: ... def __eq__(self, other: object) -> bool: ... def __ge__(self, other: object) -> bool: ... @@ -2164,6 +2164,7 @@ class Term: @disjoint_base class UnaryExpr(GenExpr): def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __abs__(self) -> GenExpr: ... @disjoint_base class VarExpr(GenExpr): From 02f57e6cb5a357e37c332b0de91b119fb2394933 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:53:10 +0800 Subject: [PATCH 05/11] Fix type declaration for cls in GenExpr.copy Changed the declaration of 'cls' in GenExpr.copy from a Python variable to a Cython object to ensure proper type handling. --- 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 c68940f52..03b81a7f0 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -648,7 +648,7 @@ cdef class GenExpr: return self._op cdef GenExpr copy(self, bool copy = True): - cls = type(self) + cdef object cls = type(self) cdef GenExpr res = cls.__new__(cls) res._op = self._op res.children = self.children.copy() if copy else self.children From 47f021483c42e00fffd941b9cd36a3e5d3fb108f Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:55:43 +0800 Subject: [PATCH 06/11] Use Py_TYPE for class retrieval in GenExpr.copy Replaces the use of type(self) with Py_TYPE(self) in the GenExpr.copy method for more efficient class retrieval in Cython. Also reorders cimport statements for clarity. --- src/pyscipopt/expr.pxi | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 03b81a7f0..516c72dae 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -45,9 +45,10 @@ import math from typing import TYPE_CHECKING -from pyscipopt.scip cimport Variable, Solution from cpython.dict cimport PyDict_Next +from cpython.object cimport Py_TYPE from cpython.ref cimport PyObject +from pyscipopt.scip cimport Variable, Solution import numpy as np @@ -648,7 +649,7 @@ cdef class GenExpr: return self._op cdef GenExpr copy(self, bool copy = True): - cdef object cls = type(self) + cdef object cls = Py_TYPE(self) cdef GenExpr res = cls.__new__(cls) res._op = self._op res.children = self.children.copy() if copy else self.children From 76b026464a3457cc615d24f49170e350ab5eb7ab Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 17:05:33 +0800 Subject: [PATCH 07/11] Fix type declaration in GenExpr.copy method Changed the type of 'cls' from 'object' to 'type' in the GenExpr.copy method to ensure correct class instantiation using Py_TYPE. --- 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 516c72dae..c519ffff6 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -649,7 +649,7 @@ cdef class GenExpr: return self._op cdef GenExpr copy(self, bool copy = True): - cdef object cls = Py_TYPE(self) + cdef type cls = Py_TYPE(self) cdef GenExpr res = cls.__new__(cls) res._op = self._op res.children = self.children.copy() if copy else self.children From 4e13b4867ea3044ca973abc7412a6c733399948f Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 17:05:39 +0800 Subject: [PATCH 08/11] Add return type annotation to UnaryExpr.__abs__ The __abs__ method in the UnaryExpr class now includes a return type annotation, improving type clarity and static analysis. --- 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 c519ffff6..f364abc8e 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -751,7 +751,7 @@ cdef class UnaryExpr(GenExpr): self.children.append(expr) self._op = op - def __abs__(self): + def __abs__(self) -> UnaryExpr: if self._op == "abs": return self.copy() return UnaryExpr(Operator.fabs, self) From 79b86d5b36d01576477c9bebf8da2c83a01960d5 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 17:05:46 +0800 Subject: [PATCH 09/11] Remove @disjoint_base decorator from UnaryExpr The @disjoint_base decorator was removed from the UnaryExpr class in the type stub. This may reflect a change in 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 ef45fab74..527fa64b9 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -2161,7 +2161,6 @@ class Term: def __lt__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... -@disjoint_base class UnaryExpr(GenExpr): def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... def __abs__(self) -> GenExpr: ... From cfaea3fb7231ccc7247cbbdd7f8183b26b05a9f0 Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 26 Jan 2026 09:12:20 +0800 Subject: [PATCH 10/11] Fix attribute assignment in GenExpr.copy method Corrects attribute assignments in the GenExpr.copy method by explicitly casting to the appropriate subclass (SumExpr, ProdExpr, PowExpr) before setting subclass-specific attributes. This ensures proper copying of attributes and avoids potential attribute errors. --- src/pyscipopt/expr.pxi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index f364abc8e..ea51ea698 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -649,17 +649,17 @@ cdef class GenExpr: return self._op cdef GenExpr copy(self, bool copy = True): - cdef type cls = Py_TYPE(self) + cdef object cls = Py_TYPE(self) cdef GenExpr res = cls.__new__(cls) res._op = self._op res.children = self.children.copy() if copy else self.children if cls is SumExpr: - res.constant = self.constant - res.coefs = self.coefs.copy() if copy else self.coefs + (res).constant = (self).constant + (res).coefs = (self).coefs.copy() if copy else (self).coefs if cls is ProdExpr: - res.constant = self.constant + (res).constant = (self).constant elif cls is PowExpr: - res.expo = self.expo + (res).expo = (self).expo return res From 5911fc4a32a954b372eb78fb3c2c2a51b97af36f Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 26 Jan 2026 09:14:02 +0800 Subject: [PATCH 11/11] Cast Py_TYPE result to type in GenExpr.copy Explicitly cast the result of Py_TYPE(self) to in the GenExpr.copy method for clarity and type safety. --- 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 ea51ea698..aac14950e 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -649,7 +649,7 @@ cdef class GenExpr: return self._op cdef GenExpr copy(self, bool copy = True): - cdef object cls = Py_TYPE(self) + cdef object cls = Py_TYPE(self) cdef GenExpr res = cls.__new__(cls) res._op = self._op res.children = self.children.copy() if copy else self.children