diff --git a/CHANGELOG.md b/CHANGELOG.md index 063334a9b..d6a43f345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ - 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 +- Deprecated `getOp` method in `GenExpr` (#1259) +- Deprecated inner enumerate class `Operator` (#1259) ## 6.2.1 - 2026.05.16 ### Fixed diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index a7a6068ec..aad037420 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -143,7 +143,7 @@ cdef class Term: res.hashval = hash(tuple(v.ptr() for v in res.vartuple)) return res - def __repr__(self): + def __repr__(self) -> str: return 'Term(%s)' % ', '.join([str(v) for v in self.vartuple]) cpdef double _evaluate(self, Solution sol) except *: @@ -288,22 +288,22 @@ cdef class ExprLike: return self.copy() def __abs__(self, /) -> AbsExpr: - return AbsExpr(Operator.fabs, buildGenExprObj(self)) + return AbsExpr(buildGenExprObj(self)) def exp(self, /) -> ExpExpr: - return ExpExpr(Operator.exp, buildGenExprObj(self)) + return ExpExpr(buildGenExprObj(self)) def log(self, /) -> LogExpr: - return LogExpr(Operator.log, buildGenExprObj(self)) + return LogExpr(buildGenExprObj(self)) def sqrt(self, /) -> SqrtExpr: - return SqrtExpr(Operator.sqrt, buildGenExprObj(self)) + return SqrtExpr(buildGenExprObj(self)) def sin(self, /) -> SinExpr: - return SinExpr(Operator.sin, buildGenExprObj(self)) + return SinExpr(buildGenExprObj(self)) def cos(self, /) -> CosExpr: - return CosExpr(Operator.cos, buildGenExprObj(self)) + return CosExpr(buildGenExprObj(self)) cpdef double _evaluate(self, Solution sol) except *: raise NotImplementedError( @@ -430,7 +430,7 @@ cdef class Expr(ExprLike): '''remove terms with coefficient of 0''' self.terms = {t:c for (t,c) in self.terms.items() if c != 0.0} - def __repr__(self): + def __repr__(self) -> str: return 'Expr(%s)' % repr(self.terms) def degree(self): @@ -511,7 +511,7 @@ cdef class ExprCons: else: raise NotImplementedError("Ranged ExprCons can only support with '<=' or '>='.") - def __repr__(self): + def __repr__(self) -> str: return 'ExprCons(%s, %s, %s)' % (self.expr, self._lhs, self._rhs) def __bool__(self): @@ -545,17 +545,6 @@ def quickprod(termlist): return result -class Op: - const = 'const' - varidx = 'var' - exp, log, sqrt, sin, cos = 'exp', 'log', 'sqrt', 'sin', 'cos' - plus, minus, mul, div, power = '+', '-', '*', '/', '**' - add = 'sum' - prod = 'prod' - fabs = 'abs' - -Operator = Op() - ##@details
 General expressions of variables with operator overloading.
 #
 #@note
@@ -566,7 +555,6 @@ Operator = Op()
 #See also the @ref ExprDetails "description" in the expr.pxi. 
 cdef class GenExpr(ExprLike):
 
-    cdef public _op
     cdef public children
 
     def __init__(self): # do we need it
@@ -581,22 +569,22 @@ cdef class GenExpr(ExprLike):
         ans = SumExpr()
 
         # add left term
-        if left.getOp() == Operator.add:
+        if Py_TYPE(left) is SumExpr:
             ans.coefs.extend(left.coefs)
             ans.children.extend(left.children)
             ans.constant += left.constant
-        elif left.getOp() == Operator.const:
+        elif Py_TYPE(left) is Constant:
             ans.constant += left.number
         else:
             ans.coefs.append(1.0)
             ans.children.append(left)
 
         # add right term
-        if right.getOp() == Operator.add:
+        if Py_TYPE(right) is SumExpr:
             ans.coefs.extend(right.coefs)
             ans.children.extend(right.children)
             ans.constant += right.constant
-        elif right.getOp() == Operator.const:
+        elif Py_TYPE(right) is Constant:
             ans.constant += right.number
         else:
             ans.coefs.append(1.0)
@@ -610,20 +598,20 @@ cdef class GenExpr(ExprLike):
     #    right = buildGenExprObj(other)
     #
     #    # transform self into sum
-    #    if self.getOp() != Operator.add:
+    #    if Py_TYPE(self) is not SumExpr:
     #        newsum = SumExpr()
-    #        if self.getOp() == Operator.const:
+    #        if Py_TYPE(self) is Constant:
     #            newsum.constant += self.number
     #        else:
     #            newsum.coefs.append(1.0)
     #            newsum.children.append(self.copy()) # TODO: what is copy?
     #        self = newsum
     #    # add right term
-    #    if right.getOp() == Operator.add:
+    #    if Py_TYPE(right) is SumExpr:
     #        self.coefs.extend(right.coefs)
     #        self.children.extend(right.children)
     #        self.constant += right.constant
-    #    elif right.getOp() == Operator.const:
+    #    elif Py_TYPE(right) is Constant:
     #        self.constant += right.number
     #    else:
     #        self.coefs.append(1.0)
@@ -639,19 +627,19 @@ cdef class GenExpr(ExprLike):
         ans = ProdExpr()
 
         # multiply left factor
-        if left.getOp() == Operator.prod:
+        if Py_TYPE(left) is ProdExpr:
             ans.children.extend(left.children)
             ans.constant *= left.constant
-        elif left.getOp() == Operator.const:
+        elif Py_TYPE(left) is Constant:
             ans.constant *= left.number
         else:
             ans.children.append(left)
 
         # multiply right factor
-        if right.getOp() == Operator.prod:
+        if Py_TYPE(right) is ProdExpr:
             ans.children.extend(right.children)
             ans.constant *= right.constant
-        elif right.getOp() == Operator.const:
+        elif Py_TYPE(right) is Constant:
             ans.constant *= right.number
         else:
             ans.children.append(right)
@@ -663,18 +651,18 @@ cdef class GenExpr(ExprLike):
     #    assert isinstance(self, Expr)
     #    right = buildGenExprObj(other)
     #    # transform self into prod
-    #    if self.getOp() != Operator.prod:
+    #    if Py_TYPE(self) is not ProdExpr:
     #        newprod = ProdExpr()
-    #        if self.getOp() == Operator.const:
+    #        if Py_TYPE(self) is Constant:
     #            newprod.constant *= self.number
     #        else:
     #            newprod.children.append(self.copy()) # TODO: what is copy?
     #        self = newprod
     #    # multiply right factor
-    #    if right.getOp() == Operator.prod:
+    #    if Py_TYPE(right) is ProdExpr:
     #        self.children.extend(right.children)
     #        self.constant *= right.constant
-    #    elif right.getOp() == Operator.const:
+    #    elif Py_TYPE(right) is Constant:
     #        self.constant *= right.number
     #    else:
     #        self.children.append(right)
@@ -682,9 +670,9 @@ cdef class GenExpr(ExprLike):
 
     def __pow__(self, other, modulo):
         expo = buildGenExprObj(other)
-        if expo.getOp() != Operator.const:
+        if Py_TYPE(expo) is not Constant:
             raise NotImplementedError("exponents must be numbers")
-        if self.getOp() == Operator.const:
+        if Py_TYPE(self) is Constant:
             return Constant(self.number**expo.number)
         ans = PowExpr()
         ans.children.append(self)
@@ -711,7 +699,7 @@ cdef class GenExpr(ExprLike):
 
         divisor = buildGenExprObj(other)
         # we can't divide by 0
-        if isinstance(divisor, GenExpr) and divisor.getOp() == Operator.const and divisor.number == 0.0:
+        if Py_TYPE(divisor) is Constant and divisor.number == 0.0:
             raise ZeroDivisionError("cannot divide by 0")
         return self * divisor**(-1)
 
@@ -724,14 +712,9 @@ cdef class GenExpr(ExprLike):
         '''Note: none of these expressions should be polynomial'''
         return INFINITY
 
-    def getOp(self):
-        '''returns operator of GenExpr'''
-        return self._op
-
     cdef GenExpr copy(self, bint copy=True):
         cdef object cls = Py_TYPE(self)
         cdef GenExpr res = cls.__new__(cls)
-        res._op = self._op
         res.children = self.children.copy() if copy else self.children
         return res
 
@@ -746,9 +729,9 @@ cdef class SumExpr(GenExpr):
         self.constant = 0.0
         self.coefs = []
         self.children = []
-        self._op = Operator.add
-    def __repr__(self):
-        return self._op + "(" + str(self.constant) + "," + ",".join(map(lambda child : child.__repr__(), self.children)) + ")"
+
+    def __repr__(self) -> str:
+        return f"sum({self.constant},{','.join(map(str, self.children))})"
 
     cpdef double _evaluate(self, Solution sol) except *:
         cdef double res = self.constant
@@ -761,7 +744,6 @@ cdef class SumExpr(GenExpr):
 
     cdef SumExpr copy(self, bint copy=True):
         cdef SumExpr res = SumExpr.__new__(SumExpr)
-        res._op = self._op
         res.children = self.children.copy() if copy else self.children
         res.constant = self.constant
         res.coefs = self.coefs.copy() if copy else self.coefs
@@ -776,15 +758,14 @@ cdef class ProdExpr(GenExpr):
     def __init__(self):
         self.constant = 1.0
         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)) + ")"
+    def __repr__(self) -> str:
+        return f"prod({self.constant},{','.join(map(str, self.children))})"
 
     cpdef double _evaluate(self, Solution sol) except *:
         cdef double res = self.constant
@@ -798,7 +779,6 @@ cdef class ProdExpr(GenExpr):
 
     cdef ProdExpr copy(self, bint copy=True):
         cdef ProdExpr res = ProdExpr.__new__(ProdExpr)
-        res._op = self._op
         res.children = self.children.copy() if copy else self.children
         res.constant = self.constant
         return res
@@ -811,10 +791,9 @@ cdef class VarExpr(GenExpr):
 
     def __init__(self, var):
         self.children = [var]
-        self._op = Operator.varidx
 
-    def __repr__(self):
-        return self.children[0].__repr__()
+    def __repr__(self) -> str:
+        return str(self.children[0])
 
     cpdef double _evaluate(self, Solution sol) except *:
         return (self.children[0])._evaluate(sol)
@@ -828,17 +807,15 @@ cdef class PowExpr(GenExpr):
     def __init__(self):
         self.expo = 1.0
         self.children = []
-        self._op = Operator.power
 
-    def __repr__(self):
-        return self._op + "(" + self.children[0].__repr__() + "," + str(self.expo) + ")"
+    def __repr__(self) -> str:
+        return f"**({self.children[0]},{self.expo})"
 
     cpdef double _evaluate(self, Solution sol) except *:
         return (self.children[0])._evaluate(sol) ** self.expo
 
     cdef PowExpr copy(self, bint copy=True):
         cdef PowExpr res = PowExpr.__new__(PowExpr)
-        res._op = self._op
         res.children = self.children.copy() if copy else self.children
         res.expo = self.expo
         return res
@@ -846,13 +823,8 @@ cdef class PowExpr(GenExpr):
 
 cdef class UnaryExpr(GenExpr):
 
-    def __init__(self, op, expr):
-        self.children = []
-        self.children.append(expr)
-        self._op = op
-
-    def __repr__(self) -> str:
-        return self._op + "(" + self.children[0].__repr__() + ")"
+    def __init__(self, expr: Union[Expr, GenExpr]):
+        self.children = [expr]
 
 
 cdef class AbsExpr(UnaryExpr):
@@ -860,18 +832,27 @@ cdef class AbsExpr(UnaryExpr):
     def __abs__(self) -> AbsExpr:
         return self.copy()
 
+    def __repr__(self) -> str:
+        return f"abs({self.children[0]})"
+
     cpdef double _evaluate(self, Solution sol) except *:
         return c_fabs((self.children[0])._evaluate(sol))
 
 
 cdef class ExpExpr(UnaryExpr):
 
+    def __repr__(self) -> str:
+        return f"exp({self.children[0]})"
+
     cpdef double _evaluate(self, Solution sol) except *:
         return c_exp((self.children[0])._evaluate(sol))
 
 
 cdef class LogExpr(UnaryExpr):
 
+    def __repr__(self) -> str:
+        return f"log({self.children[0]})"
+
     cpdef double _evaluate(self, Solution sol) except *:
         cdef double val = (self.children[0])._evaluate(sol)
         if val <= 0.0:
@@ -881,6 +862,9 @@ cdef class LogExpr(UnaryExpr):
 
 cdef class SqrtExpr(UnaryExpr):
 
+    def __repr__(self) -> str:
+        return f"sqrt({self.children[0]})"
+
     cpdef double _evaluate(self, Solution sol) except *:
         cdef double val = (self.children[0])._evaluate(sol)
         if val < 0.0:
@@ -890,6 +874,9 @@ cdef class SqrtExpr(UnaryExpr):
 
 cdef class SinExpr(UnaryExpr):
 
+    def __repr__(self) -> str:
+        return f"sin({self.children[0]})"
+
     cpdef double _evaluate(self, Solution sol) except *:
         cdef double val = (self.children[0])._evaluate(sol)
         if c_fabs(val) == INFINITY:
@@ -899,6 +886,9 @@ cdef class SinExpr(UnaryExpr):
 
 cdef class CosExpr(UnaryExpr):
 
+    def __repr__(self) -> str:
+        return f"cos({self.children[0]})"
+
     cpdef double _evaluate(self, Solution sol) except *:
         cdef double val = (self.children[0])._evaluate(sol)
         if c_fabs(val) == INFINITY:
@@ -906,19 +896,17 @@ cdef class CosExpr(UnaryExpr):
         return c_cos(val)
 
 
-# class for constant expressions
 cdef class Constant(GenExpr):
 
     cdef public number
 
-    def __init__(self,number):
+    def __init__(self, number: Union[int, float]):
         self.number = number
-        self._op = Operator.const
 
     def __neg__(self, /) -> Constant:
         return Constant(-self.number)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return str(self.number)
 
     cpdef double _evaluate(self, Solution sol) except *:
@@ -927,7 +915,6 @@ cdef class Constant(GenExpr):
     cdef Constant copy(self, bint copy=True):
         # The copy parameter doesn't work; this is for compatibility.
         cdef Constant res = Constant.__new__(Constant)
-        res._op = self._op
         res.number = self.number
         return res
 
@@ -1123,7 +1110,7 @@ def expr_to_nodes(expr):
 
 def value_to_array(val, nodes):
     """adds a given value to an array"""
-    nodes.append(tuple(['const', [val]]))
+    nodes.append((Constant, [val]))
     return len(nodes) - 1
 
 # there many hacky things here: value_to_array is trying to mimick
@@ -1133,24 +1120,26 @@ def value_to_array(val, nodes):
 # haven't even consider substractions, but I guess we would interpret them as a - b = a + (-1) * b
 def expr_to_array(expr, nodes):
     """adds expression to array"""
-    op = expr._op
-    if op == Operator.const: # FIXME: constant expr should also have children!
-        nodes.append(tuple([op, [expr.number]]))
-    elif op != Operator.varidx:
+    t = Py_TYPE(expr)
+    if t is Constant:  # FIXME: constant expr should also have children!
+        nodes.append((t, [expr.number]))
+
+    elif t is not VarExpr:
         indices = []
-        nchildren = len(expr.children)
         for child in expr.children:
-            pos = expr_to_array(child, nodes) # position of child in the final array of nodes, 'nodes'
-            indices.append(pos)
-        if op == Operator.power:
-            pos = value_to_array(expr.expo, nodes)
-            indices.append(pos)
-        elif (op == Operator.add and expr.constant != 0.0) or (op == Operator.prod and expr.constant != 1.0):
-            pos = value_to_array(expr.constant, nodes)
-            indices.append(pos)
-        nodes.append( tuple( [op, indices] ) )
-    else: # var
-        nodes.append( tuple( [op, expr.children] ) )
+            # position of child in the final array of nodes, 'nodes'
+            indices.append(expr_to_array(child, nodes))
+        if t is PowExpr:
+            indices.append(value_to_array(expr.expo, nodes))
+        elif (t is SumExpr and expr.constant != 0) or (
+            t is ProdExpr and expr.constant != 1
+        ):
+            indices.append(value_to_array(expr.constant, nodes))
+
+        nodes.append((t, indices))
+
+    else:  # var
+        nodes.append((t, expr.children))
     return len(nodes) - 1
 
 
diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi
index e20eb9558..f772a608f 100644
--- a/src/pyscipopt/scip.pxi
+++ b/src/pyscipopt/scip.pxi
@@ -6224,29 +6224,29 @@ cdef class Model:
         # that are the children of this operator. This is sorted,
         # so we are going to do is:
         # loop over the nodes and create the expression of each
-        # Note1: when the operator is Operator.const, [indices] stores the value
+        # Note1: when the operator is const, [indices] stores the value
         # Note2: we need to compute the number of variable operators to find out
         # how many variables are there.
         nvars = 0
         for node in nodes:
-            if node[0] == Operator.varidx:
+            if node[0] is VarExpr:
                 nvars += 1
 
         scipexprs =  malloc(len(nodes) * sizeof(SCIP_EXPR*))
         for i,node in enumerate(nodes):
-            opidx = node[0]
-            if opidx == Operator.varidx:
+            t = node[0]
+            if t is VarExpr:
                 assert len(node[1]) == 1
                 pyvar = node[1][0] # for vars we store the actual var!
                 wrapper = _VarArray(pyvar)
                 PY_SCIP_CALL( SCIPcreateExprVar(self._scip, &scipexprs[i], wrapper.ptr[0], NULL, NULL) )
-                continue
-            if opidx == Operator.const:
+
+            elif t is Constant:
                 assert len(node[1]) == 1
                 value = node[1][0]
                 PY_SCIP_CALL( SCIPcreateExprValue(self._scip, &scipexprs[i], value, NULL, NULL) )
-                continue
-            if opidx == Operator.add:
+
+            elif t is SumExpr:
                 nchildren = len(node[1])
                 childrenexpr =  malloc(nchildren * sizeof(SCIP_EXPR*))
                 coefs =  malloc(nchildren * sizeof(SCIP_Real))
@@ -6256,48 +6256,48 @@ cdef class Model:
                 PY_SCIP_CALL( SCIPcreateExprSum(self._scip, &scipexprs[i], nchildren, childrenexpr, coefs, 0, NULL, NULL))
                 free(coefs)
                 free(childrenexpr)
-                continue
-            if opidx == Operator.prod:
+
+            elif t is ProdExpr:
                 nchildren = len(node[1])
                 childrenexpr =  malloc(nchildren * sizeof(SCIP_EXPR*))
                 for c, pos in enumerate(node[1]):
                     childrenexpr[c] = scipexprs[pos]
                 PY_SCIP_CALL( SCIPcreateExprProduct(self._scip, &scipexprs[i], nchildren, childrenexpr, 1, NULL, NULL) )
                 free(childrenexpr)
-                continue
-            if opidx == Operator.power:
+
+            elif t is PowExpr:
                 # the second child is the exponent which is a const
                 valuenode = nodes[node[1][1]]
-                assert valuenode[0] == Operator.const
+                assert valuenode[0] is Constant
                 exponent = valuenode[1][0]
                 PY_SCIP_CALL( SCIPcreateExprPow(self._scip, &scipexprs[i], scipexprs[node[1][0]], exponent, NULL, NULL ))
-                continue
-            if opidx == Operator.exp:
+
+            elif t is ExpExpr:
                 assert len(node[1]) == 1
                 PY_SCIP_CALL( SCIPcreateExprExp(self._scip, &scipexprs[i], scipexprs[node[1][0]], NULL, NULL ))
-                continue
-            if opidx == Operator.log:
+
+            elif t is LogExpr:
                 assert len(node[1]) == 1
                 PY_SCIP_CALL( SCIPcreateExprLog(self._scip, &scipexprs[i], scipexprs[node[1][0]], NULL, NULL ))
-                continue
-            if opidx == Operator.sqrt:
+
+            elif t is SqrtExpr:
                 assert len(node[1]) == 1
                 PY_SCIP_CALL( SCIPcreateExprPow(self._scip, &scipexprs[i], scipexprs[node[1][0]], 0.5, NULL, NULL) )
-                continue
-            if opidx == Operator.sin:
+
+            elif t is SinExpr:
                 assert len(node[1]) == 1
                 PY_SCIP_CALL( SCIPcreateExprSin(self._scip, &scipexprs[i], scipexprs[node[1][0]], NULL, NULL) )
-                continue
-            if opidx == Operator.cos:
+
+            elif t is CosExpr:
                 assert len(node[1]) == 1
                 PY_SCIP_CALL( SCIPcreateExprCos(self._scip, &scipexprs[i], scipexprs[node[1][0]], NULL, NULL) )
-                continue
-            if opidx == Operator.fabs:
+
+            elif t is AbsExpr:
                 assert len(node[1]) == 1
                 PY_SCIP_CALL( SCIPcreateExprAbs(self._scip, &scipexprs[i], scipexprs[node[1][0]], NULL, NULL ))
-                continue
-            # default:
-            raise NotImplementedError
+
+            else:
+                raise NotImplementedError
 
         # create nonlinear constraint for the expression root
         PY_SCIP_CALL( SCIPcreateConsNonlinear(
diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi
index 8f96cafd3..9822803bf 100644
--- a/src/pyscipopt/scip.pyi
+++ b/src/pyscipopt/scip.pyi
@@ -10,7 +10,6 @@ CONST: Term
 EventNames: dict
 MAJOR: int
 MINOR: int
-Operator: Op
 PATCH: int
 PY_SCIP_CALL: Incomplete
 StageNames: dict
@@ -441,11 +440,9 @@ class ExprCons:
 
 @disjoint_base
 class GenExpr(ExprLike):
-    _op: Incomplete
     children: Incomplete
     def __init__(self) -> None: ...
     def degree(self) -> Incomplete: ...
-    def getOp(self) -> Incomplete: ...
     def __neg__(self, /) -> ProdExpr | Constant: ...
     @overload
     def __add__(self, other: float | ExprLike, /) -> SumExpr: ...
@@ -1844,23 +1841,6 @@ class Nodesel:
     def nodeinitsol(self) -> Incomplete: ...
     def nodeselect(self) -> Incomplete: ...
 
-class Op:
-    add: ClassVar[str] = ...
-    const: ClassVar[str] = ...
-    cos: ClassVar[str] = ...
-    div: ClassVar[str] = ...
-    exp: ClassVar[str] = ...
-    fabs: ClassVar[str] = ...
-    log: ClassVar[str] = ...
-    minus: ClassVar[str] = ...
-    mul: ClassVar[str] = ...
-    plus: ClassVar[str] = ...
-    power: ClassVar[str] = ...
-    prod: ClassVar[str] = ...
-    sin: ClassVar[str] = ...
-    sqrt: ClassVar[str] = ...
-    varidx: ClassVar[str] = ...
-
 class PY_SCIP_BENDERSENFOTYPE:
     CHECK: ClassVar[int] = ...
     LP: ClassVar[int] = ...