-
Notifications
You must be signed in to change notification settings - Fork 275
Feature: Expr and GenExpr support numpy unary func like np.sin
#1170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
452ca36
4d911c1
b119224
4a65d98
2d8945f
01658f4
f0aacb6
7e78cac
196f43a
6dd2c93
ee4c6f6
e76b0d6
06b4df4
a76ef44
5611d52
3402b32
925cb43
857c969
c94177c
588cba4
cd642f9
f8e6132
940fd93
900fdc3
c85be94
4f6058a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,13 +43,13 @@ | |
| # 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 math | ||
| from typing import TYPE_CHECKING | ||
| from typing import TYPE_CHECKING, Literal | ||
|
|
||
| import numpy as np | ||
|
|
||
| from pyscipopt.scip cimport Variable, Solution | ||
| from cpython.dict cimport PyDict_Next | ||
| from cpython.ref cimport PyObject | ||
|
|
||
| import numpy as np | ||
| from pyscipopt.scip cimport Variable, Solution | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
|
|
@@ -146,6 +146,7 @@ cdef class Term: | |
|
|
||
| CONST = Term() | ||
|
|
||
|
|
||
| # helper function | ||
| def buildGenExprObj(expr): | ||
| """helper function to generate an object of type GenExpr""" | ||
|
|
@@ -181,10 +182,45 @@ def buildGenExprObj(expr): | |
| assert isinstance(expr, GenExpr) | ||
| return expr | ||
|
|
||
|
|
||
| cdef class ExprLike: | ||
|
|
||
| def __array_ufunc__( | ||
| self, | ||
| ufunc: np.ufunc, | ||
| method: Literal["__call__", "reduce", "reduceat", "accumulate", "outer", "at"], | ||
| *args, | ||
| **kwargs, | ||
| ): | ||
| if method == "__call__": | ||
| if ufunc in UNARY_MAPPER: | ||
| return getattr(args[0], UNARY_MAPPER[ufunc])() | ||
|
|
||
| return NotImplemented | ||
|
|
||
| def __abs__(self): | ||
| return UnaryExpr(Operator.fabs, buildGenExprObj(self)) | ||
|
|
||
| def exp(self): | ||
| return UnaryExpr(Operator.exp, buildGenExprObj(self)) | ||
|
|
||
| def log(self): | ||
| return UnaryExpr(Operator.log, buildGenExprObj(self)) | ||
|
|
||
| def sqrt(self): | ||
| return UnaryExpr(Operator.sqrt, buildGenExprObj(self)) | ||
|
|
||
| def sin(self): | ||
| return UnaryExpr(Operator.sin, buildGenExprObj(self)) | ||
|
|
||
| def cos(self): | ||
| return UnaryExpr(Operator.cos, buildGenExprObj(self)) | ||
|
|
||
|
|
||
| ##@details Polynomial expressions of variables with operator overloading. \n | ||
| #See also the @ref ExprDetails "description" in the expr.pxi. | ||
| cdef class Expr: | ||
|
|
||
| cdef class Expr(ExprLike): | ||
| def __init__(self, terms=None): | ||
| '''terms is a dict of variables to coefficients. | ||
|
|
||
|
|
@@ -202,9 +238,6 @@ cdef class Expr: | |
| def __iter__(self): | ||
| return iter(self.terms) | ||
|
|
||
| def __abs__(self): | ||
| return abs(buildGenExprObj(self)) | ||
|
|
||
| def __add__(self, other): | ||
| left = self | ||
| right = other | ||
|
|
@@ -463,17 +496,13 @@ Operator = Op() | |
| # so expr[x] will generate an error instead of returning the coefficient of x </pre> | ||
| # | ||
| #See also the @ref ExprDetails "description" in the expr.pxi. | ||
| cdef class GenExpr: | ||
|
|
||
| cdef class GenExpr(ExprLike): | ||
| cdef public _op | ||
| cdef public children | ||
|
|
||
| def __init__(self): # do we need it | ||
| ''' ''' | ||
|
|
||
| def __abs__(self): | ||
| return UnaryExpr(Operator.fabs, self) | ||
|
|
||
| def __add__(self, other): | ||
| if isinstance(other, np.ndarray): | ||
| return other + self | ||
|
|
@@ -758,55 +787,20 @@ cdef class Constant(GenExpr): | |
| return self.number | ||
|
|
||
|
|
||
| def exp(expr): | ||
| """returns expression with exp-function""" | ||
| if isinstance(expr, MatrixExpr): | ||
| unary_exprs = np.empty(shape=expr.shape, dtype=object) | ||
| for idx in np.ndindex(expr.shape): | ||
| unary_exprs[idx] = UnaryExpr(Operator.exp, buildGenExprObj(expr[idx])) | ||
| return unary_exprs.view(MatrixGenExpr) | ||
| else: | ||
| return UnaryExpr(Operator.exp, buildGenExprObj(expr)) | ||
| exp = np.exp | ||
| log = np.log | ||
| sqrt = np.sqrt | ||
| sin = np.sin | ||
| cos = np.cos | ||
Zeroto521 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| cdef dict UNARY_MAPPER = { | ||
| np.absolute: "__abs__", | ||
| exp: "exp", | ||
| log: "log", | ||
| sqrt: "sqrt", | ||
| sin: "sin", | ||
| cos: "cos", | ||
| } | ||
|
|
||
| def log(expr): | ||
| """returns expression with log-function""" | ||
| if isinstance(expr, MatrixExpr): | ||
| unary_exprs = np.empty(shape=expr.shape, dtype=object) | ||
| for idx in np.ndindex(expr.shape): | ||
| unary_exprs[idx] = UnaryExpr(Operator.log, buildGenExprObj(expr[idx])) | ||
| return unary_exprs.view(MatrixGenExpr) | ||
| else: | ||
| return UnaryExpr(Operator.log, buildGenExprObj(expr)) | ||
|
|
||
| def sqrt(expr): | ||
| """returns expression with sqrt-function""" | ||
| if isinstance(expr, MatrixExpr): | ||
| unary_exprs = np.empty(shape=expr.shape, dtype=object) | ||
| for idx in np.ndindex(expr.shape): | ||
| unary_exprs[idx] = UnaryExpr(Operator.sqrt, buildGenExprObj(expr[idx])) | ||
| return unary_exprs.view(MatrixGenExpr) | ||
| else: | ||
| return UnaryExpr(Operator.sqrt, buildGenExprObj(expr)) | ||
|
|
||
| def sin(expr): | ||
| """returns expression with sin-function""" | ||
| if isinstance(expr, MatrixExpr): | ||
| unary_exprs = np.empty(shape=expr.shape, dtype=object) | ||
| for idx in np.ndindex(expr.shape): | ||
| unary_exprs[idx] = UnaryExpr(Operator.sin, buildGenExprObj(expr[idx])) | ||
| return unary_exprs.view(MatrixGenExpr) | ||
| else: | ||
| return UnaryExpr(Operator.sin, buildGenExprObj(expr)) | ||
|
|
||
| def cos(expr): | ||
| """returns expression with cos-function""" | ||
| if isinstance(expr, MatrixExpr): | ||
| unary_exprs = np.empty(shape=expr.shape, dtype=object) | ||
| for idx in np.ndindex(expr.shape): | ||
| unary_exprs[idx] = UnaryExpr(Operator.cos, buildGenExprObj(expr[idx])) | ||
| return unary_exprs.view(MatrixGenExpr) | ||
| else: | ||
| return UnaryExpr(Operator.cos, buildGenExprObj(expr)) | ||
|
Comment on lines
-801
to
-809
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| def expr_to_nodes(expr): | ||
| '''transforms tree to an array of nodes. each node is an operator and the position of the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,15 @@ | ||
| from typing import ClassVar | ||
|
|
||
| import numpy | ||
| import numpy as np | ||
| from _typeshed import Incomplete | ||
| from typing_extensions import disjoint_base | ||
|
|
||
| CONST: Term | ||
| EventNames: dict | ||
| MAJOR: int | ||
| MINOR: int | ||
| Operator: Op | ||
| PATCH: int | ||
| Operator: Op | ||
| PY_SCIP_CALL: Incomplete | ||
| StageNames: dict | ||
| TYPE_CHECKING: bool | ||
|
|
@@ -20,18 +20,18 @@ _core_sum: Incomplete | |
| _expr_richcmp: Incomplete | ||
| _is_number: Incomplete | ||
| buildGenExprObj: Incomplete | ||
| cos: Incomplete | ||
| exp: Incomplete | ||
| log: Incomplete | ||
| sin: Incomplete | ||
| cos: Incomplete | ||
| sqrt: Incomplete | ||
|
Comment on lines
+24
to
+27
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put them together |
||
| expr_to_array: Incomplete | ||
| expr_to_nodes: Incomplete | ||
| is_memory_freed: Incomplete | ||
| log: Incomplete | ||
| print_memory_in_use: Incomplete | ||
| quickprod: Incomplete | ||
| quicksum: Incomplete | ||
| readStatistics: Incomplete | ||
| sin: Incomplete | ||
| sqrt: Incomplete | ||
| str_conversion: Incomplete | ||
| value_to_array: Incomplete | ||
|
|
||
|
|
@@ -325,13 +325,27 @@ class Eventhdlr: | |
| def eventinit(self) -> Incomplete: ... | ||
| def eventinitsol(self) -> Incomplete: ... | ||
|
|
||
| class ExprLike: | ||
| def __array_ufunc__( | ||
| self, | ||
| ufunc: np.ufunc, | ||
| method: str, | ||
| *args: Incomplete, | ||
| **kwargs: Incomplete, | ||
| ) -> Incomplete: ... | ||
| def __abs__(self) -> Incomplete: ... | ||
| def exp(self) -> Incomplete: ... | ||
| def log(self) -> Incomplete: ... | ||
| def sqrt(self) -> Incomplete: ... | ||
| def sin(self) -> Incomplete: ... | ||
| def cos(self) -> Incomplete: ... | ||
|
|
||
| @disjoint_base | ||
| class Expr: | ||
| class Expr(ExprLike): | ||
| terms: Incomplete | ||
| def __init__(self, terms: Incomplete = ...) -> None: ... | ||
| def degree(self) -> Incomplete: ... | ||
| def normalize(self) -> Incomplete: ... | ||
| def __abs__(self) -> Incomplete: ... | ||
| def __add__(self, other: Incomplete) -> Incomplete: ... | ||
| def __eq__(self, other: object) -> bool: ... | ||
| def __ge__(self, other: object) -> bool: ... | ||
|
|
@@ -371,13 +385,12 @@ class ExprCons: | |
| def __ne__(self, other: object) -> bool: ... | ||
|
|
||
| @disjoint_base | ||
| class GenExpr: | ||
| class GenExpr(ExprLike): | ||
| _op: Incomplete | ||
| children: Incomplete | ||
| def __init__(self) -> None: ... | ||
| def degree(self) -> Incomplete: ... | ||
| def getOp(self) -> Incomplete: ... | ||
| def __abs__(self) -> Incomplete: ... | ||
| def __add__(self, other: Incomplete) -> Incomplete: ... | ||
| def __eq__(self, other: object) -> bool: ... | ||
| def __ge__(self, other: object) -> bool: ... | ||
|
|
@@ -496,7 +509,7 @@ class LP: | |
| def solve(self, dual: Incomplete = ...) -> Incomplete: ... | ||
| def writeLP(self, filename: Incomplete) -> Incomplete: ... | ||
|
|
||
| class MatrixConstraint(numpy.ndarray): | ||
| class MatrixConstraint(np.ndarray): | ||
| def getConshdlrName(self) -> Incomplete: ... | ||
| def isActive(self) -> Incomplete: ... | ||
| def isChecked(self) -> Incomplete: ... | ||
|
|
@@ -512,21 +525,21 @@ class MatrixConstraint(numpy.ndarray): | |
| def isSeparated(self) -> Incomplete: ... | ||
| def isStickingAtNode(self) -> Incomplete: ... | ||
|
|
||
| class MatrixExpr(numpy.ndarray): | ||
| class MatrixExpr(np.ndarray): | ||
| def _evaluate(self, sol: Incomplete) -> Incomplete: ... | ||
| def __array_ufunc__( | ||
| self, | ||
| ufunc: Incomplete, | ||
| method: Incomplete, | ||
| ufunc: np.ufunc, | ||
| method: str, | ||
| *args: Incomplete, | ||
| **kwargs: Incomplete, | ||
| ) -> Incomplete: ... | ||
|
|
||
| class MatrixExprCons(numpy.ndarray): | ||
| class MatrixExprCons(np.ndarray): | ||
| def __array_ufunc__( | ||
| self, | ||
| ufunc: Incomplete, | ||
| method: Incomplete, | ||
| ufunc: np.ufunc, | ||
| method: str, | ||
| *args: Incomplete, | ||
| **kwargs: Incomplete, | ||
| ) -> Incomplete: ... | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| import math | ||
|
|
||
| import numpy as np | ||
| import pytest | ||
|
|
||
| from pyscipopt import Model, sqrt, log, exp, sin, cos | ||
| from pyscipopt.scip import Expr, GenExpr, ExprCons, Term | ||
| from pyscipopt import Model, cos, exp, log, sin, sqrt | ||
| from pyscipopt.scip import Expr, ExprCons, GenExpr, Term | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
|
|
@@ -118,10 +118,12 @@ def test_genexpr_op_genexpr(model): | |
| assert isinstance(1/x + genexpr, GenExpr) | ||
| assert isinstance(1/x**1.5 - genexpr, GenExpr) | ||
| assert isinstance(y/x - exp(genexpr), GenExpr) | ||
| # sqrt(2) is not a constant expression and | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| # we can only power to constant expressions! | ||
| with pytest.raises(NotImplementedError): | ||
| genexpr **= sqrt(2) | ||
|
|
||
| genexpr **= sqrt(2) | ||
| assert isinstance(genexpr, GenExpr) | ||
|
|
||
| with pytest.raises(TypeError): | ||
| genexpr **= sqrt("2") | ||
|
|
||
| def test_degree(model): | ||
| m, x, y, z = model | ||
|
|
@@ -218,3 +220,21 @@ def test_getVal_with_GenExpr(): | |
|
|
||
| with pytest.raises(ZeroDivisionError): | ||
| m.getVal(1 / z) | ||
|
|
||
|
|
||
| def test_unary(model): | ||
| m, x, y, z = model | ||
|
|
||
| assert str(abs(x)) == "abs(sum(0.0,prod(1.0,x)))" | ||
| assert str(np.absolute(x)) == "abs(sum(0.0,prod(1.0,x)))" | ||
| assert str(sin([x, y])) == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y)))]" | ||
| assert ( | ||
| str(np.sin([x, y])) == "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y)))]" | ||
| ) | ||
| assert ( | ||
| str(sqrt([x, y])) == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y)))]" | ||
| ) | ||
| assert ( | ||
| str(np.sqrt([x, y])) | ||
| == "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y)))]" | ||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExprLikeis the base class and also a duck type.It defines the behavior, and its subclass defines the data.
I will use
ExprLiketo splitVariableandExprin the future.