Skip to content

Conversation

@Zeroto521
Copy link
Contributor

This PR is at least faster 1.2x than before.

  • Before optimized
    • First time running
      • Time taken for 50x50 matrix: 1.0127 seconds
      • Time taken for 100x100 matrix: 6.8916 second
    • Second time running
      • Time taken for 50x50 matrix: 0.8220 seconds
      • Time taken for 100x100 matrix: 5.8312 seconds
    • Third time running
      • Time taken for 50x50 matrix: 0.9372 seconds
      • Time taken for 100x100 matrix: 5.2823 seconds
  • After optimized Expr.__mul__(Expr):
    • First time running
      • Time taken for 50x50 matrix: 0.5335 seconds
      • Time taken for 100x100 matrix: 6.5986 seconds
    • Second time running
      • Time taken for 50x50 matrix: 0.7765 seconds
      • Time taken for 100x100 matrix: 5.2647 seconds
    • Third time running
      • Time taken for 50x50 matrix: 0.6112 seconds
      • Time taken for 100x100 matrix: 6.7011 seconds
  • After optimized Expr.__mul__(Expr) and Term.__mul__(Term)
    • First time running
      • Time taken for 50x50 matrix: 0.6092 seconds
      • Time taken for 100x100 matrix: 5.7285 seconds
    • Second time running
      • Time taken for 50x50 matrix: 0.5105 seconds
      • Time taken for 100x100 matrix: 5.0206 seconds
    • Third time running
      • Time taken for 50x50 matrix: 0.5816 seconds
      • Time taken for 100x100 matrix: 4.3978 seconds
from timeit import timeit

from pyscipopt import Model

m = Model()

n = 50
x = m.addMatrixVar((n, n))

cost = timeit(lambda: x @ x, number=5) / 5
print(f"Time taken for {n}x{n} matrix: {cost:.4f} seconds")

n = 100
x = m.addMatrixVar((n, n))
cost = timeit(lambda: x @ x, number=5) / 5
print(f"Time taken for {n}x{n} matrix: {cost:.4f} seconds")

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.
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.
Moved the 'Speed up MatrixExpr.sum(axis=...) via quicksum' entry from the Added section to the Changed section for better categorization and clarity.
Added a new entry to the changelog noting the performance improvement for Expr * Expr operations.
Copilot AI review requested due to automatic review settings January 24, 2026 08:13
def __len__(self):
return len(self.vartuple)

def __add__(self, other):
Copy link
Contributor Author

@Zeroto521 Zeroto521 Jan 24, 2026

Choose a reason for hiding this comment

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

__mul__ is better. We call this function actually to multiply

Comment on lines +135 to +144
while i < n1 and j < n2:
var1 = <Variable>PyTuple_GET_ITEM(self.vartuple, i)
var2 = <Variable>PyTuple_GET_ITEM(other.vartuple, j)
if var1.ptr() <= var2.ptr():
vartuple[k] = var1
i += 1
else:
vartuple[k] = var2
j += 1
k += 1
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is the Merge Sort Algorithm, with a time complexity of $O(n)$.
The time complexity of sorted is $O(nlog(n))$.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes the multiplication of Expr objects (polynomial expressions) by using low-level C API calls and introducing a more efficient Term.__mul__ method. According to the benchmarks provided, this results in at least 1.2x speedup for matrix multiplication operations.

Changes:

  • Introduced Term.__mul__ method using an efficient merge algorithm for combining sorted variable tuples
  • Optimized Expr.__mul__ to use C-level Python dict iteration APIs (PyDict_Next, PyDict_GetItem) and skip zero coefficients
  • Updated CHANGELOG to document the performance improvement

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/pyscipopt/expr.pxi Implements optimized Term.__mul__ method and refactors Expr.__mul__ to use low-level C APIs for faster dictionary iteration and term multiplication
CHANGELOG.md Adds entry documenting the Expr * Expr performance improvement in the Changed section

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +304 to +319
while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr):
if (v1_val := <double>(<object>v1_ptr)) == 0:
continue

pos2 = <Py_ssize_t>0
while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr):
if (v2_val := <double>(<object>v2_ptr)) == 0:
continue

child = (<Term>k1_ptr) * (<Term>k2_ptr)
prod_v = v1_val * v2_val
if (old_v_ptr := PyDict_GetItem(res, child)) != NULL:
res[child] = <double>(<object>old_v_ptr) + prod_v
else:
res[child] = prod_v
return Expr(res)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use Cython API to speed up.

Corrects the Term class in scip.pyi to define __mul__ instead of __add__, updating the method signature to accept and return Term objects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant