Skip to content

Commit 9547994

Browse files
Zeroto521CopilotJoao-Dionisio
authored
Move same magic methods from Expr and GenExpr to ExprLike (#1204)
* 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. * 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. * 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. * 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. * Update CHANGELOG.md * 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. * 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. * Update CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * 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. * Refine operator type hints in scip.pyi Replace Incomplete with object for several ExprLike operator stubs (__radd__, __sub__, __rsub__, __rmul__, __rtruediv__, __neg__) to relax/standardize return and operand typing. Also add missing __rtruediv__ stubs to Expr and GenExpr. These changes improve the type stubs for static type checkers and better reflect Python operator semantics. * Update CHANGELOG.md * Make ExprLike.__neg__ positional-only Add the positional-only marker (/) to the ExprLike.__neg__ signature in src/pyscipopt/expr.pxi. This enforces that the unary negation method cannot be called with keyword arguments and aligns the signature with Python/Cython expectations, avoiding potential warnings or misuse. * Refine ExprLike operator return types Update type stubs in src/pyscipopt/scip.pyi to change return types of several ExprLike dunder operators from object to Incomplete for improved typing precision. Affected methods: __radd__, __sub__, __rsub__, __rmul__, __rtruediv__, and __neg__. This is a type-only change with no runtime behavior modifications. * Annotate __rtruediv__ return type as GenExpr Add explicit GenExpr return annotations for __rtruediv__ across implementation and typing stub. Updated src/pyscipopt/expr.pxi to annotate ExprLike, Expr and GenExpr __rtruediv__ methods with -> GenExpr, and updated src/pyscipopt/scip.pyi to change the stub return types from object to GenExpr for Expr.__rtruediv__ and GenExpr.__rtruediv__. This improves static typing consistency between the Cython implementation and the Python stubs. * Annotate __neg__ return type Update negation operator type hints to return Union[Expr, GenExpr]. - src/pyscipopt/expr.pxi: add a Cython return annotation for __neg__. - src/pyscipopt/scip.pyi: change __neg__ from Incomplete to Union[Expr, GenExpr]. These changes improve static typing and IDE support by accurately describing the result of unary negation on expression-like objects. * Unify __rtruediv__ typing in stubs Update typing in src/pyscipopt/scip.pyi: change ExprLike.__rtruediv__ return type from Incomplete to GenExpr and remove duplicate __rtruediv__ declarations from Expr and GenExpr. This consolidates the right-division signature in the base ExprLike class to avoid conflicting or redundant stub definitions and improve type consistency. * Mark __neg__ as positional-only in scip.pyi Update the typing stub for ExprLike.__neg__ in src/pyscipopt/scip.pyi to include the positional-only marker (/). This clarifies that __neg__ accepts no keyword arguments and returns Union[Expr, GenExpr]; no runtime behavior changes. * Resolve CHANGELOG merge conflict --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com> Co-authored-by: Joao-Dionisio <joao.goncalves.dionisio@gmail.com>
1 parent 2e5d929 commit 9547994

3 files changed

Lines changed: 32 additions & 59 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Added `addConsCumulative()` for SCIP cumulative constraints (#1222)
66
### Fixed
77
### Changed
8+
- Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class (#1204)
89
- Speed up `Expr.__add__` and `Expr.__iadd__` via the C-level API
910
### Removed
1011

src/pyscipopt/expr.pxi

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,27 @@ cdef class ExprLike:
257257

258258
return NotImplemented
259259

260+
def __radd__(self, other, /):
261+
return self + other
262+
263+
def __sub__(self, other, /):
264+
return self + (-other)
265+
266+
def __rsub__(self, other, /):
267+
return (-self) + other
268+
269+
def __rmul__(self, other, /):
270+
return self * other
271+
272+
def __rtruediv__(self, other, /) -> GenExpr:
273+
return buildGenExprObj(other) / self
274+
275+
def __richcmp__(self, other, int op):
276+
return _expr_richcmp(self, other, op)
277+
278+
def __neg__(self, /) -> Union[Expr, GenExpr]:
279+
return self * -1.0
280+
260281
def __abs__(self) -> GenExpr:
261282
return UnaryExpr(Operator.fabs, buildGenExprObj(self))
262283

@@ -358,11 +379,10 @@ cdef class Expr(ExprLike):
358379
return 1.0 / other * self
359380
return buildGenExprObj(self) / other
360381

361-
def __rtruediv__(self, other):
362-
''' other / self '''
382+
def __rtruediv__(self, other, /) -> GenExpr:
363383
if not _is_expr_compatible(other):
364384
return NotImplemented
365-
return buildGenExprObj(other) / self
385+
return super().__rtruediv__(other)
366386

367387
def __pow__(self, other, modulo):
368388
if float(other).is_integer() and other >= 0:
@@ -387,25 +407,6 @@ cdef class Expr(ExprLike):
387407
raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x * scip.log(a)); got %g" % base)
388408
return (self * Constant(base).log()).exp()
389409

390-
def __neg__(self):
391-
return Expr({v:-c for v,c in self.terms.items()})
392-
393-
def __sub__(self, other):
394-
return self + (-other)
395-
396-
def __radd__(self, other):
397-
return self.__add__(other)
398-
399-
def __rmul__(self, other):
400-
return self.__mul__(other)
401-
402-
def __rsub__(self, other):
403-
return -1.0 * self + other
404-
405-
def __richcmp__(self, other, int op):
406-
'''turn it into a constraint'''
407-
return _expr_richcmp(self, other, op)
408-
409410
def normalize(self):
410411
'''remove terms with coefficient of 0'''
411412
self.terms = {t:c for (t,c) in self.terms.items() if c != 0.0}
@@ -464,7 +465,6 @@ cdef class ExprCons:
464465
if not self._rhs is None:
465466
self._rhs -= c
466467

467-
468468
def __richcmp__(self, other, op):
469469
'''turn it into a constraint'''
470470
if not _is_number(other):
@@ -690,30 +690,10 @@ cdef class GenExpr(ExprLike):
690690
raise ZeroDivisionError("cannot divide by 0")
691691
return self * divisor**(-1)
692692

693-
def __rtruediv__(self, other):
694-
''' other / self '''
693+
def __rtruediv__(self, other, /) -> GenExpr:
695694
if not _is_genexpr_compatible(other):
696695
return NotImplemented
697-
return buildGenExprObj(other) / self
698-
699-
def __neg__(self):
700-
return -1.0 * self
701-
702-
def __sub__(self, other):
703-
return self + (-other)
704-
705-
def __radd__(self, other):
706-
return self.__add__(other)
707-
708-
def __rmul__(self, other):
709-
return self.__mul__(other)
710-
711-
def __rsub__(self, other):
712-
return -1.0 * self + other
713-
714-
def __richcmp__(self, other, int op):
715-
'''turn it into a constraint'''
716-
return _expr_richcmp(self, other, op)
696+
return super().__rtruediv__(other)
717697

718698
def degree(self):
719699
'''Note: none of these expressions should be polynomial'''

src/pyscipopt/scip.pyi

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,12 @@ class ExprLike:
332332
*args: Incomplete,
333333
**kwargs: Incomplete,
334334
) -> Incomplete: ...
335+
def __radd__(self, other: object, /) -> Incomplete: ...
336+
def __sub__(self, other: object, /) -> Incomplete: ...
337+
def __rsub__(self, other: object, /) -> Incomplete: ...
338+
def __rmul__(self, other: object, /) -> Incomplete: ...
339+
def __rtruediv__(self, other: object, /) -> GenExpr: ...
340+
def __neg__(self, /) -> Union[Expr, GenExpr]: ...
335341
def __abs__(self) -> GenExpr: ...
336342
def exp(self) -> GenExpr: ...
337343
def log(self) -> GenExpr: ...
@@ -345,7 +351,6 @@ class Expr(ExprLike):
345351
def __init__(self, terms: Incomplete = ...) -> None: ...
346352
def degree(self) -> Incomplete: ...
347353
def normalize(self) -> Incomplete: ...
348-
def __abs__(self) -> GenExpr: ...
349354
def __add__(self, other: Incomplete, /) -> Incomplete: ...
350355
def __eq__(self, other: object, /) -> bool: ...
351356
def __ge__(self, other: object, /) -> bool: ...
@@ -357,14 +362,8 @@ class Expr(ExprLike):
357362
def __lt__(self, other: object, /) -> bool: ...
358363
def __mul__(self, other: Incomplete, /) -> Incomplete: ...
359364
def __ne__(self, other: object, /) -> bool: ...
360-
def __neg__(self) -> Incomplete: ...
361365
def __pow__(self, other: Incomplete, modulo: Incomplete = ..., /) -> Incomplete: ...
362-
def __radd__(self, other: Incomplete, /) -> Incomplete: ...
363-
def __rmul__(self, other: Incomplete, /) -> Incomplete: ...
364366
def __rpow__(self, other: Incomplete, /) -> Incomplete: ...
365-
def __rsub__(self, other: Incomplete, /) -> Incomplete: ...
366-
def __rtruediv__(self, other: Incomplete, /) -> Incomplete: ...
367-
def __sub__(self, other: Incomplete, /) -> Incomplete: ...
368367
def __truediv__(self, other: Incomplete, /) -> Incomplete: ...
369368

370369
@disjoint_base
@@ -391,7 +390,6 @@ class GenExpr(ExprLike):
391390
def __init__(self) -> None: ...
392391
def degree(self) -> Incomplete: ...
393392
def getOp(self) -> Incomplete: ...
394-
def __abs__(self) -> GenExpr: ...
395393
def __add__(self, other: Incomplete, /) -> Incomplete: ...
396394
def __eq__(self, other: object, /) -> bool: ...
397395
def __ge__(self, other: object, /) -> bool: ...
@@ -400,14 +398,8 @@ class GenExpr(ExprLike):
400398
def __lt__(self, other: object, /) -> bool: ...
401399
def __mul__(self, other: Incomplete, /) -> Incomplete: ...
402400
def __ne__(self, other: object, /) -> bool: ...
403-
def __neg__(self) -> Incomplete: ...
404401
def __pow__(self, other: Incomplete, modulo: Incomplete = ..., /) -> Incomplete: ...
405-
def __radd__(self, other: Incomplete, /) -> Incomplete: ...
406-
def __rmul__(self, other: Incomplete, /) -> Incomplete: ...
407402
def __rpow__(self, other: Incomplete, /) -> Incomplete: ...
408-
def __rsub__(self, other: Incomplete, /) -> Incomplete: ...
409-
def __rtruediv__(self, other: Incomplete, /) -> Incomplete: ...
410-
def __sub__(self, other: Incomplete, /) -> Incomplete: ...
411403
def __truediv__(self, other: Incomplete, /) -> Incomplete: ...
412404

413405
@disjoint_base

0 commit comments

Comments
 (0)