Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Replaced `format()` with f-strings in examples
- Made expression fields read-only to prevent accidental runtime modification
- Methods taking a list of variables (e.g. `addConsKnapsack()`) no longer convert it to a NumPy array to check its dimension
- Speed up `PowExpr._evaluate` via the C-level API
### Removed

## 6.3.0 - 2026.10.11
Expand Down
7 changes: 6 additions & 1 deletion src/pyscipopt/expr.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ from libc.math cimport cos as c_cos
from libc.math cimport exp as c_exp
from libc.math cimport fabs as c_fabs
from libc.math cimport log as c_log
from libc.math cimport pow as c_pow
from libc.math cimport sqrt as c_sqrt
from libc.math cimport sin as c_sin
from libc.math cimport INFINITY
Expand Down Expand Up @@ -839,7 +840,11 @@ cdef class PowExpr(GenExpr):
return f"**({self.children[0]},{self.expo})"

cpdef double _evaluate(self, Solution sol) except *:
return (<GenExpr>self.children[0])._evaluate(sol) ** self.expo
cdef double base = (<GenExpr>self.children[0])._evaluate(sol)
cdef double expo = <double>self.expo
if base == 0.0 and expo < 0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should also raise if we have a negative base and a fractional exponent.

raise ZeroDivisionError("0.0 cannot be raised to a negative power")
return c_pow(base, expo)

cdef PowExpr copy(self, bint copy=True):
cdef PowExpr res = PowExpr.__new__(PowExpr)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,23 @@ def test_getVal_with_GenExpr():
with pytest.raises(ValueError, match="math domain error"):
m.getVal(sin(math.inf))


def test_getVal_pow_negative_exponent():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test is in the middle of another test

m = Model()
x = m.addVar(lb=1, ub=1, name="x")
y = m.addVar(lb=2, ub=2, name="y")
z = m.addVar(lb=0, ub=0, name="z")
m.optimize()

# C-level pow with a negative exponent (matches Python: 2.0**-2.0 == 0.25)
assert 2.0 ** -2.0 == 0.25

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the point of this?

assert m.getVal(x ** -2.0) == 1

# zero to a negative power raises, like Python's 0.0**-1.0;
# z / x would short-circuit to 0 in ProdExpr before evaluating x**-1, so divide by z
with pytest.raises(ZeroDivisionError):
m.getVal(y / z)

with pytest.raises(ValueError, match="math domain error"):
m.getVal(cos(-math.inf))

Expand Down
Loading