Skip to content

Commit 8b1d1f5

Browse files
Speed up constant * Expr (#1185)
* Speed up `constant * Expr` Replace local prod_v with coef and refactor the numeric-scaling branch to iterate with PyDict_Next instead of a Python dict comprehension. Reuse coef for the product of term coefficients in the Expr * Expr branch and update res assignments accordingly. This simplifies the code and avoids creating intermediate Python dicts during scaling, improving clarity and likely performance. * Document speedup for constant * Expr Add a line to CHANGELOG.md noting a performance improvement: "Speed up `constant * Expr` via C-level API". This documents an optimization that accelerates multiplication of constants by Expr using the C-level API for the upcoming 6.0.0 release. * Add tests for Expr multiplication Extend tests/test_expr.py (test_mul) to cover multiplication of Expr by a scalar and mark Expr*Expr cases. Adds an assertion verifying (x + y) * 2.0 produces the expected string representation and a comment indicating Expr * Expr tests. * Add test for scalar * Expr multiplication Add an assertion in tests/test_expr.py to verify left-side scalar multiplication works: assert str(2.0 * (x + y)) == "Expr({Term(x): 2.0, Term(y): 2.0})". This ensures that multiplying an Expr by a scalar on the left yields the same result and string representation as Expr * scalar. * Merge branch 'master' into expr/__mul__ --------- Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com>
1 parent 75ccba9 commit 8b1d1f5

File tree

3 files changed

+14
-6
lines changed

3 files changed

+14
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Removed `Py_INCREF`/`Py_DECREF` on `Model` in `catchEvent`/`dropEvent` that caused memory leak for imbalanced usage
99
- Used `getIndex()` instead of `ptr()` for sorting nonlinear expression terms to avoid nondeterministic behavior
1010
### Changed
11+
- Speed up `constant * Expr` via C-level API
1112
### Removed
1213
- Removed outdated warning about Make build system incompatibility
1314

src/pyscipopt/expr.pxi

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,22 +289,24 @@ cdef class Expr:
289289
cdef PyObject *v2_ptr = NULL
290290
cdef PyObject *old_v_ptr = NULL
291291
cdef Term child
292-
cdef double prod_v
292+
cdef double coef
293293

294294
if _is_number(other):
295-
f = float(other)
296-
return Expr({v:f*c for v,c in self.terms.items()})
295+
coef = float(other)
296+
while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr):
297+
res[<Term>k1_ptr] = <double>(<object>v1_ptr) * coef
298+
return Expr(res)
297299

298300
elif isinstance(other, Expr):
299301
while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr):
300302
pos2 = <Py_ssize_t>0
301303
while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr):
302304
child = (<Term>k1_ptr) * (<Term>k2_ptr)
303-
prod_v = (<double>(<object>v1_ptr)) * (<double>(<object>v2_ptr))
305+
coef = (<double>(<object>v1_ptr)) * (<double>(<object>v2_ptr))
304306
if (old_v_ptr := PyDict_GetItem(res, child)) != NULL:
305-
res[child] = <double>(<object>old_v_ptr) + prod_v
307+
res[child] = <double>(<object>old_v_ptr) + coef
306308
else:
307-
res[child] = prod_v
309+
res[child] = coef
308310
return Expr(res)
309311

310312
elif isinstance(other, GenExpr):

tests/test_expr.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ def test_mul():
224224
x = m.addVar(name="x")
225225
y = m.addVar(name="y")
226226

227+
# test Expr * number
228+
assert str((x + y) * 2.0) == "Expr({Term(x): 2.0, Term(y): 2.0})"
229+
assert str(2.0 * (x + y)) == "Expr({Term(x): 2.0, Term(y): 2.0})"
230+
231+
# test Expr * Expr
227232
assert str(Expr({CONST: 1.0}) * x) == "Expr({Term(x): 1.0})"
228233
assert str(y * Expr({CONST: -1.0})) == "Expr({Term(y): -1.0})"
229234
assert str((x - x) * y) == "Expr({Term(x, y): 0.0})"

0 commit comments

Comments
 (0)