Repository: numpy/numpy

Issue:
Error in tanh for large complex argument (Trac #1726)
_Original ticket http://projects.scipy.org/numpy/ticket/1726 on 2011-01-28 by trac user mbakker, assigned to unknown._

tanh computes nans for large complex argument:

In [85]: tanh(1000+0j)
Out[85]: (nan+nan*j)

while the correct answer is 1.0

Solution: Program tanh as:

tanh(z) = (1.0 - exp(-2.0_z)) / (1.0 + exp(-2.0_z))



Discussion:
_trac user mbakker wrote on 2011-01-28_

As Pauli pointed out the suggested solution has problems for z -> -inf.
Need to check what others do.

_trac user mbakker wrote on 2012-04-07_

tanh is computed correctly for large complex argument by cmath. We may look there for the correct approach.


Resolving patch:
diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py
--- a/numpy/core/setup_common.py
+++ b/numpy/core/setup_common.py
@@ -107,6 +107,7 @@ def check_api_version(apiversion, codegen_dir):
 # sse headers only enabled automatically on amd64/x32 builds
                 "xmmintrin.h", # SSE
                 "emmintrin.h", # SSE2
+                "features.h",  # for glibc version linux
 ]
 
 # optional gcc compiler builtins and their call arguments and optional a
@@ -138,23 +139,29 @@ def check_api_version(apiversion, codegen_dir):
 OPTIONAL_VARIABLE_ATTRIBUTES = ["__thread", "__declspec(thread)"]
 
 # Subset of OPTIONAL_STDFUNCS which may alreay have HAVE_* defined by Python.h
-OPTIONAL_STDFUNCS_MAYBE = ["expm1", "log1p", "acosh", "atanh", "asinh", "hypot",
-        "copysign", "ftello", "fseeko"]
+OPTIONAL_STDFUNCS_MAYBE = [
+    "expm1", "log1p", "acosh", "atanh", "asinh", "hypot", "copysign",
+    "ftello", "fseeko"
+    ]
 
 # C99 functions: float and long double versions
-C99_FUNCS = ["sin", "cos", "tan", "sinh", "cosh", "tanh", "fabs", "floor",
-        "ceil", "rint", "trunc", "sqrt", "log10", "log", "log1p", "exp",
-        "expm1", "asin", "acos", "atan", "asinh", "acosh", "atanh",
-        "hypot", "atan2", "pow", "fmod", "modf", 'frexp', 'ldexp',
-        "exp2", "log2", "copysign", "nextafter", "cbrt"]
-
+C99_FUNCS = [
+    "sin", "cos", "tan", "sinh", "cosh", "tanh", "fabs", "floor", "ceil",
+    "rint", "trunc", "sqrt", "log10", "log", "log1p", "exp", "expm1",
+    "asin", "acos", "atan", "asinh", "acosh", "atanh", "hypot", "atan2",
+    "pow", "fmod", "modf", 'frexp', 'ldexp', "exp2", "log2", "copysign",
+    "nextafter", "cbrt"
+    ]
 C99_FUNCS_SINGLE = [f + 'f' for f in C99_FUNCS]
 C99_FUNCS_EXTENDED = [f + 'l' for f in C99_FUNCS]
-
-C99_COMPLEX_TYPES = ['complex double', 'complex float', 'complex long double']
-
-C99_COMPLEX_FUNCS = ['creal', 'cimag', 'cabs', 'carg', 'cexp', 'csqrt', 'clog',
-                  'ccos', 'csin', 'cpow']
+C99_COMPLEX_TYPES = [
+    'complex double', 'complex float', 'complex long double'
+    ]
+C99_COMPLEX_FUNCS = [
+    "cabs", "cacos", "cacosh", "carg", "casin", "casinh", "catan",
+    "catanh", "ccos", "ccosh", "cexp", "cimag", "clog", "conj", "cpow",
+    "cproj", "creal", "csin", "csinh", "csqrt", "ctan", "ctanh"
+    ]
 
 def fname2def(name):
     return "HAVE_%s" % name.upper()


Test patch:
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -1363,45 +1363,53 @@ def test_precisions_consistent(self) :
 
     def test_branch_cuts(self):
         # check branch cuts and continuity on them
-        yield _check_branch_cut, np.log,   -0.5, 1j, 1, -1
-        yield _check_branch_cut, np.log2,  -0.5, 1j, 1, -1
-        yield _check_branch_cut, np.log10, -0.5, 1j, 1, -1
-        yield _check_branch_cut, np.log1p, -1.5, 1j, 1, -1
-        yield _check_branch_cut, np.sqrt,  -0.5, 1j, 1, -1
+        yield _check_branch_cut, np.log,   -0.5, 1j, 1, -1, True
+        yield _check_branch_cut, np.log2,  -0.5, 1j, 1, -1, True
+        yield _check_branch_cut, np.log10, -0.5, 1j, 1, -1, True
+        yield _check_branch_cut, np.log1p, -1.5, 1j, 1, -1, True
+        yield _check_branch_cut, np.sqrt,  -0.5, 1j, 1, -1, True
 
-        yield _check_branch_cut, np.arcsin, [ -2, 2],   [1j, -1j], 1, -1
-        yield _check_branch_cut, np.arccos, [ -2, 2],   [1j, -1j], 1, -1
-        yield _check_branch_cut, np.arctan, [-2j, 2j],  [1,  -1 ], -1, 1
+        yield _check_branch_cut, np.arcsin, [ -2, 2],   [1j, 1j], 1, -1, True
+        yield _check_branch_cut, np.arccos, [ -2, 2],   [1j, 1j], 1, -1, True
+        yield _check_branch_cut, np.arctan, [0-2j, 2j],  [1,  1 ], -1, 1, True
 
-        yield _check_branch_cut, np.arcsinh, [-2j,  2j], [-1,   1], -1, 1
-        yield _check_branch_cut, np.arccosh, [ -1, 0.5], [1j,  1j], 1, -1
-        yield _check_branch_cut, np.arctanh, [ -2,   2], [1j, -1j], 1, -1
+        yield _check_branch_cut, np.arcsinh, [0-2j,  2j], [1,   1], -1, 1, True
+        yield _check_branch_cut, np.arccosh, [ -1, 0.5], [1j,  1j], 1, -1, True
+        yield _check_branch_cut, np.arctanh, [ -2,   2], [1j, 1j], 1, -1, True
 
         # check against bogus branch cuts: assert continuity between quadrants
-        yield _check_branch_cut, np.arcsin, [-2j, 2j], [ 1,  1], 1, 1
-        yield _check_branch_cut, np.arccos, [-2j, 2j], [ 1,  1], 1, 1
+        yield _check_branch_cut, np.arcsin, [0-2j, 2j], [ 1,  1], 1, 1
+        yield _check_branch_cut, np.arccos, [0-2j, 2j], [ 1,  1], 1, 1
         yield _check_branch_cut, np.arctan, [ -2,  2], [1j, 1j], 1, 1
 
         yield _check_branch_cut, np.arcsinh, [ -2,  2, 0], [1j, 1j, 1 ], 1, 1
-        yield _check_branch_cut, np.arccosh, [-2j, 2j, 2], [1,  1,  1j], 1, 1
-        yield _check_branch_cut, np.arctanh, [-2j, 2j, 0], [1,  1,  1j], 1, 1
+        yield _check_branch_cut, np.arccosh, [0-2j, 2j, 2], [1,  1,  1j], 1, 1
+        yield _check_branch_cut, np.arctanh, [0-2j, 2j, 0], [1,  1,  1j], 1, 1
 
-    @dec.knownfailureif(True, "These branch cuts are known to fail")
-    def test_branch_cuts_failing(self):
-        # XXX: signed zero not OK with ICC on 64-bit platform for log, see
-        # http://permalink.gmane.org/gmane.comp.python.numeric.general/25335
-        yield _check_branch_cut, np.log,   -0.5, 1j, 1, -1, True
-        yield _check_branch_cut, np.log2,  -0.5, 1j, 1, -1, True
-        yield _check_branch_cut, np.log10, -0.5, 1j, 1, -1, True
-        yield _check_branch_cut, np.log1p, -1.5, 1j, 1, -1, True
-        # XXX: signed zeros are not OK for sqrt or for the arc* functions
-        yield _check_branch_cut, np.sqrt,  -0.5, 1j, 1, -1, True
-        yield _check_branch_cut, np.arcsin, [ -2, 2],   [1j, -1j], 1, -1, True
-        yield _check_branch_cut, np.arccos, [ -2, 2],   [1j, -1j], 1, -1, True
-        yield _check_branch_cut, np.arctan, [-2j, 2j],  [1,  -1 ], -1, 1, True
-        yield _check_branch_cut, np.arcsinh, [-2j,  2j], [-1,   1], -1, 1, True
-        yield _check_branch_cut, np.arccosh, [ -1, 0.5], [1j,  1j], 1, -1, True
-        yield _check_branch_cut, np.arctanh, [ -2,   2], [1j, -1j], 1, -1, True
+    def test_branch_cuts_complex64(self):
+        # check branch cuts and continuity on them
+        yield _check_branch_cut, np.log,   -0.5, 1j, 1, -1, True, np.complex64
+        yield _check_branch_cut, np.log2,  -0.5, 1j, 1, -1, True, np.complex64
+        yield _check_branch_cut, np.log10, -0.5, 1j, 1, -1, True, np.complex64
+        yield _check_branch_cut, np.log1p, -1.5, 1j, 1, -1, True, np.complex64
+        yield _check_branch_cut, np.sqrt,  -0.5, 1j, 1, -1, True, np.complex64
+
+        yield _check_branch_cut, np.arcsin, [ -2, 2],   [1j, 1j], 1, -1, True, np.complex64
+        yield _check_branch_cut, np.arccos, [ -2, 2],   [1j, 1j], 1, -1, True, np.complex64
+        yield _check_branch_cut, np.arctan, [0-2j, 2j],  [1,  1 ], -1, 1, True, np.complex64
+
+        yield _check_branch_cut, np.arcsinh, [0-2j,  2j], [1,   1], -1, 1, True, np.complex64
+        yield _check_branch_cut, np.arccosh, [ -1, 0.5], [1j,  1j], 1, -1, True, np.complex64
+        yield _check_branch_cut, np.arctanh, [ -2,   2], [1j, 1j], 1, -1, True, np.complex64
+
+        # check against bogus branch cuts: assert continuity between quadrants
+        yield _check_branch_cut, np.arcsin, [0-2j, 2j], [ 1,  1], 1, 1, False, np.complex64
+        yield _check_branch_cut, np.arccos, [0-2j, 2j], [ 1,  1], 1, 1, False, np.complex64
+        yield _check_branch_cut, np.arctan, [ -2,  2], [1j, 1j], 1, 1, False, np.complex64
+
+        yield _check_branch_cut, np.arcsinh, [ -2,  2, 0], [1j, 1j, 1 ], 1, 1, False, np.complex64
+        yield _check_branch_cut, np.arccosh, [0-2j, 2j, 2], [1,  1,  1j], 1, 1, False, np.complex64
+        yield _check_branch_cut, np.arctanh, [0-2j, 2j, 0], [1,  1,  1j], 1, 1, False, np.complex64
 
     def test_against_cmath(self):
         import cmath, sys
@@ -1466,7 +1474,7 @@ def check(x, rtol):
             # So, give more leeway for long complex tests here:
             check(x_series, 50*eps)
         else:
-            check(x_series, 2*eps)
+            check(x_series, 2.1*eps)
         check(x_basic, 2*eps/1e-3)
 
         # Check a few points
@@ -1565,8 +1573,12 @@ def _check_branch_cut(f, x0, dx, re_sign=1, im_sign=-1, sig_zero_ok=False,
     x0 = np.atleast_1d(x0).astype(dtype)
     dx = np.atleast_1d(dx).astype(dtype)
 
-    scale = np.finfo(dtype).eps * 1e3
-    atol  = 1e-4
+    if np.dtype(dtype).char == 'F':
+        scale = np.finfo(dtype).eps * 1e2
+        atol = np.float32(1e-2)
+    else:
+        scale = np.finfo(dtype).eps * 1e3
+        atol  = 1e-4
 
     y0 = f(x0)
     yp = f(x0 + dx*scale*np.absolute(x0)/np.absolute(dx))
@@ -1581,16 +1593,20 @@ def _check_branch_cut(f, x0, dx, re_sign=1, im_sign=-1, sig_zero_ok=False,
         # check that signed zeros also work as a displacement
         jr = (x0.real == 0) & (dx.real != 0)
         ji = (x0.imag == 0) & (dx.imag != 0)
-
-        x = -x0
-        x.real[jr] = 0.*dx.real
-        x.imag[ji] = 0.*dx.imag
-        x = -x
-        ym = f(x)
-        ym = ym[jr | ji]
-        y0 = y0[jr | ji]
-        assert_(np.all(np.absolute(y0.real - ym.real*re_sign) < atol), (y0, ym))
-        assert_(np.all(np.absolute(y0.imag - ym.imag*im_sign) < atol), (y0, ym))
+        if np.any(jr):
+            x = x0[jr]
+            x.real = np.NZERO
+            ym = f(x)
+            assert_(np.all(np.absolute(y0[jr].real - ym.real*re_sign) < atol), (y0[jr], ym))
+            assert_(np.all(np.absolute(y0[jr].imag - ym.imag*im_sign) < atol), (y0[jr], ym))
+
+
+        if np.any(ji):
+            x = x0[ji]
+            x.imag = np.NZERO
+            ym = f(x)
+            assert_(np.all(np.absolute(y0[ji].real - ym.real*re_sign) < atol), (y0[ji], ym))
+            assert_(np.all(np.absolute(y0[ji].imag - ym.imag*im_sign) < atol), (y0[ji], ym))
 
 def test_copysign():
     assert_(np.copysign(1, -1) == -1)