Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

TODO: rebase Addressing Piecewise issues #1009

Open
wants to merge 13 commits into from

5 participants

@flacjacket
Collaborator

This push is to address a couple open issues with Piecewise and its syntax, notably changing the otherwise syntax. Other major changes with this commit are getting rid of the ExprCondPair class and changing how evaluation is handled. The use of True as the condition to specify otherwise conditions is deprecated. Additionally, the use of numbers as conditions is deprecated and the use of Interval's as conditions is dropped entirely. To implement the same functionality, either the '.as_relational()' or '.contains()' methods of Interval can be used.

As before, evaluation strips any bools out of the args by dropping expressions with False, but now expressions with condition=True are promoted to the otherwise statement by evaluation. Evaluation is not performed by default, but it is called when subs is called to allow for evaluation in that case. When bools end up in the args and evaluate is the default value of False, a deprecation warning is raised and evaluate is set to True to get clear the args of the bool.

The properties '.exprcondpairs' and '.otherwise' are implemented to easily access the expr/cond pairs and otherwise statement, respectively.

TODO:

This addresses the following issues:

2101 latex printing with piecewise function : reversed expr and cond

2567 Piecewise does not work when not given an "otherwise" condition

2626 Piecewise should use a different syntax for "otherwise"

2710 Cannot simplify Piecewise

2726 Piecewise((1,Interval(0,1,False,True)),(0,True)) syntax should not be allowed

Also, the following also seems like it has been handled, but not closed:

1216 series expansion of piecewise fails

@asmeurer
Owner

Thanks for working on this. I started to work on this a while back, but I ran into problems with _eval_leading_term. Did you encounter those issues?

sympy/functions/elementary/piecewise.py
((80 lines not shown))
- Usage:
+ - Expr/cond pairs where the cond is explicitly False will be removed.
+ - Expr/cond pairs which cannot be determined, e.g. x < 1 for a Symbol x,
+ are returned symbolically.
+ - For expr/cond pairs where the cond is explicitly True, e.g. 1 < 2,
+ and there are no undetermined expr/cond pairs appearing befare it,
@asmeurer Owner

typo

@flacjacket Collaborator

Thanks, fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@asmeurer
Owner

You've made things awfully complicated here? Why don't you just do what was suggested in issue 2626? Namely, use the syntax, Piecewise((expr1, cond1), ..., (exprn, condn), [otherwise]) (where the square brackets indicate that the argument is optional).

@asmeurer
Owner

Ah, well I see that that syntax does work in your branch. I suppose the complicated .args is necessary to support the old syntax as well (?) I think that .args should just use the new syntax, and all other supported syntaxes should be canonicalized to it for .args (similar to what Integral does).

By the way, I also think we ought to deprecate the old syntax, as it's very confusing.

sympy/functions/elementary/piecewise.py
((24 lines not shown))
return None
- def doit(self, **hints):
- """
- Evaluate this piecewise function.
- """
- newargs = []
- for e, c in self.args:
- if hints.get('deep', True):
- if isinstance(e, Basic):
- e = e.doit(**hints)
- if isinstance(c, Basic):
- c = c.doit(**hints)
- newargs.append((e, c))
- return Piecewise(*newargs)
+ @property
+ def ecs(self):
@asmeurer Owner

This needs a better name.

@flacjacket Collaborator

The most explanatory it could be is something like exprcondpairs, but that may be a bit verbose.

@asmeurer Owner

That doesn't sound too verbose to me. It's better than ecs, which is cryptic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@flacjacket
Collaborator

The first commit I don't think makes things terribly complicated, it mostly just adds the ability to have the otherwise expression in addition to adding the .ecs and .otherwise properties to easily get out the expr/cond pairs and otherwise statements, since the args can no longer be iterated as 'e, c in args'.

The second commit makes things more complicated as it changes the canonical args to allow evaluate to be maintained between things like subs. The more I think about it, the more I think this might not be necessary, as the only use is when you want to do something like Piecewise((x,x>0), evaluate=False).subs(x,1) and want it to return a Piecewise rather than just returning 1.

As for the old syntax, I'm not sure that can really be deprecated. or at least we can't really raise warnings, as there shouldn't be anything wrong with having True as a condition, especially since there's no way to tell a condition 1>0 or (x>0).subs(x,1) apart from True. I changed all the instances in the code to use the new otherwise syntax and changed the documentation to reflect this.

In the same vein, in issue 2626, you noted allowing a 1-tuple for an otherwise statement, I'm not entirely sure if this is necessary, but I included it as a possibility, that may be leading to an increased mess in some of the code.

I had the same problem with _eval_leading_term. Function tried to do _eval_leading_term on args[0], but since for a Piecewise that's a Tuple, it fails. What I implemented was to add all the expressions, including otherwise, together and return _eval_leading_term of that. I'm not sure if that would be the desired behavior for a Piecewise function or if it should take the expression when the variable goes to 0, but it works in as much as the old tests still pass.

@asmeurer
Owner

This needs to be merged or rebased with master. The issue is most likely the branch I recently pushed in that refactors the inequality classes.

@asmeurer
Owner

Right. To be more clear, the complication is specifically in .args, and as a result in anything that parses .args, and the code that builds it. .args should just be ((expr, cond), ..., otherwise).

The more I think about it, the more I think this might not be necessary, as the only use is when you want to do something like Piecewise((x,x>0), evaluate=False).subs(x,1) and want it to return a Piecewise rather than just returning 1.

I think the solution here is to keep 1 > 0 from being evaluated. You will need to implement the evaluate keyword in the new inequality classes. Then Piecewise can just pass the evaluate flag to that (or do you think it would be cleaner to not do that automatically and require the user to do it?).

In the same vein, in issue 2626, you noted allowing a 1-tuple for an otherwise statement, I'm not entirely sure if this is necessary, but I included it as a possibility, that may be leading to an increased mess in some of the code.

This is an added source of complication. It is not necessary to support this syntax. We decided against it.

I had the same problem with _eval_leading_term. Function tried to do _eval_leading_term on args[0], but since for a Piecewise that's a Tuple, it fails. What I implemented was to add all the expressions, including otherwise, together and return _eval_leading_term of that. I'm not sure if that would be the desired behavior for a Piecewise function or if it should take the expression when the variable goes to 0, but it works in as much as the old tests still pass.

@ness01 or someone who knows more about what that does will have to comment. Is it OK for _eval_leading_term to "overestimate", so to speak? Getting the term at 0 may not be so easy, as the conditions could be more than one dimensional, and hence be symbolic after you substitute 0.

As for the old syntax, I'm not sure that can really be deprecated. or at least we can't really raise warnings, as there shouldn't be anything wrong with having True as a condition, especially since there's no way to tell a condition 1>0 or (x>0).subs(x,1) apart from True. I changed all the instances in the code to use the new otherwise syntax and changed the documentation to reflect this.

I do think the old syntax should be removed, as this is another source of complication in the code. Just trying to understand the bulleted list of what happens with the args in the docstring was making my head spin, and I already have a good idea of what it would do.

I don't see how to avoid all ambiguities without completely dropping the old syntax (this is why the syntax needed to be changed in the first place). However, if we do that without a deprecation cycle, then any user of Piecewise will have his Piecewise objects automatically evaluated at the otherwise condition, which may be surprising.

So I think we should try to raise a warning. Just do it whenever True is interpreted as the otherwise condition. If the user got True not because he wanted otherwise but because he wanted it to evaluate to that point, then he will need to be warned anyway. We can direct the users to avoid ambiguity during the deprecation period by adding nan as a manual otherwise condition. Then, interpret all True conditions before the otherwise as evaluation (I thought you were already doing this, but Piecewise((x,x>0), (y, 0 < 1), nan) is giving me the same thing as Piecewise((x,x>0), y)).

Do you see any problems with that plan?

@ness01

As I have said elsewhere, I'm not quite certain why so many functions implement _eval_as_leading_term. According to the docstring (and the implementation!), as_leading_term may only be used on a result returned by Basic.series. Now, conceivably, this could return a piecewise. I think that the right thing for piecewise to do is to pass eval_leading_term through to its components.

But really I don't know for sure, and I am not even aware of any code that returns Piecewise in series, except for Piecewise itself. And, moreover, I don't think gruntz can handle piecewise anyway.

So in summary, my suggestion is to make as_leading_term behave just like series - simply return a piecewise with the same conditions and the inner functions replaced by their leading terms. But I am not at all confident this is right (since I know no use cases) and certainly did not write the code. Maybe @certik knows better.

@flacjacket
Collaborator

It's currently rebased, but there are some test failures with the printing.

Do you see any problems with that plan?

That sounds good. I'll see what I can do to get that going.

As for the _eval_leading_term, I'll try making it a Piecewise and seeing that it doesn't fail until I hear otherwise.

@flacjacket
Collaborator

@asmeurer So there is currently some test error in nseries that originates from somewhere in gruntz.py that I don't fully understand, but other than that, I've tried to fix this up, tho I'm not totally sure what exactly you were trying to say at some points, so please note if something is off.

I haven't changed any of the docstring for this, but here's the gist of it:

By default, evaluate isn't called and if there are any bool conds, it raises a warning and sets evaluate to True. eval then gets rid of any bools by discarding when False and promoting expressions to otherwise if their condition is True before otherwise. If subs is called, it is taken as an evaluation and eval is called to clear any bools.

With this Piecewise((x,x>0), (y, 0 < 1), nan) (which would be legal post-deprecation as Piecewise((x,x>0), (y, 0 < 1), nan, evaluate=True)) reduces to Piecewise((x,x>0), y), but only because evaluate is called. I'm not sure what you mean by:

Then, interpret all True conditions before the otherwise as evaluation

if not something like that. Do you mean the 0 < 1 is not trying to be called as an otherwise statement and thus should trigger evaluation, or because it is a True condition and not otherwise it should be kept as a expression/condition rather than evaluating to the otherwise statement? Should this use of a bool then warrant a deprecation warning?

I also put in deprecation for numbers as a condition, I see no reason why you'd want to use a number as a condition, but I could be wrong.

@asmeurer
Owner

Please clean up your commit history.

@asmeurer
Owner

Do you mean the 0 < 1 is not trying to be called as an otherwise statement and thus should trigger evaluation

Yes. If you just write Piecewise((x,x>0), (y, 0 < 1)), the y will be interpreted as otherwise. The workaround for users should be to add nan as the otherwise condition manually (this effectively shouldn't change anything, since conditions outside the range evaluate to nan by default when no otherwise is given).

I would give the warning for Piecewise((x,x>0), (y, 0 < 1)) but not Piecewise((x,x>0), (y, 0 < 1), nan), since the latter follows the new syntax, and is actually what we will recommend to people to avoid the warning (and make things correct).

@asmeurer
Owner

Are numbers currently allowed as conditions? I think this shouldn't be allowed, for the same reason that intervals can't be used (they're not Booleans). Unless I'm misunderstanding what this means.

@flacjacket
Collaborator

Alright, I think I get it.

And yes, numbers are currently allowed conditions, I can remove that as well.

@asmeurer
Owner

From what I can see, they are just evaluated as booleans, i.e., nonzero means True and zero means False. Is that right?

Yeah, let's just remove that. I wouldn't even bother deprecating it.

@flacjacket
Collaborator

Alright, I've finished cleaning up documentation, number conditions are removed and the deprecation warning is correctly implemented.

Also, the error that I was getting earlier was due to the as_leading_term. Doing abs(x - a).nseries(x, 1) adds a Piecewise with an Order. This ends up creating a new Order based off the leading term of the Piecewise, and thus it needs to be a normal function of the Symbol in question. To fix the test, I changed as_leading_term back to adding the expressions and taking the leading term of the sum.

@asmeurer
Owner

SymPy Bot Summary: There were test failures.

@flacjacket: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY7ZgKDA

Interpreter: /Library/Frameworks/Python.framework/Versions/3.2/bin/python3 (3.2.2-final-0)
Architecture: Darwin (64-bit)
Cache: yes
Test command: setup.py test
master hash: 9258bb9
branch hash: 97cc38361bdf20cce1f895ec88df7a11e523e915

Automatic review by SymPy Bot.

@asmeurer
Owner

You can ignore that test failure. There aren't any in Python 2 either, but there seems to be some problem with the bot. It just hangs with

============================== txt doctests start ==============================

bot-tmpjf8lh2/sympy/doc/src/gotchas.txt [100]                               [OK]
bot-tmpjf8lh2/sympy/doc/src/guide.txt [46]                                  [OK]
bot-tmpjf8lh2/sympy/doc/src/install.txt [10]                                [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/assumptions/index.txt [80]              [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/concrete.txt [22]                       [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/core.txt [13]                           [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/evalf.txt [69]                          [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/functions/elementary.txt [24]           [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'latex_ex.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/geometry.txt [58]                       [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/integrals/integrals.txt [7]             [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/logic.txt [15]                          [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/matrices.txt [99]                       [OK]
*** DocTestRunner.merge: 'ntheory.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'matrices.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/physics/mechanics/advanced.txt [24]     [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/physics/mechanics/examples.txt [183]    [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/physics/mechanics/kane.txt [46]         [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/physics/mechanics/kinematics.txt [62]   [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/physics/mechanics/masses.txt [23]       [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/physics/mechanics/vectors.txt [58]      [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/physics/units.txt [9]                   [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/polys/basics.txt [54]                   [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
*** DocTestRunner.merge: 'reference.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/polys/wester.txt [59]                   [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/printing.txt [30]                       [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/rewriting.txt [15]                      [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/series.txt [7]                          [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/solvers/solvers.txt [4]                 [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/statistics.txt [25]                     [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/utilities/autowrap.txt [8]              [OK]
bot-tmpjf8lh2/sympy/doc/src/modules/utilities/codegen.txt [1]               [OK]
*** DocTestRunner.merge: 'index.txt' in both testers; summing outcomes.
bot-tmpjf8lh2/sympy/doc/src/modules/utilities/iterables.txt [5]             [OK]
bot-tmpjf8lh2/sympy/doc/src/tutorial.ru.txt [189]                           [OK]
bot-tmpjf8lh2/sympy/doc/src/tutorial.txt [195]                              [OK]

Does anyone else get this?

@asmeurer
Owner

That's in Python 2.5 by the way. That might be important.

sympy/functions/elementary/piecewise.py
((49 lines not shown))
- for expr, condition in self.args:
- args.append((expr, condition))
- return tuple(args)
+ oth = [ str(o) for o in oth ]
+ raise ValueError("Only one otherwise statement can be specified, got: %s" % ', '.join(oth))
+ new_args = ecs + [oth]
+
+ # evaluation
+ if not evaluate and any([ isinstance(expr, Piecewise) or isinstance(cond, bool) for (expr, cond) in ecs ]):
+ evaluate = True
+ if evaluate:
+ evaluated = cls.eval(*new_args)
+ if evaluated is not None:
+ return evaluated
+ # check exprs and otherwise are Exprs
+ # TODO: UndefinedFunction neither Basic not Expr
@asmeurer Owner

How is this an issue? That would be like having Piecewise((sin, x> 0)) instead of Piecewise((sin(x), x > 0)).

@flacjacket Collaborator

I'm not sure about sin, but if you call there is a test that uses f = Function('f') as an expression and it raises the error because f is not an Expr. I just realized I could add another or for this, like with GeometryEntity (which subclasses tuple).

@asmeurer Owner

Well, Function('f') is the same as sin. It doesn't make sense. That test should be removed.

Regarding geometry, I don't know if it makes sense to put those in a piecewise. I wouldn't worry about it. We will hopefully rewrite that to be Basic soon anyway.

@flacjacket Collaborator

Alright, I'll change the test. The geometry stuff comes in when you look at a point inside a shape with .arbitrary_point().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@asmeurer
Owner

It appears that the evaluate keyword does not work at all. I guess this would actually rely on http://code.google.com/p/sympy/issues/detail?id=2531 to do correctly. Perhaps a new issue could be opened.

@asmeurer
Owner

Well, I didn't necessarily want all the commits squashed into one. I was mostly concerned with the one letter commit messages :)

But don't worry, it's fine. Please do mention in the message what you did to fix the leading_term issue, though.

@flacjacket
Collaborator

Yes, in any instance where there is something you could keep unevaluated, it is forced. For now, that could be changed for nested Piecewise statements, but not evaluating with bools would require that issue.

@asmeurer
Owner

I like how nan is automatically included (but not printed). That makes things very clear.

sympy/functions/elementary/piecewise.py
((25 lines not shown))
return None
- def doit(self, **hints):
- """
- Evaluate this piecewise function.
- """
- newargs = []
- for e, c in self.args:
- if hints.get('deep', True):
- if isinstance(e, Basic):
- e = e.doit(**hints)
- if isinstance(c, Basic):
- c = c.doit(**hints)
- newargs.append((e, c))
- return Piecewise(*newargs)
+ @property
+ def exprcondpairs(self):
@asmeurer Owner

Add a docstring with doctest for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/functions/elementary/piecewise.py
((30 lines not shown))
- """
- newargs = []
- for e, c in self.args:
- if hints.get('deep', True):
- if isinstance(e, Basic):
- e = e.doit(**hints)
- if isinstance(c, Basic):
- c = c.doit(**hints)
- newargs.append((e, c))
- return Piecewise(*newargs)
+ @property
+ def exprcondpairs(self):
+ return self.args[:-1]
+
+ @property
+ def otherwise(self):
@asmeurer Owner

Add a docstring with doctest for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@flacjacket
Collaborator

SymPy Bot Summary: All tests have passed.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY0_kJDA

Interpreter: /opt/python-2.5.6-32bit/bin/python (2.5.6-final-0)
Architecture: Linux (32-bit)
Cache: yes
Test command: setup.py test
master hash: 9258bb9
branch hash: e82666140d59c8908b7e2724a48989e7ce1bd92f

Automatic review by SymPy Bot.

@asmeurer
Owner

Have all the things we discussed been well tested (in particular the subtitles with the syntax change)?

@asmeurer
Owner

My final comment (for now at least) is to make it clear in the docstring that the old way is deprecated.

Speaking of which, what do you think would be a good deprecation cycle length for this?

@flacjacket
Collaborator

So the bot hung for a while at that point, but it eventually went through.

I think the new syntax is tested pretty well. I'll double check that. Some of the new stuff isn't tested the best, like the .exprcondpairs, .otherwise and the as_leading_term, I'll update when I check that.

Those 1 letter commits were temporary as I made some changes. I realized that I was undoing some of the stuff I did with the first commit, so when I got it back to a working state, I figured I'd just squash it all back down.

@flacjacket
Collaborator

I think everything discussed so far have been addressed and all the changes tested.

I've also opened http://code.google.com/p/sympy/issues/detail?id=3025 for the evaluate keyword with bool conditions.

As for the deprecation cycle, I don't think it needs to be too long, since the old syntax, while not very intuitive or clear, is still technically valid and, by default, will evaluate to the desired expression. It should probably stay in if there is a quick point release after 0.7.2, but a full cycle on the order of 0.7.1 to 0.7.2 (coming up on 6 months now) should be fine.

@asmeurer
Owner

That's why I was wondering about the length of the cycle. When we remove the old syntax, any expression that was using it will start to evaluate incorrectly. I think we should discuss deprecation cycles in general on the mailing list.

@asmeurer
Owner

OK, look at this:

In [1]: Piecewise((1, x < 0), (2, 1 > 0), (3, True))
/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/functions/elementary/piecewise.py:83: SymPyDeprecationWarning: 'Pass "otherwise" expression as parameter rather than (expr, True). Adding an otherwise parameter, such as the default NaN, silences this warning.'
  SymPyDeprecationWarning)
Out[1]: 
⎧1  for x < 0
⎨            
⎩2  otherwise

In [2]: Piecewise((1, x < 0), (2, 1 > 0), (3, True), nan)
Out[2]: 
⎧1  for x < 0
⎨            
⎩2  otherwise

I think these are wrong. They should both evaluate to 2. The first should still give the deprecation warning, though.

@asmeurer
Owner

I send a message to the mailing list about deprecation cycles in general. That doesn't block this pull request (though it may block the release), so let's move that discussion there.

@flacjacket
Collaborator

Oh, I think I see where I may have been misunderstanding you, I've been thinking about evaluation as more of a simplification (which is what it was before), but I think you're saying evaluation should return the expression of the first True statement, regardless of whether previous expressions were undetermined. That definitely makes more sense, now that I see what you're saying.

@flacjacket
Collaborator

This last 2 commits ('Change Piecewise evaluation behavior' and 'Add Piecewise .doit() to perform evaluation') change the evaluation behavior to what I think you have in mind. The commit messages and docstrings state what I changed around.

@flacjacket
Collaborator

I realized that the evaluation change broke some tests, so I traced the failures as much as I could and applied some heavy-handed changes to get the tests to pass. Namely sympy/integrals/transforms.py L258 and sympy/integrals/meijerint.py L600. Now that I see it, it might be better for the 'doit()' option to stop Piecewise evaluation be the 'piecewise' keyword, rather than 'evaluate', allowing the meijerint.py change to be a bit more precise. I'll see if I can't trace down the Piecewise's that need evaluation in transforms.py, I know they look like Piecewise(otherwise), and get them to evaluate without breaking anything. Tho, at the very least, now all tests pass and no tests raise the deprecation warning.

@flacjacket
Collaborator

I have now changed the keyword for toggling doit() evaluation from 'evaluate' to 'piecewise'. Also, giving only an otherwise expression acts like putting bools in the conditions, in that it will force evaluation unless evaluate is explicitly False. I squashed these changes into the 'Fix other code...' commit, so the line numbers referenced in my previous comment no longer make sense.

@asmeurer
Owner

SymPy Bot Summary: There were test failures.

@flacjacket: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYvqgKDA

Interpreter: /usr/local/bin/python2.5 (2.5.0-final-0)
Architecture: Darwin (64-bit)
Cache: yes
Test command: setup.py test
master hash: df02ffc
branch hash: 1554502ebdcd642f60a6df92b14a654855e41a7d

Automatic review by SymPy Bot.

@asmeurer
Owner

SymPy Bot Summary: There were test failures.

@flacjacket: Please fix the test failures.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY9YULDA

Interpreter: /Library/Frameworks/Python.framework/Versions/3.2/bin/python3 (3.2.2-final-0)
Architecture: Darwin (64-bit)
Cache: yes
Test command: setup.py test
master hash: 9d3a94e
branch hash: 1554502ebdcd642f60a6df92b14a654855e41a7d

Automatic review by SymPy Bot.

@asmeurer
Owner

Should we do something if more than one True condition is found (with new syntax)? For example, Piecewise((x, True), (y, True), nan).

sympy/functions/elementary/piecewise.py
((60 lines not shown))
- @property
- def free_symbols(self):
- """
- Return the free symbols of this pair.
- """
- # Overload Basic.free_symbols because self.args[1] may contain non-Basic
- result = self.expr.free_symbols
- if hasattr(self.cond, 'free_symbols'):
- result |= self.cond.free_symbols
- return result
+ Piecewise can also evaluate the function, which is set by the ``evaluate``
+ keyword, that is disabled by default. Having explicitly True or False
+ conditions will enable evaluation unless the ``evaluate`` option is
+ explicitly False. When evaluate is True, it returns the first expression
@asmeurer Owner
asmeurer added a note

This is not entirely true, since we can't represent the True conditions (issue 2531). Perhaps a note that that can't be done yet should be added here.

@asmeurer Owner
asmeurer added a note

I'm also not sure if this is right:

In [9]: Piecewise((x, True), (y, x < 1), nan, evaluate=False)
Out[9]: {x  otherwise
@asmeurer Owner
asmeurer added a note

If you're up to it, you could just implement issue 2531, and this would be less messy :)

@flacjacket Collaborator

I can take a crack at it. If I can't get something working nicely, the docstring should need to be changed to address this limitation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@flacjacket
Collaborator

I don't think we should necessarily disallow having multiple cases be True, a lazy set of conditions, such as x>1 then x>0, should be valid, and at least Mathematica doesn't complain at it. It might be a good idea to raise a warning on something like that so it can easily be identified as a problem if that's not intended.

@asmeurer
Owner

You're right. I didn't consider that this could come from having redundant conditions. In general, it's very difficult to tell if conditions overlap with each other, and anyway, that sort of thing should not happen in the constructor (rather it should happen in some simplification function). And sometimes it's useful to define redundant conditions (where the expr is actually the same in the overlap).

So I guess we should just choose the first one (and document it).

@flacjacket
Collaborator

So with the new Basic Booleans, there is the question of how much to use them as return values instead of bools. Some things, such as .contains() in core.sets, uses Boolean functions, and so would return a Boolean value, but most properties will still return a bool. This could be a problem if people specifically check if something is a bool (we do 33 times in master, grep -P 'isinstance\(.*bool\)' sympy -R). This shouldn't be too much of a problem if we just sympify things before doing the isinstance, but it could definitely be something that is easily missed and silently break something.

@asmeurer
Owner

Not just isinstance(bool), but also any is {True,False,None} check would be broken.

@asmeurer
Owner

I guess we can use the rule of thumb that things relating the the assumptions system (i.e., using three valued logic) should return the Basic types, and everything else can stick with the Python types. It's still a fuzzy distinction, but hopefully the majority of the time, it shouldn't matter which type is returned.

@flacjacket
Collaborator

So looking at what I have fixed so far and what is still failing, switching the assumptions and boolalg functions to use the Basic objects breaks a ton of stuff, which I'm not sure I can get to in a very timely way with school currently in full swing. If you want this pushed, I can fix up the documentation to make it very explicit what happens with boolean conds, and cherry pick the other commit to a new branch, otherwise, this pull may linger for a while.

@asmeurer
Owner

Yes, just do that. The most important thing is to get Piecewise to work.

@flacjacket
Collaborator

Alright, I pulled the Basic bools stuff into a new branch and did a rewrite of the documentation. Let me know what you think of this.

@rlamy
Collaborator

I'd like to take care of the Boolean stuff. I'm working on a branch (based on master), I hope I can get it ready soon, and that merging with this won't be too painful.

@flacjacket
Collaborator

@rlamy That'd be great, you have a much deeper understanding of the logic functionality something like that would touch.

@asmeurer
Owner

So should this branch wait for yours, or can we go ahead without it?

@rlamy
Collaborator

It might take a while, so if this can work without the new Booleans, I guess it's better not to wait.

@asmeurer
Owner

This needs to be rebased.

So I guess without the Basic booleans, we should just raise NotImplementedError when we would need them (and maybe leave in the stub code so that it's easy to make it work when we have them).

Alternately, we could just put in regular booleans and XFAIL the test in test_args. It would give more functionality, but the objects would break with many things.

@flacjacket
Collaborator

I have rebased and added a commit to cause the boolean conds with evaluate=False to throw an error. There is one part of meijerint that would need this to work to have the desired effect, so that is changed to be careful the args it passes to the Piecewise function. There were also a couple tests in piecewise that needed to move to an XFAIL.

sympy/functions/elementary/piecewise.py
((63 lines not shown))
+ # c = BasicNone
+ # new_ecs.append(Tuple(e,c))
+ #ecs = new_ecs
+ else:
+ evaluate = True
+ # Having no expr/cond pairs should also trigger evaluation unless explicitly False
+ if len(ecs) == 0 and evaluate is not False:
+ return oth
+
+ new_args = ecs + [oth]
+ if evaluate:
+ return cls.eval(*new_args)
+
+ # check exprs and otherwise are Exprs
+ from sympy.geometry.entity import GeometryEntity
+ if not all([ isinstance(expr, Expr) or isinstance(expr, GeometryEntity) for (expr, _) in ecs ]):
@rlamy Collaborator
rlamy added a note

That kind of static type-checking is more likely to be a PITA in the future than a help. I think it's best not to bother.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/functions/elementary/piecewise.py
((5 lines not shown))
- newargs = []
- for ec in args:
- pair = ExprCondPair(*ec)
- cond_type = type(pair.cond)
- if not (cond_type is bool or issubclass(cond_type, Relational) or \
- issubclass(cond_type, Number) or \
- issubclass(cond_type, Set) or issubclass(cond_type, Boolean)):
- raise TypeError(
- "Cond %s is of type %s, but must be a bool," \
- " Relational, Number or Set" % (pair.cond, cond_type))
- newargs.append(pair)
-
- if options.pop('evaluate', True):
- r = cls.eval(*newargs)
+ ecs = [ Tuple(*sympify(arg)) for arg in args if hasattr(arg, '__iter__') ]
+ oth = [ arg for arg in args if not hasattr(arg, '__iter__') ]
@rlamy Collaborator
rlamy added a note

That's very brittle: what if we want to work with objects that happen to be iterable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@flacjacket
Collaborator

Thanks @rlamy, I've addressed the couple things you mentioned. I dropped the type checking of the expression part of the expr/cond pairs and the otherwise expression, but left the checking on the conditions, only Relationals or Booleans make sense as conditions. I also changed the expr/cond pairs so they need to have a tuple, Tuple or list as the container, rather than just checking that the args are iterable.

@rlamy
Collaborator

Well, actually I think it's wrong to use any sort of type-checking to distinguish between the pairs and the "otherwise". It should only be based on position.

More generally, the more I look at the problem, the more I feel that the change in syntax only brings complications for little benefit. To introduce the new Booleans, I would actually prefer to only fix what's broken in Piecewise (ExprCondPair, conds with baroque types) without changing the syntax or adding new behaviour. That would basically mean putting this PR on hold.

@asmeurer
Owner

The syntax now is a little more complicated, but that's just because of the backwards compatibility. Once we break that, it will become cleaner in my opinion.

@rlamy
Collaborator

No, this actually makes the implementation more complicated: currently, something like for expr, cond in self.args is enough to handle all the arguments, but this forces us to deal with the "otherwise" value in addition.

A real simplification would be to replace Piecewise with nested IfElse(condition, true_expr, false_expr) objects.

@asmeurer
Owner

I disagree with that. Flat is better than nested.

expr, cond doesn't work because not every expression has a condition.

flacjacket added some commits
@flacjacket flacjacket New Piecewise syntax and fix Piecewise issues
Piecewise syntax is changed to allow for a more sensible definition of
the otherwise condition. Before, an expr/cond pair was specified where
cond was given as True. Now, otherwise can either be passed as the final
argument after a sequence of expr/cond 2-tuples. The old otherwise
syntax using (otherwise, True) raises a deprecation warning.

The ExprCondPair class is removed in favor of Tuples to store the
expr/cond pairs.

The '.as_leading_term()' function is changed so that it takes the sum of
all the expressions and, if it is not NaN, the otherwise statement, and
returns the leading term of the sum. Ideally, '.as_leading_term()' would
return a Piecewise function, but doing this breaks the evaluation of
'abs(x-a).nseries(x, 1)'.

Piecewise no longer accepts an Interval as a condition. To implement the
same functionality, either the '.as_relational()' or '.contains()'
methods of Interval can be used. Piecewise also no longer accepts
numbers as conditions.

The properties '.exprcondpairs' and '.otherwise' are implemented to
easily access the expr/cond pairs and otherwise statement, respectively.

Addresses issues: 2101, 2567, 2626, 2710, 2726
779ef82
@flacjacket flacjacket Additional Piecewise testing
Changes allowing evaluate keyword to override default behavior of
clearing nested Piecewise expressions. More testing of new Piecewise
evaluate keyword behavior, including an XFAIL test related to Issue
3025, where evaluate=False does not override behavior when bools are in
the conditions.

Includes new tests and doctests for '.exprcondpairs' and '.otherwise'
properties.

Re-enables testing for expressions in expr/cond pairs and the otherwise
statement to be Expr or GeometryEntity. GeometryEntity is needed due to
the use of Piecewise in the geometry module for '.arbitrary_point()'.
569cd41
@flacjacket flacjacket Change Piecewise evaluation behavior
Piecewise evaluation now returns the first expression which is
explicitly True. If no conditions are True, the otherwise expression is
returned. Evaluation can be triggered by setting the 'evaluate' keyword
to True. When a boolean is given as a condition, evaluation is enabled
unless specifically disabled by setting 'evaluate' to False. In this
case, any explicitly False conditions are removed and any explicitly
True conditions are given as the otherwise statement. This is done so
bools are kept out of args, see Issue 3025. The collapsing of nested
Piecewise functions is moved from eval to the __new__ method. This is
performed by default, but setting 'evaluate' to False will disable it.
4ba539e
@flacjacket flacjacket Add Piecewise .doit() to perform evaluation
Added a .doit() method to Piecewise functions. This allows for
forced evaluation of the Piecewise function. If you want to call doit()
to evaluate args but not evaluate the Piecewise function itself,
evaluate=False can be added to the hints.
c86dea6
@flacjacket flacjacket Fix other code to work with new Piecewise evaluation
Change '.doit()' keyword for evaluating Piecewise objects from
'evaluate' to 'piecewise' and set other functions to use this to
avoid evaluation of Piecewise when required. Having no expr/cond pairs
is treated like having bools in the conditions.
7fe8654
@flacjacket flacjacket Fix and add tests for Piecewise printing 54367c0
@flacjacket flacjacket Fix up documentation of Piecewise objects
Improve documentation of Piecewise objects, specifying how the new
evaluation functionality works, how it is overridden and the limitations
posed by not being able to have bools in the args.
b1b27fa
@flacjacket flacjacket Piecewise raises error with bool args and evaluate=False
Change behavior of Piecewise so if bools are given as args and it is
given the keyword evaluate=False, it raises a NotImplementedError. This
can be changed once classes implementing boolean logic that subclass
Basic are implemented. Changes are made in meijerint to parse the args
of a Piecewise function used in that module which may have bool
conditions, but should not be evaluated. This can be changed to use
evaluate=False when the Basic boolean classes are implemented.
15e54a0
@flacjacket flacjacket Fix type checking in Piecewise
Change checking in Piecewise to take expr/cond pairs only in tuples,
Tuples or lists, all other expressions go to otherwise. Drop the type
checking for the exprs and otherwise statement.
e8cbd8d
@rlamy rlamy commented on the diff
sympy/functions/elementary/tests/test_piecewise.py
@@ -8,11 +8,26 @@
def test_piecewise():
# Test canonization
- assert Piecewise((x, x < 1), (0, True)) == Piecewise((x, x < 1), (0, True))
- assert Piecewise((x, x < 1), (0, False), (-1, 1>2)) == Piecewise((x, x < 1))
- assert Piecewise((x, True)) == x
- raises(TypeError,"Piecewise(x)")
+ assert Piecewise((x, x > 1)) == Piecewise((x, x > 1), S.NaN)
+ assert Piecewise((x, x > 1), (0, True), S.NaN) == 0
@rlamy Collaborator
rlamy added a note

Huh?? This should be Piecewise((x, x > 1), 0).

@asmeurer Owner

No, this is right. NaN is the otherwise condition. See the prior discussion, both here and on the mailing list. During the deprecation cycle, you can force (expr, cond) pairs to be viewed as that (rather than (expr_otherwise, True)) by manually adding nan as the otherwise condition (this is the default otherwise condition, so this changes nothing).

@rlamy Collaborator
rlamy added a note

But the (x, x > 1) part has disappeared! That is the problem, not whether the "otherwise" should be 0 or NaN.

@asmeurer Owner

That's because the piecewise evaluated to 0, which had a True condition.

@rlamy Collaborator
rlamy added a note

Translated to Python code, Piecewise((x, x>1), (0, True), S.NaN) means

if x > 1:
    return x
elif True:
    return 0
else:
    return S.NaN

which is clearly equivalent to

if x > 1:
    return x
else:
    return 0

but not to simply return 0.

@asmeurer Owner

OK, I see what you're getting at. This is exactly why we need Piecewise(evaluate=False).

@rlamy Collaborator
rlamy added a note

Let's move the discussion to issue 3025, so that it doesn't disappear when this review is done, OK?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@asmeurer
Owner

By the way, I do think that it actually would be useful to have some method of Piecewise that would return some kind of IFElse representation. That is exactly what the lambda printer uses, as I remember, so it would essentially move the logic from there into Piecewise, where it might be useful to others as well. But the .args should be expr, cond pairs with otherwise at the end as discussed in issue 2626.

@rlamy rlamy referenced this pull request
Merged

Refactor ExprCondPair #1095

flacjacket added some commits
@flacjacket flacjacket Merging with master 256094c
@flacjacket flacjacket Merge branch 'master' into piecewise_fixes
Conflicts:
	sympy/functions/elementary/piecewise.py
	sympy/printing/tests/test_str.py
b2bc48b
@flacjacket flacjacket Merge branch 'master' into piecewise_fixes
Conflicts:
	sympy/solvers/tests/test_ode.py
e500e7d
@flacjacket flacjacket Merge branch 'master' into piecewise_fixes
Conflicts:
	sympy/printing/tests/test_str.py
	sympy/stats/crv_types.py
972d8fe
@asmeurer
Owner

SymPy Bot Summary: :exclamation: There were merge conflicts; could not test the branch.

@flacjacket: Please rebase or merge your branch with master. See the report for a list of the merge conflicts.

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY7N0aDA

Interpreter: /Library/Frameworks/Python.framework/Versions/3.2/bin/python3 (3.2.2-final-0)
Architecture: Darwin (64-bit)
Cache: yes
Test command: ./bin/test && ./bin/doctest
master hash: 53af081
branch hash: 972d8fe

Automatic review by SymPy Bot.

@jrioux
Collaborator

SymPy Bot Summary: :exclamation: There were merge conflicts (could not merge flacjacket/piecewise_fixes (972d8fe) into master (57e94e4)); could not test the branch.
@flacjacket: Please rebase or merge your branch with master. See the report for a list of the merge conflicts.

@matthew-brett matthew-brett referenced this pull request from a commit in matthew-brett/sympy
@mattpap mattpap roots() won't hang on large examples like in #1009 8c138b0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 27, 2012
  1. @flacjacket

    New Piecewise syntax and fix Piecewise issues

    flacjacket authored
    Piecewise syntax is changed to allow for a more sensible definition of
    the otherwise condition. Before, an expr/cond pair was specified where
    cond was given as True. Now, otherwise can either be passed as the final
    argument after a sequence of expr/cond 2-tuples. The old otherwise
    syntax using (otherwise, True) raises a deprecation warning.
    
    The ExprCondPair class is removed in favor of Tuples to store the
    expr/cond pairs.
    
    The '.as_leading_term()' function is changed so that it takes the sum of
    all the expressions and, if it is not NaN, the otherwise statement, and
    returns the leading term of the sum. Ideally, '.as_leading_term()' would
    return a Piecewise function, but doing this breaks the evaluation of
    'abs(x-a).nseries(x, 1)'.
    
    Piecewise no longer accepts an Interval as a condition. To implement the
    same functionality, either the '.as_relational()' or '.contains()'
    methods of Interval can be used. Piecewise also no longer accepts
    numbers as conditions.
    
    The properties '.exprcondpairs' and '.otherwise' are implemented to
    easily access the expr/cond pairs and otherwise statement, respectively.
    
    Addresses issues: 2101, 2567, 2626, 2710, 2726
  2. @flacjacket

    Additional Piecewise testing

    flacjacket authored
    Changes allowing evaluate keyword to override default behavior of
    clearing nested Piecewise expressions. More testing of new Piecewise
    evaluate keyword behavior, including an XFAIL test related to Issue
    3025, where evaluate=False does not override behavior when bools are in
    the conditions.
    
    Includes new tests and doctests for '.exprcondpairs' and '.otherwise'
    properties.
    
    Re-enables testing for expressions in expr/cond pairs and the otherwise
    statement to be Expr or GeometryEntity. GeometryEntity is needed due to
    the use of Piecewise in the geometry module for '.arbitrary_point()'.
  3. @flacjacket

    Change Piecewise evaluation behavior

    flacjacket authored
    Piecewise evaluation now returns the first expression which is
    explicitly True. If no conditions are True, the otherwise expression is
    returned. Evaluation can be triggered by setting the 'evaluate' keyword
    to True. When a boolean is given as a condition, evaluation is enabled
    unless specifically disabled by setting 'evaluate' to False. In this
    case, any explicitly False conditions are removed and any explicitly
    True conditions are given as the otherwise statement. This is done so
    bools are kept out of args, see Issue 3025. The collapsing of nested
    Piecewise functions is moved from eval to the __new__ method. This is
    performed by default, but setting 'evaluate' to False will disable it.
  4. @flacjacket

    Add Piecewise .doit() to perform evaluation

    flacjacket authored
    Added a .doit() method to Piecewise functions. This allows for
    forced evaluation of the Piecewise function. If you want to call doit()
    to evaluate args but not evaluate the Piecewise function itself,
    evaluate=False can be added to the hints.
  5. @flacjacket

    Fix other code to work with new Piecewise evaluation

    flacjacket authored
    Change '.doit()' keyword for evaluating Piecewise objects from
    'evaluate' to 'piecewise' and set other functions to use this to
    avoid evaluation of Piecewise when required. Having no expr/cond pairs
    is treated like having bools in the conditions.
  6. @flacjacket
  7. @flacjacket

    Fix up documentation of Piecewise objects

    flacjacket authored
    Improve documentation of Piecewise objects, specifying how the new
    evaluation functionality works, how it is overridden and the limitations
    posed by not being able to have bools in the args.
  8. @flacjacket

    Piecewise raises error with bool args and evaluate=False

    flacjacket authored
    Change behavior of Piecewise so if bools are given as args and it is
    given the keyword evaluate=False, it raises a NotImplementedError. This
    can be changed once classes implementing boolean logic that subclass
    Basic are implemented. Changes are made in meijerint to parse the args
    of a Piecewise function used in that module which may have bool
    conditions, but should not be evaluated. This can be changed to use
    evaluate=False when the Basic boolean classes are implemented.
  9. @flacjacket

    Fix type checking in Piecewise

    flacjacket authored
    Change checking in Piecewise to take expr/cond pairs only in tuples,
    Tuples or lists, all other expressions go to otherwise. Drop the type
    checking for the exprs and otherwise statement.
Commits on Mar 16, 2012
  1. @flacjacket

    Merging with master

    flacjacket authored
Commits on Mar 21, 2012
  1. @flacjacket

    Merge branch 'master' into piecewise_fixes

    flacjacket authored
    Conflicts:
    	sympy/functions/elementary/piecewise.py
    	sympy/printing/tests/test_str.py
Commits on Mar 25, 2012
  1. @flacjacket

    Merge branch 'master' into piecewise_fixes

    flacjacket authored
    Conflicts:
    	sympy/solvers/tests/test_ode.py
Commits on Mar 31, 2012
  1. @flacjacket

    Merge branch 'master' into piecewise_fixes

    flacjacket authored
    Conflicts:
    	sympy/printing/tests/test_str.py
    	sympy/stats/crv_types.py
This page is out of date. Refresh to see the latest.
Showing with 696 additions and 429 deletions.
  1. +3 −3 sympy/concrete/summations.py
  2. +2 −2 sympy/concrete/tests/test_sums_products.py
  3. +2 −6 sympy/core/tests/test_args.py
  4. +1 −1  sympy/core/tests/test_expr.py
  5. +1 −1  sympy/functions/elementary/complexes.py
  6. +274 −161 sympy/functions/elementary/piecewise.py
  7. +130 −49 sympy/functions/elementary/tests/test_piecewise.py
  8. +25 −21 sympy/functions/special/bsplines.py
  9. +2 −4 sympy/functions/special/hyper.py
  10. +18 −18 sympy/functions/special/tests/test_bsplines.py
  11. +6 −6 sympy/functions/special/tests/test_hyper.py
  12. +2 −2 sympy/integrals/integrals.py
  13. +44 −14 sympy/integrals/meijerint.py
  14. +5 −5 sympy/integrals/tests/test_meijerint.py
  15. +4 −4 sympy/integrals/transforms.py
  16. +2 −2 sympy/physics/quantum/state.py
  17. +9 −8 sympy/printing/ccode.py
  18. +0 −1  sympy/printing/codeprinter.py
  19. +4 −3 sympy/printing/fcode.py
  20. +12 −6 sympy/printing/lambdarepr.py
  21. +6 −9 sympy/printing/latex.py
  22. +12 −10 sympy/printing/pretty/pretty.py
  23. +34 −2 sympy/printing/pretty/tests/test_pretty.py
  24. +6 −3 sympy/printing/str.py
  25. +2 −2 sympy/printing/tests/test_ccode.py
  26. +4 −4 sympy/printing/tests/test_fcode.py
  27. +23 −27 sympy/printing/tests/test_lambdarepr.py
  28. +9 −4 sympy/printing/tests/test_latex.py
  29. +13 −5 sympy/printing/tests/test_str.py
  30. +1 −1  sympy/series/tests/test_nseries.py
  31. +2 −2 sympy/simplify/hyperexpand.py
  32. +5 −5 sympy/simplify/tests/test_hyperexpand.py
  33. +15 −18 sympy/solvers/solvers.py
  34. +1 −1  sympy/solvers/tests/test_ode.py
  35. +16 −18 sympy/utilities/tests/test_iterables.py
  36. +1 −1  sympy/utilities/tests/test_pickling.py
View
6 sympy/concrete/summations.py
@@ -530,7 +530,7 @@ def eval_sum_hyper(f, (i, a, b)):
if a == -oo:
res = _eval_sum_hyper(f.subs(i, -i), i, -b)
if res is not None:
- return Piecewise(res, (Sum(f, (i, a, b)), True))
+ return Piecewise(res, Sum(f, (i, a, b)))
else:
return None
@@ -544,9 +544,9 @@ def eval_sum_hyper(f, (i, a, b)):
cond = And(cond1, cond2)
if cond is False:
return None
- return Piecewise((res1 + res2, cond), (Sum(f, (i, a, b)), True))
+ return Piecewise((res1 + res2, cond), Sum(f, (i, a, b)))
# Now b == oo, a != -oo
res = _eval_sum_hyper(f, i, a)
if res is not None:
- return Piecewise(res, (Sum(f, (i, a, b)), True))
+ return Piecewise(res, Sum(f, (i, a, b)))
View
4 sympy/concrete/tests/test_sums_products.py
@@ -286,8 +286,8 @@ def test_hypersum():
s = summation(x**n*n, (n, -oo, 0))
assert s.is_Piecewise
- assert s.args[0].args[0] == -1/(x*(1 - 1/x)**2)
- assert s.args[0].args[1] == (abs(1/x) < 1)
+ assert s.exprcondpairs[0].args[0] == -1/(x*(1 - 1/x)**2)
+ assert s.exprcondpairs[0].args[1] == (abs(1/x) < 1)
def test_issue_1071():
assert summation(1/factorial(k), (k, 0, oo)) == E
View
8 sympy/core/tests/test_args.py
@@ -776,13 +776,9 @@ def test_sympy__functions__elementary__miscellaneous__Min():
def test_sympy__functions__elementary__miscellaneous__MinMaxBase():
pass
-def test_sympy__functions__elementary__piecewise__ExprCondPair():
- from sympy.functions.elementary.piecewise import ExprCondPair
- assert _test_args(ExprCondPair(1, True))
-
def test_sympy__functions__elementary__piecewise__Piecewise():
from sympy.functions.elementary.piecewise import Piecewise
- assert _test_args(Piecewise((1, x >= 0), (0, True)))
+ assert _test_args(Piecewise((1, x >= 0), 0))
@SKIP("abstract class")
def test_sympy__functions__elementary__trigonometric__TrigonometricFunction():
@@ -1637,7 +1633,7 @@ def test_sympy__physics__quantum__state__Wavefunction():
from sympy import Piecewise, pi
n = 1
L = 1
- g = Piecewise((0, x < 0), (0, x > L), (sqrt(2//L)*sin(n*pi*x/L), True))
+ g = Piecewise((0, x < 0), (0, x > L), sqrt(2//L)*sin(n*pi*x/L))
assert _test_args(Wavefunction(g, x))
def test_sympy__physics__quantum__tensorproduct__TensorProduct():
View
2  sympy/core/tests/test_expr.py
@@ -616,7 +616,7 @@ def test_has_piecewise():
f = (x*y + 3/y)**(3 + 2)
g = Function('g')
h = Function('h')
- p = Piecewise((g, x < -1), (1, x <= 1), (f, True))
+ p = Piecewise((g(x), x < -1), (1, x <= 1), f)
assert p.has(x)
assert p.has(y)
View
2  sympy/functions/elementary/complexes.py
@@ -363,7 +363,7 @@ def _eval_nseries(self, x, n, logx):
when = Eq(direction, 0)
return Piecewise(
((s.subs(direction, 0)), when),
- (sign(direction)*s, True),
+ sign(direction)*s
)
def _sage_(self):
View
435 sympy/functions/elementary/piecewise.py
@@ -1,49 +1,9 @@
-from sympy.core import Basic, S, Function, diff, Number, sympify, Tuple
+from sympy.core import Add, Basic, Expr, Function, S, Set, sympify, Tuple
from sympy.core.relational import Equality, Relational
from sympy.logic.boolalg import Boolean
-from sympy.core.sets import Set
-from sympy.core.symbol import Dummy
-class ExprCondPair(Tuple):
- """Represents an expression, condition pair."""
-
- true_sentinel = Dummy('True')
-
- def __new__(cls, expr, cond):
- if cond is True:
- cond = ExprCondPair.true_sentinel
- return Tuple.__new__(cls, expr, cond)
-
- @property
- def expr(self):
- """
- Returns the expression of this pair.
- """
- return self.args[0]
-
- @property
- def cond(self):
- """
- Returns the condition of this pair.
- """
- if self.args[1] == ExprCondPair.true_sentinel:
- return True
- return self.args[1]
-
- @property
- def free_symbols(self):
- """
- Return the free symbols of this pair.
- """
- # Overload Basic.free_symbols because self.args[1] may contain non-Basic
- result = self.expr.free_symbols
- if hasattr(self.cond, 'free_symbols'):
- result |= self.cond.free_symbols
- return result
-
- def __iter__(self):
- yield self.expr
- yield self.cond
+from warnings import warn
+from sympy.core.compatibility import SymPyDeprecationWarning
class Piecewise(Function):
"""
@@ -51,27 +11,72 @@ class Piecewise(Function):
Usage:
- Piecewise( (expr,cond), (expr,cond), ... )
- - Each argument is a 2-tuple defining a expression and condition
- - The conds are evaluated in turn returning the first that is True.
- If any of the evaluated conds are not determined explicitly False,
- e.g. x < 1, the function is returned in symbolic form.
- - If the function is evaluated at a place where all conditions are False,
- a ValueError exception will be raised.
- - Pairs where the cond is explicitly False, will be removed.
+ ``Piecewise( (expr,cond), (expr,cond), ..., (expr, cond), [otherwise])``
+
+ - Each expr/cond pair is a 2-tuple defining a expression and condition
+ - The otherwise expression is optional final argument. If no argument
+ is given, the default value is NaN.
+
+ Note that old otherwise syntax of using including an (otherwise, True)
+ tuple is currently deprecated. If your final expr/cond is (expr, True), you
+ must specify an otherwise expression to avoid having this expr changed to
+ an otherwise condition, which may stop a potential evaluation. The defualt
+ value, NaN, is a suitable option when no otherwise expression exists.
+
+ Evaluating a Piecewise object will return one of the expressions or the
+ otherwise condition. Evaluation of expr/cond pairs is done in the order
+ they are passed as parameters. For the first expr/cond pair where ``cond``
+ is True, evaluation returns the corresponding ``expr``. If no conditions
+ are True, the ``otherwise`` expression is returned.
+
+ The evaluation of the Piecewise object can be controlled by the
+ ``evaluate`` keyword. Under default conditions, wherever possible, any
+ nested Piecewise objects will be simplified and if there are no expr/cond
+ pairs, the otherwise value is returned. Furthermore, if any conds are
+ booleans, i.e. either True or False, the Piecewise object is evaluated, as
+ described above. Evaluation can be forced when creating the Piecewise
+ object even when none of the conds are booleans by setting
+ ``evaluate=True``. When ``evaluate`` is set to False, nested Piecewise
+ objects are not simplified and Piecewise functions may contain only an
+ otherwise expression without expr/cond pairs.
+
+ Due to limitations with having boolean values in ``.args``, when boolean
+ conditions are given with ``evaluate`` set to False, a NotImplementedError
+ is raised. This will be fixed when there are objects to do boolean logic
+ that subclass Basic.
+
+ Evaluation can be performed on an existing Piecewise object by calling the
+ ``.doit()`` method. The ``piecewise`` keyword can be used when calling
+ ``.doit()`` and sets the value of the ``evalute`` keyword. By default,
+ calling ``.doit()`` sets ``evaluate`` to True.
Examples
========
- >>> from sympy import Piecewise, log
- >>> from sympy.abc import x
- >>> f = x**2
- >>> g = log(x)
- >>> p = Piecewise( (0, x<-1), (f, x<=1), (g, True))
- >>> p.subs(x,1)
- 1
- >>> p.subs(x,5)
- log(5)
+ Create a Piecewise function and evaluate it at 1 and 5:
+
+ >>> from sympy import Piecewise, log
+ >>> from sympy.abc import x
+ >>> f = x**2
+ >>> g = log(x)
+ >>> p = Piecewise( (0, x<-1), (f, x<=1), g)
+ >>> p.subs(x,1)
+ 1
+ >>> p.subs(x,5)
+ log(5)
+
+ To create a cond checking if a variable lies in an Interval, you can use
+ the ``.contains()`` or ``.as_relational()`` methods. Evaluate a Piecewise
+ function both inside and outside of the defined intervals:
+
+ >>> from sympy import Interval
+ >>> i1 = Interval(-2,0)
+ >>> i2 = Interval(0,2)
+ >>> p = Piecewise( (-x, i1.contains(x)), (x, i2.as_relational(x)))
+ >>> p.subs(x,-1)
+ 1
+ >>> p.subs(x,3)
+ nan
See Also
========
@@ -83,93 +88,164 @@ class Piecewise(Function):
is_Piecewise = True
def __new__(cls, *args, **options):
- # (Try to) sympify args first
- newargs = []
- for ec in args:
- pair = ExprCondPair(*ec)
- cond = pair.cond
- if cond is False:
- continue
- if not isinstance(cond, (bool, Relational, Set, Boolean)):
- raise TypeError(
- "Cond %s is of type %s, but must be a bool," \
- " Relational, Number or Set" % (cond, type(cond)))
- newargs.append(pair)
- if cond is ExprCondPair.true_sentinel:
- break
-
- if options.pop('evaluate', True):
- r = cls.eval(*newargs)
+ ecs = [ Tuple(*sympify(arg)) for arg in args if isinstance(arg, list) or
+ isinstance(arg, Tuple) or
+ isinstance(arg, tuple)]
+ oth = [ arg for arg in args if arg not in ecs ]
+ evaluate = options.pop('evaluate', None)
+ if any([ len(ec) != 2 for ec in ecs ]):
+ raise ValueError("Piecewise conditions must be (expr, cond) pairs.")
+
+ if len(oth) == 0:
+ oth = S.NaN
+ elif len(oth) == 1:
+ oth = sympify(oth[0])
+ if list(args).index(oth) != len(args) - 1:
+ raise ValueError("Otherwise must be specified as final argument, found in position %d of %d" \
+ % (list(args).index(oth)+1, len(args)))
else:
- r = None
+ oth = [ str(o) for o in oth ]
+ raise ValueError("Only one otherwise statement can be specified, got: %s" % ', '.join(oth))
+
+ # deprecated otherwise behavior
+ if len(ecs) > 0 and ecs[-1][1] is True and ecs[-1] == args[-1]:
+ oth = ecs[-1][0]
+ ecs = ecs[:-1]
+ warn('Pass "otherwise" expression as parameter rather than (expr, True). ' \
+ 'Converting final expr/cond to otherwise to maintain old behavior. ' \
+ 'Manually adding an otherwise parameter, such as NaN, silences this warning and ensures evaluation.', \
+ SymPyDeprecationWarning)
+
+ # Collapse nested piecewise expressions
+ # Allow explicit evaluate=False to stop evaluation of nested Piecewise
+ if evaluate is not False and (any([ isinstance(expr, Piecewise) for (expr, _) in ecs ]) or isinstance(oth, Piecewise)):
+ ecs, oth = cls._collapse_piecewise_args(ecs, oth)
+ # TODO: See Issue 3025
+ # When evaluate=False replace True with Basic True, False with Basic False, as in commented out code block
+ # Currently, evaluate=False with bool conditions raises an error
+ if any([ isinstance(cond, bool) or cond is None for (_, cond) in ecs ]):
+ new_ecs = []
+ if evaluate is False:
+ raise NotImplementedError("Cannot suspend evaluation with bool arguments, see Issue 3025")
+ #for e, c in ecs:
+ # if c is True:
+ # c = BasicTrue
+ # elif c is False:
+ # c = BasicFalse
+ # elif c is None:
+ # c = BasicNone
+ # new_ecs.append(Tuple(e,c))
+ #ecs = new_ecs
+ else:
+ evaluate = True
+ # Having no expr/cond pairs should also trigger evaluation unless explicitly False
+ if len(ecs) == 0 and evaluate is not False:
+ return oth
+
+ new_args = ecs + [oth]
+ if evaluate:
+ return cls.eval(*new_args)
+
+ # check conds are Relational or Boolean
+ if not all([ isinstance(cond, Relational) or isinstance(cond, Boolean) for (_, cond) in ecs ]):
+ bad_args = [ "%s of type %s" % (cond, type(cond)) for (_, cond) in ecs \
+ if not (isinstance(cond, Relational) or isinstance(cond, Boolean)) ]
+ raise TypeError("Conditions can only be Relationals or Booleans, " \
+ "got: %s" % ', '.join(bad_args))
- if r is None:
- return Basic.__new__(cls, *newargs, **options)
+ return Expr.__new__(cls, *new_args)
+
+ def doit(self, **hints):
+ evaluate = hints.get('piecewise', True)
+ if hints.get('deep', True):
+ args = [ arg.doit(**hints) for arg in self.args ]
else:
- return r
+ args = self.args
+ return self.func(*args, **{'evaluate': evaluate})
@classmethod
def eval(cls, *args):
- from sympy import Or
- # Check for situations where we can evaluate the Piecewise object.
- # 1) Hit an unevaluable cond (e.g. x<1) -> keep object
- # 2) Hit a true condition -> return that expr
- # 3) Remove false conditions, if no conditions left -> raise ValueError
- all_conds_evaled = True # Do all conds eval to a bool?
- piecewise_again = False # Should we pass args to Piecewise again?
- non_false_ecpairs = []
- or1 = Or(*[cond for (_, cond) in args if cond is not True])
- for expr, cond in args:
- # Check here if expr is a Piecewise and collapse if one of
- # the conds in expr matches cond. This allows the collapsing
- # of Piecewise((Piecewise(x,x<0),x<0)) to Piecewise((x,x<0)).
- # This is important when using piecewise_fold to simplify
- # multiple Piecewise instances having the same conds.
- # Eventually, this code should be able to collapse Piecewise's
- # having different intervals, but this will probably require
- # using the new assumptions.
- if isinstance(expr, Piecewise):
- or2 = Or(*[c for (_, c) in expr.args if c is not True])
- for e, c in expr.args:
- # Don't collapse if cond is "True" as this leads to
- # incorrect simplifications with nested Piecewises.
- if c == cond and (or1 == or2 or cond is not True):
- expr = e
- piecewise_again = True
+ """
+ Evaluate Piecewise function
+
+ Return expression of the first explicitly True condition. If no
+ conditions are explicitly True, return otherwise expression.
+ """
+ ecs = args[:-1]
+ oth = args[-1]
+ for expr, cond in ecs:
cond_eval = cls.__eval_cond(cond)
- if cond_eval is None:
- all_conds_evaled = False
- non_false_ecpairs.append( (expr, cond) )
- elif cond_eval:
- if all_conds_evaled:
- return expr
- non_false_ecpairs.append( (expr, cond) )
- if len(non_false_ecpairs) != len(args) or piecewise_again:
- return Piecewise(*non_false_ecpairs)
+ if cond_eval:
+ return expr
+ return oth
- return None
+ @property
+ def exprcondpairs(self):
+ """
+ Return all expressions and conditions as 2-Tuples
- def doit(self, **hints):
+ Returns a tuple of 2-Tuples, each formatted as (expr, cond) for each of
+ the given expressions and conditions.
+
+ >>> from sympy import Piecewise
+ >>> from sympy.abc import x
+ >>> p = Piecewise((x, x > 1), (x**2, x > 0), -x)
+ >>> p.exprcondpairs
+ ((x, x > 1), (x**2, x > 0))
+
+ See Also
+ ========
+
+ otherwise
"""
- Evaluate this piecewise function.
+ return self.args[:-1]
+
+ @property
+ def otherwise(self):
"""
- newargs = []
- for e, c in self.args:
- if hints.get('deep', True):
- if isinstance(e, Basic):
- e = e.doit(**hints)
- if isinstance(c, Basic):
- c = c.doit(**hints)
- newargs.append((e, c))
- return Piecewise(*newargs)
+ Returns otherwise expression
+
+ Returns the expression given when all conditions are False. If no
+ expression was specified, this will return NaN.
+
+ >>> from sympy import Piecewise
+ >>> from sympy.abc import x
+ >>> p = Piecewise((x, x > 1), (x**2, x > 0), -x)
+ >>> p.otherwise
+ -x
+ >>> p = Piecewise((x, x > 1), (x**2, x > 0))
+ >>> p.otherwise
+ nan
+
+ See Also
+ ========
+
+ exprcondpairs
+ """
+ return self.args[-1]
@property
def is_commutative(self):
- return all(expr.is_commutative for expr, _ in self.args)
+ exprs = [expr for (expr,_) in self.exprcondpairs]
+ oth = self.otherwise
+ return all(expr.is_commutative for expr in exprs) and oth.is_commutative
+
+ def _eval_derivative(self, s):
+ from sympy.core.function import diff
+ new_args = [ (diff(e, s), c) for e, c in self.exprcondpairs ]
+ # TODO: diff(S.NaN, s) == 0
+ #new_args.append( diff(self.otherwise, s) )
+ if self.otherwise is not S.NaN:
+ new_args.append( diff(self.otherwise, s) )
+ else:
+ new_args.append( S.NaN )
+ return self.func(*new_args)
def _eval_integral(self,x):
from sympy.integrals import integrate
- return Piecewise(*[(integrate(e, x), c) for e, c in self.args])
+ new_args = [(integrate(e, x), c) for e, c in self.exprcondpairs]
+ new_args.append(integrate(self.otherwise, x))
+ return self.func(*new_args)
def _eval_interval(self, sym, a, b):
"""Evaluates the function along the sym in a given interval ab"""
@@ -182,7 +258,7 @@ def _eval_interval(self, sym, a, b):
mul = 1
if a > b:
a, b, mul = b, a, -1
- default = None
+ default = self.otherwise
# Determine what intervals the expr,cond pairs affect.
# 1) If cond is True, then log it as default
@@ -191,10 +267,12 @@ def _eval_interval(self, sym, a, b):
# update the new conds interval.
# - eg x < 1, x < 3 -> [oo,1],[1,3] instead of [oo,1],[oo,3]
# 3) Sort the intervals to make it easier to find correct exprs
- for expr, cond in self.args:
+ for expr, cond in self.exprcondpairs:
if cond is True:
default = expr
break
+ elif cond is False:
+ continue
elif isinstance(cond, Equality):
continue
@@ -212,7 +290,7 @@ def _eval_interval(self, sym, a, b):
for n in xrange(len(int_expr)):
# Part 2: remove any interval overlap. For any conflicts, the
- # iterval already there wins, and the incoming interval updates
+ # interval already there wins, and the incoming interval updates
# its bounds accordingly.
if self.__eval_cond(lower < int_expr[n][1]) and \
self.__eval_cond(lower >= int_expr[n][0]):
@@ -237,12 +315,8 @@ def _eval_interval(self, sym, a, b):
if curr_low < b:
holes.append([curr_low, b, default])
- if holes and default is not None:
+ if holes:
int_expr.extend(holes)
- elif holes and default == None:
- raise ValueError("Called interval evaluation over piecewise " \
- "function on undefined intervals %s" % \
- ", ".join([str((h[0], h[1])) for h in holes]))
# Finally run through the intervals and sum the evaluation.
ret_fun = 0
@@ -250,17 +324,14 @@ def _eval_interval(self, sym, a, b):
ret_fun += expr._eval_interval(sym, max(a, int_a), min(b, int_b))
return mul * ret_fun
- def _eval_derivative(self, s):
- return Piecewise(*[(diff(e, s), c) for e, c in self.args])
-
def _eval_subs(self, old, new):
"""
Piecewise conditions may contain Sets whose modifications
requires the use of contains rather than substitution. They
may also contain bool which are not of Basic type.
"""
- args = list(self.args)
- for i, (e, c) in enumerate(args):
+ ecs = list(self.exprcondpairs)
+ for i, (e, c) in enumerate(ecs):
try:
e = e._subs(old, new)
except TypeError:
@@ -268,35 +339,72 @@ def _eval_subs(self, old, new):
continue
e = new
- if isinstance(c, bool):
- pass
- elif isinstance(c, Set):
+ if isinstance(c, Set):
# What do we do if there are more than one symbolic
# variable. Which do we put pass to Set.contains?
c = c.contains(new)
elif isinstance(c, Basic):
c = c._subs(old, new)
- args[i] = e, c
+ ecs[i] = e, c
+ ecs.append(self.otherwise)
- return Piecewise(*args)
+ return Piecewise(*ecs)
def _eval_nseries(self, x, n, logx):
- args = map(lambda ec: (ec.expr._eval_nseries(x, n, logx), ec.cond), \
- self.args)
- return self.func(*args)
+ new_args = map(lambda ec: (ec[0]._eval_nseries(x, n, logx), ec[1]), self.exprcondpairs)
+ new_args.append( self.otherwise._eval_nseries(x, n, logx) )
+ return self.func( *new_args )
def _eval_as_leading_term(self, x):
- # This is completely wrong, cf. issue 3110
- return self.args[0][0].as_leading_term(x)
+ args = [ e for (e, _) in self.exprcondpairs]
+ if self.otherwise is not S.NaN:
+ args.append( self.otherwise )
+ return Add(*args).as_leading_term(x)
@classmethod
def __eval_cond(cls, cond):
- """Return the truth value of the condition."""
- if cond is True:
- return True
+ """ Returns cond if it's a boolean, otherwise it is undecidable and returns None. """
+ if isinstance(cond,bool):
+ return cond
return None
+ @classmethod
+ def _collapse_piecewise_args(cls, ecs, oth):
+ """
+ Collapse nested Piecewise expressions
+
+ If an (expr,cond) pair in ecs is a Piecewise and the conditions of expr
+ match the conditions in ecs, collapse expr to the expression matching
+ cond.
+
+ This allows the collapsing of Piecewise((Piecewise((x, x<0)), x<0)) to
+ Piecewise((x, x<0)).
+
+ Eventually, this code should be able to collapse Piecewise's having
+ different intervals, but this will probably require using the new
+ assumptions.
+ """
+ from sympy import Or
+ # True when args are changed and we should rerun to check for new Piecewise exprs
+ new_ecs = []
+ or1 = Or( *[c for (_, c) in ecs] )
+ for expr, cond in ecs:
+ if isinstance(expr, Piecewise):
+ or2 = Or( *[c for (_, c) in expr.exprcondpairs] )
+ for e, c in expr.exprcondpairs:
+ # Don't collapse if cond is "True" as this leads to
+ # incorrect simplifications with nested Piecewises.
+ if c == cond and (or1 == or2 or cond is not True):
+ expr = e
+ new_ecs.append( Tuple(expr, cond) )
+ if isinstance(oth, Piecewise):
+ or2 = Or( *[c for (_, c) in oth.exprcondpairs] )
+ if or1 == or2:
+ oth = oth.otherwise
+ return new_ecs, oth
+
+
def piecewise_fold(expr):
"""
Takes an expression containing a piecewise function and returns the
@@ -319,16 +427,21 @@ def piecewise_fold(expr):
if not isinstance(expr, Basic) or not expr.has(Piecewise):
return expr
new_args = map(piecewise_fold, expr.args)
- if expr.func is ExprCondPair:
- return ExprCondPair(*new_args)
+ if expr.func is Tuple:
+ return Tuple(*new_args)
piecewise_args = []
for n, arg in enumerate(new_args):
- if arg.func is Piecewise:
+ if sympify(arg).func is Piecewise:
piecewise_args.append(n)
if len(piecewise_args) > 0:
n = piecewise_args[0]
- new_args = [(expr.func(*(new_args[:n] + [e] + new_args[n+1:])), c) \
- for e, c in new_args[n].args]
+ ecs = new_args[n].exprcondpairs
+ oth = new_args[n].otherwise
+ prev_args = new_args[:n]
+ post_args = new_args[n+1:]
+ new_args = [(expr.func(*(prev_args + [e] + post_args)), c) \
+ for e, c in ecs]
+ new_args.append(expr.func(*(prev_args + [oth] + post_args)))
if len(piecewise_args) > 1:
return piecewise_fold(Piecewise(*new_args))
return Piecewise(*new_args)
View
179 sympy/functions/elementary/tests/test_piecewise.py
@@ -1,6 +1,6 @@
from sympy import (diff, expand, Eq, Integral, integrate, Interval, lambdify,
- log, oo, Piecewise, piecewise_fold, symbols, pi, solve,
- Rational, Basic)
+ log, oo, Piecewise, piecewise_fold, pi, S, symbols, solve,
+ Rational)
from sympy.utilities.pytest import XFAIL, raises
x,y = symbols('x,y')
@@ -8,11 +8,26 @@
def test_piecewise():
# Test canonization
- assert Piecewise((x, x < 1), (0, True)) == Piecewise((x, x < 1), (0, True))
- assert Piecewise((x, x < 1), (0, False), (-1, 1>2)) == Piecewise((x, x < 1))
- assert Piecewise((x, True)) == x
- raises(TypeError,"Piecewise(x)")
+ assert Piecewise((x, x > 1)) == Piecewise((x, x > 1), S.NaN)
+ assert Piecewise((x, x > 1), (0, True), S.NaN) == 0
@rlamy Collaborator
rlamy added a note

Huh?? This should be Piecewise((x, x > 1), 0).

@asmeurer Owner

No, this is right. NaN is the otherwise condition. See the prior discussion, both here and on the mailing list. During the deprecation cycle, you can force (expr, cond) pairs to be viewed as that (rather than (expr_otherwise, True)) by manually adding nan as the otherwise condition (this is the default otherwise condition, so this changes nothing).

@rlamy Collaborator
rlamy added a note

But the (x, x > 1) part has disappeared! That is the problem, not whether the "otherwise" should be 0 or NaN.

@asmeurer Owner

That's because the piecewise evaluated to 0, which had a True condition.

@rlamy Collaborator
rlamy added a note

Translated to Python code, Piecewise((x, x>1), (0, True), S.NaN) means

if x > 1:
    return x
elif True:
    return 0
else:
    return S.NaN

which is clearly equivalent to

if x > 1:
    return x
else:
    return 0

but not to simply return 0.

@asmeurer Owner

OK, I see what you're getting at. This is exactly why we need Piecewise(evaluate=False).

@rlamy Collaborator
rlamy added a note

Let's move the discussion to issue 3025, so that it doesn't disappear when this review is done, OK?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ assert Piecewise((x, x > 1), (0, False)) == S.NaN
+ assert Piecewise((x, x > 1), (0, False), 1) == 1
+ assert Piecewise((x, True), S.NaN) == x
raises(TypeError,"Piecewise((x,x**2))")
+ raises(TypeError,"Piecewise((1,Interval(0,1,False,True)),(0,x<1))")
+ raises(TypeError,"Piecewise((x,1))")
+ raises(ValueError,"Piecewise((x,))")
+ raises(ValueError,"Piecewise((x,x>1,x>0))")
+ raises(ValueError,"Piecewise(x,(x,x<1))")
+ raises(ValueError,"Piecewise(x,x)")
+
+ # Test properties
+ p1 = Piecewise((x, x > 1), x**2)
+ p2 = Piecewise((x, x > 1), (x**2, x <=1))
+ assert p1.exprcondpairs == ((x, x > 1),)
+ assert p1.otherwise == x**2
+ assert p2.exprcondpairs == ((x, x > 1),(x**2, x <= 1))
+ assert p2.otherwise == S.NaN
# Test subs
p = Piecewise((-1, x < -1), (x**2, x < 0), (log(x), x >=0))
@@ -28,6 +43,11 @@ def test_piecewise():
assert p2.subs(x,4) == -1
assert p2.subs(x,10) == 0
+ # More subs tests
+ p3 = Piecewise( (x**2, Interval(-3, 3).contains(x)) )
+ assert p3.subs(x,-2) == 4
+ assert p3.subs(x,4) == S.NaN
+
# Test evalf
assert p.evalf() == p
assert p.evalf(subs={x:-2}) == -1
@@ -36,7 +56,7 @@ def test_piecewise():
# Test doit
f_int = Piecewise((Integral(x,(x,0,1)), x < 1))
- assert f_int.doit() == Piecewise( (1.0/2.0, x < 1) )
+ assert f_int.doit(piecewise=False) == Piecewise( (1.0/2.0, x < 1) )
# Test differentiation
f = x
@@ -66,48 +86,59 @@ def test_piecewise():
p = Piecewise((x, x < 1),(x**2, -1 <= x),(x,3<x))
assert integrate(p,(x,-2,2)) == 5.0/6.0
assert integrate(p,(x,2,-2)) == -5.0/6.0
- p = Piecewise((0, x < 0), (1,x < 1), (0, x < 2), (1, x < 3), (0, True))
+ p = Piecewise((0, x < 0), (1,x < 1), (0, x < 2), (1, x < 3), 0)
assert integrate(p, (x,-oo,oo)) == 2
p = Piecewise((x, x < -10),(x**2, x <= -1),(x, 1 < x))
- raises(ValueError, "integrate(p,(x,-2,2))")
+ assert integrate(p,(x,-2,2)) == S.NaN
# Test commutativity
assert p.is_commutative is True
+ # Test leading term
+ # This may not be the best behavior, but is necessary to pass tests
+ # See XFAIL test_piecewise_leading_term for better behavior
+ p = Piecewise((x**3 + x, x < -1), (x**2 - 1, x < 0), log(x))
+ assert p.as_leading_term(x) == log(x)
+
+@XFAIL
+def test_piecewise_leading_term():
+ # Test as_leading_term
+ p = Piecewise((x**3 + x, x < -1), (x**2 - 1, x < 0), log(x))
+ assert p.as_leading_term(x) == Piecewise((x, x < -1), (-1, x < 0), log(x))
+
def test_piecewise_free_symbols():
a = symbols('a')
- f = Piecewise((x , a<0), (y, True))
+ f = Piecewise((x , a<0), y)
assert f.free_symbols == set([x, y, a])
def test_piecewise_integrate():
- # XXX Use '<=' here! '>=' is not yet implemented ..
- f = Piecewise(((x - 2)**2, 0 <= x), (1, True))
+ f = Piecewise(((x - 2)**2, x >= 0), 1)
assert integrate(f, (x, -2, 2)) == Rational(14, 3)
- g = Piecewise(((x - 5)**5, 4 <= x), (f, True))
+ g = Piecewise(((x - 5)**5, x >= 4), f)
assert integrate(g, (x, -2, 2)) == Rational(14, 3)
assert integrate(g, (x, -2, 5)) == Rational(43, 6)
- g = Piecewise(((x - 5)**5, 4 <= x), (f, x < 4))
+ g = Piecewise(((x - 5)**5, x >= 4), (f, x < 4))
assert integrate(g, (x, -2, 2)) == Rational(14, 3)
assert integrate(g, (x, -2, 5)) == Rational(43, 6)
- g = Piecewise(((x - 5)**5, 2 <= x), (f, x < 2))
+ g = Piecewise(((x - 5)**5, x >= 2), (f, x < 2))
assert integrate(g, (x, -2, 2)) == Rational(14, 3)
assert integrate(g, (x, -2, 5)) == -Rational(701, 6)
- g = Piecewise(((x - 5)**5, 2 <= x), (f, True))
+ g = Piecewise(((x - 5)**5, x >= 2), f)
assert integrate(g, (x, -2, 2)) == Rational(14, 3)
assert integrate(g, (x, -2, 5)) == -Rational(701, 6)
- g = Piecewise(((x - 5)**5, 2 <= x), (2 * f, True))
+ g = Piecewise(((x - 5)**5, x >= 2), 2 * f)
assert integrate(g, (x, -2, 2)) == 2 * Rational(14, 3)
assert integrate(g, (x, -2, 5)) == -Rational(673, 6)
g = Piecewise((1, x > 0), (0, Eq(x, 0)), (-1, x < 0))
assert integrate(g, (x, -1, 1)) == 0
- g = Piecewise((1, x - y < 0), (0, True))
+ g = Piecewise((1, x - y < 0), 0)
assert integrate(g, (y, -oo, oo)) == oo
def test_piecewise_solve():
@@ -116,10 +147,10 @@ def test_piecewise_solve():
assert solve(f, x) == [2]
assert solve(f - 1,x) == [1, 3]
- f = Piecewise(((x - 2)**2, x >= 0), (1, True))
+ f = Piecewise(((x - 2)**2, x >= 0), 1)
assert solve(f, x) == [2]
- g = Piecewise(((x - 5)**5, x >= 4), (f, True))
+ g = Piecewise(((x - 5)**5, x >= 4), f)
assert solve(g, x) == [2, 5]
g = Piecewise(((x - 5)**5, x >= 4), (f, x < 4))
@@ -128,16 +159,15 @@ def test_piecewise_solve():
g = Piecewise(((x - 5)**5, x >= 2), (f, x < 2))
assert solve(g, x) == [5]
- g = Piecewise(((x - 5)**5, x >= 2), (f, True))
+ g = Piecewise(((x - 5)**5, x >= 2), f)
assert solve(g, x) == [5]
- g = Piecewise(((x - 5)**5, x >= 2), (f, True), (10, False))
- assert solve(g, x) == [5]
+ assert solve(Piecewise((x, x < 0), x), x) == [0]
# See issue 1253 (enhance the solver to handle inequalities).
@XFAIL
def test_piecewise_solve2():
- f = Piecewise(((x - 2)**2, x >= 0), (0, True))
+ f = Piecewise(((x - 2)**2, x >= 0), 0)
assert solve(f, x) == [2, Interval(0, oo, True, True)]
def test_piecewise_fold():
@@ -146,25 +176,25 @@ def test_piecewise_fold():
assert piecewise_fold(x*p) == Piecewise((x**2, x < 1), (x, 1 <= x))
assert piecewise_fold(p+p) == Piecewise((2*x, x < 1), (2, 1 <= x))
- assert piecewise_fold(Piecewise((1, x < 0), (2, True)) \
- + Piecewise((10, x < 0), (-10, True))) == \
- Piecewise((11, x < 0), (-8, True))
+ assert piecewise_fold(Piecewise((1, x < 0), 2) \
+ + Piecewise((10, x < 0), -10)) == \
+ Piecewise((11, x < 0), -8)
- p1 = Piecewise((0, x < 0), (x, x <= 1), (0, True))
- p2 = Piecewise((0, x < 0), (1 - x, x <=1), (0, True))
+ p1 = Piecewise((0, x < 0), (x, x <= 1), 0)
+ p2 = Piecewise((0, x < 0), (1 - x, x <=1), 0)
p = 4*p1 + 2*p2
assert integrate(piecewise_fold(p),(x,-oo,oo)) == integrate(2*x + 2, (x, 0, 1))
def test_piecewise_fold_expand():
- p1 = Piecewise((1,Interval(0,1,False,True)),(0,True))
+ p1 = Piecewise((1,Interval(0,1,False,True).contains(x)),0)
- p2 = piecewise_fold(expand((1-x)*p1))
- assert p2 == Piecewise((1 - x, Interval(0,1,False,True)), \
- (Piecewise((-x, Interval(0,1,False,True)), (0, True)), True))
+ #p2 = piecewise_fold(expand((1-x)*p1))
+ #assert p2 == Piecewise((1 - x, Interval(0,1,False,True).contains(x)), \
+ # Piecewise((-x, Interval(0,1,False,True).contains(x)), 0))
p2 = expand(piecewise_fold((1-x)*p1))
- assert p2 == Piecewise((1 - x, Interval(0,1,False,True)), (0, True))
+ assert p2 == Piecewise((1 - x, Interval(0,1,False,True).contains(x)), 0)
def test_piecewise_duplicate():
p = Piecewise((x, x < -10),(x**2, x <= -1),(x, 1 < x))
@@ -173,30 +203,30 @@ def test_piecewise_duplicate():
def test_doit():
p1 = Piecewise((x, x < 1), (x**2, -1 <= x), (x, 3 < x))
p2 = Piecewise((x, x < 1), (Integral(2 * x), -1 <= x), (x, 3 < x))
- assert p2.doit() == p1
- assert p2.doit(deep = False) == p2
+ assert p2.doit(piecewise = False) == p1
+ assert p2.doit(deep = False, piecewise=False) == p2
def test_piecewise_interval():
- p1 = Piecewise((x, Interval(0,1)), (0, True))
+ p1 = Piecewise((x, Interval(0,1).contains(x)), 0)
assert p1.subs(x, -0.5) == 0
assert p1.subs(x, 0.5) == 0.5
- assert p1.diff(x) == Piecewise((1, Interval(0, 1)), (0, True))
- assert integrate(p1, x) == Piecewise((x**2/2, Interval(0, 1)), (0, True))
+ assert p1.diff(x) == Piecewise((1, Interval(0, 1).contains(x)), 0)
+ assert integrate(p1, x) == Piecewise((x**2/2, Interval(0, 1).contains(x)), 0)
def test_piecewise_collapse():
p1 = Piecewise((x, x<0),(x**2,x>1))
p2 = Piecewise((p1,x<0),(p1,x>1))
- assert p2 == Piecewise((x, x < 0), (x**2, 1 < x))
+ assert p2.doit(piecewise = False) == Piecewise((x, x < 0), (x**2, 1 < x))
- p1 = Piecewise((Piecewise((x,x<0),(1,True)),True))
- assert p1 == Piecewise((Piecewise((x,x<0),(1,True)),True))
+ p1 = Piecewise(Piecewise((x,x<0),1))
+ assert p1 == Piecewise(Piecewise((x,x<0),1))
def test_piecewise_lambdify():
p = Piecewise(
(x**2, x < 0),
- (x, Interval(0, 1, False, True)),
+ (x, Interval(0, 1, False, True).contains(x)),
(2 - x, x >= 1),
- (0, True)
+ 0
)
f = lambdify(x, p)
assert f(-2.0) == 4.0
@@ -211,9 +241,60 @@ def test_piecewise_series():
assert p1.nseries(x,n=2) == p2
def test_piecewise_evaluate():
- assert Piecewise((x, True)) == x
- assert Piecewise((x, True), evaluate=True) == x
- p = Piecewise((x, True), evaluate=False)
- assert p != x
+ # Force evaluation=False when only given otherwise
+ assert Piecewise(x) == x
+ assert Piecewise(x, evaluate=False).is_Piecewise
+
+ # Forcing evaluation when no conds True gives otherwise
+ assert Piecewise((x, x < 1)).is_Piecewise
+ assert Piecewise((x, x < 1), evaluate=True) == S.NaN
+ assert Piecewise((x, x < 1), x**2).is_Piecewise
+ assert Piecewise((x, x < 1), x**2, evaluate=True) == x**2
+
+ # Explicit evaluate=False stops automatic evaluation
+ assert Piecewise((x, True), S.NaN) == x
+ assert Piecewise((x, x < 1), (0, True), S.NaN) == 0
+
+ # Automatic evaluation of bool conds
+ assert Piecewise((x, 1 > 2), (-x, False)) == S.NaN
+
+ # Automatic simplification of nested piecewise
+ p1 = Piecewise((x, x > 0), (2*x, x <= 0), 3*x)
+ p2 = Piecewise((p1, x > 0), (2*x**2, x <= 0))
+ p3 = Piecewise((x**2, x > 0), (2*x**2, x <= 0), p1)
+ assert p2 == Piecewise((x, x > 0), (2*x**2, x <=0))
+ assert p3 == Piecewise((x**2, x > 0), (2*x**2, x<=0), 3*x)
+ # Explicit evaluate=False stops automatic simplification
+ p2 = Piecewise((p1, x > 0), (2*x**2, x <= 0), evaluate=False)
+ p3 = Piecewise((x**2, x > 0), (2*x**2, x <= 0), p1, evaluate=False)
+ assert p2.exprcondpairs[0][0].is_Piecewise
+ assert p3.otherwise.is_Piecewise
+
+ # Evaluation using .doit()
+ p1 = Piecewise((x, x < 1), x**2)
+ assert p1.is_Piecewise
+ assert p1.doit() == x**2
+ assert p1.doit(piecewise=False) == p1
+
+# Requires Issue 3025: implementation of boolean objects subclassing Basic
+@XFAIL
+def test_needs_basic_bools():
+ # from test_piecewise_evaluate
+ assert Piecewise((x, True), S.NaN, evaluate=False).is_Piecewise
+ assert Piecewise((x, 1 < 2), S.NaN, evaluate=False).is_Piecewise
+ assert Piecewise((x, 1 > 2), S.NaN, evaluate=False).is_Piecewise
+
+ p = Piecewise((x, x < 1), (0, True), S.NaN, evaluate=False)
assert p.is_Piecewise
- assert all(isinstance(i, Basic) for i in p.args)
+ assert len(p.args) == 3
+ assert Piecewise((x, 1 > 2), (-x, False), evaluate=False).is_Piecewise
+
+ assert Piecewise((x, True), S.NaN, evaluate=False).is_Piecewise
+ assert Piecewise((x, True), S.NaN, evaluate=False) == Piecewise(x, evaluate=False)
+ assert Piecewise((x, x < 1), (0, True), S.NaN, evaluate=False) == Piecewise((x, x < 1), 0)
+
+ # from test_piecewise_solve
+ abs2 = Piecewise((-x, x <= 0), (x, x > 0))
+ f = abs2.subs(x, x - 2)
+ g = Piecewise(((x - 5)**5, x >= 2), (f, True), (10, False), evaluate=False)
+ assert solve(g, x) == [5]
View
46 sympy/functions/special/bsplines.py
@@ -1,6 +1,5 @@
from sympy.core import S, sympify, expand
from sympy.functions import Piecewise, piecewise_fold
-from sympy.functions.elementary.piecewise import ExprCondPair
from sympy.core.sets import Interval
@@ -11,16 +10,16 @@ def _add_splines(c, b1, d, b2):
if b2 == S.Zero or d == S.Zero:
return expand(piecewise_fold(c*b1))
new_args = []
- n_intervals = len(b1.args)
- assert(n_intervals==len(b2.args))
- new_args.append((expand(c*b1.args[0].expr), b1.args[0].cond))
- for i in range(1, n_intervals-1):
+ n_intervals = len(b1.exprcondpairs)
+ assert(n_intervals==len(b2.exprcondpairs))
+ new_args.append((expand(c*b1.exprcondpairs[0][0]), b1.exprcondpairs[0][1]))
+ for i in range(1, n_intervals):
new_args.append((
- expand(c*b1.args[i].expr+d*b2.args[i-1].expr),
- b1.args[i].cond
+ expand(c*b1.exprcondpairs[i][0]+d*b2.exprcondpairs[i-1][0]),
+ b1.exprcondpairs[i][1]
))
- new_args.append((expand(d*b2.args[-2].expr), b2.args[-2].cond))
- new_args.append(b2.args[-1])
+ new_args.append((expand(d*b2.exprcondpairs[-1][0]), b2.exprcondpairs[-1][1]))
+ new_args.append(d*b2.otherwise)
return Piecewise(*new_args)
def bspline_basis(d, knots, n, x, close=True):
@@ -36,7 +35,7 @@ def bspline_basis(d, knots, n, x, close=True):
>>> d = 0
>>> knots = range(5)
>>> bspline_basis(d, knots, 0, x)
- Piecewise((1, [0, 1]), (0, True))
+ Piecewise((1, And(0 <= x, x <= 1)), 0)
For a given (d, knots) there are len(knots)-d-1 B-splines defined, that
are indexed by n (starting at 0).
@@ -44,7 +43,7 @@ def bspline_basis(d, knots, n, x, close=True):
Here is an example of a cubic B-spline:
>>> bspline_basis(3, range(5), 0, x)
- Piecewise((x**3/6, [0, 1)), (-x**3/2 + 2*x**2 - 2*x + 2/3, [1, 2)), (x**3/2 - 4*x**2 + 10*x - 22/3, [2, 3)), (-x**3/6 + 2*x**2 - 8*x + 32/3, [3, 4]), (0, True))
+ Piecewise((x**3/6, And(0 <= x, x < 1)), (-x**3/2 + 2*x**2 - 2*x + 2/3, And(1 <= x, x < 2)), (x**3/2 - 4*x**2 + 10*x - 22/3, And(2 <= x, x < 3)), (-x**3/6 + 2*x**2 - 8*x + 32/3, And(3 <= x, x <= 4)), 0)
By repeating knot points, you can introduce discontinuities in the
B-splines and their derivatives:
@@ -52,7 +51,7 @@ def bspline_basis(d, knots, n, x, close=True):
>>> d = 1
>>> knots = [0,0,2,3,4]
>>> bspline_basis(d, knots, 0, x)
- Piecewise((-x/2 + 1, [0, 2]), (0, True))
+ Piecewise((-x/2 + 1, And(0 <= x, x <= 2)), 0)
It is quite time consuming to construct and evaluate B-splines. If you
need to evaluate a B-splines many times, it is best to lambdify them
@@ -85,8 +84,8 @@ def bspline_basis(d, knots, n, x, close=True):
raise ValueError('n+d+1 must not exceed len(knots)-1')
if d==0:
result = Piecewise(
- (S.One, Interval(knots[n], knots[n+1], False, True)),
- (0, True)
+ (S.One, Interval(knots[n], knots[n+1], False, True).as_relational(x)),
+ 0
)
elif d > 0:
denom = knots[n+d] - knots[n]
@@ -106,12 +105,17 @@ def bspline_basis(d, knots, n, x, close=True):
else:
raise ValueError('degree must be non-negative: %r' % n)
if close:
- final_ec_pair = result.args[-2]
- final_cond = final_ec_pair.cond
- final_expr = final_ec_pair.expr
- new_args = final_cond.args[:3] + (False,)
- new_ec_pair = ExprCondPair(final_expr, Interval(*new_args))
- new_args = result.args[:-2] + (new_ec_pair, result.args[-1])
+ final_ec_pair = result.exprcondpairs[-1]
+ final_cond = final_ec_pair[1]
+ final_expr = final_ec_pair[0]
+ final_cond = final_cond.args
+ from sympy import And
+ if final_cond[0].lts == x:
+ final_cond = And(final_cond[0].lts <= final_cond[0].gts, final_cond[1])
+ else:
+ final_cond = And(final_cond[0], final_cond[1].lts <= final_cond[1].gts)
+ new_ec_pair = (final_expr, final_cond)
+ new_args = tuple(result.exprcondpairs[:-1]) + (new_ec_pair, result.otherwise)
result = Piecewise(*new_args)
return result
@@ -131,7 +135,7 @@ def bspline_basis_set(d, knots, x):
>>> knots = range(5)
>>> splines = bspline_basis_set(d, knots, x)
>>> splines
- [Piecewise((x**2/2, [0, 1)), (-x**2 + 3*x - 3/2, [1, 2)), (x**2/2 - 3*x + 9/2, [2, 3]), (0, True)), Piecewise((x**2/2 - x + 1/2, [1, 2)), (-x**2 + 5*x - 11/2, [2, 3)), (x**2/2 - 4*x + 8, [3, 4]), (0, True))]
+ [Piecewise((x**2/2, And(0 <= x, x < 1)), (-x**2 + 3*x - 3/2, And(1 <= x, x < 2)), (x**2/2 - 3*x + 9/2, And(2 <= x, x <= 3)), 0), Piecewise((x**2/2 - x + 1/2, And(1 <= x, x < 2)), (-x**2 + 5*x - 11/2, And(2 <= x, x < 3)), (x**2/2 - 4*x + 8, And(3 <= x, x <= 4)), 0)]
See Also
========
View
6 sympy/functions/special/hyper.py
@@ -1,10 +1,8 @@
"""Hypergeometric and Meijer G-functions"""
from sympy import S
-from sympy.core.compatibility import iterable
from sympy.core.function import Function, ArgumentIndexError
from sympy.core.containers import Tuple
-from sympy.core.sympify import sympify
from sympy.core.mul import Mul
# TODO should __new__ accept **options?
@@ -540,7 +538,7 @@ def get_period(self):
12*pi
"""
# This follows from slater's theorem.
- from sympy import oo, ilcm, pi, Min, Mod
+ from sympy import oo, ilcm, pi, Mod
def compute(l):
# first check that no two differ by an integer
for i, b in enumerate(l):
@@ -688,7 +686,7 @@ def _eval_rewrite_as_nonrep(self, *args):
if big == small:
return small
- return Piecewise((big, abs(x) > 1), (small, True))
+ return Piecewise((big, abs(x) > 1), small)
def _eval_rewrite_as_nonrepsmall(self, *args):
x, n = self.args[-1].extract_branch_factor(allow_half=True)
View
36 sympy/functions/special/tests/test_bsplines.py
@@ -1,4 +1,4 @@
-from sympy.functions import bspline_basis, bspline_basis_set
+from sympy.functions import bspline_basis_set
from sympy import Piecewise, Interval
from sympy import symbols, Rational
@@ -9,22 +9,22 @@ def test_basic_degree_0():
knots = range(5)
splines = bspline_basis_set(d, knots, x)
for i in range(len(splines)):
- assert splines[i] == Piecewise((1, Interval(i, i+1)), (0, True))
+ assert splines[i] == Piecewise((1, Interval(i, i+1).as_relational(x)), 0)
def test_basic_degree_1():
d = 1
knots = range(5)
splines = bspline_basis_set(d, knots, x)
- assert splines[0] == Piecewise((x,Interval(0,1,False,True)),(2-x,Interval(1,2)),(0,True))
- assert splines[1] == Piecewise((-1+x,Interval(1,2,False,True)),(3-x,Interval(2,3)),(0,True))
- assert splines[2] == Piecewise((-2+x,Interval(2,3,False,True)),(4-x,Interval(3,4)),(0,True))
+ assert splines[0] == Piecewise((x,Interval(0,1,False,True).as_relational(x)),(2-x,Interval(1,2).as_relational(x)),0)
+ assert splines[1] == Piecewise((-1+x,Interval(1,2,False,True).as_relational(x)),(3-x,Interval(2,3).as_relational(x)),0)
+ assert splines[2] == Piecewise((-2+x,Interval(2,3,False,True).as_relational(x)),(4-x,Interval(3,4).as_relational(x)),0)
def test_basic_degree_2():
d = 2
knots = range(5)
splines = bspline_basis_set(d, knots, x)
- b0 = Piecewise((x**2/2,Interval(0,1,False,True)),(Rational('-3/2')+3*x-x**2,Interval(1,2,False,True)),(Rational(9,2)-3*x+x**2/2,Interval(2,3)),(0,True))
- b1 = Piecewise((Rational(1,2)-x+x**2/2,Interval(1,2,False,True)),(Rational(-11,2)+5*x-x**2,Interval(2,3,False,True)),(8-4*x+x**2/2,Interval(3,4)),(0,True))
+ b0 = Piecewise((x**2/2,Interval(0,1,False,True).as_relational(x)),(Rational('-3/2')+3*x-x**2,Interval(1,2,False,True).as_relational(x)),(Rational(9,2)-3*x+x**2/2,Interval(2,3).as_relational(x)),0)
+ b1 = Piecewise((Rational(1,2)-x+x**2/2,Interval(1,2,False,True).as_relational(x)),(Rational(-11,2)+5*x-x**2,Interval(2,3,False,True).as_relational(x)),(8-4*x+x**2/2,Interval(3,4).as_relational(x)),0)
assert splines[0] == b0
assert splines[1] == b1
@@ -33,11 +33,11 @@ def test_basic_degree_3():
knots = range(5)
splines = bspline_basis_set(d, knots, x)
b0 = Piecewise(
- (x**3/6, Interval(0,1,False,True)),
- (Rational(2,3) - 2*x + 2*x**2 - x**3/2, Interval(1,2,False,True)),
- (Rational(-22,3) + 10*x - 4*x**2 + x**3/2, Interval(2,3,False,True)),
- (Rational(32,3) - 8*x + 2*x**2 - x**3/6, Interval(3,4)),
- (0, True)
+ (x**3/6, Interval(0,1,False,True).as_relational(x)),
+ (Rational(2,3) - 2*x + 2*x**2 - x**3/2, Interval(1,2,False,True).as_relational(x)),
+ (Rational(-22,3) + 10*x - 4*x**2 + x**3/2, Interval(2,3,False,True).as_relational(x)),
+ (Rational(32,3) - 8*x + 2*x**2 - x**3/6, Interval(3,4).as_relational(x)),
+ 0
)
assert splines[0] == b0
@@ -45,10 +45,10 @@ def test_repeated_degree_1():
d = 1
knots = [0,0,1,2,2,3,4,4]
splines = bspline_basis_set(d, knots, x)
- assert splines[0] == Piecewise((1 - x, Interval(0,1)), (0, True))
- assert splines[1] == Piecewise((x, Interval(0,1,False,True)), (2 - x, Interval(1,2)), (0, True))
- assert splines[2] == Piecewise((-1 + x, Interval(1,2)), (0, True))
- assert splines[3] == Piecewise((3 - x, Interval(2,3)), (0, True))
- assert splines[4] == Piecewise((-2 + x, Interval(2,3,False,True)), (4 - x, Interval(3,4)), (0, True))
- assert splines[5] == Piecewise((-3 + x, Interval(3,4)), (0, True))
+ assert splines[0] == Piecewise((1 - x, Interval(0,1).as_relational(x)), 0)
+ assert splines[1] == Piecewise((x, Interval(0,1,False,True).as_relational(x)), (2 - x, Interval(1,2).as_relational(x)), 0)
+ assert splines[2] == Piecewise((-1 + x, Interval(1,2).as_relational(x)), 0)
+ assert splines[3] == Piecewise((3 - x, Interval(2,3).as_relational(x)), 0)
+ assert splines[4] == Piecewise((-2 + x, Interval(2,3,False,True).as_relational(x)), (4 - x, Interval(3,4).as_relational(x)), 0)
+ assert splines[5] == Piecewise((-3 + x, Interval(3,4).as_relational(x)), 0)
View
12 sympy/functions/special/tests/test_hyper.py
@@ -191,17 +191,17 @@ def _expr_small_minus(cls, x): return b
def _expr_big(cls, x, n): return c*n
@classmethod
def _expr_big_minus(cls, x, n): return d*n
- assert myrep(z).rewrite('nonrep') == Piecewise((0, abs(z) > 1), (a, True))
+ assert myrep(z).rewrite('nonrep') == Piecewise((0, abs(z) > 1), a)
assert myrep(exp_polar(I*pi)*z).rewrite('nonrep') == \
- Piecewise((0, abs(z) > 1), (b, True))
+ Piecewise((0, abs(z) > 1), b)
assert myrep(exp_polar(2*I*pi)*z).rewrite('nonrep') == \
- Piecewise((c, abs(z) > 1), (a, True))
+ Piecewise((c, abs(z) > 1), a)
assert myrep(exp_polar(3*I*pi)*z).rewrite('nonrep') == \
- Piecewise((d, abs(z) > 1), (b, True))
+ Piecewise((d, abs(z) > 1), b)
assert myrep(exp_polar(4*I*pi)*z).rewrite('nonrep') == \
- Piecewise((2*c, abs(z) > 1), (a, True))
+ Piecewise((2*c, abs(z) > 1), a)
assert myrep(exp_polar(5*I*pi)*z).rewrite('nonrep') == \
- Piecewise((2*d, abs(z) > 1), (b, True))
+ Piecewise((2*d, abs(z) > 1), b)
assert myrep(z).rewrite('nonrepsmall') == a
assert myrep(exp_polar(I*pi)*z).rewrite('nonrepsmall') == b
View
4 sympy/integrals/integrals.py
@@ -526,7 +526,7 @@ def try_meijerg(function, xab):
f, cond = res
if conds == 'piecewise':
ret = Piecewise((f, cond),
- (Integral(function, (x, a, b)), True))
+ Integral(function, (x, a, b)))
elif conds == 'separate':
if len(self.limits) != 1:
raise ValueError('conds=separate not supported in ' \
@@ -1159,7 +1159,7 @@ def integrate(*args, **kwargs):
in interactive sessions and should be avoided in library code.
>>> integrate(x**a*exp(-x), (x, 0, oo)) # same as conds='piecewise'
- Piecewise((gamma(a + 1), -re(a) < 1), (Integral(x**a*exp(-x), (x, 0, oo)), True))
+ Piecewise((gamma(a + 1), -re(a) < 1), Integral(x**a*exp(-x), (x, 0, oo)))
>>> integrate(x**a*exp(-x), (x, 0, oo), conds='none')
gamma(a + 1)
View
58 sympy/integrals/meijerint.py
@@ -603,7 +603,8 @@ def _eval_cond(cond):
""" Re-evaluate the conditions. """
if isinstance(cond, bool):
return cond
- return _condsimp(cond.doit())
+ # Keep Piecewises from being evaluated
+ return _condsimp(cond.doit(**{'piecewise': False}))
####################################################################
# Now the "backbone" functions to do actual integration.
@@ -877,14 +878,39 @@ def _check_antecedents(g1, g2, x):
def lambda_s0(c1, c2):
return c1*(q-p)*abs(omega)**(1/(q-p))*sin(psi) \
+ c2*(v-u)*abs(sigma)**(1/(v-u))*sin(theta)
- lambda_s = Piecewise(
- ((lambda_s0(+1, +1)*lambda_s0(-1, -1)),
- And(Eq(arg(sigma), 0), Eq(arg(omega), 0))),
- (lambda_s0(sign(arg(omega)), +1)*lambda_s0(sign(arg(omega)), -1),
- And(Eq(arg(sigma), 0), Ne(arg(omega), 0))),
- (lambda_s0(+1, sign(arg(sigma)))*lambda_s0(-1, sign(arg(sigma))),
- And(Ne(arg(sigma), 0), Eq(arg(omega), 0))),
- (lambda_s0(sign(arg(omega)), sign(arg(sigma))), True))
+
+ # TODO: Requires Issue 3025
+ # Can't set evaluate=False, due to possible boolean conditions
+ # must parse expr/conds and deal with conds accordingly
+ otherwise = lambda_s0(sign(arg(omega)), sign(arg(sigma)))
+ piecewise_args = [(lambda_s0(+1, +1)*lambda_s0(-1, -1),
+ And(Eq(arg(sigma), 0), Eq(arg(omega), 0))),
+ (lambda_s0(sign(arg(omega)), +1)*lambda_s0(sign(arg(omega)), -1),
+ And(Eq(arg(sigma), 0), Ne(arg(omega), 0))),
+ (lambda_s0(+1, sign(arg(sigma)))*lambda_s0(-1, sign(arg(sigma))),
+ And(Ne(arg(sigma), 0), Eq(arg(omega), 0)))
+ ]
+ new_args = []
+ for e, c in piecewise_args:
+ if c is False:
+ continue
+ if c is True:
+ new_args.append(e)
+ break
+ new_args.append((e,c))
+ else:
+ new_args.append(otherwise)
+ lambda_s = Piecewise(*new_args)
+ #lambda_s = Piecewise(
+ # ((lambda_s0(+1, +1)*lambda_s0(-1, -1)),
+ # And(Eq(arg(sigma), 0), Eq(arg(omega), 0))),
+ # (lambda_s0(sign(arg(omega)), +1)*lambda_s0(sign(arg(omega)), -1),
+ # And(Eq(arg(sigma), 0), Ne(arg(omega), 0))),
+ # (lambda_s0(+1, sign(arg(sigma)))*lambda_s0(-1, sign(arg(sigma))),
+ # And(Ne(arg(sigma), 0), Eq(arg(omega), 0))),
+ # lambda_s0(sign(arg(omega)), sign(arg(sigma))),
+ # **{'evaluate': False}
+ #)
_debug('Checking antecedents:')
_debug(' sigma=%s, s=%s, t=%s, u=%s, v=%s, b*=%s, rho=%s'
@@ -1400,7 +1426,7 @@ def my_integrator(f, x):
res, cond = r
res = _my_unpolarify(hyperexpand(res, rewrite='nonrepsmall'))
return Piecewise((res, cond),
- (Integral(f, (x, 0, oo)), True))
+ Integral(f, (x, 0, oo)))
return Integral(f, (x, 0, oo))
try:
F, strip, _ = mellin_transform(f, x, s, integrator=my_integrator,
@@ -1547,13 +1573,17 @@ def tr(p): return [a + rho + 1 for a in p]
res = piecewise_fold(res)
if res.is_Piecewise:
nargs = []
- for expr, cond in res.args:
+ for expr, cond in res.exprcondpairs:
expr = _my_unpolarify(Add(*expand_mul(expr).as_coeff_add(x)[1]))
- nargs += [(expr, cond)]
+ nargs.append((expr, cond))
+ if res.otherwise is not S.NaN:
+ expr = _my_unpolarify(Add(*expand_mul(res.otherwise).as_coeff_add(x)[1]))
+ nargs.append(expr)
+ cond = True
res = Piecewise(*nargs)
else:
res = _my_unpolarify(Add(*expand_mul(res).as_coeff_add(x)[1]))
- return Piecewise((res, _my_unpolarify(cond)), (Integral(f, x), True))
+ return Piecewise((res, _my_unpolarify(cond)), Integral(f, x))
@timeit
def meijerint_definite(f, x, a, b):
@@ -1891,4 +1921,4 @@ def meijerint_inversion(f, x, t):
if not isinstance(cond, bool):
cond = cond.subs(t, t + shift)
return Piecewise((res.subs(t, t_), cond),
- (Integral(f_*exp(x*t), (x, c - oo*I, c + oo*I)).subs(t, t_), True))
+ Integral(f_*exp(x*t), (x, c - oo*I, c + oo*I)).subs(t, t_))
View
10 sympy/integrals/tests/test_meijerint.py
@@ -546,9 +546,9 @@ def test_expint():
assert integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True) == log(2)/2
def test_messy():
- from sympy import (laplace_transform, Si, Ci, Shi, Chi, atan, Piecewise,
- atanh, acoth, E1, besselj, acosh, asin, Ne, And, re,
- fourier_transform, sqrt, Abs)
+ from sympy import (laplace_transform, Si, Shi, Chi, atan, Piecewise, acoth,
+ E1, besselj, acosh, asin, And, re, fourier_transform,
+ sqrt)
assert laplace_transform(Si(x), x, s) == ((pi - 2*atan(s))/(2*s), 0, True)
assert laplace_transform(Shi(x), x, s) == (acoth(s)/s, 1, True)
@@ -564,7 +564,7 @@ def test_messy():
# NOTE s < 0 can be done, but argument reduction is not good enough yet
assert fourier_transform(besselj(1, x)/x, x, s, noconds=False) == \
(Piecewise((0, 1 < 4*abs(pi**2*s**2)),
- (2*sqrt(-4*pi**2*s**2 + 1), True)), 0 < s)
+ 2*sqrt(-4*pi**2*s**2 + 1)), 0 < s)
# TODO FT(besselj(0,x)) - conditions are messy (but for acceptable reasons)
# - folding could be better
@@ -574,7 +574,7 @@ def test_messy():
== log(S(1)/2 + sqrt(2)/2)
assert integrate(1/x/sqrt(1 - x**2), x, meijerg=True) == \
- Piecewise((-acosh(1/x), 1 < abs(x**(-2))), (I*asin(1/x), True))
+ Piecewise((-acosh(1/x), 1 < abs(x**(-2))), I*asin(1/x))
def test_3023():
assert integrate(exp(-I*x**2), (x, -oo, oo), meijerg=True) == \
View
8 sympy/integrals/transforms.py
@@ -214,7 +214,7 @@ def _mellin_transform(f, x, s_, integrator=_default_integrator, simplify=True):
if not F.is_Piecewise:
raise IntegralTransformError('Mellin', f, 'could not compute integral')
- F, cond = F.args[0]
+ F, cond = F.exprcondpairs[0]
if F.has(Integral):
raise IntegralTransformError('Mellin', f, 'integral in unexpected form')
@@ -920,7 +920,7 @@ def _laplace_transform(f, t, s_, simplify=True):
if not F.is_Piecewise:
raise IntegralTransformError('Laplace', f, 'could not compute integral')
- F, cond = F.args[0]
+ F, cond = F.exprcondpairs[0]
if F.has(Integral):
raise IntegralTransformError('Laplace', f, 'integral in unexpected form')
@@ -1099,7 +1099,7 @@ def pw_simp(*args):
if f is None: