changeset 25434:859ad1f0b85e

use templates to eliminate duplicate code in random number generators * randgamma.h, randgamma.cc: Use templates to eliminate nearly duplicate specializations. * randpoisson.h, randpoisson.cc: Likewise.
author John W. Eaton <jwe@octave.org>
date Sat, 17 Mar 2018 11:59:05 -0500
parents 49e0447413ad
children a52e6fb674b1
files liboctave/numeric/randgamma.cc liboctave/numeric/randgamma.h liboctave/numeric/randpoisson.cc liboctave/numeric/randpoisson.h
diffstat 4 files changed, 51 insertions(+), 244 deletions(-) [+]
line wrap: on
line diff
--- a/liboctave/numeric/randgamma.cc	Sat Mar 17 07:02:30 2018 -0500
+++ b/liboctave/numeric/randgamma.cc	Sat Mar 17 11:59:05 2018 -0500
@@ -89,37 +89,31 @@
 
 namespace octave
 {
-
-#define INFINITE lo_ieee_isinf
-#define RUNI rand_uniform<double> ()
-#define RNOR rand_normal<double> ()
-#define REXP rand_exponential<double> ()
-
-  template <> void rand_gamma<double> (double a, octave_idx_type n, double *r)
+  template <typename T> void rand_gamma (T a, octave_idx_type n, T *r)
   {
     octave_idx_type i;
     /* If a < 1, start by generating gamma (1+a) */
-    const double d = (a < 1. ? 1.+a : a) - 1./3.;
-    const double c = 1./std::sqrt (9.*d);
+    const T d = (a < 1. ? 1.+a : a) - 1./3.;
+    const T c = 1./std::sqrt (9.*d);
 
     /* Handle invalid cases */
-    if (a <= 0 || INFINITE(a))
+    if (a <= 0 || lo_ieee_isinf (a))
       {
         for (i=0; i < n; i++)
-          r[i] = numeric_limits<double>::NaN ();
+          r[i] = numeric_limits<T>::NaN ();
         return;
       }
 
     for (i=0; i < n; i++)
       {
-        double x, xsq, v, u;
+        T x, xsq, v, u;
       restart:
-        x = RNOR;
+        x = rand_normal<T> ();
         v = (1+c*x);
         v *= v*v;
         if (v <= 0)
           goto restart; /* rare, so don't bother moving up */
-        u = RUNI;
+        u = rand_uniform<T> ();
         xsq = x*x;
         if (u >= 1.-0.0331*xsq*xsq && std::log (u) >= 0.5*xsq + d*(1-v+std::log (v)))
           goto restart;
@@ -130,55 +124,10 @@
         /* Use gamma(a) = gamma(1+a)*U^(1/a) */
         /* Given REXP = -log(U) then U^(1/a) = exp(-REXP/a) */
         for (i = 0; i < n; i++)
-          r[i] *= exp (-REXP/a);
+          r[i] *= exp (-rand_exponential<T> () / a);
       }
   }
 
-#undef RUNI
-#undef RNOR
-#undef REXP
-
-#define RUNI rand_uniform<float> ()
-#define RNOR rand_normal<float> ()
-#define REXP rand_exponential<float> ()
-
-  template <> void rand_gamma<float> (float a, octave_idx_type n, float *r)
-  {
-    octave_idx_type i;
-    /* If a < 1, start by generating gamma(1+a) */
-    const float d = (a < 1. ? 1.+a : a) - 1./3.;
-    const float c = 1./std::sqrt (9.*d);
-
-    /* Handle invalid cases */
-    if (a <= 0 || INFINITE(a))
-      {
-        for (i=0; i < n; i++)
-          r[i] = numeric_limits<float>::NaN ();
-        return;
-      }
-
-    for (i=0; i < n; i++)
-      {
-        float x, xsq, v, u;
-      frestart:
-        x = RNOR;
-        v = (1+c*x);
-        v *= v*v;
-        if (v <= 0)
-          goto frestart; /* rare, so don't bother moving up */
-        u = RUNI;
-        xsq = x*x;
-        if (u >= 1.-0.0331*xsq*xsq && std::log (u) >= 0.5*xsq + d*(1-v+std::log (v)))
-          goto frestart;
-        r[i] = d*v;
-      }
-    if (a < 1)
-      {
-        /* Use gamma(a) = gamma(1+a)*U^(1/a) */
-        /* Given REXP = -log(U) then U^(1/a) = exp(-REXP/a) */
-        for (i = 0; i < n; i++)
-          r[i] *= exp (-REXP/a);
-      }
-  }
-
+  template void rand_gamma (double, octave_idx_type, double *);
+  template void rand_gamma (float, octave_idx_type, float *);
 }
--- a/liboctave/numeric/randgamma.h	Sat Mar 17 07:02:30 2018 -0500
+++ b/liboctave/numeric/randgamma.h	Sat Mar 17 11:59:05 2018 -0500
@@ -34,12 +34,6 @@
   void
   rand_gamma (T a, octave_idx_type n, T *p);
 
-  template <> void
-  rand_gamma<double> (double a, octave_idx_type n, double  *p);
-
-  template <> void
-  rand_gamma<float> (float a, octave_idx_type n, float  *p);
-
   template <typename T>
   T
   rand_gamma (T a)
--- a/liboctave/numeric/randpoisson.cc	Sat Mar 17 07:02:30 2018 -0500
+++ b/liboctave/numeric/randpoisson.cc	Sat Mar 17 11:59:05 2018 -0500
@@ -264,19 +264,23 @@
 
   /* The remainder of the file is by Paul Kienzle */
 
+  /* Table size is predicated on the maximum value of lambda
+   * we want to store in the table, and the maximum value of
+   * returned by the uniform random number generator on [0,1).
+   * With lambda==10 and u_max = 1 - 1/(2^32+1), we
+   * have poisson_pdf(lambda,36) < 1-u_max.  If instead our
+   * generator uses more bits of mantissa or returns a value
+   * in the range [0,1], then for lambda==10 we need a table
+   * size of 46 instead.  For long doubles, the table size
+   * will need to be longer still.  */
+#define TABLESIZE 46
+
   /* Given uniform u, find x such that CDF(L,x)==u.  Return x. */
-  static void poisson_cdf_lookup (double lambda, double *p, size_t n)
+
+  template <typename T>
+  static void
+  poisson_cdf_lookup (double lambda, T *p, size_t n)
   {
-    /* Table size is predicated on the maximum value of lambda
-     * we want to store in the table, and the maximum value of
-     * returned by the uniform random number generator on [0,1).
-     * With lambda==10 and u_max = 1 - 1/(2^32+1), we
-     * have poisson_pdf(lambda,36) < 1-u_max.  If instead our
-     * generator uses more bits of mantissa or returns a value
-     * in the range [0,1], then for lambda==10 we need a table
-     * size of 46 instead.  For long doubles, the table size
-     * will need to be longer still.  */
-#define TABLESIZE 46
     double t[TABLESIZE];
 
     /* Precompute the table for the u up to and including 0.458.
@@ -311,7 +315,7 @@
       nextk:
         if (u <= t[k])
           {
-            p[i] = static_cast<double> (k);
+            p[i] = static_cast<T> (k);
             continue;
           }
         if (++k < tableidx)
@@ -333,81 +337,14 @@
         /* We are assuming that the table size is big enough here.
          * This should be true even if rand_uniform is returning values in
          * the range [0,1] rather than [0,1). */
-        p[i] = static_cast<double> (tableidx-1);
-      }
-  }
-
-  static void poisson_cdf_lookup_float (double lambda, float *p, size_t n)
-  {
-    double t[TABLESIZE];
-
-    /* Precompute the table for the u up to and including 0.458.
-     * We will almost certainly need it. */
-    int intlambda = static_cast<int> (std::floor (lambda));
-    double P;
-    int tableidx;
-    size_t i = n;
-
-    t[0] = P = exp (-lambda);
-    for (tableidx = 1; tableidx <= intlambda; tableidx++)
-      {
-        P = P*lambda/static_cast<double> (tableidx);
-        t[tableidx] = t[tableidx-1] + P;
-      }
-
-    while (i-- > 0)
-      {
-        double u = octave::rand_uniform<double> ();
-        int k = (u > 0.458 ? intlambda : 0);
-      nextk:
-        if (u <= t[k])
-          {
-            p[i] = static_cast<float> (k);
-            continue;
-          }
-        if (++k < tableidx)
-          goto nextk;
-
-        while (tableidx < TABLESIZE)
-          {
-            P = P*lambda/static_cast<double> (tableidx);
-            t[tableidx] = t[tableidx-1] + P;
-            if (t[tableidx] == t[tableidx-1]) t[tableidx] = 1.0;
-            tableidx++;
-            if (u <= t[tableidx-1]) break;
-          }
-
-        p[i] = static_cast<float> (tableidx-1);
+        p[i] = static_cast<T> (tableidx-1);
       }
   }
 
   /* From Press, et al., Numerical Recipes */
-  static void poisson_rejection (double lambda, double *p, size_t n)
-  {
-    double sq = std::sqrt (2.0*lambda);
-    double alxm = std::log (lambda);
-    double g = lambda*alxm - xlgamma (lambda+1.0);
-    size_t i;
-
-    for (i = 0; i < n; i++)
-      {
-        double y, em, t;
-        do
-          {
-            do
-              {
-                y = tan (M_PI*octave::rand_uniform<double> ());
-                em = sq * y + lambda;
-              } while (em < 0.0);
-            em = std::floor (em);
-            t = 0.9*(1.0+y*y)*exp (em*alxm-flogfak (em)-g);
-          } while (octave::rand_uniform<double> () > t);
-        p[i] = em;
-      }
-  }
-
-  /* From Press, et al., Numerical Recipes */
-  static void poisson_rejection_float (double lambda, float *p, size_t n)
+  template <typename T>
+  static void
+  poisson_rejection (double lambda, T *p, size_t n)
   {
     double sq = std::sqrt (2.0*lambda);
     double alxm = std::log (lambda);
@@ -442,17 +379,18 @@
    * and large enough. */
 
   /* Generate a set of poisson numbers with the same distribution */
-  template <> void rand_poisson<double> (double L, octave_idx_type n, double *p)
+  template <typename T> void rand_poisson (T L_arg, octave_idx_type n, T *p)
   {
+    double L = L_arg;
     octave_idx_type i;
     if (L < 0.0 || lo_ieee_isinf (L))
       {
         for (i=0; i<n; i++)
-          p[i] = numeric_limits<double>::NaN ();
+          p[i] = numeric_limits<T>::NaN ();
       }
     else if (L <= 10.0)
       {
-        poisson_cdf_lookup (L, p, n);
+        poisson_cdf_lookup<T> (L, p, n);
       }
     else if (L <= 1e8)
       {
@@ -465,18 +403,22 @@
         const double sqrtL = std::sqrt (L);
         for (i = 0; i < n; i++)
           {
-            p[i] = std::floor (rand_normal<double> () * sqrtL + L + 0.5);
+            p[i] = std::floor (rand_normal<T> () * sqrtL + L + 0.5);
             if (p[i] < 0.0)
               p[i] = 0.0; /* will probably never happen */
           }
       }
   }
 
+  template void rand_poisson<double> (double, octave_idx_type, double *);
+  template void rand_poisson<float> (float, octave_idx_type, float *);
+
   /* Generate one poisson variate */
-  template <> double rand_poisson<double> (double L)
+  template <typename T> T rand_poisson (T L_arg)
   {
-    double ret;
-    if (L < 0.0) ret = numeric_limits<double>::NaN ();
+    double L = L_arg;
+    T ret;
+    if (L < 0.0) ret = numeric_limits<T>::NaN ();
     else if (L <= 12.0)
       {
         /* From Press, et al. Numerical recipes */
@@ -486,98 +428,30 @@
         do
           {
             ++em;
-            t *= rand_uniform<double> ();
+            t *= rand_uniform<T> ();
           } while (t > g);
         ret = em;
       }
     else if (L <= 1e8)
       {
         /* numerical recipes */
-        poisson_rejection (L, &ret, 1);
+        poisson_rejection<T> (L, &ret, 1);
       }
     else if (lo_ieee_isinf (L))
       {
         /* FIXME: R uses NaN, but the normal approximation suggests that
          * limit should be Inf.  Which is correct? */
-        ret = numeric_limits<double>::NaN ();
+        ret = numeric_limits<T>::NaN ();
       }
     else
       {
         /* normal approximation: from Phys. Rev. D (1994) v50 p1284 */
-        ret = std::floor (rand_normal<double> () * std::sqrt (L) + L + 0.5);
+        ret = std::floor (rand_normal<T> () * std::sqrt (L) + L + 0.5);
         if (ret < 0.0) ret = 0.0; /* will probably never happen */
       }
     return ret;
   }
 
-  /* Generate a set of poisson numbers with the same distribution */
-  template <> void rand_poisson<float> (float FL, octave_idx_type n, float *p)
-  {
-    double L = FL;
-    octave_idx_type i;
-    if (L < 0.0 || lo_ieee_isinf (L))
-      {
-        for (i=0; i<n; i++)
-          p[i] = numeric_limits<double>::NaN ();
-      }
-    else if (L <= 10.0)
-      {
-        poisson_cdf_lookup_float (L, p, n);
-      }
-    else if (L <= 1e8)
-      {
-        for (i=0; i<n; i++)
-          p[i] = pprsc (L);
-      }
-    else
-      {
-        /* normal approximation: from Phys. Rev. D (1994) v50 p1284 */
-        const double sqrtL = std::sqrt (L);
-        for (i = 0; i < n; i++)
-          {
-            p[i] = std::floor (rand_normal<float> () * sqrtL + L + 0.5);
-            if (p[i] < 0.0)
-              p[i] = 0.0; /* will probably never happen */
-          }
-      }
-  }
-
-  /* Generate one poisson variate */
-  template <> float rand_poisson<float> (float FL)
-  {
-    double L = FL;
-    float ret;
-    if (L < 0.0) ret = numeric_limits<float>::NaN ();
-    else if (L <= 12.0)
-      {
-        /* From Press, et al. Numerical recipes */
-        double g = exp (-L);
-        int em = -1;
-        double t = 1.0;
-        do
-          {
-            ++em;
-            t *= rand_uniform<float> ();
-          } while (t > g);
-        ret = em;
-      }
-    else if (L <= 1e8)
-      {
-        /* numerical recipes */
-        poisson_rejection_float (L, &ret, 1);
-      }
-    else if (lo_ieee_isinf (L))
-      {
-        /* FIXME: R uses NaN, but the normal approximation suggests that
-         * limit should be Inf. Which is correct? */
-        ret = numeric_limits<float>::NaN ();
-      }
-    else
-      {
-        /* normal approximation: from Phys. Rev. D (1994) v50 p1284 */
-        ret = std::floor (rand_normal<float> () * std::sqrt (L) + L + 0.5);
-        if (ret < 0.0) ret = 0.0; /* will probably never happen */
-      }
-    return ret;
-  }
+  template double rand_poisson<double> (double);
+  template float rand_poisson<float> (float);
 }
--- a/liboctave/numeric/randpoisson.h	Sat Mar 17 07:02:30 2018 -0500
+++ b/liboctave/numeric/randpoisson.h	Sat Mar 17 11:59:05 2018 -0500
@@ -30,19 +30,9 @@
 
 namespace octave
 {
-  template <typename T> T rand_poisson (T L);
-
-  template <> double rand_poisson<double> (double L);
-  template <> float rand_poisson<float> (float L);
+  template <typename T> void rand_poisson (T L, octave_idx_type n, T *p);
 
-  template <typename T> void
-  rand_poisson (T L, octave_idx_type n, T *p);
-
-  template <> void
-  rand_poisson<double> (double L, octave_idx_type n, double *p);
-
-  template <> void
-  rand_poisson<float> (float L, octave_idx_type n, float *p);
+  template <typename T> T rand_poisson (T L);
 }
 
 OCTAVE_DEPRECATED (4.4, "use 'octave::rand_poisson<double>' instead")