CARVIEW |
Navigation Menu
-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Fixed pretty printing and codegen error messages for empty piecewise #15010
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
Fixed pretty printing and codegen error messages for empty piecewise #15010
Conversation
1. pprint called on an empty Piecewise function now prints "undefined" 2. codegen print functions raise the relevant "No otherwise statement error" when encountering Piecewise functions
âś… Hi, I am the SymPy bot (v127). I'm here to make sure this pull request has a release notes entry. Please read the guide on how to write release notes. Click here to see the pull request description that was parsed.
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.2.1. 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. Update The release notes on the wiki have been updated. |
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'm not sure if this is the fix we want but here are some suggestions to improve it.
Please check if Julia etc changes are really needed
sympy/printing/julia.py
Outdated
@@ -418,7 +418,7 @@ def _print_yn(self, expr): | |||
|
|||
|
|||
def _print_Piecewise(self, expr): | |||
if expr.args[-1].cond != True: | |||
if len(expr.args) == 0 or expr.args[-1].cond != True: |
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 thought all of these codegen were working correctly before?
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 changed this because the codegens were raising a tuple index out of range error later on in the function (when given an empty piecewise function) instead of raising the relevant ValueError that there isn't a default block.
sympy/printing/pretty/pretty.py
Outdated
D.baseline = D.height()//2 | ||
D.binding = prettyForm.OPEN | ||
return D | ||
|
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 needs a test for pprint and latex
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.
Have added the test case for both
Fixed the same bug (reported for pprint) for latex printing too Added test cases for pprint and latex printing as suggested by review
I didn't know empty Piecewise was allowed. What does it represent? Is it actually used somewhere? I'm not a fan of "undefined" for the pretty printing. I think just printing it as |
This could use a release notes entry. |
As noted on the issue, I think the empty |
@@ -84,11 +85,16 @@ def test_piecewise(): | |||
).subs(x, 1) == Piecewise((-1, y < 1), (2, True)) | |||
assert Piecewise((1, Eq(x**2, -1)), (2, x < 0)).subs(x, I) == 1 | |||
|
|||
p6 = Piecewise((x, x > 0)) | |||
n = symbols('n', negative = True) | |||
assert p6.subs(x, n) == Undefined |
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 "Undefined"?
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.
"Undefined" was defined as S.NaN in sympy/functions/elementary/tests/piecewise.py and used in the piecewise integration code, and the tests for piecewise integration so Undefined == nan (I used that to stay consistent with the piecewise integration code but could use S.NaN if that's clearer)
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.
Note the class docs say:
- If the function is evaluated at a place where all conditions are False,
a ValueError exception will be raised.
which isn't true after this change (?)
@@ -84,11 +85,16 @@ def test_piecewise(): | |||
).subs(x, 1) == Piecewise((-1, y < 1), (2, True)) | |||
assert Piecewise((1, Eq(x**2, -1)), (2, x < 0)).subs(x, I) == 1 | |||
|
|||
p6 = Piecewise((x, x > 0)) | |||
n = symbols('n', negative = True) |
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.
negative=True
(when used in kwargs)
# False condition is never retained | ||
assert Piecewise((x, False)) == Piecewise( | ||
(x, False), evaluate=False) == Piecewise() | ||
raises (TypeError, lambda: Piecewise((x, False))) |
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 is a lossy change. Add your new test but modify the existing one as:
assert Piecewise((2*x, x < 0), (x, False)) ==
Piecewise((2*x, x < 0), (x, False), evaluate=False) ==
Piecewise((2*x, x < 0))
@@ -30,9 +30,10 @@ def test_piecewise(): | |||
Piecewise((x, Or(x < 1, x < 2)), (0, True)) | |||
assert Piecewise((x, x < 1), (x, x < 2), (x, True)) == x | |||
assert Piecewise((x, True)) == x | |||
#Empty Piecewise not accepted |
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.
# Empty...
(with space)
e = e._subs(old, new) | ||
args[i] = (e, c) | ||
if c == True: | ||
break | ||
if not args_exist: | ||
return Undefined |
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 haven't learning about _eval_subs
but is it ok to return something other than self.func...
here? Is it safer to do if not args_exist: args = ((Undefined, True))
?
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.
Also, is this the best place to make this change? Is this the only code path that looks like evaluate/simplify/subs/canonicalize/etc where we want nan
instead of exception?
This code starts with # this is strictly not necessary,
and yet now it will be necessary.
(Note this is not a well-informed opinion: if you've thought about this and this is the right place, then say so.)
Fixed issues based on the reviews given
I think the modified function (_eval_subs) was already necessary. If I try to bypass this function by defining it as
I think the comment might mean that the function could have been written more compactly like so:
which seems to give the correct result as expected. Instead, it is written the way it is to avoid evluating e._subs when c is known to be False and to avoid looking at the remaining (e,c) values when we've already found one c value that evaluates to True. I think this part of the function is not strictly necessarily, but makes it more efficient. So I think this is a good place to make the change, because it is always passed through when calling the subs/evaluate methods and the function overall is already necessary. About returning Undefined at the end, I looked through the code and I don't see an instance where returning Undefined straightaway would be a problem, but I'm not sure either so I agree that it is safer to do Relating to what the class docs said, I'm not sure if they were true originally either because the original behavior seems to be that subs/evaluate returns I've also updated the test cases and the syntax issues pointed out. Thanks |
assert Piecewise((2*x, x < 0), (x, False)) == \ | ||
Piecewise((2*x, x < 0), (x, False), evaluate=False) == \ | ||
Piecewise((2*x, x < 0)) | ||
raises (TypeError, lambda: Piecewise((x, False))) |
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'm not sure about this one. I think it would be better for this case to also return nan, and only do TypeError for explicit Piecewise()
with no arguments. It's possible for piecewise like these to be constructed, for instance, if a condition evaluates to False. Plus the error message would be confusing in this case.
… error Piecewise with arguments that are all False now return nan, not raise TypeError
I've changed the code so that only explicitly constructed empty piecewise functions raise a TypeError. So the new behavior is as follows:
Is this ok? Thanks |
Yes, that behavior is more reasonable to me. |
Sorry for "one more thing" but I get the impression
|
I think here it could still work for generators because args would be a tuple even if a generator argument was passed in (since the function argument includes *args, which would automatically package the arguments into the new tuple args). I tested this out in the following cases, where it works:
I'm not sure if there is a case involving a generator that would break it, but I don't think so because I think *args always implies that args is a tuple. |
ok, thanks for checking! Will merge in 24 hours. |
New behavior:
References to other Issues or PRs
Fixes #15005 based on the discussion there
Brief description of what is fixed or changed
Other comments
Release Notes