From 6874ae46dfc85851ff93501393be4cc1088bffb9 Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 6 Apr 2026 19:58:05 +0800 Subject: [PATCH 1/9] Consolidate operator overloads into ExprLike Move common operator dunder methods (__neg__, __radd__, __sub__, __rsub__, __rmul__, __richcmp__) into the base ExprLike class and remove their duplicate implementations from Expr and GenExpr. This consolidates operator behavior across expression types, forwarding rich comparisons to _expr_richcmp and reducing code duplication for negation, arithmetic and reflected operations. --- src/pyscipopt/expr.pxi | 57 +++++++++++++----------------------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index b26b87997..5c4d15e18 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -279,6 +279,24 @@ cdef class ExprLike: def cos(self) -> GenExpr: return UnaryExpr(Operator.cos, buildGenExprObj(self)) + def __neg__(self): + return self * -1.0 + + def __radd__(self, other): + return self + other + + def __sub__(self, other): + return self + (-other) + + def __rsub__(self, other): + return (-self) + other + + def __rmul__(self, other): + return self * other + + def __richcmp__(self, other, int op): + return _expr_richcmp(self, other, op) + ##@details Polynomial expressions of variables with operator overloading. \n #See also the @ref ExprDetails "description" in the expr.pxi. @@ -411,25 +429,6 @@ cdef class Expr(ExprLike): else: raise TypeError(f"Unsupported base type {type(other)} for exponentiation.") - def __neg__(self): - return Expr({v:-c for v,c in self.terms.items()}) - - def __sub__(self, other): - return self + (-other) - - def __radd__(self, other): - return self.__add__(other) - - def __rmul__(self, other): - return self.__mul__(other) - - def __rsub__(self, other): - return -1.0 * self + other - - def __richcmp__(self, other, op): - '''turn it into a constraint''' - return _expr_richcmp(self, other, op) - def normalize(self): '''remove terms with coefficient of 0''' self.terms = {t:c for (t,c) in self.terms.items() if c != 0.0} @@ -488,7 +487,6 @@ cdef class ExprCons: if not self._rhs is None: self._rhs -= c - def __richcmp__(self, other, op): '''turn it into a constraint''' if op == 1: # <= @@ -720,25 +718,6 @@ cdef class GenExpr(ExprLike): otherexpr = buildGenExprObj(other) return otherexpr.__truediv__(self) - def __neg__(self): - return -1.0 * self - - def __sub__(self, other): - return self + (-other) - - def __radd__(self, other): - return self.__add__(other) - - def __rmul__(self, other): - return self.__mul__(other) - - def __rsub__(self, other): - return -1.0 * self + other - - def __richcmp__(self, other, op): - '''turn it into a constraint''' - return _expr_richcmp(self, other, op) - def degree(self): '''Note: none of these expressions should be polynomial''' return float('inf') From d8f2cceabf281428032fae775b861a5c52b75262 Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 6 Apr 2026 20:01:21 +0800 Subject: [PATCH 2/9] Consolidate ExprLike operator methods Move and centralize operator overloads for ExprLike: __radd__, __sub__, __rsub__, __rmul__, __neg__ and __richcmp__ are added earlier in the class and the duplicate implementations later in the file were removed. This refactor cleans up the class definition and avoids duplicated method definitions. --- src/pyscipopt/expr.pxi | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 5c4d15e18..4c22ebaf2 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -261,6 +261,24 @@ cdef class ExprLike: return NotImplemented + def __radd__(self, other): + return self + other + + def __sub__(self, other): + return self + (-other) + + def __rsub__(self, other): + return (-self) + other + + def __rmul__(self, other): + return self * other + + def __richcmp__(self, other, int op): + return _expr_richcmp(self, other, op) + + def __neg__(self): + return self * -1.0 + def __abs__(self) -> GenExpr: return UnaryExpr(Operator.fabs, buildGenExprObj(self)) @@ -279,24 +297,6 @@ cdef class ExprLike: def cos(self) -> GenExpr: return UnaryExpr(Operator.cos, buildGenExprObj(self)) - def __neg__(self): - return self * -1.0 - - def __radd__(self, other): - return self + other - - def __sub__(self, other): - return self + (-other) - - def __rsub__(self, other): - return (-self) + other - - def __rmul__(self, other): - return self * other - - def __richcmp__(self, other, int op): - return _expr_richcmp(self, other, op) - ##@details Polynomial expressions of variables with operator overloading. \n #See also the @ref ExprDetails "description" in the expr.pxi. From d140fb7ccbd70ea2c5a0743c8e1003a3c21b4e84 Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 6 Apr 2026 20:02:24 +0800 Subject: [PATCH 3/9] Move __rtruediv__ to ExprLike base class Consolidate reflected true-division handling by adding __rtruediv__ to the ExprLike base class and removing duplicate implementations from Expr and GenExpr. The reflected division now uniformly uses buildGenExprObj(other) / self, reducing code duplication and ensuring consistent behavior across expression types. --- src/pyscipopt/expr.pxi | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 4c22ebaf2..e9ff02f42 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -273,6 +273,9 @@ cdef class ExprLike: def __rmul__(self, other): return self * other + def __rtruediv__(self, other): + return buildGenExprObj(other) / self + def __richcmp__(self, other, int op): return _expr_richcmp(self, other, op) @@ -401,10 +404,6 @@ cdef class Expr(ExprLike): selfexpr = buildGenExprObj(self) return selfexpr.__truediv__(other) - def __rtruediv__(self, other): - ''' other / self ''' - return buildGenExprObj(other) / self - def __pow__(self, other, modulo): if float(other).is_integer() and other >= 0: exp = int(other) @@ -713,11 +712,6 @@ cdef class GenExpr(ExprLike): raise ZeroDivisionError("cannot divide by 0") return self * divisor**(-1) - def __rtruediv__(self, other): - ''' other / self ''' - otherexpr = buildGenExprObj(other) - return otherexpr.__truediv__(self) - def degree(self): '''Note: none of these expressions should be polynomial''' return float('inf') From 69989448fdd39edb02a10e7f08b42d95f47e64e7 Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 6 Apr 2026 20:05:21 +0800 Subject: [PATCH 4/9] Update changelog: move magic methods to ExprLike Document a refactor that moves several dunder methods (__radd__, __sub__, __rsub__, __rmul__, __richcmp__, __neg__, __rtruediv__) into the ExprLike base class to centralize operator behavior. This change only updates CHANGELOG.md to record the API/internal restructuring. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9e1944cf..5ad4a3255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Changed - Speed up `constant * Expr` via C-level API - Speed up `Term.__eq__` via the C-level API +- Move `__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__` to `ExprLike` base class ### Removed - Removed outdated warning about Make build system incompatibility - Removed `Term.ptrtuple` to optimize `Term` memory usage From 9d2d2512795c7b730d58e93ca2b9b53b86ebe5d1 Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 6 Apr 2026 20:05:53 +0800 Subject: [PATCH 5/9] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad4a3255..a8fcdcf62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changed - Speed up `constant * Expr` via C-level API - Speed up `Term.__eq__` via the C-level API -- Move `__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__` to `ExprLike` base class +- Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class ### Removed - Removed outdated warning about Make build system incompatibility - Removed `Term.ptrtuple` to optimize `Term` memory usage From 043b032af450be6fbcdd60b873b00636eb0b0e5b Mon Sep 17 00:00:00 2001 From: 40% Date: Tue, 7 Apr 2026 20:49:12 +0800 Subject: [PATCH 6/9] Mark reflected dunder methods positional-only Add the positional-only marker ('/') to several operator method signatures in ExprLike to prevent passing the operand as a keyword and to align with CPython semantics. Affected methods: __radd__, __sub__, __rsub__, __rmul__, and __rtruediv__. This is a signature-level change only and should not alter runtime behavior. --- 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 e9ff02f42..83ef4b5c9 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -261,19 +261,19 @@ cdef class ExprLike: return NotImplemented - def __radd__(self, other): + def __radd__(self, other, /): return self + other - def __sub__(self, other): + def __sub__(self, other, /): return self + (-other) - def __rsub__(self, other): + def __rsub__(self, other, /): return (-self) + other - def __rmul__(self, other): + def __rmul__(self, other, /): return self * other - def __rtruediv__(self, other): + def __rtruediv__(self, other, /): return buildGenExprObj(other) / self def __richcmp__(self, other, int op): From 11d18017c72b773ab462e5013a7bf4ae0452e638 Mon Sep 17 00:00:00 2001 From: 40% Date: Tue, 7 Apr 2026 20:54:21 +0800 Subject: [PATCH 7/9] Consolidate arithmetic dunders into ExprLike Update src/pyscipopt/scip.pyi: add missing arithmetic/operator dunder declarations (__radd__, __sub__, __rsub__, __rmul__, __rtruediv__, __neg__) to the ExprLike base stub and remove redundant/operator declarations from Expr and GenExpr. This consolidates common operator signatures in the base protocol, reduces duplication, and improves typing/stub consistency. --- src/pyscipopt/scip.pyi | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 741e35ff9..2db1eba6c 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -333,6 +333,12 @@ class ExprLike: *args: Incomplete, **kwargs: Incomplete, ) -> Incomplete: ... + def __radd__(self, other: Incomplete, /) -> Incomplete: ... + def __sub__(self, other: Incomplete, /) -> Incomplete: ... + def __rsub__(self, other: Incomplete, /) -> Incomplete: ... + def __rmul__(self, other: Incomplete, /) -> Incomplete: ... + def __rtruediv__(self, other: Incomplete, /) -> Incomplete: ... + def __neg__(self) -> Incomplete: ... def __abs__(self) -> GenExpr: ... def exp(self) -> GenExpr: ... def log(self) -> GenExpr: ... @@ -340,13 +346,13 @@ class ExprLike: def sin(self) -> GenExpr: ... def cos(self) -> GenExpr: ... + @disjoint_base class Expr(ExprLike): terms: Incomplete def __init__(self, terms: Incomplete = ...) -> None: ... def degree(self) -> Incomplete: ... def normalize(self) -> Incomplete: ... - def __abs__(self) -> GenExpr: ... def __add__(self, other: Incomplete, /) -> Incomplete: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... @@ -358,14 +364,8 @@ class Expr(ExprLike): def __lt__(self, other: object, /) -> bool: ... def __mul__(self, other: Incomplete, /) -> Incomplete: ... def __ne__(self, other: object, /) -> bool: ... - def __neg__(self) -> Incomplete: ... def __pow__(self, other: Incomplete, modulo: Incomplete = ..., /) -> Incomplete: ... - def __radd__(self, other: Incomplete, /) -> Incomplete: ... - def __rmul__(self, other: Incomplete, /) -> Incomplete: ... def __rpow__(self, other: Incomplete, /) -> Incomplete: ... - def __rsub__(self, other: Incomplete, /) -> Incomplete: ... - def __rtruediv__(self, other: Incomplete, /) -> Incomplete: ... - def __sub__(self, other: Incomplete, /) -> Incomplete: ... def __truediv__(self, other: Incomplete, /) -> Incomplete: ... @disjoint_base @@ -392,7 +392,6 @@ class GenExpr(ExprLike): def __init__(self) -> None: ... def degree(self) -> Incomplete: ... def getOp(self) -> Incomplete: ... - def __abs__(self) -> GenExpr: ... def __add__(self, other: Incomplete, /) -> Incomplete: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... @@ -401,14 +400,8 @@ class GenExpr(ExprLike): def __lt__(self, other: object, /) -> bool: ... def __mul__(self, other: Incomplete, /) -> Incomplete: ... def __ne__(self, other: object, /) -> bool: ... - def __neg__(self) -> Incomplete: ... def __pow__(self, other: Incomplete, modulo: Incomplete = ..., /) -> Incomplete: ... - def __radd__(self, other: Incomplete, /) -> Incomplete: ... - def __rmul__(self, other: Incomplete, /) -> Incomplete: ... def __rpow__(self, other: Incomplete, /) -> Incomplete: ... - def __rsub__(self, other: Incomplete, /) -> Incomplete: ... - def __rtruediv__(self, other: Incomplete, /) -> Incomplete: ... - def __sub__(self, other: Incomplete, /) -> Incomplete: ... def __truediv__(self, other: Incomplete, /) -> Incomplete: ... @disjoint_base From b814b400c68c409cea3e3cd84046b01b883530e1 Mon Sep 17 00:00:00 2001 From: 40% Date: Tue, 7 Apr 2026 20:54:39 +0800 Subject: [PATCH 8/9] Update CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8fcdcf62..dcf1aa014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changed - Speed up `constant * Expr` via C-level API - Speed up `Term.__eq__` via the C-level API -- Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class +- Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class ### Removed - Removed outdated warning about Make build system incompatibility - Removed `Term.ptrtuple` to optimize `Term` memory usage From c7f6715b725519fc97e41be14cf4f3d2d260b7b5 Mon Sep 17 00:00:00 2001 From: 40% Date: Tue, 7 Apr 2026 21:03:35 +0800 Subject: [PATCH 9/9] style: format scip.pyi with ruff Delete an extraneous blank line between the end of ExprLike and the @disjoint_base decorator/Expr class in src/pyscipopt/scip.pyi to tidy file formatting. No functional changes. --- src/pyscipopt/scip.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 2db1eba6c..c46527a62 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -346,7 +346,6 @@ class ExprLike: def sin(self) -> GenExpr: ... def cos(self) -> GenExpr: ... - @disjoint_base class Expr(ExprLike): terms: Incomplete