From 08b11b7d110f3a1daad26e11598cfd3e225415b9 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 15:36:47 +0800 Subject: [PATCH 01/16] Refactor Expr multiplication logic and Term operator Replaces Term.__add__ with Term.__mul__ and updates Expr.__mul__ to use more efficient Cython dict iteration and item access. This improves performance and correctness when multiplying expressions, especially for large term dictionaries. --- src/pyscipopt/expr.pxi | 44 +++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 07d6ab031..e49a94f88 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -45,9 +45,9 @@ import math from typing import TYPE_CHECKING -from pyscipopt.scip cimport Variable, Solution -from cpython.dict cimport PyDict_Next +from cpython.dict cimport PyDict_Next, PyDict_GetItem from cpython.ref cimport PyObject +from pyscipopt.scip cimport Variable, Solution import numpy as np @@ -122,9 +122,8 @@ cdef class Term: def __len__(self): return len(self.vartuple) - def __add__(self, other): - both = self.vartuple + other.vartuple - return Term(*both) + def __mul__(self, other): + return Term(*(self.vartuple + other.vartuple)) def __repr__(self): return 'Term(%s)' % ', '.join([str(v) for v in self.vartuple]) @@ -251,19 +250,42 @@ cdef class Expr: if isinstance(other, np.ndarray): return other * self + cdef dict res = {} + cdef Py_ssize_t pos1 = 0, pos2 = 0 + cdef PyObject *k1_ptr = NULL + cdef PyObject *v1_ptr = NULL + cdef PyObject *k2_ptr = NULL + cdef PyObject *v2_ptr = NULL + cdef PyObject *old_v_ptr = NULL + cdef Term child + cdef double v1_val, v2_val, prod_v + if _is_number(other): f = float(other) return Expr({v:f*c for v,c in self.terms.items()}) + elif _is_number(self): f = float(self) return Expr({v:f*c for v,c in other.terms.items()}) + elif isinstance(other, Expr): - terms = {} - for v1, c1 in self.terms.items(): - for v2, c2 in other.terms.items(): - v = v1 + v2 - terms[v] = terms.get(v, 0.0) + c1 * c2 - return Expr(terms) + while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): + if (v1_val := (v1_ptr)) == 0: + continue + + pos2 = 0 + while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr): + if (v2_val := (v2_ptr)) == 0: + continue + + child = (k1_ptr) * (k2_ptr) + prod_v = v1_val * v2_val + if (old_v_ptr := PyDict_GetItem(res, child)) != NULL: + res[child] = (old_v_ptr) + prod_v + else: + res[child] = prod_v + return Expr(res) + elif isinstance(other, GenExpr): return buildGenExprObj(self) * other else: From b8e2249f3756d5c7aba493f0865f77e775f1d782 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:01:48 +0800 Subject: [PATCH 02/16] Optimize Term multiplication in expr.pxi Replaces the simple concatenation in Term.__mul__ with an efficient merge that maintains variable order based on pointer values. This improves performance and correctness when multiplying Term objects. --- src/pyscipopt/expr.pxi | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index e49a94f88..c4257570d 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -46,6 +46,7 @@ import math from typing import TYPE_CHECKING from cpython.dict cimport PyDict_Next, PyDict_GetItem +from cpython.tuple cimport PyTuple_GET_ITEM from cpython.ref cimport PyObject from pyscipopt.scip cimport Variable, Solution @@ -122,8 +123,39 @@ cdef class Term: def __len__(self): return len(self.vartuple) - def __mul__(self, other): - return Term(*(self.vartuple + other.vartuple)) + def __mul__(self, Term other): + cdef int n1 = len(self) + cdef int n2 = len(other) + if n1 == 0: return other + if n2 == 0: return self + + cdef list vartuple = [None] * (n1 + n2) + cdef int i = 0, j = 0, k = 0 + cdef Variable var1, var2 + while i < n1 and j < n2: + var1 = PyTuple_GET_ITEM(self.vartuple, i) + var2 = PyTuple_GET_ITEM(other.vartuple, j) + if var1.ptr() <= var2.ptr(): + vartuple[k] = var1 + i += 1 + else: + vartuple[k] = var2 + j += 1 + k += 1 + while i < n1: + vartuple[k] = PyTuple_GET_ITEM(self.vartuple, i) + i += 1 + k += 1 + while j < n2: + vartuple[k] = PyTuple_GET_ITEM(other.vartuple, j) + j += 1 + k += 1 + + cdef Term res = Term.__new__(Term) + res.vartuple = tuple(vartuple) + res.ptrtuple = tuple(v.ptr() for v in res.vartuple) + res.hashval = hash(res.ptrtuple) + return res def __repr__(self): return 'Term(%s)' % ', '.join([str(v) for v in self.vartuple]) From 28de39e93dd491f5ddf41d2dea8d76ab101a5d35 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:02:29 +0800 Subject: [PATCH 03/16] Update CHANGELOG to reorder quicksum optimization entry Moved the 'Speed up MatrixExpr.sum(axis=...) via quicksum' entry from the Added section to the Changed section for better categorization and clarity. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfed00f5..c8c982d94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ ### Added - Added automated script for generating type stubs - Include parameter names in type stubs -- Speed up MatrixExpr.sum(axis=...) via quicksum - Added pre-commit hook for automatic stub regeneration (see .pre-commit-config.yaml) - Wrapped isObjIntegral() and test - Added structured_optimization_trace recipe for structured optimization progress tracking @@ -19,6 +18,7 @@ - Fixed segmentation fault when using Variable or Constraint objects after freeTransform() or Model destruction ### Changed - changed default value of enablepricing flag to True +- Speed up MatrixExpr.sum(axis=...) via quicksum - Speed up MatrixExpr.add.reduce via quicksum - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs From 8ec24a419149bf5cda6382b9a483636036581548 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:03:04 +0800 Subject: [PATCH 04/16] Update changelog with Expr multiplication speedup Added a new entry to the changelog noting the performance improvement for Expr * Expr operations. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c982d94..a29495758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Speed up MatrixExpr.sum(axis=...) via quicksum - Speed up MatrixExpr.add.reduce via quicksum - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr +- Speed up Expr * Expr - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs ### Removed From fd06702c00c19c66bc077372765815842aebe31c Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 24 Jan 2026 16:19:37 +0800 Subject: [PATCH 05/16] Fix Term class operator signature in type stub Corrects the Term class in scip.pyi to define __mul__ instead of __add__, updating the method signature to accept and return Term objects. --- src/pyscipopt/scip.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 61c4ba773..31e4ad577 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -2150,7 +2150,7 @@ class Term: ptrtuple: Incomplete vartuple: Incomplete def __init__(self, *vartuple: Incomplete) -> None: ... - def __add__(self, other: Incomplete) -> Incomplete: ... + def __mul__(self, other: Term) -> Term: ... def __eq__(self, other: object) -> bool: ... def __ge__(self, other: object) -> bool: ... def __getitem__(self, index: Incomplete) -> Incomplete: ... From faccb2cf60a225e211d83f4181e8d87f8c884182 Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 29 Jan 2026 09:55:28 +0800 Subject: [PATCH 06/16] Add tests for expression multiplication Introduces test_mul to verify correct string representations of multiplied expressions involving variables and constants. --- tests/test_expr.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_expr.py b/tests/test_expr.py index c9135d2fa..890b2ffbe 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -218,3 +218,19 @@ def test_getVal_with_GenExpr(): with pytest.raises(ZeroDivisionError): m.getVal(1 / z) + + +def test_mul(): + m = Model() + x = m.addVar(name="x") + y = m.addVar(name="y") + + + assert ( + str((x + 1) * (y - 1)) + == "Expr({Term(x, y): 1.0, Term(x): -1.0, Term(y): 1.0, Term(): -1.0})" + ) + assert ( + str((x + 1) * (x + 1) * y) + == "Expr({Term(x, x, y): 1.0, Term(x, y): 2.0, Term(y): 1.0})" + ) From 27b75c1cf39e6b82bc9af559045bf82f8614b38c Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 29 Jan 2026 10:02:38 +0800 Subject: [PATCH 07/16] Update tests for Expr multiplication behavior Replaces Term with CONST import from pyscipopt.scip and adds new assertions in test_mul to verify multiplication involving constants and variables. Removes redundant CONST definition. --- tests/test_expr.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 890b2ffbe..1a6092926 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -3,7 +3,7 @@ import pytest from pyscipopt import Model, sqrt, log, exp, sin, cos -from pyscipopt.scip import Expr, GenExpr, ExprCons, Term +from pyscipopt.scip import Expr, GenExpr, ExprCons, CONST @pytest.fixture(scope="module") @@ -14,7 +14,6 @@ def model(): z = m.addVar("z") return m, x, y, z -CONST = Term() def test_upgrade(model): m, x, y, z = model @@ -225,7 +224,9 @@ def test_mul(): x = m.addVar(name="x") y = m.addVar(name="y") - + assert str(Expr({CONST: 1.0}) * x) == "Expr({Term(x): 1.0})" + assert str(y * Expr({CONST: -1.0})) == "Expr({Term(y): -1.0})" + assert str((x - x) * y) == "Expr({Term(x, y): 0.0})" assert ( str((x + 1) * (y - 1)) == "Expr({Term(x, y): 1.0, Term(x): -1.0, Term(y): 1.0, Term(): -1.0})" From 3e6376dd47b5ea543916b859b722cac8d101b06d Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 29 Jan 2026 10:25:46 +0800 Subject: [PATCH 08/16] Add test for commutativity in multiplication with zero Added an assertion to test that multiplying y by (x - x) yields the same zero term as (x - x) * y. This ensures correct handling of zero expressions in multiplication. --- tests/test_expr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 1a6092926..e4c0f84cf 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -226,7 +226,8 @@ def test_mul(): assert str(Expr({CONST: 1.0}) * x) == "Expr({Term(x): 1.0})" assert str(y * Expr({CONST: -1.0})) == "Expr({Term(y): -1.0})" - assert str((x - x) * y) == "Expr({Term(x, y): 0.0})" + assert str((x - x) * y) == "Expr({Term(): 0.0})" + assert str(y * (x - x)) == "Expr({Term(): 0.0})" assert ( str((x + 1) * (y - 1)) == "Expr({Term(x, y): 1.0, Term(x): -1.0, Term(y): 1.0, Term(): -1.0})" From 923a5f6a756ea48881f5364c7470e485094767a7 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 30 Jan 2026 10:05:52 +0800 Subject: [PATCH 09/16] Update changelog for Expr and Term multiplication improvements Documented performance enhancements for Expr * Expr and Term * Term operations, including use of C-level API and an O(n) algorithm. Also clarified method renaming from Term.__add__ to Term.__mul__. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c307a6ac..d95ad3575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,9 @@ - Speed up MatrixExpr.sum(axis=...) via quicksum - Speed up MatrixExpr.add.reduce via quicksum - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr -- Speed up Expr * Expr +- Speed up Expr * Expr via C-level API and Term * Term +- Speed up Term * Term via $O(n)$ algorithm instead of Python sorted function. `Term.__mul__` requires that Term.vartuple is sorted. +- Rename from `Term.__add__` to `Term.__mul__`, due to this method only works with Expr * Expr. - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs - Set `__array_priority__` for MatrixExpr and MatrixExprCons - changed addConsNode() and addConsLocal() to mirror addCons() and accept ExprCons instead of Constraint From b4b6fbf5cc0f535a3ab288e521a0f91134ce11cc Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 30 Jan 2026 10:08:55 +0800 Subject: [PATCH 10/16] Add note about sorted vartuple requirement in Term Added a comment in the Term.__mul__ method to highlight that Term.vartuple must be sorted for correct merging. Suggests ensuring sorting in the Term constructor to avoid potential issues. --- src/pyscipopt/expr.pxi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index c4257570d..6890e0b3b 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -124,6 +124,8 @@ cdef class Term: return len(self.vartuple) def __mul__(self, Term other): + # NOTE: `Term.vartuple` reqiuires to be sorted. Otherwise, this merge will not + # work correctly. We should ensure this in the constructor of Term. cdef int n1 = len(self) cdef int n2 = len(other) if n1 == 0: return other From 458079b6466b391e58b86459fba2af552ec55f08 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 30 Jan 2026 10:11:09 +0800 Subject: [PATCH 11/16] Clarify algorithm complexity in changelog Updated the description of the Term * Term speedup to specify the use of an O(n) sort algorithm instead of Python's O(log(n)) sorted function. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d95ad3575..acb9718d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ - Speed up MatrixExpr.add.reduce via quicksum - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr - Speed up Expr * Expr via C-level API and Term * Term -- Speed up Term * Term via $O(n)$ algorithm instead of Python sorted function. `Term.__mul__` requires that Term.vartuple is sorted. +- Speed up Term * Term via a $O(n)$ sort algorithm instead of Python $O(\log(n))$ sorted function. `Term.__mul__` requires that Term.vartuple is sorted. - Rename from `Term.__add__` to `Term.__mul__`, due to this method only works with Expr * Expr. - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs - Set `__array_priority__` for MatrixExpr and MatrixExprCons From b646ed9b491b3e17bebf38087d31963545ef8040 Mon Sep 17 00:00:00 2001 From: 40% Date: Fri, 30 Jan 2026 11:15:24 +0800 Subject: [PATCH 12/16] Correct complexity notation in changelog Updated the changelog to fix the time complexity notation for the Term * Term sort algorithm from O(log(n)) to O(n log(n)). --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acb9718d2..e85de3e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ - Speed up MatrixExpr.add.reduce via quicksum - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr - Speed up Expr * Expr via C-level API and Term * Term -- Speed up Term * Term via a $O(n)$ sort algorithm instead of Python $O(\log(n))$ sorted function. `Term.__mul__` requires that Term.vartuple is sorted. +- Speed up Term * Term via a $O(n)$ sort algorithm instead of Python $O(n\log(n))$ sorted function. `Term.__mul__` requires that Term.vartuple is sorted. - Rename from `Term.__add__` to `Term.__mul__`, due to this method only works with Expr * Expr. - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs - Set `__array_priority__` for MatrixExpr and MatrixExprCons From 1685fd21e5abaae1829b62fe57a7263242586cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dion=C3=ADsio?= <57299939+Joao-Dionisio@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:41:40 +0000 Subject: [PATCH 13/16] Apply suggestion from @Joao-Dionisio --- src/pyscipopt/expr.pxi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 6890e0b3b..8c3bece7f 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -124,8 +124,8 @@ cdef class Term: return len(self.vartuple) def __mul__(self, Term other): - # NOTE: `Term.vartuple` reqiuires to be sorted. Otherwise, this merge will not - # work correctly. We should ensure this in the constructor of Term. + # NOTE: This merge algorithm requires a sorted `Term.vartuple`. + # This should be ensured in the constructor of Term. cdef int n1 = len(self) cdef int n2 = len(other) if n1 == 0: return other From 17c00c5af753e1130501d2d4906cc6349bc8aa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dion=C3=ADsio?= <57299939+Joao-Dionisio@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:43:14 +0000 Subject: [PATCH 14/16] Apply suggestion from @Joao-Dionisio --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81cc3b62e..e112ad793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ - Speed up np.ndarray(..., dtype=np.float64) @ MatrixExpr - Speed up Expr * Expr via C-level API and Term * Term - Speed up Term * Term via a $O(n)$ sort algorithm instead of Python $O(n\log(n))$ sorted function. `Term.__mul__` requires that Term.vartuple is sorted. -- Rename from `Term.__add__` to `Term.__mul__`, due to this method only works with Expr * Expr. +- Rename from `Term.__add__` to `Term.__mul__`, due to this method only working with Expr * Expr. - MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs - Set `__array_priority__` for MatrixExpr and MatrixExprCons - changed addConsNode() and addConsLocal() to mirror addCons() and accept ExprCons instead of Constraint From 347f10fd6c361a14c23032c078584501fd4591b1 Mon Sep 17 00:00:00 2001 From: 40% Date: Sat, 31 Jan 2026 19:34:21 +0800 Subject: [PATCH 15/16] Fix indentation in Expr multiplication logic Corrected the indentation of the isinstance(other, Expr) block in the Expr class to ensure proper execution flow during multiplication operations. --- src/pyscipopt/expr.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 90d654c60..a2b5631c0 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -294,7 +294,7 @@ cdef class Expr: f = float(other) return Expr({v:f*c for v,c in self.terms.items()}) - elif isinstance(other, Expr): + elif isinstance(other, Expr): while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): if (v1_val := (v1_ptr)) == 0: continue From 293ad162128f1cabadfb05ed852b21a26977c2ad Mon Sep 17 00:00:00 2001 From: 40% Date: Mon, 2 Feb 2026 18:43:55 +0800 Subject: [PATCH 16/16] Preserve zero-coefficient terms in Expr mul Do not skip terms with 0.0 coefficients when multiplying Expr objects: remove earlier zero-checks and compute product values inline in src/pyscipopt/expr.pxi. This causes zero-product terms to be retained in the resulting expression. Update tests (tests/test_expr.py) to expect the preserved zero-coefficient terms for cases like (x - x) * y and y * (x - x). --- src/pyscipopt/expr.pxi | 10 ++-------- tests/test_expr.py | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 854a1db8f..f782a46da 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -289,7 +289,7 @@ cdef class Expr: cdef PyObject *v2_ptr = NULL cdef PyObject *old_v_ptr = NULL cdef Term child - cdef double v1_val, v2_val, prod_v + cdef double prod_v if _is_number(other): f = float(other) @@ -297,16 +297,10 @@ cdef class Expr: elif isinstance(other, Expr): while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): - if (v1_val := (v1_ptr)) == 0: - continue - pos2 = 0 while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr): - if (v2_val := (v2_ptr)) == 0: - continue - child = (k1_ptr) * (k2_ptr) - prod_v = v1_val * v2_val + prod_v = ((v1_ptr)) * ((v2_ptr)) if (old_v_ptr := PyDict_GetItem(res, child)) != NULL: res[child] = (old_v_ptr) + prod_v else: diff --git a/tests/test_expr.py b/tests/test_expr.py index cdf6229e4..a4e739b76 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -226,8 +226,8 @@ def test_mul(): assert str(Expr({CONST: 1.0}) * x) == "Expr({Term(x): 1.0})" assert str(y * Expr({CONST: -1.0})) == "Expr({Term(y): -1.0})" - assert str((x - x) * y) == "Expr({Term(): 0.0})" - assert str(y * (x - x)) == "Expr({Term(): 0.0})" + assert str((x - x) * y) == "Expr({Term(x, y): 0.0})" + assert str(y * (x - x)) == "Expr({Term(x, y): 0.0})" assert ( str((x + 1) * (y - 1)) == "Expr({Term(x, y): 1.0, Term(x): -1.0, Term(y): 1.0, Term(): -1.0})"