Skip to content

Commit 968cda4

Browse files
committed
Merge remote-tracking branch 'upstream/master' into expr/notimplemented
2 parents 51a2183 + 8b1d1f5 commit 968cda4

3 files changed

Lines changed: 24 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Used `getIndex()` instead of `ptr()` for sorting nonlinear expression terms to avoid nondeterministic behavior
1010
### Changed
1111
- Return NotImplemented for `Expr` and `GenExpr` operators, if they can't handle input types in the calculation
12+
- Speed up `constant * Expr` via C-level API
1213
### Removed
1314
- Removed outdated warning about Make build system incompatibility
1415

src/pyscipopt/expr.pxi

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,6 @@ cdef class Expr:
248248
if not isinstance(other, EXPR_OP_TYPES):
249249
return NotImplemented
250250

251-
if _is_number(other):
252-
f = float(other)
253-
return Expr({v: f * c for v, c in self.terms.items()})
254-
255251
cdef dict res = {}
256252
cdef Py_ssize_t pos1 = <Py_ssize_t>0, pos2 = <Py_ssize_t>0
257253
cdef PyObject *k1_ptr = NULL
@@ -260,25 +256,31 @@ cdef class Expr:
260256
cdef PyObject *v2_ptr = NULL
261257
cdef PyObject *old_v_ptr = NULL
262258
cdef Term child
263-
cdef double prod_v
264-
265-
while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr):
266-
pos2 = <Py_ssize_t>0
267-
while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr):
268-
child = (<Term>k1_ptr) * (<Term>k2_ptr)
269-
prod_v = (<double>(<object>v1_ptr)) * (<double>(<object>v2_ptr))
270-
if (old_v_ptr := PyDict_GetItem(res, child)) != NULL:
271-
res[child] = <double>(<object>old_v_ptr) + prod_v
272-
else:
273-
res[child] = prod_v
259+
cdef double coef
260+
261+
if _is_number(other):
262+
coef = <double>other
263+
while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr):
264+
res[<Term>k1_ptr] = <double>(<object>v1_ptr) * coef
265+
266+
elif isinstance(other, Expr):
267+
while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr):
268+
pos2 = <Py_ssize_t>0
269+
while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr):
270+
child = (<Term>k1_ptr) * (<Term>k2_ptr)
271+
coef = (<double>(<object>v1_ptr)) * (<double>(<object>v2_ptr))
272+
if (old_v_ptr := PyDict_GetItem(res, child)) != NULL:
273+
res[child] = <double>(<object>old_v_ptr) + coef
274+
else:
275+
res[child] = coef
274276
return Expr(res)
275277

276278
def __truediv__(self, other):
277279
if not isinstance(other, EXPR_OP_TYPES):
278280
return NotImplemented
279281

280282
if _is_number(other):
281-
return 1.0 / other * self
283+
return 1.0 / <double>other * self
282284
return buildGenExprObj(self) / other
283285

284286
def __rtruediv__(self, other):

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)