From 1b33e413c2355ee4fc3ce078d4a4af904cc67ede Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:00:12 +0800 Subject: [PATCH 1/7] Speed up -ProdExpr --- src/pyscipopt/expr.pxi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 3b232fea2..a45902e9a 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -767,6 +767,11 @@ cdef class ProdExpr(GenExpr): self.children = [] self._op = Operator.prod + def __neg__(self, /) -> ProdExpr: + cdef ProdExpr res = self.copy(copy=True) + res.constant = -res.constant + return res + def __repr__(self): return self._op + "(" + str(self.constant) + "," + ",".join(map(lambda child : child.__repr__(), self.children)) + ")" From fd3034ed1e9feb32e3a98f84d626273da6aebfd8 Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:00:31 +0800 Subject: [PATCH 2/7] Speed up `-Constant` --- src/pyscipopt/expr.pxi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index a45902e9a..d8b7c227a 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -860,6 +860,9 @@ cdef class Constant(GenExpr): self.number = number self._op = Operator.const + def __neg__(self, /) -> Constant: + return Constant(-self.number) + def __repr__(self): return str(self.number) From 22aa1a5d0a5d90cdaf64e5d20bd963399939d14a Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:01:41 +0800 Subject: [PATCH 3/7] Update scip.pyi --- src/pyscipopt/scip.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 1e3164109..4084a977c 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -252,6 +252,7 @@ class Conshdlr: class Constant(GenExpr): number: Incomplete def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __neg__(self, /) -> Constant: ... def __pow__( # type: ignore[override] self, other: float | Constant, mod: Incomplete = ..., / ) -> Constant: ... @@ -2127,6 +2128,7 @@ class Pricer: class ProdExpr(GenExpr): constant: Incomplete def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __neg__(self, /) -> ProdExpr: ... @disjoint_base class Prop: From 6d881ce252e396422642cb5128f706e3bd146bbc Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:04:07 +0800 Subject: [PATCH 4/7] add readable blank lines --- src/pyscipopt/expr.pxi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index d8b7c227a..b136df930 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -855,7 +855,9 @@ cdef class UnaryExpr(GenExpr): # class for constant expressions cdef class Constant(GenExpr): + cdef public number + def __init__(self,number): self.number = number self._op = Operator.const From 78313a5c5905a74bbb6e7984ea4939dedf124d60 Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:06:50 +0800 Subject: [PATCH 5/7] test -prod and -constant --- tests/test_expr.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 1b51e4f2a..2596fc81f 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -4,7 +4,15 @@ import pytest from pyscipopt import Model, cos, exp, log, quickprod, sin, sqrt -from pyscipopt.scip import CONST, Expr, ExprCons, GenExpr, MatrixGenExpr +from pyscipopt.scip import ( + CONST, + Constant, + Expr, + ExprCons, + GenExpr, + MatrixGenExpr, + ProdExpr, +) @pytest.fixture(scope="module") @@ -594,3 +602,25 @@ def test_pos(): e = +c assert str(e) == str(c) assert e is not c + +def test_neg(): + m = Model() + x = m.addVar(name="x") + + expr = (x + 1) ** 3 + neg_expr = -expr + assert isinstance(expr, Expr) + assert isinstance(neg_expr, Expr) + assert ( + str(neg_expr) + == "Expr({Term(x, x, x): -1.0, Term(x, x): -3.0, Term(x): -3.0, Term(): -1.0})" + ) + + base = sqrt(x) + expr = base * -1 + neg_expr = -expr + assert isinstance(expr, ProdExpr) + assert isinstance(neg_expr, ProdExpr) + assert str(neg_expr) == "prod(1.0,sqrt(sum(0.0,prod(1.0,x))))" + + assert str(-Constant(3.0)) == "-3.0" From 1440b7a6329a76953acd16669ac8ae46e84e0a3b Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:11:41 +0800 Subject: [PATCH 6/7] log this change --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae37c3270..058fc0986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ - Made `test_markDoNotAggrVar_and_getStatus` robust to SCIP presolve changes by discovering the aggregated/multi-aggregated variables instead of hardcoding them ### Changed - Move magic methods (`__radd__`, `__sub__`, `__rsub__`, `__rmul__`, `__richcmp__`, `__neg__`, and `__rtruediv__`) to `ExprLike` base class (#1204) -- Speed up `Expr.__add__` and `Expr.__iadd__` via the C-level API +- Speed up `Expr.__add__` and `Expr.__iadd__` via the C-level API (#1205) +- Speed up `ProdExpr.__neg__` and `Constant.__neg__` via C-level API (#1250) - Extended `structured_optimization_trace` recipe to support context-managed JSONL tracing with final `run_end` records, alongside the existing attach-style in-memory tracing. ### Removed From 33a4b587bbeefe24ad65b25eba66d694140d8d28 Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 6 Sep 2026 15:28:44 +0800 Subject: [PATCH 7/7] fix mypy type checking --- src/pyscipopt/scip.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 4084a977c..d707f0e81 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -356,6 +356,7 @@ class ExprLike: **kwargs: Incomplete, ) -> Incomplete: ... def __pos__(self, /) -> Self: ... + def __neg__(self, /) -> ExprLike: ... def __abs__(self, /) -> UnaryExpr: ... def exp(self) -> UnaryExpr: ... def log(self) -> UnaryExpr: ... @@ -445,7 +446,7 @@ class GenExpr(ExprLike): def __init__(self) -> None: ... def degree(self) -> Incomplete: ... def getOp(self) -> Incomplete: ... - def __neg__(self, /) -> ProdExpr: ... + def __neg__(self, /) -> ProdExpr | Constant: ... @overload def __add__(self, other: float | ExprLike, /) -> SumExpr: ... @overload