/*
* Roby Joehanes
*
* Copyright 2007 Roby Joehanes
* This file is distributed under the GNU General Public License version 3.0.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package jdistlib.math;
import static java.lang.Math.*;
// Ported from Distlib by hand by Roby Joehanes
// Incorporated some changes from R as well.
/**
*
* Mathlib : A C Library of Special Functions
* Copyright (C) 1998 Ross Ihaka
* Copyright (C) 2000-2007 the R Development Core Team
* Copyright (C) 2004 The R Foundation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, a copy is available at
* http://www.r-project.org/Licenses/
*
* SYNOPSIS
*
* #include
* double[] dpsifn(double x, int n, int kode, int m)
* double digamma(double x);
* double trigamma(double x)
* double tetragamma(double x)
* double pentagamma(double x)
* double psigamma(double x, double n)
*
* DESCRIPTION
*
* Compute the derivatives of the psi function
* and polygamma functions.
*
* The following definitions are used in dpsifn:
*
* Definition 1
*
* psi(x) = d/dx (ln(gamma(x)), the first derivative of
* the log gamma function.
*
* Definition 2
* k k
* psi(k,x) = d /dx (psi(x)), the k-th derivative
* of psi(x).
*
*
* "dpsifn" computes a sequence of scaled derivatives of
* the psi function; i.e. for fixed x and m it computes
* the m-member sequence
*
* (-1)^(k+1) / gamma(k+1) * psi(k,x)
* for k = n,...,n+m-1
*
* where psi(k,x) is as defined above. For kode=1, dpsifn
* returns the scaled derivatives as described. kode=2 is
* operative only when k=0 and in that case dpsifn returns
* -psi(x) + ln(x). That is, the logarithmic behavior for
* large x is removed when kode=2 and k=0. When sums or
* differences of psi functions are computed the logarithmic
* terms can be combined analytically and computed separately
* to help retain significant digits.
*
* Note that dpsifn(x, 0, 1, 1, ans) results in ans = -psi(x).
*
* INPUT
*
* x - argument, x > 0.
*
* n - first member of the sequence, 0 <= n <= 100
* n == 0 gives ans(1) = -psi(x) for kode=1
* -psi(x)+ln(x) for kode=2
*
* kode - selection parameter
* kode == 1 returns scaled derivatives of the
* psi function.
* kode == 2 returns scaled derivatives of the
* psi function except when n=0. In this case,
* ans(1) = -psi(x) + ln(x) is returned.
*
* m - number of members of the sequence, m >= 1
*
* OUTPUT
*
* ans - a vector of length at least m whose first m
* components contain the sequence of derivatives
* scaled according to kode.
*
* nz - underflow flag
* nz == 0, a normal return
* nz != 0, underflow, last nz components of ans are
* set to zero, ans(m-k+1)=0.0, k=1,...,nz
*
* ierr - error flag
* ierr=0, a normal return, computation completed
* ierr=1, input error, no computation
* ierr=2, overflow, x too small or n+m-1 too
* large or both
* ierr=3, error, n too large. dimensioned
* array trmr(nmax) is not large enough for n
*
* The nominal computational accuracy is the maximum of unit
* roundoff (d1mach(4)) and 1e-18 since critical constants
* are given to only 18 digits.
*
* The basic method of evaluation is the asymptotic expansion
* for large x >= xmin followed by backward recursion on a two
* term recursion relation
*
* w(x+1) + x^(-n-1) = w(x).
*
* this is supplemented by a series
*
* sum( (x+k)^(-n-1) , k=0,1,2,... )
*
* which converges rapidly for large n. both xmin and the
* number of terms of the series are calculated from the unit
* roundoff of the machine environment.
*
* AUTHOR
*
* Amos, D. E. (Fortran)
* Ross Ihaka (C Translation)
* Martin Maechler (x < 0, and psigamma())
* Roby Joehanes (Java translation)
*
* REFERENCES
*
* Handbook of Mathematical Functions,
* National Bureau of Standards Applied Mathematics Series 55,
* Edited by M. Abramowitz and I. A. Stegun, equations 6.3.5,
* 6.3.18, 6.4.6, 6.4.9 and 6.4.10, pp.258-260, 1964.
*
* D. E. Amos, (1983). "A Portable Fortran Subroutine for
* Derivatives of the Psi Function", Algorithm 610,
* TOMS 9(4), pp. 494-502.
*
*
*/
public class PolyGamma
{
private static final double
klog10Of2 = log10(2),
kDefaultWDTol = max(pow(2, -53), 0.5e-18);
private static final int
kMaxValue = 100,
DBL_MANT_DIG = 53,
DBL_MIN_EXP = -1021;
private static final String sErrorDomain = "Math Error: DOMAIN"; //$NON-NLS-1$
// Bernoulli Numbers
static private double bvalues[] = {
1.00000000000000000e+00,
-5.00000000000000000e-01,
1.66666666666666667e-01,
-3.33333333333333333e-02,
2.38095238095238095e-02,
-3.33333333333333333e-02,
7.57575757575757576e-02,
-2.53113553113553114e-01,
1.16666666666666667e+00,
-7.09215686274509804e+00,
5.49711779448621554e+01,
-5.29124242424242424e+02,
6.19212318840579710e+03,
-8.65802531135531136e+04,
1.42551716666666667e+06,
-2.72982310678160920e+07,
6.01580873900642368e+08,
-1.51163157670921569e+10,
4.29614643061166667e+11,
-1.37116552050883328e+13,
4.88332318973593167e+14,
-1.92965793419400681e+16
};
public static final double[] dpsifn(double x, int n, int kode, int m) {
double ans[] = new double[n + 1];
int i, j, k, mm, mx, nn, np, nx, fn;
double arg, den, elim, eps, fln, fx, rln, rxsq;
double s, slope, t, ta, tk, tol, tols, tss, tst;
double tt, t1, t2, xdmln, xdmy = 0, xinc = 0, xln = 0, xm, xmin;
double xq, yint;
double trm[] = new double[23], trmr[] = new double[kMaxValue + 1];
boolean flag1 = false;
if (n < 0 || kode < 1 || kode > 2 || m < 1)
return null;
if (x <= 0.) {
/* use Abramowitz & Stegun 6.4.7 "Reflection Formula"
* psi(k, x) = (-1)^k psi(k, 1-x) - pi^{n+1} (d/dx)^n cot(x)
*/
if (x == (long)x) {
/* non-positive integer : +Inf or NaN depends on n */
for(j=0; j < m; j++) /* k = j + n : */
ans[j] = ((j+n) % 2 == 1) ? Double.POSITIVE_INFINITY : Double.NaN;
return ans;
}
dpsifn(1. - x, n, 1, m);
/* ans[j] == (-1)^(k+1) / gamma(k+1) * psi(k, 1 - x)
* for j = 0:(m-1) , k = n + j
*/
/* Cheat for now: only work for m = 1, n in {0,1,2,3} : */
if(m > 1 || n > 3) /* doesn't happen for digamma() .. pentagamma() */
return null;
x *= PI; /* pi * x */
if (n == 0)
tt = cos(x)/sin(x);
else if (n == 1)
tt = -1/pow(sin(x),2);
else if (n == 2)
tt = 2*cos(x)/pow(sin(x),3);
else if (n == 3)
tt = -2*(2* pow(cos(x),2) + 1)/pow(sin(x),4);
else /* can not happen! */
tt = Double.NaN;
/* end cheat */
s = (n % 2 == 1) ? -1. : 1.;/* s = (-1)^n */
/* t := pi^(n+1) * d_n(x) / gamma(n+1) , where
* d_n(x) := (d/dx)^n cot(x)*/
t1 = t2 = s = 1.;
for(k=0, j=k-n; j < m; k++, j++, s = -s) {
/* k == n+j , s = (-1)^k */
t1 *= PI;/* t1 == pi^(k+1) */
if(k >= 2)
t2 *= k;/* t2 == k! == gamma(k+1) */
if(j >= 0) /* by cheat above, tt === d_k(x) */
ans[j] = s*(ans[j] + t1/t2 * tt);
}
if (n == 0 && kode == 2)
ans[0] += xln;
return ans;
} /* x <= 0 */
//nz = 0;
mm = m;
nx = -DBL_MIN_EXP; //min(-DBL_MIN_EXP, DBL_MAX_EXP);
//r1m5 = klog10Of2;
//r1m4 = pow(FLT_RADIX, 1-DBL_MANT_DIG) * 0.5;
//wdtol = kDefaultWDTol; //max(pow(FLT_RADIX, 1-DBL_MANT_DIG) * 0.5, 0.5e-18);
/* elim = approximate exponential over and underflow limit */
elim = 2.302 * (nx * klog10Of2 - 3.0);
xln = log(x);
xdmln = xln;
for(;;) {
nn = n + mm - 1;
fn = nn;
t = (fn + 1) * xln;
/* overflow and underflow test for small and large x */
/*!* if (fabs(t) > elim) { *!*/
if (abs(t) > elim) {
if (t <= 0.0)
return null;
}
else {
if (x < kDefaultWDTol) {
ans[0] = pow(x, -n-1.0);
if (mm != 1) {
for(k = 1; k < mm ; k++)
ans[k] = ans[k-1] / x;
}
if (n == 0 && kode == 2)
ans[0] += xln;
return ans;
}
/* compute xmin and the number of terms of the series, fln+1 */
rln = klog10Of2 * DBL_MANT_DIG;
rln = min(rln, 18.06);
/*!* fln = fmax2(rln, 3.0) - 3.0; *!*/
fln = max(rln, 3.0) - 3.0;
yint = 3.50 + 0.40 * fln;
slope = 0.21 + fln * (0.0006038 * fln + 0.008677);
xm = yint + slope * fn;
mx = (int)xm + 1;
xmin = mx;
if (n != 0) {
xm = -2.302 * rln - min(0.0, xln);
arg = xm / n;
arg = min(0.0, arg);
eps = exp(arg);
xm = 1.0 - eps;
if (abs(arg) < 1.0e-3)
xm = -arg;
fln = x * xm / eps;
xm = xmin - x;
if (xm > 7.0 && fln < 15.0)
break;
}
xdmy = x;
xdmln = xln;
xinc = 0.0;
if (x < xmin) {
nx = (int)x;
xinc = xmin - nx;
xdmy = x + xinc;
xdmln = log(xdmy);
}
/* generate w(n+mm-1, x) by the asymptotic expansion */
t = fn * xdmln;
t1 = xdmln + xdmln;
t2 = t + xdmln;
/*!* tk = fmax2(fabs(t), fmax2(fabs(t1), fabs(t2))); *!*/
tk = max(abs(t), max(abs(t1), abs(t2)));
if (tk <= elim)
{
flag1 = true;
break;
}
}
//nz++;
mm--;
ans[mm] = 0.0;
if (mm == 0)
return ans;
} // end for(;;;)
if (!flag1)
{
nn = (int)fln + 1;
np = n + 1;
t1 = (n + 1) * xln;
t = exp(-t1);
s = t;
den = x;
for(i=1 ; i<=nn ; i++) {
den = den + 1.0;
trm[i] = pow(den, -np);
s += trm[i];
}
ans[0] = s;
if (n == 0 && kode == 2)
ans[0] = s + xln;
if (mm!=1) {
/* generate higher derivatives, j > n */
tol = kDefaultWDTol / 5.0;
for(j=1; j= tst) { *!*/
if (abs(s) >= tst) {
tk = 2.0;
for(k=4 ; k<=22 ; k++) {
t = t * ((tk + fn + 1)/(tk + 1.0))*((tk + fn)/(tk + 2.0)) * rxsq;
trm[k] = t * bvalues[k - 1];
/*!* if (fabs(trm[k]) < tst) *!*/
if (abs(trm[k]) < tst)
break;
s += trm[k];
tk += 2.0;
}
}
s = (s + t1) * tss;
if (xinc != 0.0) {
/* backward recur from xdmy to x */
nx = (int)xinc;
np = nn + 1;
if (nx > kMaxValue)
return null;
if (nn==0)
{
for(i=1 ; i<=nx ; i++)
s += 1.0 / (x + nx - i);
if (kode!=2)
ans[0] = s - xdmln;
else if (xdmy != x) {
xq = xdmy / x;
ans[0] = s - log(xq);
}
return ans;
}
xm = xinc - 1.0;
fx = x + xm;
/* this loop should not be changed. fx is accurate when x is small */
for(i=1 ; i<=nx ; i++) {
trmr[i] = pow(fx, -np);
s += trmr[i];
xm -= 1.0;
fx = x + xm;
}
}
ans[mm - 1] = s;
if (fn == 0)
{
if (kode!=2)
ans[0] = s - xdmln;
else if (xdmy != x) {
xq = xdmy / x;
ans[0] = s - log(xq);
}
return ans;
}
/* generate lower derivatives, j < n+mm-1 */
for(j=2 ; j<=mm ; j++) {
fn--;
tss *= xdmy;
t1 = tt;
if (fn!=0)
t1 = tt + 1.0 / fn;
t = (fn + 1) * ta;
s = t * bvalues[2];
if (abs(s) >= tst) {
tk = 4 + fn;
for(k=4 ; k<=22 ; k++) {
trm[k] = trm[k] * (fn + 1) / tk;
if (abs(trm[k]) < tst)
break;
s += trm[k];
tk += 2.0;
}
}
s = (s + t1) * tss;
if (xinc != 0.0) {
if (fn == 0)
{
for(i=1 ; i<=nx ; i++)
s += 1.0 / (x + nx - i);
if (kode!=2)
ans[0] = s - xdmln;
else if (xdmy != x) {
xq = xdmy / x;
ans[0] = s - log(xq);
}
}
xm = xinc - 1.0;
fx = x + xm;
for(i=1 ; i<=nx ; i++) {
trmr[i] = trmr[i] * fx;
s += trmr[i];
xm -= 1.0;
fx = x + xm;
}
}
ans[mm - j] = s;
if (fn == 0)
{
if (kode!=2)
ans[0] = s - xdmln;
else if (xdmy != x) {
xq = xdmy / x;
ans[0] = s - log(xq);
}
return ans;
}
} // end for(j=2 ; j<=mm ; j++)
return ans;
}
public static final double psigamma(double x, int n) {
/* n-th derivative of psi(x); e.g., psigamma(x,0) == digamma(x) */
double[] ans;
//int n = (int) rint(deriv);
//if(n > kMaxValue) return Double.NaN;
ans = dpsifn(x, n, 1, 1);
if(ans == null)
return Double.NaN;
/* ans == A := (-1)^(n+1) / gamma(n+1) * psi(n, x) */
double result = -ans[0]; /* = (-1)^(0+1) * gamma(0+1) * A */
for(int k = 1; k <= n; k++)
result *= (-k);/* = (-1)^(k+1) * gamma(k+1) * A */
return result;/* = psi(n, x) */
}
public static final double digamma(double x) {
double ans[] = dpsifn(x, 0, 1, 1);
if(ans == null)
throw new ArithmeticException(sErrorDomain);
return -ans[0];
}
public static final double trigamma(double x) {
double ans[] = dpsifn(x, 1, 1, 1);
if(ans == null)
throw new ArithmeticException(sErrorDomain);
return ans[0];
}
public static final double tetragamma(double x) {
double ans[] = dpsifn(x, 2, 1, 1);
if(ans == null)
throw new ArithmeticException(sErrorDomain);
return -2.0 * ans[0];
}
public static final double pentagamma(double x) {
double ans[] = dpsifn(x, 3, 1, 1);
if(ans == null)
throw new ArithmeticException(sErrorDomain);
return 6.0 * ans[0];
}
public static final double[] psigamma(double[] x, int deriv) {
int n = x.length;
double[] r = new double[n];
for (int i = 0; i < n; i++)
r[i] = psigamma(x[i], deriv);
return r;
}
public static final double[] digamma(double[] x) {
return psigamma(x, 0);
}
public static final double[] trigamma(double[] x) {
return psigamma(x, 1);
}
public static final double[] tetragamma(double[] x) {
return psigamma(x, 2);
}
public static final double[] pentagamma(double[] x) {
return psigamma(x, 3);
}
/**
* Log of multivariate psigamma function
* By: Roby Joehanes
* @param a
* @param p the dimension or order
* @param deriv digamma = 0, trigamma = 1, ... etc.
* @return log multivariate psigamma
*/
public static final double lmvpsigammafn(double a, int p, int deriv) {
double sum = 0;
for (int j = 1; j <= p; j++)
sum += log(psigamma(a + (1-j)/2.0, deriv));
return sum;
}
}
Ads help maintain this website.