[gnumeric] fn-r: fix problem with R.PSNORM



commit 7a8f7a86e7c4ef5795c6ad95ecc66d46da6ef5be
Author: Morten Welinder <terra gnome org>
Date:   Fri Apr 5 08:50:58 2013 -0400

    fn-r: fix problem with R.PSNORM
    
    Our gnm_owent implementation has accuracy problems for the small-h-large-a
    case.  And perhaps others.

 ChangeLog              |    4 +
 NEWS                   |    1 +
 plugins/fn-r/ChangeLog |    5 +
 plugins/fn-r/extra.c   |  112 ++++---------------
 src/mathfunc.c         |  287 +++++++++++++++++++++++++++++++++++++++++++++++-
 src/mathfunc.h         |    1 +
 6 files changed, 319 insertions(+), 91 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 0bdb44a..c0a1044 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2013-04-05  Morten Welinder  <terra gnome org>
+
+       * src/mathfunc.c (gnm_owent): New function.
+
 2013-04-04  Jean Brefort  <jean brefort normalesup org>
 
        * src/graph.c (gnm_go_data_vector_load_len): correctly evaluate array
diff --git a/NEWS b/NEWS
index 8339db5..94f7ba0 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,7 @@ Morten:
        * Make it easier to see what sheet is selected.  [#659317]
        * Implement R.PST, R.QST, and R.QSNORM.
        * Fix dependency tabulation.
+       * FIx problems with R.PSNORM.  [#697293]
 
 --------------------------------------------------------------------------
 Gnumeric 1.12.1
diff --git a/plugins/fn-r/ChangeLog b/plugins/fn-r/ChangeLog
index ced672b..6af0051 100644
--- a/plugins/fn-r/ChangeLog
+++ b/plugins/fn-r/ChangeLog
@@ -1,3 +1,8 @@
+2013-04-05  Morten Welinder  <terra gnome org>
+
+       * extra.c (psnorm): Base on new gnm_owent.  Fixes #697293.  Clamp
+       to [0;1].
+
 2013-04-04  Morten Welinder  <terra gnome org>
 
        * extra.c (pst): Implement.
diff --git a/plugins/fn-r/extra.c b/plugins/fn-r/extra.c
index 1eb94e9..9b8ca20 100644
--- a/plugins/fn-r/extra.c
+++ b/plugins/fn-r/extra.c
@@ -38,95 +38,6 @@ qcauchy (gnm_float p, gnm_float location, gnm_float scale,
        return location + scale / gnm_tan(M_PIgnum * p);
 }
 
-
-/* ------------------------------------------------------------------------- */
-
-/* This implementation of Owen's T function is based on code licensed under GPL v.2: */
-
-/*  GNU General Public License Agreement */
-/*  Copyright (C) 2004-2007 CodeCogs, Zyba Ltd, Broadwood, Holford, TA5 1DU, England. */
-
-/*  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 CodeCogs.  */
-/*  You must retain a copy of this licence in all copies.  */
-
-/*  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. */
-/*  --------------------------------------------------------------------------------- */
-/* ! Evaluates the Owen&#039;s T function. */
-
-#define LIM1 1E-35
-#define LIM2 15.0
-#define LIM3 15.0
-#define LIM4 1E-5
-
-#define TWOPI_INVERSE 1/(2*M_PIgnum)
-
-static gnm_float
-gnm_owent (gnm_float h, gnm_float a)
-{
-       gnm_float weight[10] = { GNM_const(0.0666713443086881375935688098933),
-                                GNM_const(0.149451349150580593145776339658),
-                                GNM_const(0.219086362515982043995534934228),
-                                GNM_const(0.269266719309996355091226921569),
-                                GNM_const(0.295524224714752870173892994651),
-                                GNM_const(0.295524224714752870173892994651),
-                                GNM_const(0.269266719309996355091226921569),
-                                GNM_const(0.219086362515982043995534934228),
-                                GNM_const(0.149451349150580593145776339658),
-                                GNM_const(0.0666713443086881375935688098933)};
-       gnm_float xtab[10] = {GNM_const(0.026093471482828279922035987916),
-                             GNM_const(0.134936633311015489267903311577),
-                             GNM_const(0.320590431700975593765672634885),
-                             GNM_const(0.566604605870752809200734056834),
-                             GNM_const(0.85112566101836878911517399887),
-                             GNM_const(1.148874338981631210884826001130),
-                             GNM_const(1.433395394129247190799265943166),
-                             GNM_const(1.679409568299024406234327365115),
-                             GNM_const(1.865063366688984510732096688423),
-                             GNM_const(1.973906528517171720077964012084)};
-       gnm_float hs, h2, as, rt;
-       int i;
-
-       if (fabs(h) < LIM1) return atan(a) * TWOPI_INVERSE;
-       if (fabs(h) > LIM2 || fabs(a) < LIM1) return 0.0;
-
-       hs = -0.5 * h * h;
-       h2 = a;
-       as = a * a;
-
-       if (log(1.0 + as) - hs * as >= LIM3)
-       {
-               gnm_float h1 = 0.5 * a;
-               as *= 0.25;
-               while (1)
-               {
-                       gnm_float rt = as + 1.0;
-                       h2 = h1 + (hs * as + LIM3 - log(rt))
-                               / (2.0 * h1 * (1.0 / rt - hs));
-                       as = h2 * h2;
-                       if (fabs(h2 - h1) < LIM4) break;
-                       h1 = h2;
-               }
-       }
-
-       rt = 0.0;
-       for (i = 0; i < 10; i++)
-       {
-               gnm_float x = 0.5 * h2 * xtab[i], tmp = 1.0 + x * x;
-               rt += weight[i] * gnm_exp (hs * tmp) / tmp;
-       }
-       return 0.5 * rt * h2 * TWOPI_INVERSE;
-}
-
-
-#undef LIM1
-#undef LIM2
-#undef LIM3
-#undef LIM4
-#undef TWOPI_INVERSE
-
 /* ------------------------------------------------------------------------- */
 
 /* The skew-normal distribution.  */
@@ -134,6 +45,9 @@ gnm_owent (gnm_float h, gnm_float a)
 gnm_float
 dsnorm (gnm_float x, gnm_float shape, gnm_float location, gnm_float scale, gboolean give_log)
 {
+       if (gnm_isnan (x) || gnm_isnan (shape) || gnm_isnan (location) || gnm_isnan (scale))
+               return gnm_nan;
+
        if (shape == 0.)
                return dnorm (x, location, scale, give_log);
        else if (give_log)
@@ -147,6 +61,9 @@ psnorm (gnm_float x, gnm_float shape, gnm_float location, gnm_float scale, gbool
 {
        gnm_float result, a, b;
 
+       if (gnm_isnan (x) || gnm_isnan (shape) || gnm_isnan (location) || gnm_isnan (scale))
+               return gnm_nan;
+
        if (shape == 0.)
                return pnorm (x, location, scale, lower_tail, log_p);
 
@@ -154,6 +71,12 @@ psnorm (gnm_float x, gnm_float shape, gnm_float location, gnm_float scale, gbool
        b = 2 * gnm_owent ((x - location) / scale, shape);
        result = lower_tail ? a - b : a + b;
 
+       /*
+        * Negatives can occur due to rounding errors and hopefully for no
+        * other reason.
+        */
+       result= CLAMP (result, 0.0, 1.0);
+
        if (log_p)
                return gnm_log (result);
        else
@@ -180,6 +103,9 @@ qsnorm (gnm_float p, gnm_float shape, gnm_float location, gnm_float scale,
        gnm_float x0;
        gnm_float params[3];
 
+       if (gnm_isnan (p) || gnm_isnan (shape) || gnm_isnan (location) || gnm_isnan (scale))
+               return gnm_nan;
+
        if (shape == 0.)
                return qnorm (p, location, scale, lower_tail, log_p);
 
@@ -199,6 +125,9 @@ qsnorm (gnm_float p, gnm_float shape, gnm_float location, gnm_float scale,
 gnm_float
 dst (gnm_float x, gnm_float n, gnm_float shape, gboolean give_log)
 {
+       if (n <= 0 || gnm_isnan (x) || gnm_isnan (n) || gnm_isnan (shape))
+               return gnm_nan;
+
        if (shape == 0.)
                return dt (x, n, give_log);
        else {
@@ -214,7 +143,7 @@ pst (gnm_float x, gnm_float n, gnm_float shape, gboolean lower_tail, gboolean lo
 {
        gnm_float p;
 
-       if (n <= 0)
+       if (n <= 0 || gnm_isnan (x) || gnm_isnan (n) || gnm_isnan (shape))
                return gnm_nan;
 
        if (shape == 0.)
@@ -320,6 +249,9 @@ qst (gnm_float p, gnm_float n, gnm_float shape,
        gnm_float x0;
        gnm_float params[2];
 
+       if (n <= 0 || gnm_isnan (p) || gnm_isnan (n) || gnm_isnan (shape))
+               return gnm_nan;
+
        if (shape == 0.)
                return qt (p, n, lower_tail, log_p);
 
diff --git a/src/mathfunc.c b/src/mathfunc.c
index bd8e827..e0234f6 100644
--- a/src/mathfunc.c
+++ b/src/mathfunc.c
@@ -25,7 +25,7 @@
  * into C, adapted to Gnumeric naming convensions, and R's API conventions
  * by Morten Welinder.  Blame me for problems.)
  *
- * Copyright � Ian Smith 2002-2003
+ * Copyright © Ian Smith 2002-2003
  * Version 1.0.24
  * Thanks to Jerry W. Lewis for help with testing of and improvements to the code.
  *
@@ -6819,3 +6819,288 @@ gnm_float_hash (gnm_float const *d)
 }
 
 /* ------------------------------------------------------------------------- */
+
+static gnm_float
+gnm_owent_T1 (gnm_float h, gnm_float a, int order)
+{
+       const gnm_float hs = -0.5 * (h * h);
+       const gnm_float dhs = gnm_exp (hs);
+       const gnm_float as = a * a;
+       gnm_float aj = a / (M_PIgnum * 2);
+       gnm_float dj = gnm_expm1 (hs);
+       gnm_float gj = hs * dhs;
+       gnm_float res = gnm_atan (a) / (M_PIgnum * 2);
+       int j;
+
+       for (j = 1; j <= order; j++) {
+               res += dj * aj / (j + j - 1);
+
+               aj *= as;
+               dj = gj - dj;
+               gj *= hs / (j + 1);
+       }
+
+       return res;
+}
+
+static gnm_float
+gnm_owent_T2 (gnm_float h, gnm_float a, int order)
+{
+       const gnm_float ah = a * h;
+       const gnm_float as = -a * a;
+       const gnm_float y = 1 / (h * h);
+       gnm_float val = 0;
+       gnm_float vi = a * dnorm (ah, 0, 1, FALSE);
+       gnm_float z = gnm_erf (ah / M_SQRT2gnum) / (2 * h);
+       int i;
+
+       for (i = 1; i <= 2 * order + 1; i += 2) {
+               val += z;
+               z = y * (vi - i * z);
+               vi *= as;
+       }
+       return val * dnorm (h, 0, 1, FALSE);
+}
+
+static gnm_float
+gnm_owent_T3 (gnm_float h, gnm_float a, int order)
+{
+       static const gnm_float c2[] = {
+               GNM_const(+0.99999999999999987510),
+               GNM_const(-0.99999999999988796462),
+               GNM_const(+0.99999999998290743652),
+               GNM_const(-0.99999999896282500134),
+               GNM_const(+0.99999996660459362918),
+               GNM_const(-0.99999933986272476760),
+               GNM_const(+0.99999125611136965852),
+               GNM_const(-0.99991777624463387686),
+               GNM_const(+0.99942835555870132569),
+               GNM_const(-0.99697311720723000295),
+               GNM_const(+0.98751448037275303682),
+               GNM_const(-0.95915857980572882813),
+               GNM_const(+0.89246305511006708555),
+               GNM_const(-0.76893425990463999675),
+               GNM_const(+0.58893528468484693250),
+               GNM_const(-0.38380345160440256652),
+               GNM_const(+0.20317601701045299653),
+               GNM_const(-0.82813631607004984866E-01),
+               GNM_const(+0.24167984735759576523E-01),
+               GNM_const(-0.44676566663971825242E-02),
+               GNM_const(+0.39141169402373836468E-03)
+       };
+
+       const gnm_float ah = a * h;
+       const gnm_float as = a * a;
+       const gnm_float y = 1 / (h * h);
+       gnm_float vi = a * dnorm (ah, 0, 1, FALSE);
+       gnm_float zi = gnm_erf (ah / M_SQRT2gnum) / (2 * h);
+       gnm_float val = 0;
+       int i;
+
+       g_return_val_if_fail (order < (int)G_N_ELEMENTS(c2), gnm_nan);
+
+       for (i = 0; i <= order; i++) {
+               val += zi * c2[i];
+               zi = y * ((i + i + 1) * zi - vi);
+               vi *= as;
+       }
+       return val * dnorm (h, 0, 1, FALSE);
+}
+
+static gnm_float
+gnm_owent_T4 (gnm_float h, gnm_float a, int order)
+{
+       const gnm_float hs = h * h;
+       const gnm_float as = -a * a;
+       gnm_float ai = a * gnm_exp (-0.5 * hs * (1 - as)) / (2 * M_PIgnum);
+       gnm_float yi = 1;
+       gnm_float val = 0;
+       int i;
+
+       for (i = 1; i <= 2 * order + 1; i += 2) {
+               val += ai * yi;
+               yi = (1 - hs * yi) / (i + 2);
+               ai *= as;
+       }
+       return val;
+}
+
+static gnm_float
+gnm_owent_T5 (gnm_float h, gnm_float a, int order)
+{
+       static const gnm_float pts[] = {
+               GNM_const(0.35082039676451715489E-02),
+               GNM_const(0.31279042338030753740E-01),
+               GNM_const(0.85266826283219451090E-01),
+               GNM_const(0.16245071730812277011),
+               GNM_const(0.25851196049125434828),
+               GNM_const(0.36807553840697533536),
+               GNM_const(0.48501092905604697475),
+               GNM_const(0.60277514152618576821),
+               GNM_const(0.71477884217753226516),
+               GNM_const(0.81475510988760098605),
+               GNM_const(0.89711029755948965867),
+               GNM_const(0.95723808085944261843),
+               GNM_const(0.99178832974629703586)
+       };
+       static const gnm_float wts[] = {
+               0.18831438115323502887E-01,
+               0.18567086243977649478E-01,
+               0.18042093461223385584E-01,
+               0.17263829606398753364E-01,
+               0.16243219975989856730E-01,
+               0.14994592034116704829E-01,
+               0.13535474469662088392E-01,
+               0.11886351605820165233E-01,
+               0.10070377242777431897E-01,
+               0.81130545742299586629E-02,
+               0.60419009528470238773E-02,
+               0.38862217010742057883E-02,
+               0.16793031084546090448E-02
+       };
+       const gnm_float as = a * a;
+       const gnm_float hs = -0.5 * h * h;
+       gnm_float val = 0;
+       int i;
+
+       g_return_val_if_fail (order <= (int)G_N_ELEMENTS(pts), gnm_nan);
+       g_return_val_if_fail (order <= (int)G_N_ELEMENTS(wts), gnm_nan);
+
+       for (i = 0; i < order; i++) {
+               gnm_float r = 1 + as * pts[i];
+               val += wts[i] * gnm_exp (hs * r) / r;
+       }
+
+       return val * a;
+}
+
+static gnm_float
+gnm_owent_T6 (gnm_float h, gnm_float a, int order)
+{
+       const gnm_float normh = pnorm (h, 0, 1, FALSE, FALSE);
+       const gnm_float normhC = 1 - normh;
+       const gnm_float y = 1 - a;
+       const gnm_float r = gnm_atan2 (y, 1 + a);
+       gnm_float val = 0.5 * normh * normhC;
+
+       if (r != 0)
+               val -= r * gnm_exp (-0.5 * y * h * h / r) / (2 * M_PIgnum);
+
+       return val;
+}
+
+static gnm_float
+gnm_owent_helper (gnm_float h, gnm_float a)
+{
+       static const double hrange[] = {
+               0.02, 0.06, 0.09, 0.125, 0.26, 0.4,  0.6,
+               1.6,  1.7,  2.33,  2.4,  3.36, 3.4,  4.8
+       };
+       static const double arange[] = {
+               0.025, 0.09, 0.15, 0.36, 0.5, 0.9, 0.99999
+       };
+       static const guint8 method[] = {
+               1, 1, 2,13,13,13,13,13,13,13,13,16,16,16, 9,
+               1, 2, 2, 3, 3, 5, 5,14,14,15,15,16,16,16, 9,
+               2, 2, 3, 3, 3, 5, 5,15,15,15,15,16,16,16,10,
+               2, 2, 3, 5, 5, 5, 5, 7, 7,16,16,16,16,16,10,
+               2, 3, 3, 5, 5, 6, 6, 8, 8,17,17,17,12,12,11,
+               2, 3, 5, 5, 5, 6, 6, 8, 8,17,17,17,12,12,12,
+               2, 3, 4, 4, 6, 6, 8, 8,17,17,17,17,17,12,12,
+               2, 3, 4, 4, 6, 6,18,18,18,18,17,17,17,12,12
+       };
+       int ai, hi;
+
+       g_return_val_if_fail (h >= 0, gnm_nan);
+       g_return_val_if_fail (a >= 0 && a <= 1, gnm_nan);
+
+       for (ai = 0; ai < (int)G_N_ELEMENTS(arange); ai++)
+               if (a <= arange[ai])
+                       break;
+       for (hi = 0; hi < (int)G_N_ELEMENTS(hrange); hi++)
+               if (h <= hrange[hi])
+                       break;
+
+       switch (method[ai * (1 + G_N_ELEMENTS(hrange)) + hi]) {
+       case  1: return gnm_owent_T1 (h, a, 2);
+       case  2: return gnm_owent_T1 (h, a, 3);
+       case  3: return gnm_owent_T1 (h, a, 4);
+       case  4: return gnm_owent_T1 (h, a, 5);
+       case  5: return gnm_owent_T1 (h, a, 7);
+       case  6: return gnm_owent_T1 (h, a, 10);
+       case  7: return gnm_owent_T1 (h, a, 12);
+       case  8: return gnm_owent_T1 (h, a, 18);
+       case  9: return gnm_owent_T2 (h, a, 10);
+       case 10: return gnm_owent_T2 (h, a, 20);
+       case 11: return gnm_owent_T2 (h, a, 30);
+       case 12: return gnm_owent_T3 (h, a, 20);
+       case 13: return gnm_owent_T4 (h, a, 4);
+       case 14: return gnm_owent_T4 (h, a, 7);
+       case 15: return gnm_owent_T4 (h, a, 8);
+       case 16: return gnm_owent_T4 (h, a, 20);
+       case 17: return gnm_owent_T5 (h, a, 13);
+       case 18: return gnm_owent_T6 (h, a, 0);
+       default:
+               g_assert_not_reached ();
+       }
+}
+
+/*
+ * See "Fast and Accurate Calculation of Owen’s T-Function" by
+ * Mike Patefield and David Tandy.  Journal of Statistical Software,
+ * Volume 5, Issue 5, July 2000.
+ *
+ * Original code licensed under GPLv2+.
+ */
+gnm_float
+gnm_owent (gnm_float h, gnm_float a)
+{
+       gnm_float res;
+       gboolean neg;
+
+       /* Even in the "h" argument.  */
+       h = gnm_abs (h);
+
+       /* Odd in the "a" argument.  */
+       neg = (a < 0);
+       a = gnm_abs (a);
+
+       if (a == 0)
+               res = 0;
+       else if (h == 0)
+               res = gnm_atan (a) / (2 * M_PIgnum);
+       else if (a == 1)
+               res = 0.5 * pnorm (h, 0, 1, TRUE, FALSE) *
+                       pnorm (h, 0, 1, FALSE, TRUE);
+       else if (a <= 1)
+               res = gnm_owent_helper (h, a);
+       else {
+               gnm_float ah = h * a;
+               /*
+                * Use formula (2):
+                *
+                * T(h,a) = .5N(h) + .5N(ha) - N(h)N(ha) - T(ha,1/a)
+                *
+                * with care to avoid cancellation.
+                */
+               if (h <= 0.67) {
+                       gnm_float nh = 0.5 * gnm_erf (h / M_SQRT2gnum);
+                       gnm_float nah = 0.5 * gnm_erf (ah / M_SQRT2gnum);
+                       res = 0.25 - nh * nah -
+                               gnm_owent_helper (ah, 1 / a);
+               } else {
+                       gnm_float nh = pnorm (h, 0, 1, FALSE, FALSE);
+                       gnm_float nah = pnorm (ah, 0, 1, FALSE, FALSE);
+                       res = 0.5 * (nh + nah) - nh * nah -
+                               gnm_owent_helper (ah, 1 / a);
+               }
+       }
+
+       /* Odd in the "a" argument.  */
+       if (neg)
+               res = 0 - res;
+
+       return res;
+}
+
+/* ------------------------------------------------------------------------- */
diff --git a/src/mathfunc.h b/src/mathfunc.h
index 67acb33..0adca1d 100644
--- a/src/mathfunc.h
+++ b/src/mathfunc.h
@@ -35,6 +35,7 @@ gnm_float logfbit (gnm_float x);
 gnm_float logspace_add (gnm_float logx, gnm_float logy);
 gnm_float logspace_sub (gnm_float logx, gnm_float logy);
 gnm_float stirlerr(gnm_float n);
+gnm_float gnm_owent (gnm_float h, gnm_float a);
 
 gnm_float gnm_cot (gnm_float x);
 gnm_float gnm_acot (gnm_float x);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]