From 8ca29ff69c4d0dd3b97dee6666cb3cda59c27f45 Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 4 Feb 2026 19:50:38 +0800 Subject: [PATCH 1/5] 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. --- src/pyscipopt/expr.pxi | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index f782a46da..440107668 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -289,22 +289,24 @@ cdef class Expr: cdef PyObject *v2_ptr = NULL cdef PyObject *old_v_ptr = NULL cdef Term child - cdef double prod_v + cdef double coef if _is_number(other): - f = float(other) - return Expr({v:f*c for v,c in self.terms.items()}) - + coef = float(other) + while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): + res[k1_ptr] = (v1_ptr) * coef + return Expr(res) + elif isinstance(other, Expr): while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): pos2 = 0 while PyDict_Next(other.terms, &pos2, &k2_ptr, &v2_ptr): child = (k1_ptr) * (k2_ptr) - prod_v = ((v1_ptr)) * ((v2_ptr)) + coef = ((v1_ptr)) * ((v2_ptr)) if (old_v_ptr := PyDict_GetItem(res, child)) != NULL: - res[child] = (old_v_ptr) + prod_v + res[child] = (old_v_ptr) + coef else: - res[child] = prod_v + res[child] = coef return Expr(res) elif isinstance(other, GenExpr): From a85e92898b95f943d8cde1b191d83e73f3d698fa Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 4 Feb 2026 19:51:27 +0800 Subject: [PATCH 2/5] 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. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c41f56a78..d50e10f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - changed `addConsNode()` and `addConsLocal()` to mirror `addCons()` and accept `ExprCons` instead of `Constraint` - Improved `chgReoptObjective()` performance - Return itself for `abs` to `UnaryExpr(Operator.fabs)` +- Speed up `constant * Expr` via C-level API ### Removed ## 6.0.0 - 2025.11.28 From e2612b50e8a91398f1e84614b6e708ae4816ca34 Mon Sep 17 00:00:00 2001 From: 40% Date: Wed, 4 Feb 2026 19:57:26 +0800 Subject: [PATCH 3/5] 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. --- tests/test_expr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_expr.py b/tests/test_expr.py index a4e739b76..f127d2d7f 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -224,6 +224,10 @@ def test_mul(): x = m.addVar(name="x") y = m.addVar(name="y") + # test Expr * number + assert str((x + y) * 2.0) == "Expr({Term(x): 2.0, Term(y): 2.0})" + + # test Expr * Expr 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})" From f1433c72a8474a1d33f7d3a27f15f38ae7feb5a4 Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 5 Feb 2026 08:36:56 +0800 Subject: [PATCH 4/5] 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. --- src/pyscipopt/expr.pxi | 2 +- tests/test_expr.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 440107668..d8ba55c8c 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -296,7 +296,7 @@ cdef class Expr: while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): res[k1_ptr] = (v1_ptr) * coef return Expr(res) - + elif isinstance(other, Expr): while PyDict_Next(self.terms, &pos1, &k1_ptr, &v1_ptr): pos2 = 0 diff --git a/tests/test_expr.py b/tests/test_expr.py index f127d2d7f..855f47cff 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -226,6 +226,7 @@ def test_mul(): # test Expr * number assert str((x + y) * 2.0) == "Expr({Term(x): 2.0, Term(y): 2.0})" + assert str(2.0 * (x + y)) == "Expr({Term(x): 2.0, Term(y): 2.0})" # test Expr * Expr assert str(Expr({CONST: 1.0}) * x) == "Expr({Term(x): 1.0})" From 8419c8c41e9a68e33254f619807311c3f8accc87 Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 5 Feb 2026 18:21:33 +0800 Subject: [PATCH 5/5] Merge branch 'master' into expr/__mul__ --- CHANGELOG.md | 9 ++++++++- docs/build.rst | 2 ++ pyproject.toml | 14 ++++++-------- setup.py | 2 +- src/pyscipopt/_version.py | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d50e10f84..efc3d5f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased ### Added +### Fixed +### Changed +- Speed up `constant * Expr` via C-level API +### Removed + +## 6.1.0 - 2026.01.31 +### Added +- Support for SCIP 10.0.1 - Added automated script for generating type stubs - Include parameter names in type stubs - Added pre-commit hook for automatic stub regeneration (see `.pre-commit-config.yaml`) @@ -32,7 +40,6 @@ - changed `addConsNode()` and `addConsLocal()` to mirror `addCons()` and accept `ExprCons` instead of `Constraint` - Improved `chgReoptObjective()` performance - Return itself for `abs` to `UnaryExpr(Operator.fabs)` -- Speed up `constant * Expr` via C-level API ### Removed ## 6.0.0 - 2025.11.28 diff --git a/docs/build.rst b/docs/build.rst index f849b1895..7ce7f766f 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -21,6 +21,8 @@ To download SCIP please either use the pre-built SCIP Optimization Suite availab * - SCIP - PySCIPOpt + * - 10.0.1 + - 6.1 * - 10.0.0 - 6.0 * - 9.2 diff --git a/pyproject.toml b/pyproject.toml index d990f8d50..52732b737 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,17 +11,15 @@ authors = [ dependencies = ['numpy >=1.16.0'] requires-python = ">=3.8" readme = "README.md" -license = {text = "MIT License"} classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Education", "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", "Programming Language :: Cython", "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering :: Mathematics", ] -dynamic = ["version"] +dynamic = ["version", "license"] [project.urls] Homepage = "https://github.com/SCIP-Interfaces/PySCIPOpt" @@ -51,9 +49,9 @@ AARCH=$(uname -m) echo "------" echo $AARCH if [[ $AARCH == "aarch64" ]]; then - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-linux-arm.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.11.0/libscip-linux-arm.zip -O scip.zip else - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-linux.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.11.0/libscip-linux.zip -O scip.zip fi unzip scip.zip mv scip_install scip @@ -67,10 +65,10 @@ before-all = ''' #!/bin/bash brew install wget zlib gcc if [[ $CIBW_ARCHS == *"arm"* ]]; then - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-macos-arm.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.11.0/libscip-macos-arm.zip -O scip.zip export MACOSX_DEPLOYMENT_TARGET=14.0 else - wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-macos-intel.zip -O scip.zip + wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.11.0/libscip-macos-intel.zip -O scip.zip export MACOSX_DEPLOYMENT_TARGET=14.0 fi unzip scip.zip @@ -96,7 +94,7 @@ repair-wheel-command = ''' skip="pp* cp36* cp37*" before-all = [ "choco install 7zip wget", - "wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.10.0/libscip-windows.zip -O scip.zip", + "wget https://github.com/scipopt/scipoptsuite-deploy/releases/download/v0.11.0/libscip-windows.zip -O scip.zip", "\"C:\\Program Files\\7-Zip\\7z.exe\" x \"scip.zip\" -o\"scip-test\"", "mv .\\scip-test\\scip_install .\\test", "mv .\\test .\\scip" diff --git a/setup.py b/setup.py index 9494cd5d5..9a076a28c 100644 --- a/setup.py +++ b/setup.py @@ -133,7 +133,7 @@ setup( name="PySCIPOpt", - version="6.0.0", + version="6.1.0", description="Python interface and modeling environment for SCIP", long_description=long_description, long_description_content_type="text/markdown", diff --git a/src/pyscipopt/_version.py b/src/pyscipopt/_version.py index 74233fb54..935ff4228 100644 --- a/src/pyscipopt/_version.py +++ b/src/pyscipopt/_version.py @@ -1 +1 @@ -__version__: str = '6.0.0' +__version__: str = '6.1.0'