core/vnl/vnl_gamma.cxx
Go to the documentation of this file.
00001 // This is core/vnl/vnl_gamma.cxx
00002 #include "vnl_gamma.h"
00003 //:
00004 // \file
00005 // \brief Complete and incomplete gamma function approximations
00006 // \author Tim Cootes
00007 
00008 #include <vcl_iostream.h>
00009 #include <vcl_cassert.h>
00010 
00011 #if defined(__INTEL_COMPILER)
00012 # pragma warning (disable:279) /* controlling expression is constant */
00013 #endif
00014 
00015 //: Approximate gamma function
00016 //  Uses 6 parameter Lanczos approximation as described by Viktor Toth
00017 //  (http://www.rskey.org/gamma.htm)
00018 //  Accurate to about 3e-11.
00019 double vnl_log_gamma(double x)
00020 {
00021   double zp = 2.50662827563479526904;
00022   zp += 225.525584619175212544/x;
00023   zp -= 268.295973841304927459/(x+1.0);
00024   zp += 80.9030806934622512966/(x+2.0);
00025   zp -= 5.00757863970517583837/(x+3.0);
00026   zp += 0.0114684895434781459556/(x+4.0);
00027 
00028   double x1 = x+4.65;
00029 
00030   return vcl_log(zp)+(x-0.5)*vcl_log(x1)-x1;
00031 }
00032 
00033 const int MAX_ITS = 100;
00034 const double MaxRelError = 3.0e-7;
00035 const double vnl_very_small = 1.0e-30;
00036 
00037 //: Use series expansion of incomplete gamma function
00038 static double vnl_gamma_series(double a, double x)
00039 {
00040   if (x>0)
00041   {
00042     double a_i=a;
00043     double term_i=1.0/a;
00044     double sum = term_i;
00045     for (int i=1;i<=MAX_ITS;++i)
00046     {
00047       a_i+=1;
00048       term_i *= x/a_i;
00049       sum += term_i;
00050       if (vcl_fabs(term_i) < vcl_fabs(sum)*MaxRelError)
00051         return sum*vcl_exp(-x+a*vcl_log(x)-vnl_log_gamma(a));
00052     }
00053     vcl_cerr<<"vnl_gamma_series : Failed to converge in "<<MAX_ITS<<" steps\n"
00054             <<"a = "<<a<<"   x= "<< x <<"\nReturning best guess.\n";
00055     return sum*vcl_exp(-x+a*vcl_log(x)-vnl_log_gamma(a));
00056   }
00057   else if (x < 0.0)
00058     assert(!"vnl_gamma_series - x less than 0");
00059 
00060   return 0.0;
00061 }
00062 
00063 //: Incomplete gamma using continued fraction representation
00064 // Use Lentz's algorithm
00065 // Continued fraction with terms a_i/b_i
00066 // a_i = i*(a-i), b_i = (x+a-2i)
00067 static double vnl_gamma_cont_frac(double a, double x)
00068 {
00069   double b_i=x+1.0-a;
00070   double c=1.0/vnl_very_small;
00071   double d=1.0/b_i;
00072   double cf=d;
00073   for (int i=1;i<=MAX_ITS;i++)
00074   {
00075     double a_i = i*(a-i);
00076     b_i += 2.0;
00077     d=a_i*d+b_i;
00078     if (vcl_fabs(d) < vnl_very_small) d=vnl_very_small;
00079     c=b_i+a_i/c;
00080     if (vcl_fabs(c) < vnl_very_small) c=vnl_very_small;
00081     d=1.0/d;
00082     double delta=d*c;
00083     cf *= delta;
00084     if (vcl_fabs(delta-1.0) < MaxRelError)
00085       return vcl_exp(-x+a*vcl_log(x)-vnl_log_gamma(a))*cf;
00086   }
00087 
00088   vcl_cerr<<"vnl_gamma_cont_frac : Failed to converge in "<<MAX_ITS<<" steps\n"
00089           <<"a = "<<a<<"   x= "<<x<<vcl_endl;
00090   return vcl_exp(-x+a*vcl_log(x)-vnl_log_gamma(a))*cf;
00091 }
00092 
00093 double vnl_gamma_p(double a, double x)
00094 {
00095   if (x < 0.0 || a <= 0.0)
00096     assert(!"vnl_gamma_p - Invalid arguments.");
00097 
00098   if (x < a+1.0)
00099     return vnl_gamma_series(a,x); // Use series representation
00100   else
00101     return 1.0 - vnl_gamma_cont_frac(a,x); // Use continued fraction representation
00102 }
00103 
00104 double vnl_gamma_q(double a, double x)
00105 {
00106   if (x < 0.0 || a <= 0.0)
00107     assert(!"vnl_gamma_q - Invalid arguments.");
00108 
00109   if (x < a+1.0)
00110     return 1.0-vnl_gamma_series(a,x); // Use series representation
00111   else
00112     return vnl_gamma_cont_frac(a,x); // Use continued fraction representation
00113 }
00114 
00115 double vnl_digamma(double z)
00116 {
00117   double t0 = (z-0.5)/(z+4.65)-1.0;
00118   double tlg = vcl_log(4.65+z);
00119   double tc = 2.50662827563479526904;
00120   double t1 = 225.525584619175212544/z;
00121   double t2 = -268.295973841304927459/(1+z);
00122   double t3 = +80.9030806934622512966/(2+z);
00123   double t4 = -5.00757863970517583837/(3+z);
00124   double t5 = 0.0114684895434781459556/(4+z);
00125   double neu = t1/z + t2/(1+z) + t3/(2+z) + t4/(3+z) + t5/(4+z);
00126   double den = tc + t1 + t2 + t3 + t4 + t5;
00127   return t0 -(neu/den) + tlg;
00128 }