diff --git a/sympy/concrete/products.py b/sympy/concrete/products.py index 7b1ab1078923..9e329fa600cd 100644 --- a/sympy/concrete/products.py +++ b/sympy/concrete/products.py @@ -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 diff --git a/sympy/concrete/summations.py b/sympy/concrete/summations.py index 67777627da54..185369d9b6a0 100644 --- a/sympy/concrete/summations.py +++ b/sympy/concrete/summations.py @@ -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 diff --git a/sympy/core/basic.py b/sympy/core/basic.py index 0ed849cb6c8a..989350a0241f 100644 --- a/sympy/core/basic.py +++ b/sympy/core/basic.py @@ -560,21 +560,12 @@ 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 @@ -582,6 +573,18 @@ def is_number(self): @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 diff --git a/sympy/core/expr.py b/sympy/core/expr.py index c619e1b95f59..19da93c23844 100644 --- a/sympy/core/expr.py +++ b/sympy/core/expr.py @@ -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: @@ -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 @@ -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: @@ -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. diff --git a/sympy/core/tests/test_expr.py b/sympy/core/tests/test_expr.py index e3ce3ab17327..3c6b983dedd1 100644 --- a/sympy/core/tests/test_expr.py +++ b/sympy/core/tests/test_expr.py @@ -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 @@ -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 @@ -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 diff --git a/sympy/functions/elementary/tests/test_complexes.py b/sympy/functions/elementary/tests/test_complexes.py index 74624ca3ea52..cbeb0cccebeb 100644 --- a/sympy/functions/elementary/tests/test_complexes.py +++ b/sympy/functions/elementary/tests/test_complexes.py @@ -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 @@ -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) diff --git a/sympy/integrals/integrals.py b/sympy/integrals/integrals.py index 2b9ed78d181d..3e7e20eba89d 100644 --- a/sympy/integrals/integrals.py +++ b/sympy/integrals/integrals.py @@ -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