GNU Octave 11.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
psi.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2016-2026 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 "oct-specfun.h"
31
32#include "ov.h"
33#include "defun.h"
34#include "error.h"
35#include "dNDArray.h"
36#include "fNDArray.h"
37
39
40DEFUN (psi, args, ,
41 doc: /* -*- texinfo -*-
42@deftypefn {} {@var{y} =} psi (@var{z})
43@deftypefnx {} {@var{y} =} 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@tex
53$$
54\Psi (z) = {d (log (\Gamma (z))) \over dx}
55$$
56@end tex
57@ifnottex
58
59@example
60@group
61psi (z) = d (log (gamma (z))) / dx
62@end group
63@end example
64
65@end ifnottex
66
67When computing the digamma function (when @var{k} equals zero), @var{z}
68can have any value real or complex value. However, for polygamma functions
69(@var{k} higher than 0), @var{z} must be real and non-negative.
70
71@seealso{gamma, gammainc, gammaln}
72@end deftypefn */)
73{
74 int nargin = args.length ();
75
76 if (nargin < 1 || nargin > 2)
77 print_usage ();
78
79 const octave_value oct_z = (nargin == 1) ? args(0) : args(1);
80 const octave_idx_type k = (nargin == 1) ? 0 : args(0).strict_idx_type_value ("psi: K must be an integer");
81 if (k < 0)
82 error ("psi: K must be non-negative");
83
84 octave_value retval;
85
86 if (k == 0)
87 {
88#define FLOAT_BRANCH(T, A, M, E) \
89 if (oct_z.is_ ## T ##_type ()) \
90 { \
91 const A ## NDArray z = oct_z.M ## array_value (); \
92 A ## NDArray psi_z (z.dims ()); \
93 \
94 const E *zv = z.data (); \
95 E *psi_zv = psi_z.rwdata (); \
96 const octave_idx_type n = z.numel (); \
97 for (octave_idx_type i = 0; i < n; i++) \
98 *psi_zv++ = math::psi (*zv++); \
99 \
100 retval = psi_z; \
101 }
102
103 if (oct_z.iscomplex ())
104 {
105 FLOAT_BRANCH(double, Complex, complex_, Complex)
106 else FLOAT_BRANCH(single, FloatComplex, float_complex_, FloatComplex)
107 else
108 error ("psi: Z must be a floating point");
109 }
110 else
111 {
112 FLOAT_BRANCH(double,,, double)
113 else FLOAT_BRANCH(single, Float, float_, float)
114 else
115 error ("psi: Z must be a floating point");
116 }
117
118#undef FLOAT_BRANCH
119 }
120 else
121 {
122 if (! oct_z.isreal ())
123 error ("psi: Z must be real value for polygamma (K > 0)");
124
125#define FLOAT_BRANCH(T, A, M, E) \
126 if (oct_z.is_ ## T ##_type ()) \
127 { \
128 const A ## NDArray z = oct_z.M ## array_value (); \
129 A ## NDArray psi_z (z.dims ()); \
130 \
131 const E *zv = z.data (); \
132 E *psi_zv = psi_z.rwdata (); \
133 const octave_idx_type n = z.numel (); \
134 for (octave_idx_type i = 0; i < n; i++) \
135 { \
136 if (*zv < 0) \
137 error ("psi: Z must be non-negative for polygamma (K > 0)"); \
138 \
139 *psi_zv++ = math::psi (k, *zv++); \
140 } \
141 retval = psi_z; \
142 }
143
144 FLOAT_BRANCH(double,,, double)
145 else FLOAT_BRANCH(single, Float, float_, float)
146 else
147 error ("psi: Z must be a floating point for polygamma (K > 0)");
148
149#undef FLOAT_BRANCH
150 }
151
152 return retval;
153}
154
155/*
156%!shared em
157%! em = 0.577215664901532860606512090082402431042; # Euler-Mascheroni Constant
158
159%!assert (psi (ones (7, 3, 5)), repmat (-em, [7 3 5]))
160%!assert (psi ([0 1]), [-Inf -em])
161%!assert (psi ([-20:1]), [repmat(-Inf, [1 21]) -em])
162%!assert (psi (single ([0 1])), single ([-Inf -em]))
163
164## Abramowitz and Stegun, page 258, eq 6.3.5
165%!test
166%! z = [-100:-1 1:200] ./ 10; # drop the 0
167%! assert (psi (z + 1), psi (z) + 1 ./ z, eps*1000);
168
169## Abramowitz and Stegun, page 258, eq 6.3.2
170%!assert (psi (1), -em)
171
172## Abramowitz and Stegun, page 258, eq 6.3.3
173%!assert (psi (1/2), -em - 2 * log (2))
174
175## The following tests are from Pascal Sebah and Xavier Gourdon (2002)
176## "Introduction to the Gamma Function"
177
178## Interesting identities of the digamma function, in section of 5.1.3
179%!assert (psi (1/3), - em - (3/2) * log (3) - ((sqrt (3) / 6) * pi), eps*10)
180%!assert (psi (1/4), - em -3 * log (2) - pi/2, eps*10)
181%!assert (psi (1/6),
182%! - 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_END_NAMESPACE(octave)
bool isreal() const
Definition ov.h:736
bool iscomplex() const
Definition ov.h:739
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage()
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:1008
std::complex< double > Complex
Definition oct-cmplx.h:33
std::complex< float > FloatComplex
Definition oct-cmplx.h:34
double psi(double z)
#define FLOAT_BRANCH(T, A, M, E)