File: _cunity.pxd

package info (click to toggle)
python-scipy 0.18.1-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 75,464 kB
  • ctags: 79,406
  • sloc: python: 143,495; cpp: 89,357; fortran: 81,650; ansic: 79,778; makefile: 364; sh: 265
file content (118 lines) | stat: -rw-r--r-- 3,698 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
cimport numpy as np
from libc.math cimport fabs, sin, cos, exp
from _complexstuff cimport (
    zisfinite, zabs, zpack, npy_cdouble_from_double_complex,
    double_complex_from_npy_cdouble)

cdef extern from "_complexstuff.h":
    double npy_atan2(double y, double x) nogil
    np.npy_cdouble npy_clog(np.npy_cdouble x) nogil
    np.npy_cdouble npy_cexp(np.npy_cdouble x) nogil
    

cdef extern from "c_misc/double2.h":
    ctypedef struct double2_t:
        double x[2]

    void double2_init(double2_t* a, double y) nogil
    void double2_add(double2_t* a, double2_t* b, double2_t* c) nogil
    void double2_mul(double2_t* a, double2_t* b, double2_t* c) nogil
    double double2_double(double2_t* a) nogil

cdef extern from "cephes.h":
    double log1p(double x) nogil
    double expm1(double x) nogil
    double cosm1(double x) nogil

# log(z + 1) = log(x + 1 + 1j*y)
#             = log(sqrt((x+1)**2 + y**2)) + 1j*atan2(y, x+1)
# 
# Using atan2(y, x+1) for the imaginary part is always okay.  The real part
# needs to be calculated more carefully.  For |z| large, the naive formula
# log(z + 1) can be used.  When |z| is small, rewrite as 
#
# log(sqrt((x+1)**2 + y**2)) = 0.5*log(x**2 + 2*x +1 + y**2)
#       = 0.5 * log1p(x**2 + y**2 + 2*x)
#       = 0.5 * log1p(hypot(x,y) * (hypot(x, y) + 2*x/hypot(x,y)))
# 
# This expression suffers from cancelation when x < 0 and
# y = +/-sqrt(2*fabs(x)). To get around this cancelation problem, we use
# double-double precision when necessary.
cdef inline double complex clog1p(double complex z) nogil:
    cdef double zr, zi, x, y, az, azi
    cdef np.npy_cdouble ret

    if not zisfinite(z):
        z = z + 1 
        ret = npy_clog(npy_cdouble_from_double_complex(z))
        return double_complex_from_npy_cdouble(ret)

    zr = z.real
    zi = z.imag

    if zi == 0.0 and zr >= -1.0:
        return zpack(log1p(zr), 0.0) 

    az = zabs(z)
    if az < 0.707:
        azi = fabs(zi)
        if zr < 0 and fabs(-zr - azi*azi/2)/(-zr) < 0.5:
            return clog1p_ddouble(zr, zi)
        else:
            x = 0.5 * log1p(az*(az + 2*zr/az))
            y = npy_atan2(zi, zr + 1.0)
            return zpack(x, y)

    z = z + 1.0
    ret = npy_clog(npy_cdouble_from_double_complex(z))
    return double_complex_from_npy_cdouble(ret)

cdef inline double complex clog1p_ddouble(double zr, double zi) nogil:
    cdef double x, y
    cdef double2_t r, i, two, rsqr, isqr, rtwo, absm1

    double2_init(&r, zr)
    double2_init(&i, zi)
    double2_init(&two, 2.0)
    
    double2_mul(&r, &r, &rsqr)
    double2_mul(&i, &i, &isqr)
    double2_mul(&two, &r, &rtwo)
    double2_add(&rsqr, &isqr, &absm1)
    double2_add(&absm1, &rtwo, &absm1)

    x = 0.5 * log1p(double2_double(&absm1))
    y = npy_atan2(zi, zr+1.0)
    return zpack(x, y) 
 
# cexpm1(z) = cexp(z) - 1
# 
# The imaginary part of this is easily computed via exp(z.real)*sin(z.imag)
# The real part is difficult to compute when there is cancelation e.g. when
# z.real = -log(cos(z.imag)).  There isn't a way around this problem  that
# doesn't involve computing exp(z.real) and/or cos(z.imag) to higher
# precision.
cdef inline double complex cexpm1(double complex z) nogil:
    cdef double zr, zi, ezr, x, y
    cdef np.npy_cdouble ret

    if not zisfinite(z):
        ret = npy_cexp(npy_cdouble_from_double_complex(z))
        return double_complex_from_npy_cdouble(ret) - 1.0

    zr = z.real
    zi = z.imag

    if zr <= -40:
        x = -1.0
    else:
        ezr = expm1(zr)
        x = ezr*cos(zi) + cosm1(zi)
    # don't compute exp(zr) too, unless necessary
    if zr > -1.0:
        y = (ezr + 1.0)*sin(zi)
    else:
        y = exp(zr)*sin(zi)

    return zpack(x, y)