GNU Octave 7.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
psi.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2016-2022 The Octave Project Developers
4//
5// See the file COPYRIGHT.md in the top-level directory of this
6// distribution or <https://octave.org/copyright/>.
7//
8// This file is part of Octave.
9//
10// Octave is free software: you can redistribute it and/or modify it
11// under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// Octave is distributed in the hope that it will be useful, but
16// WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with Octave; see the file COPYING. If not, see
22// <https://www.gnu.org/licenses/>.
23//
24////////////////////////////////////////////////////////////////////////
25
26#if defined (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#include "ov.h"
31#include "defun.h"
32#include "error.h"
33#include "dNDArray.h"
34#include "fNDArray.h"
35
36#include "lo-specfun.h"
37
38OCTAVE_NAMESPACE_BEGIN
39
40DEFUN (psi, args, ,
41 doc: /* -*- texinfo -*-
42@deftypefn {} {} psi (@var{z})
43@deftypefnx {} {} psi (@var{k}, @var{z})
44Compute the psi (polygamma) function.
45
46The polygamma functions are the @var{k}th derivative of the logarithm
47of the gamma function. If unspecified, @var{k} defaults to zero. A value
48of zero computes the digamma function, a value of 1, the trigamma function,
49and so on.
50
51The digamma function is defined:
52
53@tex
54$$
55\Psi (z) = {d (log (\Gamma (z))) \over dx}
56$$
57@end tex
58@ifnottex
59
60@example
61@group
62psi (z) = d (log (gamma (z))) / dx
63@end group
64@end example
65
66@end ifnottex
67
68When computing the digamma function (when @var{k} equals zero), @var{z}
69can have any value real or complex value. However, for polygamma functions
70(@var{k} higher than 0), @var{z} must be real and non-negative.
71
72@seealso{gamma, gammainc, gammaln}
73@end deftypefn */)
74{
75 int nargin = args.length ();
76
77 if (nargin < 1 || nargin > 2)
78 print_usage ();
79
80 const octave_value oct_z = (nargin == 1) ? args(0) : args(1);
81 const octave_idx_type k = (nargin == 1) ? 0 : args(0).xidx_type_value ("psi: K must be an integer");
82 if (k < 0)
83 error ("psi: K must be non-negative");
84
85 octave_value retval;
86
87 if (k == 0)
88 {
89#define FLOAT_BRANCH(T, A, M, E) \
90 if (oct_z.is_ ## T ##_type ()) \
91 { \
92 const A ## NDArray z = oct_z.M ## array_value (); \
93 A ## NDArray psi_z (z.dims ()); \
94 \
95 const E *zv = z.data (); \
96 E *psi_zv = psi_z.fortran_vec (); \
97 const octave_idx_type n = z.numel (); \
98 for (octave_idx_type i = 0; i < n; i++) \
99 *psi_zv++ = math::psi (*zv++); \
100 \
101 retval = psi_z; \
102 }
103
104 if (oct_z.iscomplex ())
105 {
106 FLOAT_BRANCH(double, Complex, complex_, Complex)
107 else FLOAT_BRANCH(single, FloatComplex, float_complex_, FloatComplex)
108 else
109 error ("psi: Z must be a floating point");
110 }
111 else
112 {
113 FLOAT_BRANCH(double, , , double)
114 else FLOAT_BRANCH(single, Float, float_, float)
115 else
116 error ("psi: Z must be a floating point");
117 }
118
119#undef FLOAT_BRANCH
120 }
121 else
122 {
123 if (! oct_z.isreal ())
124 error ("psi: Z must be real value for polygamma (K > 0)");
125
126#define FLOAT_BRANCH(T, A, M, E) \
127 if (oct_z.is_ ## T ##_type ()) \
128 { \
129 const A ## NDArray z = oct_z.M ## array_value (); \
130 A ## NDArray psi_z (z.dims ()); \
131 \
132 const E *zv = z.data (); \
133 E *psi_zv = psi_z.fortran_vec (); \
134 const octave_idx_type n = z.numel (); \
135 for (octave_idx_type i = 0; i < n; i++) \
136 { \
137 if (*zv < 0) \
138 error ("psi: Z must be non-negative for polygamma (K > 0)"); \
139 \
140 *psi_zv++ = math::psi (k, *zv++); \
141 } \
142 retval = psi_z; \
143 }
144
145 FLOAT_BRANCH(double, , , double)
146 else FLOAT_BRANCH(single, Float, float_, float)
147 else
148 error ("psi: Z must be a floating point for polygamma (K > 0)");
149
150#undef FLOAT_BRANCH
151 }
152
153 return retval;
154}
155
156/*
157%!shared em
158%! em = 0.577215664901532860606512090082402431042; # Euler-Mascheroni Constant
159
160%!assert (psi (ones (7, 3, 5)), repmat (-em, [7 3 5]))
161%!assert (psi ([0 1]), [-Inf -em])
162%!assert (psi ([-20:1]), [repmat(-Inf, [1 21]) -em])
163%!assert (psi (single ([0 1])), single ([-Inf -em]))
164
165## Abramowitz and Stegun, page 258, eq 6.3.5
166%!test
167%! z = [-100:-1 1:200] ./ 10; # drop the 0
168%! assert (psi (z + 1), psi (z) + 1 ./ z, eps*1000);
169
170## Abramowitz and Stegun, page 258, eq 6.3.2
171%!assert (psi (1), -em)
172
173## Abramowitz and Stegun, page 258, eq 6.3.3
174%!assert (psi (1/2), -em - 2 * log (2))
175
176## The following tests are from Pascal Sebah and Xavier Gourdon (2002)
177## "Introduction to the Gamma Function"
178
179## Interesting identities of the digamma function, in section of 5.1.3
180%!assert (psi (1/3), - em - (3/2) * log (3) - ((sqrt (3) / 6) * pi), eps*10)
181%!assert (psi (1/4), - em -3 * log (2) - pi/2, eps*10)
182%!assert (psi (1/6), - em -2 * log (2) - (3/2) * log (3) - ((sqrt (3) / 2) * pi), eps*10)
183
184## First 6 zeros of the digamma function, in section of 5.1.5 (and also on
185## Abramowitz and Stegun, page 258, eq 6.3.19)
186%!assert (psi ( 1.46163214496836234126265954232572132846819620400644), 0, eps)
187%!assert (psi (-0.504083008264455409258269304533302498955385182368579), 0, eps*2)
188%!assert (psi (-1.573498473162390458778286043690434612655040859116846), 0, eps*2)
189%!assert (psi (-2.610720868444144650001537715718724207951074010873480), 0, eps*10)
190%!assert (psi (-3.635293366436901097839181566946017713948423861193530), 0, eps*10)
191%!assert (psi (-4.653237761743142441714598151148207363719069416133868), 0, eps*100)
192
193## Tests for complex values
194%!shared z
195%! z = [-100:-1 1:200] ./ 10; # drop the 0
196
197## Abramowitz and Stegun, page 259 eq 6.3.10
198%!assert (real (psi (i*z)), real (psi (1 - i*z)))
199
200## Abramowitz and Stegun, page 259 eq 6.3.11
201%!assert (imag (psi (i*z)), 1/2 .* 1./z + 1/2 * pi * coth (pi * z), eps *10)
202
203## Abramowitz and Stegun, page 259 eq 6.3.12
204%!assert (imag (psi (1/2 + i*z)), 1/2 * pi * tanh (pi * z), eps*10)
205
206## Abramowitz and Stegun, page 259 eq 6.3.13
207%!assert (imag (psi (1 + i*z)), - 1./(2*z) + 1/2 * pi * coth (pi * z), eps*10)
208
209## Abramowitz and Stegun, page 260 eq 6.4.5
210%!test
211%! for z = 0:20
212%! assert (psi (1, z + 0.5),
213%! 0.5 * (pi^2) - 4 * sum ((2*(1:z) -1) .^(-2)),
214%! eps*10);
215%! endfor
216
217## Abramowitz and Stegun, page 260 eq 6.4.6
218%!test
219%! z = 0.1:0.1:20;
220%! for n = 0:8
221%! ## our precision goes down really quick when computing n is too high.
222%! assert (psi (n, z+1),
223%! psi (n, z) + ((-1)^n) * factorial (n) * (z.^(-n-1)), 0.1);
224%! endfor
225
226## Test input validation
227%!error psi ()
228%!error psi (1, 2, 3)
229%!error <Z must be> psi ("non numeric")
230%!error <K must be an integer> psi ({5.3}, 1)
231%!error <K must be non-negative> psi (-5, 1)
232%!error <Z must be non-negative for polygamma> psi (5, -1)
233%!error <Z must be a floating point> psi (5, uint8 (-1))
234%!error <Z must be real value for polygamma> psi (5, 5i)
235
236*/
237
238OCTAVE_NAMESPACE_END
bool isreal(void) const
Definition: ov.h:783
bool iscomplex(void) const
Definition: ov.h:786
OCTINTERP_API void print_usage(void)
Definition: defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void error(const char *fmt,...)
Definition: error.cc:980
double psi(double z)
Definition: lo-specfun.cc:2099
std::complex< double > Complex
Definition: oct-cmplx.h:33
std::complex< float > FloatComplex
Definition: oct-cmplx.h:34
#define FLOAT_BRANCH(T, A, M, E)