Bugs in Relational classes

60 views
Skip to first unread message

Brian Granger

unread,
Feb 26, 2010, 6:30:14 PM2/26/10
to sympy
Hi,

Here is my summary of some bugs i have found in Relational.  I would fix them, but I think there are larger issues that need discussing.

What is the truth value of a symbolic expression?
=================================================

The current general pattern is that symbolic expressions evaluate to True:

In [61]: bool(x)
Out[61]: True

In [62]: bool(x+1)
Out[62]: True

In [63]: bool(x*2)
Out[63]: True

In [64]: bool(2*x)
Out[64]: True

In [65]: bool(x>0)
Out[65]: True

But:

In [66]: bool(x<0)
Out[66]: False

This is obviously not consistent and it a bug in Relational.__nonzero__

Relational classes don't properly evaluate to booleans
======================================================

Consider these examples:

In [67]: Eq(0,0)
Out[67]: 0 == 0

In [68]: Ge(1,0)
Out[68]: 0 <= 1

Obviously, these should resolve to booleans. You may think, why wouldn't
you just do 0==0 or 1<0? But consider this:

In [69]: e = Ge(x,0)

In [70]: e.subs(x,1)
Out[70]: 0 <= 1    # Just Ge(1,0)!

Thus. it is common and easy to get Relational classes that should evaluate to
a boolean but don't.  This behavior also affects boolean logic classes
like And/Or, etc. as well as Interval:

In [71]: e = And(x>0,x<1)

In [72]: e.subs(x,0.5)
Out[72]: And(0 < 0.5, 0.5 < 1)

The problem is that the __new__ method of Relational does NOT actually
try to compare the lhs and rhs.  That is, Ge.__new__(lhs, rhs) doesn't actually
try lhs >= rhs.  But, we can't have it try that or it will generate an
infinite loop!  Resolving this will be quite subtle.

Cheers,

Brian


Aaron S. Meurer

unread,
Feb 26, 2010, 7:13:02 PM2/26/10
to sy...@googlegroups.com
Hi. Here are my opinions on the matter.

On Feb 26, 2010, at 4:30 PM, Brian Granger wrote:

> Hi,
>
> Here is my summary of some bugs i have found in Relational. I would fix them, but I think there are larger issues that need discussing.
>
> What is the truth value of a symbolic expression?
> =================================================
>
> The current general pattern is that symbolic expressions evaluate to True:
>
> In [61]: bool(x)
> Out[61]: True
>
> In [62]: bool(x+1)
> Out[62]: True
>
> In [63]: bool(x*2)
> Out[63]: True
>
> In [64]: bool(2*x)
> Out[64]: True
>
> In [65]: bool(x>0)
> Out[65]: True
>
> But:
>
> In [66]: bool(x<0)
> Out[66]: False
>
> This is obviously not consistent and it a bug in Relational.__nonzero__

Yes, let's make any SymPy expression return True if and only if it is not the object S.Zero (or maybe also some empty containers, like Matrix([])).
I'm not sure what to do about the relational (see below).

>
> Relational classes don't properly evaluate to booleans
> ======================================================
>
> Consider these examples:
>
> In [67]: Eq(0,0)
> Out[67]: 0 == 0
>
> In [68]: Ge(1,0)
> Out[68]: 0 <= 1
>
> Obviously, these should resolve to booleans. You may think, why wouldn't
> you just do 0==0 or 1<0? But consider this:

Just to be clear we overload __lt__, etc. to return the Le() class, but == is just the simple equality testing (not the Eq class). This is fine, but like you say, there needs to still be a way to do actual comparison. The problem I have seen is that it likes to turn things into booleans that don't make sense that way, other than being non-empty. For example, the x<0 above.

Another thing, automatic evaluation should proceed on a SymPy object only if three conditions are met:
1. The evaluation will always make the object simpler.
2. The evaluation is very cheap in every case.
3. The evaluation is so trivial that no one will ever not want it done.

For example, we auto-simplify exp(x)*exp(x) to exp(2*x), but we leave exp(x)*exp(y) alone, because if we auto-combined it, it would be impossible to actually get exp(x)*exp(y) instead of exp(x + y) (things used to be this way back in SymPy 0.6.4 until I spent a very headacheful few weeks changing it last summer).

(1) is clearly the case for turning inequalities into booleans. (2) will only be true for the simple case of number < number. Otherwise, even if the assumption for Ask(x - y, Q.positive) is True for x > y, we should leave it alone in Gt.__new__ and leave the work to refine(). Actually, even if this were cheap, I would still want to leave it alone because of (3).

Also, should we prevent bool(relational) from working otherwise, much like the TypeError you get when you do 1j < 1? I would rather have bool(x<y) raise an error than mislead me by returning True (misleading in the sense that bool(x>y) would also be True). Semantically speaking, relational.__nonzero__ should be True unless the relational is explicitly False, and relational.__bool__ should be either the explicit truth value of the relational or raise TypeError if there is none, but I do not know the actual implementation difference between these two in Python.

>
> In [69]: e = Ge(x,0)
>
> In [70]: e.subs(x,1)
> Out[70]: 0 <= 1 # Just Ge(1,0)!
>
> Thus. it is common and easy to get Relational classes that should evaluate to
> a boolean but don't. This behavior also affects boolean logic classes
> like And/Or, etc. as well as Interval:
>
> In [71]: e = And(x>0,x<1)
>
> In [72]: e.subs(x,0.5)
> Out[72]: And(0 < 0.5, 0.5 < 1)
>
> The problem is that the __new__ method of Relational does NOT actually
> try to compare the lhs and rhs. That is, Ge.__new__(lhs, rhs) doesn't actually
> try lhs >= rhs. But, we can't have it try that or it will generate an
> infinite loop! Resolving this will be quite subtle.

This should at least work with refine:

In [17]: refine(e.subs(x,0.5))
Out[17]: And(0 < 0.5, 0.5 < 1)

I wonder if internally, x > y should be represented as Assume(x - y, Q.positive) and x >= y as Assume(x - y, Q.nonnegative) (except there doesn't seem to be a Q.nonnegative yet for some reason), except that it would also have to keep track of what the original kind of inequality it was and what terms were on what side. Anyway, Ask(x - y, Q.positive) is the proper way to evaluate x < y, not checking x>=y, etc.

Aaron Meurer
>
> Cheers,
>
> Brian
>
>
>
> --
> You received this message because you are subscribed to the Google Groups "sympy" group.
> To post to this group, send email to sy...@googlegroups.com.
> To unsubscribe from this group, send email to sympy+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/sympy?hl=en.

Brian Granger

unread,
Feb 26, 2010, 7:40:06 PM2/26/10
to sy...@googlegroups.com
Aaron,

Thanks for the response...replies inline

> In [66]: bool(x<0)
> Out[66]: False
>
> This is obviously not consistent and it a bug in Relational.__nonzero__
 
Yes, let's make any SymPy expression return True if and only if it is not the object S.Zero (or maybe also some empty containers, like Matrix([])).

Yep and that is what Basic.__nonzero__ does.
 
I'm not sure what to do about the relational (see below).

Right.
 
> Relational classes don't properly evaluate to booleans
> ======================================================
>
> Consider these examples:
>
> In [67]: Eq(0,0)
> Out[67]: 0 == 0
>
> In [68]: Ge(1,0)
> Out[68]: 0 <= 1
>
> Obviously, these should resolve to booleans. You may think, why wouldn't
> you just do 0==0 or 1<0? But consider this:

Just to be clear we overload __lt__, etc. to return the Le() class, but == is just the simple equality testing (not the Eq class).  This is fine, but like you say, there needs to still be a way to do actual comparison.  The problem I have seen is that it likes to turn things into booleans that don't make sense that way, other than being non-empty.  For example, the x<0 above.

Right.
 
Another thing, automatic evaluation should proceed on a SymPy object only if three conditions are met:
1. The evaluation will always make the object simpler.
2. The evaluation is very cheap in every case.
3. The evaluation is so trivial that no one will ever not want it done.

For example, we auto-simplify exp(x)*exp(x) to exp(2*x), but we leave exp(x)*exp(y) alone, because if we auto-combined it, it would be impossible to actually get exp(x)*exp(y) instead of exp(x + y) (things used to be this way back in SymPy 0.6.4 until I spent a very headacheful few weeks changing it last summer).

(1) is clearly the case for turning inequalities into booleans.  (2) will only be true for the simple case of number < number.  

I could imagine other mathematical objects that have a clear concept of < and > and that are cheap to find. 
 
Otherwise, even if the assumption for Ask(x - y, Q.positive) is True for x > y, we should leave it alone in Gt.__new__ and leave the work to refine().  Actually, even if this were cheap, I would still want to leave it alone because of (3).

Yes, I agree that refine should be used in this cases.
 
Also, should we prevent bool(relational) from working otherwise, much like the TypeError you get when you do 1j < 1?

The problem with this is that there is little different between bool(x) and bool(x<0).
 
 I would rather have bool(x<y) raise an error than mislead me by returning True (misleading in the sense that bool(x>y) would also be True).  

But it would also be misleading that bool(2*x) is True, but bool(x<0) raises an exception.
 
Semantically speaking, relational.__nonzero__ should be True unless the relational is explicitly False, and relational.__bool__ should be either the explicit truth value of the relational or raise TypeError if there is none, but I do not know the actual implementation difference between these two in Python.

Yes, there is this issue as well.  
 
> In [69]: e = Ge(x,0)
>
> In [70]: e.subs(x,1)
> Out[70]: 0 <= 1    # Just Ge(1,0)!
>
> Thus. it is common and easy to get Relational classes that should evaluate to
> a boolean but don't.  This behavior also affects boolean logic classes
> like And/Or, etc. as well as Interval:
>
> In [71]: e = And(x>0,x<1)
>
> In [72]: e.subs(x,0.5)
> Out[72]: And(0 < 0.5, 0.5 < 1)
>
> The problem is that the __new__ method of Relational does NOT actually
> try to compare the lhs and rhs.  That is, Ge.__new__(lhs, rhs) doesn't actually
> try lhs >= rhs.  But, we can't have it try that or it will generate an
> infinite loop!  Resolving this will be quite subtle.

This should at least work with refine:

In [17]: refine(e.subs(x,0.5))
Out[17]: And(0 < 0.5, 0.5 < 1)

Not sure what you mean by this.  Are you saying you *should* get this:

In [17]: refine(e.subs(x,0.5))
Out[17]: True


 
I wonder if internally, x > y should be represented as Assume(x - y, Q.positive) and x >= y as Assume(x - y, Q.nonnegative) (except there doesn't seem to be a Q.nonnegative yet for some reason), except that it would also have to keep track of what the original kind of inequality it was and what terms were on what side.  Anyway, Ask(x - y, Q.positive) is the proper way to evaluate x < y, not checking x>=y, etc.


Not sure about using Assume for all of this.  Inequalities seem different than assumptions to me...

Brian
 

Aaron S. Meurer

unread,
Feb 26, 2010, 10:11:27 PM2/26/10
to sy...@googlegroups.com
Yes.  I think we are simplifying things like x < x + 1 right now too.  But there is also (3) to consider for that.  

 
Otherwise, even if the assumption for Ask(x - y, Q.positive) is True for x > y, we should leave it alone in Gt.__new__ and leave the work to refine().  Actually, even if this were cheap, I would still want to leave it alone because of (3).

Yes, I agree that refine should be used in this cases.
 
Also, should we prevent bool(relational) from working otherwise, much like the TypeError you get when you do 1j < 1?

The problem with this is that there is little different between bool(x) and bool(x<0).
 
 I would rather have bool(x<y) raise an error than mislead me by returning True (misleading in the sense that bool(x>y) would also be True).  

But it would also be misleading that bool(2*x) is True, but bool(x<0) raises an exception.
I guess the problem is what if you want to do an actual comparison.  We override <, >, etc., but that is what you would normally use in an if statement, though I guess you should really be making a call to Ask().  So as long as this is clear, it shouldn't be a problem either way.  

 
Semantically speaking, relational.__nonzero__ should be True unless the relational is explicitly False, and relational.__bool__ should be either the explicit truth value of the relational or raise TypeError if there is none, but I do not know the actual implementation difference between these two in Python.

Yes, there is this issue as well.  
 
> In [69]: e = Ge(x,0)
>
> In [70]: e.subs(x,1)
> Out[70]: 0 <= 1    # Just Ge(1,0)!
>
> Thus. it is common and easy to get Relational classes that should evaluate to
> a boolean but don't.  This behavior also affects boolean logic classes
> like And/Or, etc. as well as Interval:
>
> In [71]: e = And(x>0,x<1)
>
> In [72]: e.subs(x,0.5)
> Out[72]: And(0 < 0.5, 0.5 < 1)
>
> The problem is that the __new__ method of Relational does NOT actually
> try to compare the lhs and rhs.  That is, Ge.__new__(lhs, rhs) doesn't actually
> try lhs >= rhs.  But, we can't have it try that or it will generate an
> infinite loop!  Resolving this will be quite subtle.

This should at least work with refine:

In [17]: refine(e.subs(x,0.5))
Out[17]: And(0 < 0.5, 0.5 < 1)

Not sure what you mean by this.  Are you saying you *should* get this:

In [17]: refine(e.subs(x,0.5))
Out[17]: True

Yes, that is what I mean.  Refine should recursively check if an inequality will evaluate to True or False, even in the case where it is only because of assumptions on variables.  So this should return True also:

In [6]: refine(x > y, Assume(x - y, Q.positive))
Out[6]: y < x

But like you say, 0 < 0.5 should automatically return True even without refine(), unless some evaluate=False flag is sent to Lt().

 
I wonder if internally, x > y should be represented as Assume(x - y, Q.positive) and x >= y as Assume(x - y, Q.nonnegative) (except there doesn't seem to be a Q.nonnegative yet for some reason), except that it would also have to keep track of what the original kind of inequality it was and what terms were on what side.  Anyway, Ask(x - y, Q.positive) is the proper way to evaluate x < y, not checking x>=y, etc.


Not sure about using Assume for all of this.  Inequalities seem different than assumptions to me…
An inequality is not different from assumptions.  x < y is the same as saying x - y is positive.  It's similar to how we usually use x - y in SymPy instead of x == y.  Expressions are assumed by default to be equal to 0.  This is why having an assumptions system that allows assumptions on expressions instead of just variables is essential for inequality manipulation.  Any SymPy algorithm when presented with an Equality class will first subtract one side from the other and proceed with it as an expression (c.f. the top of the sources of solve and dsolve()).  I would imagine that a similar thing would be true for an inequality, only also carrying the Assume(…, Q.positive) or Assume(…, Q.nonnegative) object around with it, where … is the smaller side subtracted from the larger.  

By the way, another thing that I have noticed about inequalities, though it is only moderately annoying rather than show stopping, is that they do not always retain the order that they are initialized in:

In [9]: x > y
Out[9]: y < x

Internally, there should be little distinction between x > y and y < x, but for humans, reading inequalities can be much easier if they are in a certain order, which will be the order that the user enters them in.  

Aaron Meurer

Brian Granger

unread,
Feb 26, 2010, 11:25:25 PM2/26/10
to sy...@googlegroups.com
Aaron,

Thanks for the thoughtful replies.  As I have been looking at this further, I think the core question is this:  how are Sympy objects converted to bools?

Is seem that there are two notions:

* The primitive notion of truth built into python itself.  This is handled by Python in the __nonzero__ method.  The pattern followed in 90% of Sympy is that __nonzero__ returns True unless the object is Zero.  In this concept of truth is makes sense to ask the truth value of single objects:

bool(x)
bool(1)
etc.

* The mathematical idea related to whether or not a relational equation is satisfied:

1 < 0
x < y
2*x + y == 0

To me it seems that these two types of truth concepts should be *completely* orthogonal.  What are the consequences of this?

1. Converting a relational equation to a boolean using bool [bool(x<0)] is not asking the mathematical question "is x less than 0".  It is asking the the more primitive question of "is this a nonzero python object".

2. There must be a different API for testing if a relational equation is satisfied.  I don't see this separate interface.  Maybe it is the assumption system, but assumptions seem to be different conceptually than equations.  But I don't really understand how assumptions work in sympy.  Whatever this other API is, it needs to be capable of giving the following answers:

* It is satisfied
* It is not satisfied
* It is undetermined.

The current situation is lacking in two respects:

* The classes in relational (Equality, Inequality, StrictInequality, etc.) violate this clean separation of truth types by having __nonzero__ method that combines and confuses the two types.  You see things like this:

    def __nonzero__(self):
        if self.lhs.is_comparable and self.rhs.is_comparable:
            if self.lhs.is_Number and self.rhs.is_Number:
                return self.lhs < self.rhs
            return self.lhs.evalf()<self.rhs.evalf()
        return self.lhs.compare(self.rhs)==-1

* There is no standard interface for asking about the second (relational equation) type of truth.  This is probably why Relational classes are confusing the two.

I tried commenting out the __nonzero__ method in Relational, but a good number of tests fail - some with a RuntimeError saying the recursion limit has been reached. 

I am not sure how to proceed.  What is the most up to date description of the assumption system in sympy?  Is the new assumption system in place and the recommended approach.

Cheers,

Brian

Aaron S. Meurer

unread,
Feb 27, 2010, 2:31:53 AM2/27/10
to sy...@googlegroups.com
I'm still working out how the new assumptions work myself.  They are not fully merged in yet, so I don't know how much will actually work.  I guess you would just have to try it.  We may need to finish merging the assumptions in order to full sort out inequalities (ironically, we need assumptions to do inequalities, but we also need to ability to solve inequalities to have some kinds of assumptions).  

Yes, Ask(whatever, assumptions) will return True if whatever is true under assumptions, False if it is false, and None if it is undetermined.  Or at least it is supposed to, again, the system is not fully merged in yet, so there could be problems.  This is to become the standard interface.  I believe your concerns were one of the reasons for creating the new assumptions system in the first place.  

I agree that that __nonzero__ is a mess.  Based on your discussion below, I think the inequalities should automatically return True or False if it is number < number, and if it stays as an inequality class, then the bool should just be false (i.e., replace __nonzero__ with simply "return True").  

I think I figured out most of your problems.  See the attached patch for a demonstration (it needs cleaning up, and I only did StrictInequality).  See also the commit message.
0001-Basic-demonstrative-fixes-for-StrictInequality.patch

Brian Granger

unread,
Feb 27, 2010, 4:16:52 AM2/27/10
to sy...@googlegroups.com
Aaron,

Thanks for looking at this.

I'm still working out how the new assumptions work myself.  They are not fully merged in yet, so I don't know how much will actually work.  I guess you would just have to try it.  We may need to finish merging the assumptions in order to full sort out inequalities (ironically, we need assumptions to do inequalities, but we also need to ability to solve inequalities to have some kinds of assumptions).  
 
OK.  Hopefully others can fill in some details about the status of the new assumptions system.
 
Yes, Ask(whatever, assumptions) will return True if whatever is true under assumptions, False if it is false, and None if it is undetermined.  Or at least it is supposed to, again, the system is not fully merged in yet, so there could be problems.  This is to become the standard interface.  I believe your concerns were one of the reasons for creating the new assumptions system in the first place.  

Nice, that sounds like it should address this issue exactly.

I agree that that __nonzero__ is a mess.  Based on your discussion below, I think the inequalities should automatically return True or False if it is number < number, and if it stays as an inequality class, then the bool should just be false (i.e., replace __nonzero__ with simply "return True").  


Yes, __nonzero__ should just return True to be consistent with how it is handled elsewhere in sympy.  I also agree for numbers.  The only case the this doesn't cover is other object that have well defined < and > that should be simplified to True or False when compared.
 
I think I figured out most of your problems.  See the attached patch for a demonstration (it needs cleaning up, and I only did StrictInequality).  See also the commit message.


Nice, this will help me to get going.  If I have a chance this weekend I will continue the work you started and report back.

Thanks Aaron!

Cheers,

Brian
 
--
You received this message because you are subscribed to the Google Groups "sympy" group.
To post to this group, send email to sy...@googlegroups.com.
To unsubscribe from this group, send email to sympy+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/sympy?hl=en.



Someone else will need to answer your question on how far we are with the assumptions system.  I haven't been working on it.  

Aaron Meurer

jegerjensen

unread,
Mar 30, 2010, 6:26:25 AM3/30/10
to sympy
Am I right that you have concluded that bool() of a relational should
always return True?
That would allow some rather weird things:

In [1]: if x < y and x > y:
...: print "I would not expect to see this printed"

Today I get no output from this and it would surprise me to get
anything else.

Instead I propose that we just agree that bool(x<y) has nothing to do
with the mathematical relation, but will completely hand over the
relation to python for evaluation. (Isn't this the present
behavior?) If we accept this rule, Out[65] and Out[66] are not
inconsistent, but shows rather how python works internally. The
language reference states that "objects of different types always
compare unequal, and are ordered consistently but arbitrarily.".

If on the other hand you want to test the mathematical expression, you
should use ask() as you have discussed, or maybe a method like
Relational.is_valid().

Øyvind

> > sympy+un...@googlegroups.com <sympy%2Bunsu...@googlegroups.com>.

> ...
>
> les mer »

Aaron S. Meurer

unread,
Mar 30, 2010, 9:32:34 AM3/30/10
to sy...@googlegroups.com

On Mar 30, 2010, at 4:26 AM, jegerjensen wrote:

> Am I right that you have concluded that bool() of a relational should
> always return True?
> That would allow some rather weird things:
>
> In [1]: if x < y and x > y:
> ...: print "I would not expect to see this printed"
>

But why would you want to do this? "if x < y" doesn't make sense if x and y are symbols.

> Today I get no output from this and it would surprise me to get
> anything else.
>
> Instead I propose that we just agree that bool(x<y) has nothing to do
> with the mathematical relation, but will completely hand over the
> relation to python for evaluation.

But then we loose the shortcut x < y => Lt(x, y).

> (Isn't this the present
> behavior?) If we accept this rule, Out[65] and Out[66] are not
> inconsistent, but shows rather how python works internally. The
> language reference states that "objects of different types always
> compare unequal, and are ordered consistently but arbitrarily.
>

This will change in Python 3. There, objects have to be the same type to be compared.

Aaron Meurer

> To unsubscribe from this group, send email to sympy+un...@googlegroups.com.

Vinzent Steinberg

unread,
Mar 30, 2010, 12:32:17 PM3/30/10
to sympy
I think the definition of bool() is quite clear, it should be always
True, except for the zero case. bool() is not a mathematical function,
it just returns True or False. If you want, it converts any Python
object to a bit. It should be rarely if at all used in a CAS context.

Vinzent

jegerjensen

unread,
Mar 30, 2010, 12:52:35 PM3/30/10
to sympy
> > In [1]: if x < y and x > y:
> >   ...:     print "I would not expect to see this printed"
>
> But why would you want to do this?  "if x < y" doesn't make sense if x and y are symbols.

Because it could make sense to compare python objects that way. (but
see below)

> But then we loose the shortcut x < y => Lt(x, y).

Actually no, x < y can return Lt(x, y). The comparison is only handed
over to python when you try to evaluate it with bool(Lt(x, y)). (I
think I am only describing how things work today, but from a different
perspective.)

> This will change in Python 3. There, objects have to be the same type to be compared.

Oh, that changes the situation. The future python programmer will not
expect the comparison operators to apply everywhere, so then I can
understand your point that "x < y doesn't make sense if x and y are
symbols."

But still, it would be easy to work with a rule that says that
bool(Relational(...)) hands over the
evaluation to python. This would mean that "if x < y:" acts like the
comparison python would normally do, and "if ask(x < y, Assume(x-
y,Q.whatever):" tries to determine the mathematical question.

I think the central point in my objection is that if you write a piece
of python code using the sympy libraries, it would be very confusing
if some comparisons turn out to be always True. Because of this, I
suggest that relational objects should not simply return True to
indicate that they are ``nonzero''. Since the mathematical problem
can be non-trivial or impossible to determine, that is not an option,
so instead the comparisons should be handed over to python, completely
transparent to the user.

All that is needed if we go for this solution is a section in the
"gotchas and pitfalls" explaining what the user should do to evaluate
a mathematical inequality, and what "if x<y:" really means.

Øyvind

Brian Granger

unread,
Mar 30, 2010, 1:43:34 PM3/30/10
to sy...@googlegroups.com
All,

I should clarify the current state of things because I am the one who
last touched the code in this respect.

* There is now logic in Relational that tries to actually evaluate the
truth value of a Relational when it is constructed. Thus you get the
following:

In [20]: Lt(0,2)
Out[20]: True

In [21]: Lt(2,0)
Out[21]: False

This uses the new _eval_relation to and is a mathematical test.

* When Relationals are symbolic, if you do .subs on them, you can get
to a bool. Again, this result has a mathematical meaning:

In [22]: Lt(x,0).subs(x,-1)
Out[22]: True

In [23]: Lt(x,0).subs(x,1)
Out[23]: False

* The only time you access the other meaning (the more primitive
Python one) is if you *cast* a Relational to a bool:

In [24]: bool(Lt(x,y))
Out[24]: True

But currently, you don't always get true:

In [25]: bool(Lt(x,0))
Out[25]: False

The reason is that the __nonzero__ methods of different Relational
subclasses use the .compare method in different ways. I didn't write
this code, BUT I seem to remember that many tests fail if you simply
have __nonzero__ return True. I didn't investigate why, but my guess
is that this is used to order different Relationals in expressions.

* Doing the following is dangerous:

if x < y:
...

The reason is that depending on what x and y are, you may be getting
back the primitive or Mathematical version of less than.

* There is another inconsistency in that == and != don't return
Relational subclasses, but <, >, <= and >= do.

* Some of this changes with python 3, so whatever we do, we need to
move towards a solutions that won't change the user interface of sympy
when we transition to python 3.

Here is what I propose:

Someone (unfortunately I don't have time right now, I may have time
later in the quarter) should go through and write a summary
(preferably in the dev section of the Sphinx docs) that summarizes:

* How Python 2 and 3 handle object equality, comparisons, etc. This
includes cmp, bool, all the __lt__ methods, __nonzero__, etc.

* How we currently handle mathematical truth in sympy. This is mostly
focused on the math of the Relational classes and how they are
constructed (x<y shorthand for Lt(x,y)).

* How we currently handle the more primitive type of truth in sympy.
This is relevant in both casting sympy objects to bool AND how we
order sympy objects in expressions.

* A proposal of how to handle all of this in a uniform and consistent
manner in both python 2 and 3.

I think we should do this before making any major changes to how all
this is handled. Otherwise, we are going chase our tails around and
go crazy.

Cheers,

Brian

> To unsubscribe from this group, send email to sympy+un...@googlegroups.com.

Brian Granger

unread,
Mar 30, 2010, 1:56:44 PM3/30/10
to sy...@googlegroups.com
On Tue, Mar 30, 2010 at 3:26 AM, jegerjensen <jensen...@gmail.com> wrote:
> Am I right that you have concluded that bool() of a relational should
> always return True?

The problem with them always returning True, is that this behavior is
implemented in the __nonzero__ method of Relational. This method is
also used to order Relationals in expressions. Thus, it needs to
return 1, 0, -1 to allow that ordering.

> That would allow some rather weird things:
>
> In [1]: if x < y and x > y:
>   ...:     print "I would not expect to see this printed"

With our current code base, I would not ever do this. The reason (see
my other email) is that x<y can either be a bool that represents
mathematical < OR bool(Lt()) which is is the more primitive type of
truth.

For now I would do:
e = x<y
if isinstance(e, Relational):
...
elif isinstance(e, bool):
...

A pain, but until we decide what to do with all of this, this is the
safe thing to do.

Cheers,

Brian

> To unsubscribe from this group, send email to sympy+un...@googlegroups.com.

Vinzent Steinberg

unread,
Mar 31, 2010, 4:58:11 PM3/31/10
to sympy
On Mar 30, 6:52 pm, jegerjensen <jensen.oyv...@gmail.com> wrote:
> > > In [1]: if x < y and x > y:
> > >   ...:     print "I would not expect to see this printed"
>
> > But why would you want to do this?  "if x < y" doesn't make sense if x and y are symbols.
>
> Because it could make sense to compare python objects that way.  (but
> see below)
>
> > But then we loose the shortcut x < y => Lt(x, y).
>
> Actually no, x < y can return Lt(x, y).  The comparison is only handed
> over to python when you try to evaluate it with bool(Lt(x, y)).  (I
> think I am only describing how things work today, but from a different
> perspective.)
>
> > This will change in Python 3.  There, objects have to be the same type to be compared.
>
> Oh, that changes the situation. The future python programmer will not
> expect the comparison operators to apply everywhere, so then I can
> understand your point that "x < y doesn't make sense if x and y are
> symbols."
>
> But still, it would be easy to work with a rule that says that
> bool(Relational(...)) hands over the
> evaluation to python.  This would mean that "if x < y:" acts like the
> comparison python would normally do, and "if ask(x < y, Assume(x-
> y,Q.whatever):" tries to determine the mathematical question.

I strongly discourage the usage of bool() for such things, you should
rather use a method, like .doit() or something. It confuses the math's
and python's concept of truth.

You could have code like

if x:
do something

which tests for example if x was defined, so it should just return
True (python's concept). But mathematically, rather None should be
returned, because we do not know, whether the symbol is True or not.

And it is not even technically possible, because ask() would quite
often return None, which can not be returned by bool().


> I think the central point in my objection is that if you write a piece
> of python code using the sympy libraries, it would be very confusing
> if some comparisons turn out to be always True.  Because of this, I
> suggest that relational objects should not simply return True to
> indicate that they are ``nonzero''.  Since the mathematical problem
> can be non-trivial or impossible to determine, that is not an option,
> so instead the comparisons should be handed over to python, completely
> transparent to the user.

In my opinion they should just return True, and the comparison should
not be abused. Currently it's usage is not very transparent I think.
Maybe it should be better completely disabled.

>
> All that is needed if we go for this solution is a section in the
> "gotchas and pitfalls" explaining what the user should do to evaluate
> a mathematical inequality, and what "if x<y:" really means.

The operators '==' and '!=' are strictly necessary in a non-
mathematical way. Currently the relational operators are used
mathematically. If want to be strictly consistent, we have to remove
them. Also, the only work for real symbols, not for general symbols.
This can be a nice shortcut, but it is dangerous if you confuse them
with the non-mathematical operators.

Also, should (x < y) raise an exception when x and y are not real or
automatically create such assumptions?

Vinzent

Vinzent Steinberg

unread,
Mar 31, 2010, 5:02:44 PM3/31/10
to sympy
On Mar 30, 7:43 pm, Brian Granger <ellisonbg....@gmail.com> wrote:
> The reason is that the __nonzero__ methods of different Relational
> subclasses use the .compare method in different ways.  I didn't write
> this code, BUT I seem to remember that many tests fail if you simply
> have __nonzero__ return True.  I didn't investigate why, but my guess
> is that this is used to order different Relationals in expressions.

This is imho a bug that should be fixed. Such things should not be
used for ordering, this is not a clean approach and this should also
be fixed.

Vinzent

jegerjensen

unread,
Apr 1, 2010, 8:10:59 AM4/1/10
to sympy
Brian and Vincent,
Thanks for explaining.

If I understand you correctly, the decision is that a>b should only be
used in the mathematical meaning. I have also learned (from Vincent I
think) that cmp(a,b) will be removed from python 3. What I don't
understand then is how Sympy objects can be sorted? If __lt__,
__gt___, etc. are overloaded to construct objects, and __cmp__ is
deprecated, what is the plan for canonical ordering?

Øyvind

On 31 Mar, 23:02, Vinzent Steinberg <vinzent.steinb...@googlemail.com>
wrote:

Aaron S. Meurer

unread,
Apr 1, 2010, 9:51:20 AM4/1/10
to sy...@googlegroups.com
Python 3 removes canonical ordering of different types. See http://docs.python.org/py3k/whatsnew/3.0.html#ordering-comparisons

I personally don't see any need to do this, but if you *really* need it, you could always do:

sorted(list_of_sympy_objects, key=hash)

and it will be canonical, at least on the same machine.

Aaron Meurer

Reply all
Reply to author
Forward
0 new messages