Repository: sympy/sympy

Issue:
Should polygamma(a, b) raise NotImplementedError for noninteger (numeric) a?
mpmath's implementation of polygamma is valid only for integer first arguments. SymPy's implementation does not care, but this leads to many issues if you actually try to do things with such objects:

```
>>> (I*polygamma(I, pi)).as_real_imag()
Traceback (most recent call last):
  File "/home/eward/se2/sympy/core/assumptions.py", line 262, in getit
    return self._assumptions[fact]
KeyError: 'zero'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/eward/se2/sympy/core/assumptions.py", line 262, in getit
    return self._assumptions[fact]
KeyError: 'finite'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/eward/se2/sympy/core/assumptions.py", line 262, in getit
    return self._assumptions[fact]
KeyError: 'extended_positive'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/eward/se2/sympy/core/evalf.py", line 1308, in evalf
    rf = evalf_table[x.func]
KeyError: polygamma

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/eward/se2/sympy/core/mul.py", line 798, in as_real_imag
    if i.is_zero:
  File "/home/eward/se2/sympy/core/assumptions.py", line 266, in getit
    return _ask(fact, self)
  File "/home/eward/se2/sympy/core/assumptions.py", line 321, in _ask
    _ask(pk, obj)
  File "/home/eward/se2/sympy/core/assumptions.py", line 309, in _ask
    a = evaluate(obj)
  File "/home/eward/se2/sympy/core/expr.py", line 864, in _eval_is_negative
    finite = self.is_finite
  File "/home/eward/se2/sympy/core/assumptions.py", line 266, in getit
    return _ask(fact, self)
  File "/home/eward/se2/sympy/core/assumptions.py", line 321, in _ask
    _ask(pk, obj)
  File "/home/eward/se2/sympy/core/assumptions.py", line 309, in _ask
    a = evaluate(obj)
  File "/home/eward/se2/sympy/core/expr.py", line 857, in _eval_is_positive
    extended_positive = self.is_extended_positive
  File "/home/eward/se2/sympy/core/assumptions.py", line 266, in getit
    return _ask(fact, self)
  File "/home/eward/se2/sympy/core/assumptions.py", line 309, in _ask
    a = evaluate(obj)
  File "/home/eward/se2/sympy/core/expr.py", line 882, in _eval_is_extended_positive
    n2 = self._eval_evalf(2)
  File "/home/eward/se2/sympy/core/function.py", line 565, in _eval_evalf
    args = [arg._to_mpmath(prec + 5) for arg in self.args]
  File "/home/eward/se2/sympy/core/function.py", line 565, in <listcomp>
    args = [arg._to_mpmath(prec + 5) for arg in self.args]
  File "/home/eward/se2/sympy/core/evalf.py", line 1489, in _to_mpmath
    re, im, _, _ = evalf(self, prec, {})
  File "/home/eward/se2/sympy/core/evalf.py", line 1314, in evalf
    xe = x._eval_evalf(prec)
  File "/home/eward/se2/sympy/core/function.py", line 590, in _eval_evalf
    v = func(*args)
  File "/usr/local/lib/python3.6/dist-packages/mpmath/ctx_mp.py", line 265, in psi
    m = int(m)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'mpc'
>>> (tanh(polygamma(I, 1))).rewrite(exp)
...
    m = int(m)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'mpc'
>>> (I / polygamma(I, 4)).rewrite(exp)
...
    raise TypeError("Invalid comparison of complex %s" % me)
TypeError: Invalid comparison of complex I
```

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v147). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



* No release notes entry will be added for this pull request.

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. <details><summary>Click here to see the pull request description that was parsed.</summary>

    Closes #17350.

    #### Brief description of what is fixed or changed
    Add `_eval_evalf` to `polygamma`. Also check first argument is real in `_eval_is_positive` and `_eval_is_negative`.

    #### Release Notes

    <!-- BEGIN RELEASE NOTES -->
    NO ENTRY
    <!-- END RELEASE NOTES -->


</details><p>

codecov[bot]: # [Codecov](https://codecov.io/gh/sympy/sympy/pull/17375?src=pr&el=h1) Report
> Merging [#17375](https://codecov.io/gh/sympy/sympy/pull/17375?src=pr&el=desc) into [master](https://codecov.io/gh/sympy/sympy/commit/36c2e2abec058d0011102cda59d9848055de086c?src=pr&el=desc) will **increase** coverage by `0.035%`.
> The diff coverage is `100%`.

```diff
@@              Coverage Diff              @@
##            master    #17375       +/-   ##
=============================================
+ Coverage   74.669%   74.705%   +0.035%     
=============================================
  Files          631       631               
  Lines       163172    163179        +7     
  Branches     38282     38286        +4     
=============================================
+ Hits        121840    121903       +63     
+ Misses       35996     35948       -48     
+ Partials      5336      5328        -8
```


Patch:
diff --git a/sympy/functions/special/gamma_functions.py b/sympy/functions/special/gamma_functions.py
index 40b8873cdf..1a75256c78 100644
--- a/sympy/functions/special/gamma_functions.py
+++ b/sympy/functions/special/gamma_functions.py
@@ -586,6 +586,12 @@ class polygamma(Function):
     .. [4] http://functions.wolfram.com/GammaBetaErf/PolyGamma2/
     """
 
+    def _eval_evalf(self, prec):
+        n = self.args[0]
+        # the mpmath polygamma implementation valid only for nonnegative integers
+        if n.is_number and n.is_real:
+            if (n.is_integer or n == int(n)) and n.is_nonnegative:
+                return super(polygamma, self)._eval_evalf(prec)
 
     def fdiff(self, argindex=2):
         if argindex == 2:
@@ -594,17 +600,18 @@ def fdiff(self, argindex=2):
         else:
             raise ArgumentIndexError(self, argindex)
 
+    def _eval_is_real(self):
+        if self.args[0].is_positive and self.args[1].is_positive:
+            return True
+
     def _eval_is_positive(self):
-        if self.args[1].is_positive and (self.args[0] > 0) == True:
+        if self.args[0].is_positive and self.args[1].is_positive:
             return self.args[0].is_odd
 
     def _eval_is_negative(self):
-        if self.args[1].is_positive and (self.args[0] > 0) == True:
+        if self.args[0].is_positive and self.args[1].is_positive:
             return self.args[0].is_even
 
-    def _eval_is_real(self):
-        return self.args[0].is_real
-
     def _eval_aseries(self, n, args0, x, logx):
         from sympy import Order
         if args0[1] != oo or not \
@@ -745,10 +752,9 @@ def _eval_expand_func(self, **hints):
         return polygamma(n, z)
 
     def _eval_rewrite_as_zeta(self, n, z, **kwargs):
-        if n >= S.One:
-            return (-1)**(n + 1)*factorial(n)*zeta(n + 1, z)
-        else:
-            return self
+        if n.is_integer:
+            if (n - S.One).is_nonnegative:
+                return (-1)**(n + 1)*factorial(n)*zeta(n + 1, z)
 
     def _eval_rewrite_as_harmonic(self, n, z, **kwargs):
         if n.is_integer:


Test patch:
diff --git a/sympy/functions/special/tests/test_gamma_functions.py b/sympy/functions/special/tests/test_gamma_functions.py
index 37b02996d2..c842f62362 100644
--- a/sympy/functions/special/tests/test_gamma_functions.py
+++ b/sympy/functions/special/tests/test_gamma_functions.py
@@ -2,7 +2,7 @@
     Symbol, Dummy, gamma, I, oo, nan, zoo, factorial, sqrt, Rational,
     multigamma, log, polygamma, EulerGamma, pi, uppergamma, S, expand_func,
     loggamma, sin, cos, O, lowergamma, exp, erf, erfc, exp_polar, harmonic,
-    zeta, conjugate, Ei)
+    zeta, conjugate, Ei, im, re, tanh, Abs)
 
 from sympy.core.expr import unchanged
 from sympy.core.function import ArgumentIndexError
@@ -251,6 +251,17 @@ def t(m, n):
     assert polygamma(0, x).rewrite(zeta) == polygamma(0, x)
     assert polygamma(1, x).rewrite(zeta) == zeta(2, x)
     assert polygamma(2, x).rewrite(zeta) == -2*zeta(3, x)
+    assert polygamma(I, 2).rewrite(zeta) == polygamma(I, 2)
+    n1 = Symbol('n1')
+    n2 = Symbol('n2', real=True)
+    n3 = Symbol('n3', integer=True)
+    n4 = Symbol('n4', positive=True)
+    n5 = Symbol('n5', positive=True, integer=True)
+    assert polygamma(n1, x).rewrite(zeta) == polygamma(n1, x)
+    assert polygamma(n2, x).rewrite(zeta) == polygamma(n2, x)
+    assert polygamma(n3, x).rewrite(zeta) == polygamma(n3, x)
+    assert polygamma(n4, x).rewrite(zeta) == polygamma(n4, x)
+    assert polygamma(n5, x).rewrite(zeta) == (-1)**(n5 + 1) * factorial(n5) * zeta(n5 + 1, x)
 
     assert polygamma(3, 7*x).diff(x) == 7*polygamma(4, 7*x)
 
@@ -278,6 +289,39 @@ def t(m, n):
     # Test a bug
     assert polygamma(0, -x).expand(func=True) == polygamma(0, -x)
 
+    assert polygamma(2, 2.5).is_positive == False
+    assert polygamma(2, -2.5).is_positive == False
+    assert polygamma(3, 2.5).is_positive == True
+    assert polygamma(3, -2.5).is_positive is None
+    assert polygamma(-2, -2.5).is_positive is None
+    assert polygamma(-3, -2.5).is_positive is None
+
+    assert polygamma(2, 2.5).is_negative == True
+    assert polygamma(3, 2.5).is_negative == False
+    assert polygamma(3, -2.5).is_negative == False
+    assert polygamma(2, -2.5).is_negative is None
+    assert polygamma(-2, -2.5).is_negative is None
+    assert polygamma(-3, -2.5).is_negative is None
+
+    assert polygamma(I, 2).is_positive is None
+    assert polygamma(I, 3).is_negative is None
+
+    # issue 17350
+    assert polygamma(pi, 3).evalf() == polygamma(pi, 3)
+    assert (I*polygamma(I, pi)).as_real_imag() == \
+           (-im(polygamma(I, pi)), re(polygamma(I, pi)))
+    assert (tanh(polygamma(I, 1))).rewrite(exp) == \
+           (exp(polygamma(I, 1)) - exp(-polygamma(I, 1)))/(exp(polygamma(I, 1)) + exp(-polygamma(I, 1)))
+    assert (I / polygamma(I, 4)).rewrite(exp) == \
+           I*sqrt(re(polygamma(I, 4))**2 + im(polygamma(I, 4))**2)\
+           /((re(polygamma(I, 4)) + I*im(polygamma(I, 4)))*Abs(polygamma(I, 4)))
+    assert unchanged(polygamma, 2.3, 1.0)
+
+    # issue 12569
+    assert unchanged(im, polygamma(0, I))
+    assert polygamma(Symbol('a', positive=True), Symbol('b', positive=True)).is_real is True
+    assert polygamma(0, I).is_real is None
+
 
 def test_polygamma_expand_func():
     assert polygamma(0, x).expand(func=True) == polygamma(0, x)