From b6cac69217bc3f0011cef5b544b6c2cea1a1717c Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 23 Oct 2025 14:35:30 +0100 Subject: [PATCH 1/8] should be it --- CHANGELOG.md | 1 + src/pyscipopt/expr.pxi | 26 ++++++++++++++++++++++++++ tests/test_expr.py | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d891c599..b4d64a6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- Added possibility of having variables in exponent. - Added basic type stubs to help with IDE autocompletion and type checking. ### Fixed - Implemented all binary operations between MatrixExpr and GenExpr diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 431a4abd4..263e59901 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -272,6 +272,19 @@ cdef class Expr: res *= self return res + def __rpow__(self, other): + """ + Implements base**x as scip.exp(x * scip.log(base)). + Note: base must be positive. + """ + if _is_number(other): + base = float(other) + if base <= 0.0: + raise ValueError("Base of a**x must be positive; got %g" % base) + return exp(self * log(base)) + else: + raise TypeError(f"Unsupported base type {type(other)} for exponentiation.") + def __neg__(self): return Expr({v:-c for v,c in self.terms.items()}) @@ -544,6 +557,19 @@ cdef class GenExpr: return ans + def __rpow__(self, other): + """ + Implements base**x as scip.exp(x * scip.log(base)). + Note: base must be positive. + """ + if _is_number(other): + base = float(other) + if base <= 0.0: + raise ValueError("Base of a**x must be positive; got %g" % base) + return exp(self * log(base)) + else: + raise TypeError(f"Unsupported base type {type(other)} for exponentiation.") + #TODO: ipow, idiv, etc def __truediv__(self,other): divisor = buildGenExprObj(other) diff --git a/tests/test_expr.py b/tests/test_expr.py index ccc2d797c..922bc2bcd 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -172,8 +172,26 @@ def test_equation(model): assert equat._lhs == equat._rhs assert equat._lhs == 0.0 +def test_rpow_constant_base(model): + # m, x, y, z = model + m = Model() + x = m.addVar("x") + y = m.addVar("y") + z = m.addVar("z") + a = 2**x + b = exp(x * log(2.0)) + assert isinstance(a, GenExpr) + # Structural equality is not implemented; compare textual forms + assert repr(a) == repr(b) + m.addCons(2**x <= 1) + + with pytest.raises(ValueError): + c = (-2)**x + equat = x == 1 + x**1.2 assert isinstance(equat, ExprCons) assert isinstance(equat.expr, GenExpr) assert equat._lhs == equat._rhs assert equat._lhs == 0.0 + +test_rpow_constant_base(None) \ No newline at end of file From e5bffd4905037e12a11d1465d00189b9b1468c82 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 23 Oct 2025 14:37:52 +0100 Subject: [PATCH 2/8] remove forgotten stuff --- tests/test_expr.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 922bc2bcd..60eb9c7eb 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -173,16 +173,11 @@ def test_equation(model): assert equat._lhs == 0.0 def test_rpow_constant_base(model): - # m, x, y, z = model - m = Model() - x = m.addVar("x") - y = m.addVar("y") - z = m.addVar("z") + m, x, y, z = model a = 2**x b = exp(x * log(2.0)) assert isinstance(a, GenExpr) - # Structural equality is not implemented; compare textual forms - assert repr(a) == repr(b) + assert repr(a) == repr(b) # Structural equality is not implemented; compare strings m.addCons(2**x <= 1) with pytest.raises(ValueError): @@ -193,5 +188,3 @@ def test_rpow_constant_base(model): assert isinstance(equat.expr, GenExpr) assert equat._lhs == equat._rhs assert equat._lhs == 0.0 - -test_rpow_constant_base(None) \ No newline at end of file From 646ca576e730397f415de1e6bc5801dff6be222d 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: Thu, 23 Oct 2025 14:45:18 +0100 Subject: [PATCH 3/8] remove duplicate code --- tests/test_expr.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_expr.py b/tests/test_expr.py index 60eb9c7eb..1da20ac95 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -182,9 +182,3 @@ def test_rpow_constant_base(model): with pytest.raises(ValueError): c = (-2)**x - - equat = x == 1 + x**1.2 - assert isinstance(equat, ExprCons) - assert isinstance(equat.expr, GenExpr) - assert equat._lhs == equat._rhs - assert equat._lhs == 0.0 From d5e3ea01db0370080c5912543372f82457c13c9c 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: Thu, 23 Oct 2025 14:46:57 +0100 Subject: [PATCH 4/8] code was just out of place --- tests/test_expr.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_expr.py b/tests/test_expr.py index 1da20ac95..ce79b7cc5 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -172,6 +172,12 @@ def test_equation(model): assert equat._lhs == equat._rhs assert equat._lhs == 0.0 + equat = x == 1 + x**1.2 + assert isinstance(equat, ExprCons) + assert isinstance(equat.expr, GenExpr) + assert equat._lhs == equat._rhs + assert equat._lhs == 0.0 + def test_rpow_constant_base(model): m, x, y, z = model a = 2**x From 042bfb356e811aeca21f8c114b7e2c5a774e7119 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: Thu, 23 Oct 2025 14:58:31 +0100 Subject: [PATCH 5/8] Update src/pyscipopt/expr.pxi Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- 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 263e59901..16cef141e 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -274,7 +274,7 @@ cdef class Expr: def __rpow__(self, other): """ - Implements base**x as scip.exp(x * scip.log(base)). + Implements base**x as scip.exp(x * scip.log(base)). Note: base must be positive. """ if _is_number(other): From 572654dec845e294a6976c1ee90b65a0554e5765 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: Thu, 23 Oct 2025 14:58:37 +0100 Subject: [PATCH 6/8] Update src/pyscipopt/expr.pxi Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- 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 16cef141e..b118a8017 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -559,8 +559,8 @@ cdef class GenExpr: def __rpow__(self, other): """ - Implements base**x as scip.exp(x * scip.log(base)). - Note: base must be positive. + Implements base**x as scip.exp(x * scip.log(base)). + Note: base must be positive. """ if _is_number(other): base = float(other) From 6ae55f3b7960bc8870dae9dd320d1276b1c4c6d7 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, 24 Oct 2025 16:34:30 +0100 Subject: [PATCH 7/8] improve error message --- 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 b118a8017..3c153b144 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -280,7 +280,7 @@ cdef class Expr: if _is_number(other): base = float(other) if base <= 0.0: - raise ValueError("Base of a**x must be positive; got %g" % base) + raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x + scip.log(a)); got %g" % base) return exp(self * log(base)) else: raise TypeError(f"Unsupported base type {type(other)} for exponentiation.") @@ -565,7 +565,7 @@ cdef class GenExpr: if _is_number(other): base = float(other) if base <= 0.0: - raise ValueError("Base of a**x must be positive; got %g" % base) + raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x + scip.log(a)); got %g" % base) return exp(self * log(base)) else: raise TypeError(f"Unsupported base type {type(other)} for exponentiation.") From e88350928f637f2327f51b9749adaa8b040c53a0 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, 24 Oct 2025 16:35:58 +0100 Subject: [PATCH 8/8] typo --- 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 3c153b144..f0c406fcb 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -280,7 +280,7 @@ cdef class Expr: if _is_number(other): base = float(other) if base <= 0.0: - raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x + scip.log(a)); got %g" % base) + raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x * scip.log(a)); got %g" % base) return exp(self * log(base)) else: raise TypeError(f"Unsupported base type {type(other)} for exponentiation.") @@ -565,7 +565,7 @@ cdef class GenExpr: if _is_number(other): base = float(other) if base <= 0.0: - raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x + scip.log(a)); got %g" % base) + raise ValueError("Base of a**x must be positive, as expression is reformulated to scip.exp(x * scip.log(a)); got %g" % base) return exp(self * log(base)) else: raise TypeError(f"Unsupported base type {type(other)} for exponentiation.")