GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
psi.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2016-2024 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 
39 
40 DEFUN (psi, args, ,
41  doc: /* -*- texinfo -*-
42 @deftypefn {} {@var{y} =} psi (@var{z})
43 @deftypefnx {} {@var{y} =} psi (@var{k}, @var{z})
44 Compute the psi (polygamma) function.
45 
46 The polygamma functions are the @var{k}th derivative of the logarithm
47 of the gamma function. If unspecified, @var{k} defaults to zero. A value
48 of zero computes the digamma function, a value of 1, the trigamma function,
49 and so on.
50 
51 The 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
62 psi (z) = d (log (gamma (z))) / dx
63 @end group
64 @end example
65 
66 @end ifnottex
67 
68 When computing the digamma function (when @var{k} equals zero), @var{z}
69 can 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),
183 %! - em -2 * log (2) - (3/2) * log (3) - ((sqrt (3) / 2) * pi), eps*10)
184 
185 ## First 6 zeros of the digamma function, in section of 5.1.5 (and also on
186 ## Abramowitz and Stegun, page 258, eq 6.3.19)
187 %!assert (psi ( 1.46163214496836234126265954232572132846819620400644), 0, eps)
188 %!assert (psi (-0.504083008264455409258269304533302498955385182368579), 0, eps*2)
189 %!assert (psi (-1.573498473162390458778286043690434612655040859116846), 0, eps*2)
190 %!assert (psi (-2.610720868444144650001537715718724207951074010873480), 0, eps*10)
191 %!assert (psi (-3.635293366436901097839181566946017713948423861193530), 0, eps*10)
192 %!assert (psi (-4.653237761743142441714598151148207363719069416133868), 0, eps*100)
193 
194 ## Tests for complex values
195 %!shared z
196 %! z = [-100:-1 1:200] ./ 10; # drop the 0
197 
198 ## Abramowitz and Stegun, page 259 eq 6.3.10
199 %!assert (real (psi (i*z)), real (psi (1 - i*z)))
200 
201 ## Abramowitz and Stegun, page 259 eq 6.3.11
202 %!assert (imag (psi (i*z)), 1/2 .* 1./z + 1/2 * pi * coth (pi * z), eps *10)
203 
204 ## Abramowitz and Stegun, page 259 eq 6.3.12
205 %!assert (imag (psi (1/2 + i*z)), 1/2 * pi * tanh (pi * z), eps*10)
206 
207 ## Abramowitz and Stegun, page 259 eq 6.3.13
208 %!assert (imag (psi (1 + i*z)), - 1./(2*z) + 1/2 * pi * coth (pi * z), eps*10)
209 
210 ## Abramowitz and Stegun, page 260 eq 6.4.5
211 %!test
212 %! for z = 0:20
213 %! assert (psi (1, z + 0.5),
214 %! 0.5 * (pi^2) - 4 * sum ((2*(1:z) -1) .^(-2)),
215 %! eps*10);
216 %! endfor
217 
218 ## Abramowitz and Stegun, page 260 eq 6.4.6
219 %!test
220 %! z = 0.1:0.1:20;
221 %! for n = 0:8
222 %! ## our precision goes down really quick when computing n is too high.
223 %! assert (psi (n, z+1),
224 %! psi (n, z) + ((-1)^n) * factorial (n) * (z.^(-n-1)), 0.1);
225 %! endfor
226 
227 ## Test input validation
228 %!error psi ()
229 %!error psi (1, 2, 3)
230 %!error <Z must be> psi ("non numeric")
231 %!error <K must be an integer> psi ({5.3}, 1)
232 %!error <K must be non-negative> psi (-5, 1)
233 %!error <Z must be non-negative for polygamma> psi (5, -1)
234 %!error <Z must be a floating point> psi (5, uint8 (-1))
235 %!error <Z must be real value for polygamma> psi (5, 5i)
236 
237 */
238 
239 OCTAVE_END_NAMESPACE(octave)
bool isreal() const
Definition: ov.h:738
bool iscomplex() const
Definition: ov.h:741
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
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:988
double psi(double z)
Definition: lo-specfun.cc:2061
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)