Skip to content

Commit

Permalink
Merge pull request sympy#1990 from smichr/isnumber
Browse files Browse the repository at this point in the history
is_number docstrings updated; equals and is_constant modified
  • Loading branch information
smichr committed Apr 9, 2013
2 parents e639afb + 3f17de8 commit 73d90bf
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 52 deletions.
4 changes: 2 additions & 2 deletions sympy/concrete/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ def is_number(self):
"""
Return True if the Product will result in a number, else False.
sympy considers anything that will result in a number to have
is_number == True.
Examples
========
>>> from sympy import log, Product
>>> from sympy.abc import x, y, z
Expand Down
10 changes: 3 additions & 7 deletions sympy/concrete/summations.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,14 @@ def is_number(self):
"""
Return True if the Sum will result in a number, else False.
sympy considers anything that will result in a number to have
is_number == True.
>>> from sympy import log
>>> log(2).is_number
True
Sums are a special case since they contain symbols that can
be replaced with numbers. Whether the integral can be done or not is
another issue. But answering whether the final result is a number is
not difficult.
Examples
========
>>> from sympy import Sum
>>> from sympy.abc import x, y
>>> Sum(x, (y, 1, x)).is_number
Expand Down
31 changes: 17 additions & 14 deletions sympy/core/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,28 +560,31 @@ def is_hypergeometric(self, k):

@property
def is_number(self):
"""Returns ``True`` if 'self' is a number.
"""Returns ``True`` if 'self' contains no free symbols.
>>> from sympy import log, Integral
>>> from sympy.abc import x, y
>>> x.is_number
False
>>> (2*x).is_number
False
>>> (2 + log(2)).is_number
True
>>> (2 + Integral(2, x)).is_number
False
>>> (2 + Integral(2, (x, 1, 2))).is_number
True
See Also
========
is_comparable
sympy.core.expr.is_number
"""
# should be overriden by subclasses
return False

@property
def is_comparable(self):
"""Return True if self can be computed to a real number
with precision, else False.
Examples
========
>>> from sympy import exp_polar, pi, I
>>> (I*exp_polar(I*pi/2)).is_comparable
True
>>> (I*exp_polar(I*pi*2)).is_comparable
False
"""
is_real = self.is_real
if is_real is False:
return False
Expand Down
100 changes: 82 additions & 18 deletions sympy/core/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,21 +291,26 @@ def _from_mpmath(x, prec):

@property
def is_number(self):
"""Returns True if 'self' is a number.
"""Returns True if 'self' has no free symbols.
It will be faster than `if not self.free_symbols`, however, since
`is_number` will fail as soon as it hits a free symbol.
>>> from sympy import log, Integral
>>> from sympy.abc import x, y
Examples
========
>>> x.is_number
False
>>> (2*x).is_number
False
>>> (2 + log(2)).is_number
True
>>> (2 + Integral(2, x)).is_number
False
>>> (2 + Integral(2, (x, 1, 2))).is_number
True
>>> from sympy import log, Integral
>>> from sympy.abc import x, y
>>> x.is_number
False
>>> (2*x).is_number
False
>>> (2 + log(2)).is_number
True
>>> (2 + Integral(2, x)).is_number
False
>>> (2 + Integral(2, (x, 1, 2))).is_number
True
"""
if not self.args:
Expand Down Expand Up @@ -496,6 +501,9 @@ def is_constant(self, *wrt, **flags):

# simplify unless this has already been done
if simplify:
self = self.as_content_primitive()[1]
if self.is_commutative:
self = self.cancel()
self = self.simplify()

# is_zero should be a quick assumptions check; it can be wrong for
Expand Down Expand Up @@ -560,6 +568,10 @@ def equals(self, other, failing_expression=False):
used to return True or False.
"""
from sympy.simplify.simplify import nsimplify, simplify
from sympy.solvers.solvers import solve
from sympy.polys.polyerrors import NotAlgebraic
from sympy.polys.numberfields import minimal_polynomial

other = sympify(other)
if self == other:
Expand All @@ -570,26 +582,78 @@ def equals(self, other, failing_expression=False):
# because if the expression ever goes to 0 then the subsequent
# simplification steps that are done will be very fast.
diff = (self - other).as_content_primitive()[1]
if diff.is_commutative:
diff = diff.cancel()
diff = factor_terms(diff.simplify(), radical=True)

if not diff:
return True

if all(f.is_Atom for m in Add.make_args(diff)
for f in Mul.make_args(m)):
if not diff.has(Add):
# if there is no expanding to be done after simplifying
# then this can't be a zero
return False

constant = diff.is_constant(simplify=False, failing_number=True)
if constant is False or \
not diff.free_symbols and not diff.is_number:

if constant is False:
return False
elif constant is True:

if constant is None and (diff.free_symbols or not diff.is_number):
# e.g. unless the right simplification is done, a symbolic
# zero is possible (see expression of issue 3730: without
# simplification constant will be None).
return

if constant is True:
ndiff = diff._random()
if ndiff:
return False

# sometimes we can use a simplified result to give a clue as to
# what the expression should be; if the expression is *not* zero
# then we should have been able to compute that and so now
# we can just consider the cases where the approximation appears
# to be zero -- we try to prove it via minimal_polynomial.
if diff.is_number:
approx = diff.nsimplify()
if not approx:
# try to prove via self-consistency
surds = [s for s in diff.atoms(Pow) if s.args[0].is_Integer]
# it seems to work better to try big ones first
surds.sort(key=lambda x: -x.args[0])
for s in surds:
try:
# simplify is False here -- this expression has already
# been identified as being hard to identify as zero;
# we will handle the checking ourselves using nsimplify
# to see if we are in the right ballpark or not and if so
# *then* the simplification will be attempted.
sol = solve(diff, s, check=False, simplify=False)
if sol:
if s in sol:
return True
if any(nsimplify(si, [s]) == s and simplify(si) == s
for si in sol):
return True
except NotImplementedError:
pass

# try to prove with minimal_polynomial but know when
# *not* to use this or else it can take a long time.
# Pernici noted the following:
# >>> q = -73*sqrt(3) + 1 + 128*sqrt(5) + 1315*sqrt(2)
# >>> p = expand(q**3)**Rational(1, 3)
# >>> minimal_polynomial(p - q) # hangs for at least 15 minutes
if False: # change False to condition that assures non-hang
try:
mp = minimal_polynomial(diff)
if mp.is_Symbol:
return True
return False
except NotAlgebraic:
pass

# diff has not simplified to zero; constant is either None, True
# or the number with significance (prec != 1) that was randomly
# calculated twice as the same value.
Expand Down
40 changes: 39 additions & 1 deletion sympy/core/tests/test_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Piecewise, Mul, Pow, nsimplify, ratsimp, trigsimp, radsimp, powsimp,
simplify, together, collect, factorial, apart, combsimp, factor, refine,
cancel, Tuple, default_sort_key, DiracDelta, gamma, Dummy, Sum, E,
exp_polar, Lambda)
exp_polar, Lambda, expand)
from sympy.core.function import AppliedUndef
from sympy.physics.secondquant import FockState
from sympy.physics.units import meter
Expand Down Expand Up @@ -1414,6 +1414,10 @@ def test_equals():
assert (sqrt(5) + pi).equals(0) is False
assert meter.equals(0) is False
assert (3*meter**2).equals(0) is False
eq = -(-1)**(S(3)/4)*6**(S(1)/4) + (-6)**(S(1)/4)*I
if eq != 0: # if canonicalization makes this zero, skip the test
assert eq.equals(0)
assert sqrt(x).equals(0) is False

# from integrate(x*sqrt(1+2*x), x);
# diff is zero only when assumptions allow
Expand All @@ -1429,6 +1433,40 @@ def test_equals():
assert diff.subs(x, p).equals(0) is True
assert diff.subs(x, -1).equals(0) is True

# prove via minimal_polynomial or self-consistency
eq = sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) - sqrt(10 + 6*sqrt(3))
assert eq.equals(0)
q = 3**Rational(1, 3) + 3
p = expand(q**3)**Rational(1, 3)
assert (p - q).equals(0)

# issue 3730
# eq = q*x + q/4 + x**4 + x**3 + 2*x**2 - S(1)/3
# z = eq.subs(x, solve(eq, x)[0])
q = symbols('q')
z = (q*(-sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/12)/2 - sqrt((2*q - S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 -
S(2197)/13824)**(S(1)/3) - S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 -
S(2197)/13824)**(S(1)/3) - S(13)/6)/2 - S(1)/4) + q/4 + (-sqrt(-2*(-(q
- S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) - S(13)/12)/2 - sqrt((2*q
- S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/6)/2 - S(1)/4)**4 + (-sqrt(-2*(-(q - S(7)/8)**S(2)/8 -
S(2197)/13824)**(S(1)/3) - S(13)/12)/2 - sqrt((2*q -
S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/6)/2 - S(1)/4)**3 + 2*(-sqrt(-2*(-(q - S(7)/8)**S(2)/8 -
S(2197)/13824)**(S(1)/3) - S(13)/12)/2 - sqrt((2*q -
S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S(1)/3) -
S(13)/6)/2 - S(1)/4)**2 - S(1)/3)
assert z.equals(0)

# SELF UPDATING CODE TEST
# If this tests fails then the cancel in line 581 of expr.py
# can be deleted and then below, "!= 0" -> "is S.Zero"
assert simplify(z) != 0


def test_random():
from sympy import posify, lucas
Expand Down
27 changes: 24 additions & 3 deletions sympy/functions/elementary/tests/test_complexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,25 @@ def test_sign():
assert sign(nz)**2 == 1
assert (sign(nz)**3).args == (sign(nz), 3)

# evaluate what can be evaluated
assert sign(exp_polar(I*pi)*pi) is S.NegativeOne

x, y = Symbol('x', real=True), Symbol('y')
assert sign(x).rewrite(Piecewise) == \
Piecewise((1, x > 0), (-1, x < 0), (0, True))
assert sign(y).rewrite(Piecewise) == sign(y)

# evaluate what can be evaluated
assert sign(exp_polar(I*pi)*pi) is S.NegativeOne

eq = -sqrt(10 + 6*sqrt(3)) + sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3))
# if there is a fast way to know when and when you cannot prove an
# expression like this is zero then the equality to zero is ok
assert sign(eq).func is sign or sign(eq) == 0
# but sometimes it's hard to do this so it's better not to load
# abs down with tests that will be very slow
q = 1 + sqrt(2) - 2*sqrt(3) + 1331*sqrt(6)
p = expand(q**3)**Rational(1, 3)
d = p - q
assert sign(d).func is sign or sign(d) == 0


def test_as_real_imag():
n = pi**1000
Expand Down Expand Up @@ -299,6 +310,16 @@ def test_Abs():
x = Symbol('x', imaginary=True)
assert Abs(x).diff(x) == -sign(x)

eq = -sqrt(10 + 6*sqrt(3)) + sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3))
# if there is a fast way to know when and when you cannot prove an
# expression like this is zero then the equality to zero is ok
assert abs(eq).func is Abs or abs(eq) == 0
# but sometimes it's hard to do this so it's better not to load
# abs down with tests that will be very slow
q = 1 + sqrt(2) - 2*sqrt(3) + 1331*sqrt(6)
p = expand(q**3)**Rational(1, 3)
d = p - q
assert abs(d).func is Abs or abs(d) == 0

def test_Abs_rewrite():
x = Symbol('x', real=True)
Expand Down
10 changes: 3 additions & 7 deletions sympy/integrals/integrals.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,14 @@ def is_number(self):
"""
Return True if the Integral will result in a number, else False.
sympy considers anything that will result in a number to have
is_number == True.
>>> from sympy import log
>>> log(2).is_number
True
Integrals are a special case since they contain symbols that can
be replaced with numbers. Whether the integral can be done or not is
another issue. But answering whether the final result is a number is
not difficult.
Examples
========
>>> from sympy import Integral
>>> from sympy.abc import x, y
>>> Integral(x).is_number
Expand Down

0 comments on commit 73d90bf

Please sign in to comment.