comparison libinterp/corefcn/psi.cc @ 20161:65e22ba879f0

psi: add support to compute the polygamma function (kth-derivative). * libinterp/corefcn/psi.cc: previously, only the digamma function, k == 0, was being computed. Add support for polygamma function, add tests, and improve documentation. * liboctave/cruft/slatec-fn/dpsifn.f, liboctave/cruft/slatec-fn/psifn.f: the two functions that actually compute the the polygamma functions, copied verbatim from SLATEC, and under public domain. * liboctave/cruft/slatec-fn/module.mk: add dpsifn.f and psifn.f to the build system. * liboctave/numeric/lo-specfun.cc: add new signature for function psi to compute polygamma function that wraps the Fortran DPSIFN and PSIFN functions. * liboctave/numeric/lo-specfun.h: declare new function and document all psi() with doxygen.
author Carnë Draug <carandraug@octave.org>
date Sun, 03 May 2015 22:52:07 +0100
parents bd565f3e0ecb
children
comparison
equal deleted inserted replaced
20157:e410d62ae2c8 20161:65e22ba879f0
32 32
33 #include "lo-specfun.h" 33 #include "lo-specfun.h"
34 34
35 DEFUN (psi, args, , 35 DEFUN (psi, args, ,
36 "-*- texinfo -*-\n\ 36 "-*- texinfo -*-\n\
37 @deftypefn {Function File} {} psi (@var{z})\n\ 37 @deftypefn {Function File} {} psi (@var{z})\n\
38 Compute the psi (digamma) function.\n\ 38 @deftypefnx {Function File} {} psi (@var{k}, @var{z})\n\
39 Compute the psi (polygamma) function.\n\
40 \n\
41 The polygamma functions are the @var{k}th derivative of the logarithm\n\
42 of the gamma function. If unspecified, @var{k} defaults to zero. A value\n\
43 of zero computes the digamma function, a value of 1, the trigamma function,\n\
44 and so on.\n\
45 \n\
46 The digamma function is defined:\n\
39 \n\ 47 \n\
40 @tex\n\ 48 @tex\n\
41 $$\n\ 49 $$\n\
42 \\Psi (z) = {d (log (\\Gamma (z))) \\over dx}\n\ 50 \\Psi (z) = {d (log (\\Gamma (z))) \\over dx}\n\
43 $$\n\ 51 $$\n\
48 psi (z) = d (log (gamma (z))) / dx\n\ 56 psi (z) = d (log (gamma (z))) / dx\n\
49 @end group\n\ 57 @end group\n\
50 @end example\n\ 58 @end example\n\
51 @end ifnottex\n\ 59 @end ifnottex\n\
52 \n\ 60 \n\
61 When computing the digamma function (when @var{k} equals zero), @var{z}\n\
62 can have any value real or complex value. However, for polygamma functions\n\
63 (@var{k} higher than 0), @var{z} must be real and non-negative.\n\
64 \n\
53 @seealso{gamma, gammainc, gammaln}\n\ 65 @seealso{gamma, gammainc, gammaln}\n\
54 @end deftypefn") 66 @end deftypefn")
55 { 67 {
56 octave_value retval; 68 octave_value retval;
57 69
58 const octave_idx_type nargin = args.length (); 70 const octave_idx_type nargin = args.length ();
59 71 if (nargin < 1 || nargin > 2)
60 if (nargin != 1)
61 { 72 {
62 print_usage (); 73 print_usage ();
63 return retval; 74 return retval;
64 } 75 }
65 76
77 const octave_value oct_z = (nargin == 1) ? args(0) : args(1);
78 const octave_idx_type k = (nargin == 1) ? 0 : args(0).idx_type_value ();
79 if (error_state || k < 0)
80 {
81 error ("psi: K must be a non-negative integer");
82 return retval;
83 }
84 else if (k == 0)
85 {
66 #define FLOAT_BRANCH(T, A, M, E) \ 86 #define FLOAT_BRANCH(T, A, M, E) \
67 if (args(0).is_ ## T ##_type ()) \ 87 if (oct_z.is_ ## T ##_type ()) \
68 { \ 88 { \
69 const A ## NDArray z = args(0).M ## array_value (); \ 89 const A ## NDArray z = oct_z.M ## array_value (); \
70 A ## NDArray psi_z (z.dims ()); \ 90 A ## NDArray psi_z (z.dims ()); \
71 \ 91 \
72 const E* zv = z.data (); \ 92 const E* zv = z.data (); \
73 E* psi_zv = psi_z.fortran_vec (); \ 93 E* psi_zv = psi_z.fortran_vec (); \
74 const octave_idx_type n = z.numel (); \ 94 const octave_idx_type n = z.numel (); \
75 for (octave_idx_type i = 0; i < n; i++) \ 95 for (octave_idx_type i = 0; i < n; i++) \
76 psi_zv[i] = psi (zv[i]); \ 96 *psi_zv++ = psi (*zv++); \
77 \ 97 \
78 retval = psi_z; \ 98 retval = psi_z; \
79 } 99 }
80 100
81 if (args(0).is_complex_type ()) 101 if (oct_z.is_complex_type ())
82 { 102 {
83 FLOAT_BRANCH(double, Complex, complex_, Complex) 103 FLOAT_BRANCH(double, Complex, complex_, Complex)
84 else FLOAT_BRANCH(single, FloatComplex, float_complex_, FloatComplex) 104 else FLOAT_BRANCH(single, FloatComplex, float_complex_, FloatComplex)
105 else
106 {
107 error ("psi: Z must be a floating point");
108 }
109 }
85 else 110 else
86 { 111 {
87 error ("psi: Z must be a floating point"); 112 FLOAT_BRANCH(double, , , double)
88 } 113 else FLOAT_BRANCH(single, Float, float_, float)
114 else
115 {
116 error ("psi: Z must be a floating point");
117 }
118 }
119
120 #undef FLOAT_BRANCH
89 } 121 }
90 else 122 else
91 { 123 {
124 if (! oct_z.is_real_type ())
125 {
126 error ("psi: Z must be real value for polygamma (K > 0)");
127 return retval;
128 }
129
130 #define FLOAT_BRANCH(T, A, M, E) \
131 if (oct_z.is_ ## T ##_type ()) \
132 { \
133 const A ## NDArray z = oct_z.M ## array_value (); \
134 A ## NDArray psi_z (z.dims ()); \
135 \
136 const E* zv = z.data (); \
137 E* psi_zv = psi_z.fortran_vec (); \
138 const octave_idx_type n = z.numel (); \
139 for (octave_idx_type i = 0; i < n; i++) \
140 { \
141 if (*zv < 0) \
142 { \
143 error ("psi: Z must be non-negative for polygamma (K > 0)"); \
144 return retval; \
145 } \
146 *psi_zv++ = psi (k, *zv++); \
147 } \
148 retval = psi_z; \
149 }
150
92 FLOAT_BRANCH(double, , , double) 151 FLOAT_BRANCH(double, , , double)
93 else FLOAT_BRANCH(single, Float, float_, float) 152 else FLOAT_BRANCH(single, Float, float_, float)
94 else 153 else
95 { 154 {
96 error ("psi: Z must be a floating point"); 155 error ("psi: Z must be a floating point for polygamma (K > 0)");
97 } 156 }
98 }
99 157
100 #undef FLOAT_BRANCH 158 #undef FLOAT_BRANCH
159 }
101 160
102 return retval; 161 return retval;
103 } 162 }
104 163
105 /* 164 /*
153 %!assert (imag (psi (1/2 + i*z)), 1/2 * pi * tanh (pi * z), eps) 212 %!assert (imag (psi (1/2 + i*z)), 1/2 * pi * tanh (pi * z), eps)
154 213
155 ## Abramowitz and Stegun, page 259 eq 6.3.13 214 ## Abramowitz and Stegun, page 259 eq 6.3.13
156 %!assert (imag (psi (1 + i*z)), - 1./(2*z) + 1/2 * pi * coth (pi * z), eps*10) 215 %!assert (imag (psi (1 + i*z)), - 1./(2*z) + 1/2 * pi * coth (pi * z), eps*10)
157 216
217 ## Abramowitz and Stegun, page 260 eq 6.4.5
218 %!test
219 %! for z = 0:20
220 %! assert (psi (1, z + 0.5), 0.5 * (pi^2) - 4 * sum ((2*(1:z) -1) .^(-2)), eps*10)
221 %! endfor
222
223 ## Abramowitz and Stegun, page 260 eq 6.4.6
224 %!test
225 %! z = 0.1:0.1:20;
226 %! for n = 0:8
227 %! ## our precision goes down really quick when computing n is too high,
228 %! assert (psi (n, z+1), psi (n, z) + ((-1)^n) * factorial (n) * (z.^(-n-1)), 0.1)
229 %! endfor
230
231 ## Test input validation
232 %!error psi ()
233 %!error psi (1, 2, 3)
234 %!error <Z must be> psi ("non numeric")
235 %!error <K must be a non-negative integer> psi (-5, 1)
236 %!error <Z must be non-negative for polygamma> psi (5, -1)
237 %!error <Z must be a floating point> psi (5, uint8 (-1))
238 %!error <Z must be real value for polygamma> psi (5, 5i)
239
158 */ 240 */