-
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Make Expr.__add__ etc return NotImplemented for non-Expr Basic subclasses #18116
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
Conversation
|
✅ Hi, I am the SymPy bot (v149). I'm here to help you write a release notes entry. Please read the guide on how to write release notes. Your release notes are in good order. Here is what the release notes will look like:
This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.6. Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. Click here to see the pull request description that was parsed.
Update The release notes on the wiki have been updated. |
sympy/codegen/array_utils.py
Outdated
| return obj | ||
|
|
||
| # XXX: Maybe __mul__ and __rmul__ should have some type checking. These | ||
| # methods were added because Expr no longer accepts non-Expr in __mul__. |
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.
I think that it is possible to avoid calling these methods. This is called from
sympy/sympy/simplify/trigsimp.py
Line 1170 in 270cf68
| return coeff*e |
with
coeff = 1 and e an instance of CoordSys3D (that appears as the second argument of a BaseScalar B.x). There could probably be
return coeff*e if isinstance(e, Expr) else e
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.
That's a better idea. I've fixed this in futrig. However I found that I was actually getting the TypeError from multiplying Tuple there. I guess that there is some exception masking going on because there is an except TypeError: pass in trigsimp - probably that should be removed as it is maybe hiding bugs.
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.
All try ... except code blocks are potential for trouble. We should discourage developers from using try ... except.
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.
Agreed. There are good ways to use try/except but I see so many bad ways of doing it in SymPy code that I think we should discourage all try/except unless absolutely necessary and even then insist that the scope of try/except be limited as much as possible.
sympy/codegen/array_utils.py
Outdated
| return Mul(self, other) | ||
|
|
||
| def __rmul__(self, other): | ||
| return Mul(other, self) |
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.
I would recommend:
def __mul__(self, other):
raise NotImplementedError("Product of N-dim arrays is not uniquely defined. Use another method.")At least you can specify the error message.
In the future we may decide to convert these objects into an Expr (it's not so far-fetched, you may wish to put these expressions into mathematical formulae?).
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.
Thanks. I've added that but I included a special case for multiplying by 1 since presumably that's okay and that was the case in the test failure.
|
I've edited the OP. This is ready for review. |
|
Actually I'll add more tests first |
|
This is ready for review |
sympy/core/tests/test_expr.py
Outdated
| objects when interacting with Expr instances.''' | ||
| e = Expr() | ||
| for ne in [NonBasic(), NonExpr()]: | ||
| ne = NonExpr() |
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.
This should probably be removed?
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.
Thanks. That's thrown up some errors. I'll have to fix them...
|
This looks fine to me though there is one thing that I'd like to change. The boilerplate sympy/sympy/core/decorators.py Lines 89 to 91 in f7390e5
should be checked before passing it to func(a, b).
|
I did try that like this from sympy import Basic
from sympy.core.decorators import _sympifyit
class Expr(Basic):
@_sympifyit('other', NotImplemented, Expr)
def __add__(self, other):
return Add(self, other)The problem is that this gives name |
|
This looks like the problem I had with |
|
I don't see where I'd set the from sympy import Basic
from sympy.core.decorators import _sympifyit
class Expr(Basic):
def __add__(self, other):
return Add(self, other)
Expr.__add__ = _sympifyit('other', NotImplemented, Expr)(Expr.__add__)That could be done as a class decorator: @sympify_binary_methods()
class Expr(Basic):
def __add__(self, other):
return Add(self, other) |
|
I like this approach. It looks promising but I'll take some time to look into it more carefully. |
sympy/core/decorators.py
Outdated
| In the above it is important that we return NotImplemented when other is | ||
| not sympifiable and also when the sympified result is not of the expected | ||
| type. This allows the MyTuple class to be used cooperatively with other | ||
| classes that overload __add__ and want to do something else in bombination |
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.
->combination
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.
Fixed
|
I haven't changed this throughout sympy/core/numbers.py apart from ensuring that NotImplemented is returned when needed because I think that a broader rethink of these methods in |
|
I think this is ready to be merged. |
References to other Issues or PRs
I found that I needed this when attempting to fix #4986 by introducing a new
Brief description of what is fixed or changed
Return
NotImplementedfrom__add__etc methods inSetandExprif the sympifiedotheroperand is not of the expected type (i.e.SetorExpr). On current masterNotImplementedis only returned if_sympifyfails. With this PR it will be returned also if_sympifyreturns an object that is not of the expected type. There are two improvements resulting from this:ExprBasicclasses that support arithmetic operations withExprso ifais non-ExprandbisExprthentype(a)can control botha + bandb + a. On current masterExprwill forceainto anAddeven if it doesn't belong there.Master:
PR
Mulof aSet.Master:
PR
There was a bug in
dsolvedue to these garbage results. Because asetwillsympifyto aFiniteSetthe following would happen:Under this PR both of those gives
TypeError.Other comments
There were apparently a couple of parts of the codebase depending on this behaviour. I've added a couple of fixes for those. In general I expect that there are lots of bugs to do with this hiding in different places due to e.g. expecting to do something like
trigsimp(non-expr)which will sometimes work even though it is really aimed atExpr. We need a docmented way for functions liketrigsimpto be able to identifyExprparts of a non-Exprobject (likeSet,Tuple,Matrixetc).Release Notes
a + bwhere one ofaorbis an instance of Expr. This also means that any non-Expr Basic subclasses can not depend onExpr.__add__to createAdd(a, b): if a class is not a subclass of Expr and wants to define binary operations with Expr it must do so explicitly in its own__add__method. For classes depending on this this is not a backward compatible change.a + bwhere one ofaorbis an instance of Set. This also means that any non-Set Basic subclasses can not depend onExpr.__add__to createUnion(a, b): if a class is not a subclass of Set and wants to define binary operations with Set it must do so explicitly in its own__add__method. For classes depending on this this is not a backward compatible change.