Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6874ae4
Consolidate operator overloads into ExprLike
Zeroto521 Apr 6, 2026
d8f2cce
Consolidate ExprLike operator methods
Zeroto521 Apr 6, 2026
d140fb7
Move __rtruediv__ to ExprLike base class
Zeroto521 Apr 6, 2026
6998944
Update changelog: move magic methods to ExprLike
Zeroto521 Apr 6, 2026
9d2d251
Update CHANGELOG.md
Zeroto521 Apr 6, 2026
043b032
Mark reflected dunder methods positional-only
Zeroto521 Apr 7, 2026
11d1801
Consolidate arithmetic dunders into ExprLike
Zeroto521 Apr 7, 2026
b814b40
Update CHANGELOG.md
Zeroto521 Apr 7, 2026
27ff00f
Merge branch 'ExprLike' of github.com:Zeroto521/PySCIPOpt into ExprLike
Zeroto521 Apr 7, 2026
c7f6715
style: format scip.pyi with ruff
Zeroto521 Apr 7, 2026
483528a
Merge branch 'scipopt:master' into ExprLike
Zeroto521 Apr 24, 2026
d88206c
Merge branch 'master' into ExprLike
Zeroto521 May 20, 2026
7dbf944
Refine operator type hints in scip.pyi
Zeroto521 May 20, 2026
8e8bf59
Update CHANGELOG.md
Zeroto521 May 20, 2026
64e6947
Merge branch 'master' into ExprLike
Joao-Dionisio May 20, 2026
6ca96c9
Make ExprLike.__neg__ positional-only
Zeroto521 May 23, 2026
823b94c
Refine ExprLike operator return types
Zeroto521 May 23, 2026
f21d708
Merge branch 'ExprLike' of github.com:Zeroto521/PySCIPOpt into ExprLike
Zeroto521 May 23, 2026
ec64ba2
Annotate __rtruediv__ return type as GenExpr
Zeroto521 May 23, 2026
9fdca93
Annotate __neg__ return type
Zeroto521 May 23, 2026
61cea57
Unify __rtruediv__ typing in stubs
Zeroto521 May 23, 2026
dcd64b9
Merge branch 'master' into ExprLike
Zeroto521 May 23, 2026
aa6ae86
Mark __neg__ as positional-only in scip.pyi
Zeroto521 May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
### Added
### Fixed
### Changed
<<<<<<< ExprLike
- Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class
=======
- Speed up `Expr.__add__` and `Expr.__iadd__` via the C-level API
>>>>>>> master
### Removed

## 6.2.1 - 2026.05.16
Expand Down
70 changes: 25 additions & 45 deletions src/pyscipopt/expr.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,27 @@ 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 __rtruediv__(self, other, /) -> GenExpr:
return buildGenExprObj(other) / self

def __richcmp__(self, other, int op):
return _expr_richcmp(self, other, op)

def __neg__(self, /) -> Union[Expr, GenExpr]:
return self * -1.0

def __abs__(self) -> GenExpr:
return UnaryExpr(Operator.fabs, buildGenExprObj(self))

Expand Down Expand Up @@ -358,11 +379,10 @@ cdef class Expr(ExprLike):
return 1.0 / other * self
return buildGenExprObj(self) / other

def __rtruediv__(self, other):
''' other / self '''
def __rtruediv__(self, other, /) -> GenExpr:
if not _is_expr_compatible(other):
return NotImplemented
return buildGenExprObj(other) / self
return super().__rtruediv__(other)

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

def __neg__(self):
return Expr({v:-c for v,c in self.terms.items()})
Copy link
Copy Markdown
Contributor Author

@Zeroto521 Zeroto521 May 23, 2026

Choose a reason for hiding this comment

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

This benefits from #1185:

  • master PR calls __neg__ for Expr via Python list comprehension: 4.7777s on the following example
  • this PR calls __neg__ for Expr via C-API: 3.8283s on the same example
from timeit import timeit

from pyscipopt import Model


m = Model()

n = 1000
x = m.addMatrixVar(n)
e = x.sum()

number = 100_000
cost = timeit(lambda: -e, number=number)
print(f"Cost of negating an expression over {number} runs: {cost:.4f} seconds")


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, int 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}
Expand Down Expand Up @@ -464,7 +465,6 @@ cdef class ExprCons:
if not self._rhs is None:
self._rhs -= c


def __richcmp__(self, other, op):
'''turn it into a constraint'''
if not _is_number(other):
Expand Down Expand Up @@ -690,30 +690,10 @@ cdef class GenExpr(ExprLike):
raise ZeroDivisionError("cannot divide by 0")
return self * divisor**(-1)

def __rtruediv__(self, other):
''' other / self '''
def __rtruediv__(self, other, /) -> GenExpr:
if not _is_genexpr_compatible(other):
return NotImplemented
return buildGenExprObj(other) / self

def __neg__(self):
return -1.0 * self
Comment on lines -699 to -700
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This should be a bit faster now.

  • master PR calls __neg__ on GenExpr: GenExpr.__neg__()(-1.0).__mul__(GenExpr)GenExpr.__rmul__(-1.0)
  • this PR calls __neg__ on GenExpr via C-API: GenExpr.__neg__()GenExpr.__rmul__(-1.0)


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, int op):
'''turn it into a constraint'''
return _expr_richcmp(self, other, op)
return super().__rtruediv__(other)

def degree(self):
'''Note: none of these expressions should be polynomial'''
Expand Down
20 changes: 6 additions & 14 deletions src/pyscipopt/scip.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ class ExprLike:
*args: Incomplete,
**kwargs: Incomplete,
) -> Incomplete: ...
def __radd__(self, other: object, /) -> Incomplete: ...
def __sub__(self, other: object, /) -> Incomplete: ...
def __rsub__(self, other: object, /) -> Incomplete: ...
def __rmul__(self, other: object, /) -> Incomplete: ...
def __rtruediv__(self, other: object, /) -> GenExpr: ...
def __neg__(self, /) -> Union[Expr, GenExpr]: ...
def __abs__(self) -> GenExpr: ...
def exp(self) -> GenExpr: ...
def log(self) -> GenExpr: ...
Expand All @@ -344,7 +350,6 @@ class Expr(ExprLike):
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: ...
Expand All @@ -356,14 +361,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: ...

Comment thread
Zeroto521 marked this conversation as resolved.
@disjoint_base
Expand All @@ -390,7 +389,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: ...
Expand All @@ -399,14 +397,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
Expand Down
Loading