Skip to content

Commit 64e6947

Browse files
Merge branch 'master' into ExprLike
2 parents 8e8bf59 + aa0e243 commit 64e6947

3 files changed

Lines changed: 60 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
### Added
55
### Fixed
66
### Changed
7+
<<<<<<< ExprLike
78
- Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class
9+
=======
10+
- Speed up `Expr.__add__` and `Expr.__iadd__` via the C-level API
11+
>>>>>>> master
812
### Removed
913

1014
## 6.2.1 - 2026.05.16

src/pyscipopt/expr.pxi

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ from cpython.object cimport Py_LE, Py_EQ, Py_GE, Py_TYPE
5555
from cpython.ref cimport PyObject
5656
from cpython.tuple cimport PyTuple_GET_ITEM
5757

58+
cimport numpy as cnp
5859
from pyscipopt.scip cimport Variable, Solution
5960

6061

@@ -321,29 +322,22 @@ cdef class Expr(ExprLike):
321322
if not _is_expr_compatible(other):
322323
return NotImplemented
323324

324-
left = self
325-
right = other
326-
terms = left.terms.copy()
325+
if _is_number(other):
326+
terms = self.terms.copy()
327+
terms[CONST] = terms.get(CONST, 0.0) + <double>other
328+
return Expr(terms)
327329

328-
if isinstance(right, Expr):
329-
# merge the terms by component-wise addition
330-
for v,c in right.terms.items():
331-
terms[v] = terms.get(v, 0.0) + c
332-
elif _is_number(right):
333-
c = float(right)
334-
terms[CONST] = terms.get(CONST, 0.0) + c
335-
return Expr(terms)
330+
return Expr(_to_dict(self, other, copy=True))
336331

337332
def __iadd__(self, other):
338333
if not _is_expr_compatible(other):
339334
return NotImplemented
340335

341-
if isinstance(other, Expr):
342-
for v,c in other.terms.items():
343-
self.terms[v] = self.terms.get(v, 0.0) + c
344-
elif _is_number(other):
345-
c = float(other)
346-
self.terms[CONST] = self.terms.get(CONST, 0.0) + c
336+
if _is_number(other):
337+
self.terms[CONST] = self.terms.get(CONST, 0.0) + <double>other
338+
else:
339+
_to_dict(self, other, copy=False)
340+
347341
return self
348342

349343
def __mul__(self, other):
@@ -999,6 +993,26 @@ cdef inline object _ensure_matrix(object arg):
999993
matrix = MatrixExpr if isinstance(arg, Expr) else MatrixGenExpr
1000994
return np.array(arg, dtype=object).view(matrix)
1001995

996+
cdef dict _to_dict(Expr expr, Expr other, bool copy = True):
997+
cdef dict children = expr.terms.copy() if copy else expr.terms
998+
cdef Py_ssize_t pos = <Py_ssize_t>0
999+
cdef PyObject* k_ptr = NULL
1000+
cdef PyObject* v_ptr = NULL
1001+
cdef PyObject* old_v_ptr = NULL
1002+
cdef double other_v
1003+
cdef object k_obj
1004+
1005+
while PyDict_Next(other.terms, &pos, &k_ptr, &v_ptr):
1006+
other_v = <double>(<object>v_ptr)
1007+
k_obj = <object>k_ptr
1008+
old_v_ptr = PyDict_GetItem(children, k_obj)
1009+
if old_v_ptr != NULL:
1010+
children[k_obj] = <double>(<object>old_v_ptr) + other_v
1011+
else:
1012+
children[k_obj] = other_v
1013+
1014+
return children
1015+
10021016

10031017
def expr_to_nodes(expr):
10041018
'''transforms tree to an array of nodes. each node is an operator and the position of the

tests/test_expr.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,28 @@ def test_term_eq():
522522
assert t3 != t4 # same length, but different term
523523
assert t1 != t3 # different length
524524
assert t1 != "not a term" # different type
525+
526+
527+
def test_Expr_add_Expr():
528+
m = Model()
529+
x = m.addVar(name="x")
530+
y = m.addVar(name="y")
531+
532+
e1 = -x + 1
533+
e2 = y - 1
534+
e3 = e1 + e2
535+
assert str(e1) == "Expr({Term(x): -1.0, Term(): 1.0})"
536+
assert str(e2) == "Expr({Term(y): 1.0, Term(): -1.0})"
537+
assert str(e3) == "Expr({Term(x): -1.0, Term(): 0.0, Term(y): 1.0})"
538+
539+
540+
def test_Expr_iadd_Expr():
541+
m = Model()
542+
x = m.addVar(name="x")
543+
y = m.addVar(name="y")
544+
545+
e1 = -x + 1
546+
e2 = y - 1
547+
e1 += e2
548+
assert str(e1) == "Expr({Term(x): -1.0, Term(): 0.0, Term(y): 1.0})"
549+
assert str(e2) == "Expr({Term(y): 1.0, Term(): -1.0})"

0 commit comments

Comments
 (0)