-
Notifications
You must be signed in to change notification settings - Fork 277
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
a1501e6
259f4ce
cfa8d5a
0517639
c82d570
9c78ad6
55c1343
b4d45b1
3688217
2579e87
8c4b75f
bc873b8
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,16 +43,16 @@ | |
| # 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 cpython.dict cimport PyDict_Next, PyDict_GetItem | ||
| from cpython.object cimport Py_TYPE | ||
| from cpython.ref cimport PyObject | ||
| from cpython.tuple cimport PyTuple_GET_ITEM | ||
| from pyscipopt.scip cimport Variable, Solution | ||
|
|
||
| import numpy as np | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| double = float | ||
|
|
@@ -180,6 +180,7 @@ cdef class Term: | |
|
|
||
| CONST = Term() | ||
|
|
||
|
|
||
| # helper function | ||
| def buildGenExprObj(expr): | ||
| """helper function to generate an object of type GenExpr""" | ||
|
|
@@ -215,10 +216,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) -> GenExpr: | ||
| return UnaryExpr(Operator.fabs, buildGenExprObj(self)) | ||
|
|
||
| def exp(self) -> GenExpr: | ||
| return UnaryExpr(Operator.exp, buildGenExprObj(self)) | ||
|
|
||
| def log(self) -> GenExpr: | ||
| return UnaryExpr(Operator.log, buildGenExprObj(self)) | ||
|
|
||
| def sqrt(self) -> GenExpr: | ||
| return UnaryExpr(Operator.sqrt, buildGenExprObj(self)) | ||
|
|
||
| def sin(self) -> GenExpr: | ||
| return UnaryExpr(Operator.sin, buildGenExprObj(self)) | ||
|
|
||
| def cos(self) -> GenExpr: | ||
| 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. | ||
|
|
||
|
|
@@ -236,9 +272,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 | ||
|
|
@@ -502,17 +535,14 @@ 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 | ||
|
|
@@ -816,55 +846,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)) | ||
|
|
||
| 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)) | ||
| 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", | ||
| } | ||
|
Comment on lines
+849
to
+861
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. All functions have test cases now. Cython can't directly trace numpy ufunc. |
||
|
|
||
| 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)) | ||
Joao-Dionisio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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,9 +1,10 @@ | ||||||||||||||||||||||||
| import math | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| from pyscipopt import Model, sqrt, log, exp, sin, cos | ||||||||||||||||||||||||
| from pyscipopt.scip import Expr, GenExpr, ExprCons, CONST | ||||||||||||||||||||||||
| from pyscipopt import Model, cos, exp, log, sin, sqrt | ||||||||||||||||||||||||
| from pyscipopt.scip import CONST, Expr, ExprCons, GenExpr | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @pytest.fixture(scope="module") | ||||||||||||||||||||||||
|
|
@@ -117,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.
Member
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. are we sure that SCIP's and numpy's trignometric functions are the same? Wondering whether precision differences might affect the results
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. I thought we shouldn't worry about this. No matter whether we use Because of the following things, it's better be a constant, not a
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. There is an example from PySCIPOpt.
PySCIPOpt/src/pyscipopt/expr.pxi Lines 624 to 634 in 660db29
|
||||||||||||||||||||||||
| # 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 | ||||||||||||||||||||||||
|
|
@@ -219,6 +222,49 @@ def test_getVal_with_GenExpr(): | |||||||||||||||||||||||
| m.getVal(1 / z) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def test_unary(model): | ||||||||||||||||||||||||
| m, x, y, z = model | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| res = "abs(sum(0.0,prod(1.0,x)))" | ||||||||||||||||||||||||
| assert str(abs(x)) == res | ||||||||||||||||||||||||
| assert str(np.absolute(x)) == res | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| res = "[sin(sum(0.0,prod(1.0,x))) sin(sum(0.0,prod(1.0,y)))]" | ||||||||||||||||||||||||
| assert str(sin([x, y])) == res | ||||||||||||||||||||||||
| assert str(np.sin([x, y])) == res | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| res = "[cos(sum(0.0,prod(1.0,x))) cos(sum(0.0,prod(1.0,y)))]" | ||||||||||||||||||||||||
| assert str(cos([x, y])) == res | ||||||||||||||||||||||||
| assert str(np.cos([x, y])) == res | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| res = "[sqrt(sum(0.0,prod(1.0,x))) sqrt(sum(0.0,prod(1.0,y)))]" | ||||||||||||||||||||||||
| assert str(sqrt([x, y])) == res | ||||||||||||||||||||||||
| assert str(np.sqrt([x, y])) == res | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| res = "[exp(sum(0.0,prod(1.0,x))) exp(sum(0.0,prod(1.0,y)))]" | ||||||||||||||||||||||||
| assert str(exp([x, y])) == res | ||||||||||||||||||||||||
| assert str(np.exp([x, y])) == res | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| res = "[log(sum(0.0,prod(1.0,x))) log(sum(0.0,prod(1.0,y)))]" | ||||||||||||||||||||||||
| assert str(log([x, y])) == res | ||||||||||||||||||||||||
| assert str(np.log([x, y])) == res | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| assert sqrt(4) == np.sqrt(4) | ||||||||||||||||||||||||
| assert all(sqrt([4, 4]) == np.sqrt([4, 4])) | ||||||||||||||||||||||||
| assert exp(3) == np.exp(3) | ||||||||||||||||||||||||
| assert all(exp([3, 3]) == np.exp([3, 3])) | ||||||||||||||||||||||||
| assert log(5) == np.log(5) | ||||||||||||||||||||||||
| assert all(log([5, 5]) == np.log([5, 5])) | ||||||||||||||||||||||||
| assert sin(1) == np.sin(1) | ||||||||||||||||||||||||
| assert all(sin([1, 1]) == np.sin([1, 1])) | ||||||||||||||||||||||||
| assert cos(1) == np.cos(1) | ||||||||||||||||||||||||
| assert all(cos([1, 1]) == np.cos([1, 1])) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # test invalid unary operations | ||||||||||||||||||||||||
| with pytest.raises(TypeError): | ||||||||||||||||||||||||
| np.arcsin(x) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def test_mul(): | ||||||||||||||||||||||||
| m = Model() | ||||||||||||||||||||||||
| x = m.addVar(name="x") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
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.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.
What is the main benefit of this?
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.
Variableis the subclass ofExpr. SoVariablecould operate likeExpr. And we don't have to add additional logic to handleVariable. This idea is good.But the relationships among
Variable,Term, andExprare very confusing.Variableis the unit of calculation. Why is it a subclass ofExpr?So
ExprLikecould splitVariableandExpr. And letVariablework withExpreasily.ExprLikeis a duck type, and it defines the operator's inputs and outputs.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.
For now,
ExprLikecould save code.__array_ufunc__won't repeat in Expr, and GenExpr.