Open petschge opened 3 years ago
A simpler way to reproduce the bug is
In [57]: e = Piecewise((2, Abs(arg(x)) < pi), (1, True))
In [58]: e
Out[58]:
⎧2 for │arg(x)│ < π
⎨
⎩1 otherwise
In [59]: e.is_constant()
---------------------------------------------------------------------------
TypeError
A simpler way to reproduce the bug is
In [57]: e = Piecewise((2, Abs(arg(x)) < pi), (1, True)) In [58]: e Out[58]: ⎧2 for │arg(x)│ < π ⎨ ⎩1 otherwise In [59]: e.is_constant() --------------------------------------------------------------------------- TypeError
Actually, the problem lies here
>>> arg(0)
nan
is_constant
tries to check if the values are different at two points. One of the selected points, as can be seen, is 0
. Thus this is bound to give an error.
https://github.com/sympy/sympy/blob/2346054bb4888ef7eec2f6dad6c3dd52bf1fe927/sympy/core/expr.py#L657-L659
A possible solutions is to set arg(0)
to 0
. Though I don't know if this change would be backwards compatible.
We could change arg(0)
to be zero but it still remains the case that the is_constant
method is unreliable for trying to substitute random values into an unknown expression and needs to be made more robust.
https://github.com/sympy/sympy/blob/b564d9ba2705ee2978766e1ee2102750834df68b/sympy/core/expr.py#L657-L664
The original code has considered the case when substitution fails and tries to catch such exceptions. The only problem is that it assumes only ZeroDivisionError
may occur in the procedure, while in this case, a TypeError
has been raised and thus not caught. Adding this type of exception to the code may be a quick fix.
The problem is that TypeError
can be raised for all sorts of things including bugs in the code:
In [20]: def f(x): return x**2
In [21]: f()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-21-c43e34e6d405> in <module>
----> 1 f()
TypeError: f() missing 1 required positional argument: 'x'
We need more specific exception classes. It would be much better to do except NaNError
than except TypeError
.
In any case when adding any kind of except clause like this there needs to be clear comment explaining why it is being caught and what are examples of input that lead to the exception being caught.
Then I guess we should change this exception here raised to a more specified exception class:
https://github.com/sympy/sympy/blob/b564d9ba2705ee2978766e1ee2102750834df68b/sympy/core/relational.py#L672-L677
But we don't have a NaNError
yet, and for consistency there would be other places we may have to change so as to raise/catch this new exception.
By the way, though I agree that a overly general exception should not be caught, I'm not sure about the reason why usages of except TypeError
can be found in 100+ places all over the repo. I personally feel it is also okay to use it here.
By the way, though I agree that a overly general exception should not be caught, I'm not sure about the reason why usages of
except TypeError
can be found in 100+ places all over the repo. I personally feel it is also okay to use it here.
I think most of those places should be changed and that we should really try to cleanup exception handling.
The first problem though is the number of places that raise TypeError. If TypeError is raised then it's not possible to catch something more specific.
What we could do is this:
# sympy.core.coreerrors
class NaNError(BaseCoreError):
"""Raised for invalid operations involving NaN"""
pass
class NaNTypeError(NaNError, TypeError):
"""Raised for a NaNError that previously raised TypeError
This is raised for backwards compatibility in places where TypeError
was previously raised so that code that previously caught the TypeError
can still catch the exception.
New code should catch NaNError rather than NaNTypeError.
"""
pass
With those we can change the places that raise TypeError for invalid operations involving NaN so that they now raise NaNTypeError. Then in the places where we want to catch the exception we can catch NaNError.
Running the following code
results in the following error message after a few minutes