Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added functionality to Q.real and Q.imaginary and fixed issue:2324 #1156

Merged
merged 15 commits into from Nov 28, 2012
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 55 additions & 12 deletions sympy/assumptions/handlers/sets.py
Expand Up @@ -3,6 +3,7 @@
"""
from sympy.assumptions import Q, ask
from sympy.assumptions.handlers import CommonHandler
from sympy import I


class AskIntegerHandler(CommonHandler):
Expand Down Expand Up @@ -191,7 +192,7 @@ class AskRealHandler(CommonHandler):

@staticmethod
def _number(expr, assumptions):
return not expr.as_real_imag()[1]
return not expr.as_real_imag()[1].evalf()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this change made? Can you evalf to a lower precision, say 2, and still get the answer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smichr sorry for the late response, could you please explain what you mean by lower precision. Just to explain why I have added evalf is for the case of ask(Q.real((-1)**I))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you just seeing if the imaginary part is zero or not? might .evalf(2) work? evalf() will compute to 15 digits, or something like that. So if the answer involves a complicated imaginary part there is no need to calculate it to high precision just to learn that it is not zero.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I am checking whether its imaginary or not, sure I will reduce the precision to 2.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something you have to be careful of is expressions that may not be zero but have no significance in their evaluation. It's been a while, but if you look at my work in round or int (and commits added around that time by me) you will see me returning None when the answer is calculated without precision. I think I put some notes in the commit messages about that. Have you used gitk to browse the commit log? It's really nice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. Commits dealing with equals, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a pertinent piece of code:


    @property
    def is_comparable(self):
        is_real = self.is_real
        if is_real is False:
            return False
        is_number = self.is_number
        if is_number is False:
            return False
        if is_real and is_number:
            return True
        n, i = self.evalf(2).as_real_imag()
        if not i.is_Number or not n.is_Number:
            return False
        if i:
            if i._prec != 1:
                return False
        return n._prec != 1

A problem with this, however, is that for q=(-1)**I q.is_real comes back as False. That's a bug. Otherwise, the idea from the n, i = part that tries to be careful about reaching a conclusion. If the precision is 1 then you don't know what the number is and in the assumption system, you should return None in that case, not True or False as is done in the above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You asked whether it makes sense to return None...not if you can figure out what sort of number you are working with. But not all expressions that are zero can be, with a given piece of code, be proved to be zero. And we shouldn't use a numerical approximation to guess that it is zero. It might just be an expression with a very small value. In the code above, if we know something is a number, we try to evaluate it to 2 digits of precision. But if we only get 1 -- and it doesn't matter if we had attempted more, we would still get 1 -- it's because the evaluation system couldn't be sure that that value was or wasn't zero. For most non-pathological cases, the above is going to return True or False. It's going to have trouble when it gets an expression where the imaginary part is a zero that can't be proved to be so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the information, I will go through my code and merge the pull made by you, test it and will get back to you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, issue 3068 has some examples where difficulties in evalf'ing are encountered.

http://code.google.com/p/sympy/issues/detail?id=3068


@staticmethod
def Add(expr, assumptions):
Expand Down Expand Up @@ -230,21 +231,32 @@ def Pow(expr, assumptions):
Positive**Real -> Real
Real**(Integer/Even) -> Real if base is nonnegative
Real**(Integer/Odd) -> Real
Real**Imaginary -> ?
Imaginary**Real -> ?
Real**Real -> ?
"""
if expr.is_number:
return AskRealHandler._number(expr, assumptions)
if ask(Q.real(expr.base), assumptions):
if ask(Q.integer(expr.exp), assumptions):
return True
elif expr.exp.is_Rational:
if (expr.exp.q % 2 == 0):
return ask(Q.real(expr.base), assumptions) and \
not ask(Q.negative(expr.base), assumptions)
else:
if ask(Q.imaginary(expr.base), assumptions):
if ask(Q.real(expr.exp), assumptions):
if ask(Q.odd(expr.exp), assumptions):
return False
elif ask(Q.even(expr.exp), assumptions):
return True
elif ask(Q.real(expr.base), assumptions):
if ask(Q.imaginary(expr.exp), assumptions):
if expr.base == 1 or expr.base == -1:
return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is not covered with a test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smichr they are covered in the tests 1. ask(Q.real(I3)) and 2. ask(@.real(I2)) etc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was basing my comment on a coverage report. So unless I mistook this line for another, at the time I wrote the comment, it wasn't covered. This might mean that an expression you thought you were testing auto-evaluated to something else and didn't hit the line of code you thought it would. Perhaps ask(Q.real(Pow(I, 3, evaluate=False))) is necessary, for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's for the lines 248 and 249, they are already been covered in 238 and 239, I have made necessary changes and removed unwanted code

elif ask(Q.real(expr.exp), assumptions):
if ask(Q.positive(expr.base), assumptions):
if expr.exp.is_Rational and \
ask(Q.even(expr.exp.q), assumptions):
return ask(Q.positive(expr.base), assumptions)
elif ask(Q.integer(expr.exp), assumptions):
return True
elif ask(Q.positive(expr.base), assumptions):
return True
elif ask(Q.negative(expr.base), assumptions):
return False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is not covered with a test


@staticmethod
def Rational(expr, assumptions):
Expand Down Expand Up @@ -422,7 +434,7 @@ class AskImaginaryHandler(CommonHandler):
@staticmethod
def _number(expr, assumptions):
# helper method
return not expr.as_real_imag()[0]
return not expr.as_real_imag()[0].evalf()

@staticmethod
def Add(expr, assumptions):
Expand Down Expand Up @@ -468,7 +480,38 @@ def Mul(expr, assumptions):
return False
return result

Pow = Add
@staticmethod
def Pow(expr, assumptions):
"""
Imaginary**integer -> Imaginary if integer % 2 == 1
Imaginary**integer -> real if integer % 2 == 0
Imaginary**Imaginary -> ?
Imaginary**Real -> ?
"""
if expr.is_number:
return AskImaginaryHandler._number(expr, assumptions)
if ask(Q.imaginary(expr.base), assumptions):
if ask(Q.real(expr.exp), assumptions):
if ask(Q.odd(expr.exp), assumptions):
return True
elif ask(Q.even(expr.exp), assumptions):
return False
elif ask(Q.real(expr.base), assumptions):
if ask(Q.imaginary(expr.exp), assumptions):
if expr.base == 1 or expr.base == -1:
return False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not covered with a test

elif ask(Q.real(expr.exp), assumptions):
if expr.exp.is_Rational and \
ask(Q.even(expr.exp.q), assumptions):
return ask(Q.negative(expr.base),assumptions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not covered with a test

elif ask(Q.integer(expr.exp), assumptions):
return False
elif ask(Q.positive(expr.base), assumptions):
return False
elif ask(Q.negative(expr.base), assumptions):
return True



@staticmethod
def Number(expr, assumptions):
Expand Down
86 changes: 86 additions & 0 deletions sympy/assumptions/tests/test_query.py
Expand Up @@ -467,6 +467,54 @@ def test_I():
assert ask(Q.hermitian(z)) is False
assert ask(Q.antihermitian(z)) is False

z = I**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True

z = (-I)**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True

z = (3*I)**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is False

z = (1)**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True

z = (-1)**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True

z = (1+I)**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is False

z = (I)**(I+3)
assert ask(Q.imaginary(z)) is True
assert ask(Q.real(z)) is False

z = (I)**(I+2)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True

z = (I)**(2)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True

z = (I)**(3)
assert ask(Q.imaginary(z)) is True
assert ask(Q.real(z)) is False

z = (3)**(I)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is False

z = (I)**(0)
assert ask(Q.imaginary(z)) is False
assert ask(Q.real(z)) is True


@slow
def test_bounded():
Expand Down Expand Up @@ -965,6 +1013,16 @@ def test_bounded_xfail():
assert ask(Q.bounded(cos(x)**x)) is True
assert ask(Q.bounded(sin(x) ** x)) is True

@XFAIL
def test_imaginary_xfail():
assert ask(Q.imaginary(0**I)) is None
assert ask(Q.imaginary(0**(-I))) is None

@XFAIL
def test_real_xfail():
assert ask(Q.real(0**I)) is None
assert ask(Q.real(0**(-I))) is None


def test_commutative():
"""By default objects are Q.commutative that is why it returns True
Expand Down Expand Up @@ -1268,13 +1326,31 @@ def test_imaginary():
assert ask(Q.imaginary(I*x), Q.real(x)) is True
assert ask(Q.imaginary(I*x), Q.imaginary(x)) is False
assert ask(Q.imaginary(I*x), Q.complex(x)) is None
assert ask(Q.imaginary(I**x), Q.negative(x)) is None
assert ask(Q.imaginary(I**x), Q.positive(x)) is None
assert ask(Q.imaginary(I**x), Q.even(x)) is False
assert ask(Q.imaginary(I**y), Q.odd(y)) is True
assert ask(Q.imaginary(x*y), Q.imaginary(x) & Q.real(y)) is True

assert ask(Q.imaginary(x + y + z), Q.real(x) & Q.real(y) & Q.real(z)) is False
assert ask(Q.imaginary(x + y + z),
Q.real(x) & Q.real(y) & Q.imaginary(z)) is None
assert ask(Q.imaginary(x + y + z),
Q.real(x) & Q.imaginary(y) & Q.imaginary(z)) is False
assert ask(Q.imaginary(x**0), Q.imaginary(x)) is False
assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.imaginary(y)) is None
assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.real(y)) is None
assert ask(Q.imaginary(x**y), Q.real(x) & Q.imaginary(y)) is None
assert ask(Q.imaginary(x**y), Q.real(x) & Q.real(y)) is None
assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.integer(y)) is None
assert ask(Q.imaginary(x**y), Q.imaginary(y) & Q.integer(x)) is None
assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.odd(y)) is True
assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.rational(y)) is None
assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.even(y)) is False
assert ask(Q.imaginary(x**y), Q.real(x) & Q.integer(y)) is False
assert ask(Q.imaginary(x**y), Q.positive(x) & Q.real(y)) is False
assert ask(Q.imaginary(x**y), Q.negative(x) & Q.real(y)) is True
assert ask(Q.imaginary(x**y), Q.integer(x) & Q.imaginary(y)) is None


def test_infinitesimal():
Expand Down Expand Up @@ -1492,6 +1568,16 @@ def test_real():
assert ask(Q.real(x**y), Q.real(x) & Q.integer(y)) is True
assert ask(Q.real(x**y), Q.real(x) & Q.real(y)) is None
assert ask(Q.real(x**y), Q.positive(x) & Q.real(y)) is True
assert ask(Q.real(x**y), Q.imaginary(x) & Q.imaginary(y)) is None
assert ask(Q.real(x**y), Q.imaginary(x) & Q.real(y)) is None
assert ask(Q.real(x**y), Q.real(x) & Q.imaginary(y)) is None
assert ask(Q.real(x**0), Q.imaginary(x)) is True
assert ask(Q.real(x**y), Q.real(x) & Q.integer(y)) is True
assert ask(Q.real(x**y), Q.positive(x) & Q.real(y)) is True
assert ask(Q.real(x**y), Q.real(x) & Q.rational(y)) is None
assert ask(Q.real(x**y), Q.imaginary(x) & Q.integer(y)) is None
assert ask(Q.real(x**y), Q.imaginary(x) & Q.odd(y)) is False
assert ask(Q.real(x**y), Q.imaginary(x) & Q.even(y)) is True

# trigonometric functions
assert ask(Q.real(sin(x))) is None
Expand Down