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 @@ -15,6 +15,7 @@
- Replaced `format()` with f-strings in examples
- Refactor general expression conversion into per-class `to_scip_expr` recursion
- Made expression fields read-only to prevent accidental runtime modification
- Expression fields now use concrete Cython types (`double`/`list`), so values are converted on construction
- 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
- `PowExpr._evaluate` now raises `ValueError("math domain error")` for a negative base with a non-integer exponent and `OverflowError` when the result overflows.
Expand Down
35 changes: 20 additions & 15 deletions src/pyscipopt/expr.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
# gets called (I guess) and so a copy is returned.
# Modifying the expression directly would be a bug, given that the expression might be re-used by the user. </pre>
import warnings
from typing import TYPE_CHECKING, Literal, Union
from typing import TYPE_CHECKING, Literal, Optional, Union

import numpy as np

Expand Down Expand Up @@ -466,11 +466,17 @@ cdef class Expr(ExprLike):

cdef class ExprCons:
'''Constraints with a polynomial expressions and lower/upper bounds.'''
cdef public expr
cdef public _lhs
cdef public _rhs

def __init__(self, expr, lhs=None, rhs=None):
cdef readonly ExprLike expr
cdef readonly object _lhs
cdef readonly object _rhs

def __init__(
self,
ExprLike expr not None,
lhs: Optional[float]=None,
rhs: Optional[float]=None,
):
Comment on lines +470 to +479

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since ExprCons is fewer than 100 lines, I'd like to refactor all ExprCons-related code in a single PR. But it's okay to do in this PR.

self.expr = expr
self._lhs = lhs
self._rhs = rhs
Expand Down Expand Up @@ -575,7 +581,7 @@ Operator = Op()
cdef class GenExpr(ExprLike):

cdef public _op
cdef readonly children
cdef readonly list children

def __init__(self): # do we need it
''' '''
Expand Down Expand Up @@ -756,7 +762,7 @@ cdef class GenExpr(ExprLike):
# Sum Expressions
cdef class SumExpr(GenExpr):

cdef readonly constant
cdef readonly double constant

def __init__(self):
self.constant = 0.0
Expand Down Expand Up @@ -810,7 +816,7 @@ cdef class SumExpr(GenExpr):
# Prod Expressions
cdef class ProdExpr(GenExpr):

cdef readonly constant
cdef readonly double constant

def __init__(self):
self.constant = 1.0
Expand Down Expand Up @@ -887,7 +893,7 @@ cdef class VarExpr(GenExpr):
# Pow Expressions
cdef class PowExpr(GenExpr):

cdef readonly expo
cdef readonly double expo

def __init__(self):
self.expo = 1.0
Expand All @@ -906,14 +912,13 @@ cdef class PowExpr(GenExpr):

cpdef double _evaluate(self, Solution sol) except *:
cdef double base = (<GenExpr>self.children[0])._evaluate(sol)
cdef double expo = <double>self.expo
if base == 0.0 and expo < 0:
if base == 0.0 and self.expo < 0:
raise ZeroDivisionError("0.0 cannot be raised to a negative power")
elif base < 0.0 and expo != c_floor(expo):
elif base < 0.0 and self.expo != c_floor(self.expo):
raise ValueError("math domain error")

cdef double res = c_pow(base, expo)
if c_fabs(res) == INFINITY and c_fabs(base) != INFINITY and c_fabs(expo) != INFINITY:
cdef double res = c_pow(base, self.expo)
if c_fabs(res) == INFINITY and c_fabs(base) != INFINITY and c_fabs(self.expo) != INFINITY:
raise OverflowError("math range error")
return res

Expand Down Expand Up @@ -1045,7 +1050,7 @@ cdef class CosExpr(UnaryExpr):

cdef class Constant(GenExpr):

cdef readonly number
cdef readonly double number

def __init__(self, number: Union[int, float]):
self.number = number
Expand Down
19 changes: 11 additions & 8 deletions src/pyscipopt/scip.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class Conshdlr:
@disjoint_base
class Constant(GenExpr):
@property
def number(self) -> SupportsFloat: ...
def number(self) -> float: ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

numebr is float now, not like float. SupportsFloat uses the __float__ protocol.

def __init__(self, number: SupportsFloat) -> None: ...
def __neg__(self, /) -> Constant: ...
def __pow__( # type: ignore[override]
Expand Down Expand Up @@ -428,11 +428,14 @@ class Expr(ExprLike):

@disjoint_base
class ExprCons:
_lhs: float | None
_rhs: float | None
expr: Expr | GenExpr
@property
def expr(self) -> ExprLike: ...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change is causing some false positives in type checked code because expr_cons.expr.degree() and expr_cons.expr + 1 are now flagged as errors. It works whether the value is Expr or GenExpr, but ExprLike doesn't declare the needed methods.

Suggested change
def expr(self) -> ExprLike: ...
def expr(self) -> Expr | GenExpr: ...

@property
def _lhs(self) -> float | None: ...
@property
def _rhs(self) -> float | None: ...
def __init__(
self, expr: Expr | GenExpr, lhs: float | None = None, rhs: float | None = None
self, expr: ExprLike, lhs: float | None = None, rhs: float | None = None
) -> None: ...
def normalize(self) -> None: ...
def __bool__(self) -> bool: ...
Expand Down Expand Up @@ -2165,7 +2168,7 @@ class PY_SCIP_STATUS:
@disjoint_base
class PowExpr(GenExpr):
@property
def expo(self) -> SupportsFloat: ...
def expo(self) -> float: ...
def __init__(self) -> None: ...
def __neg__(self, /) -> ProdExpr: ...

Expand Down Expand Up @@ -2197,7 +2200,7 @@ class Pricer:
@disjoint_base
class ProdExpr(GenExpr):
@property
def constant(self) -> SupportsFloat: ...
def constant(self) -> float: ...
def __init__(self) -> None: ...
def __neg__(self, /) -> ProdExpr: ...

Expand Down Expand Up @@ -2397,7 +2400,7 @@ class Statistics:
@disjoint_base
class SumExpr(GenExpr):
@property
def constant(self) -> SupportsFloat: ...
def constant(self) -> float: ...
def __init__(self) -> None: ...
def __neg__(self, /) -> ProdExpr: ...

Expand Down
Loading
Loading