blob: 0bdbcb49eeae7cebd1240b62d49ff07a3d21301c [file] [log] [blame]
/*
* Mathlib : A C Library of Special Functions
* Copyright (C) 2000-2021 The R Core Team
* Copyright (C) 2005-2021 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
* https://www.R-project.org/Licenses/
*/
/* This is #included from ./qnbinom.c , .........
*/
#define PST_0(a, b) a ## b
#define PASTE(a, b) PST_0(a, b)
#define CHR_0(x) #x
#define AS_CHAR(x) CHR_0(x)
#define _pDIST_ PASTE(p, _thisDIST_)
#define _qDIST_ PASTE(q, _thisDIST_)
/**
For a discrete distribution on the integers,
For P(x) := <pDist>(x, <distPars>), find p-quantile y(p) :<==> P(y) < p <= P(y)
@param y current guess
@param *z := <pDist>(y, ..)
@param p target probability
@param <distPars> parameters of the respective distribution function
@param incr increment, integer valued >= 1.
@param lower_tail "logical" int; if 1 (true), have lower tail probabilities; otherwise upper tail
@param log "logical" int; if 1 (true) the probabilities are log(<probability>)
@return root 'y' and z[0] = <pDist>(y, ..)
*/
#define DO_SEARCH_FUN(...) \
do_search(double y, double *z, double p, __VA_ARGS__, \
double incr, int lower_tail, int log_p)
// this is used in the caller file qnbinom.c etc :
#define DO_SEARCH_(Y_, incr_, ...) do_search(Y_, &z, p, __VA_ARGS__, incr_, lower_tail, log_p)
#define P_DIST(Y_, ...) _pDIST_(Y_, __VA_ARGS__, lower_tail, log_p)
#ifdef MATHLIB_STANDALONE
# define MAYBE_R_CheckUserInterrupt()
#else
# define MAYBE_R_CheckUserInterrupt() R_CheckUserInterrupt()
#endif
static double DO_SEARCH_FUN(_dist_PARS_DECL_)
{
Rboolean left = (lower_tail ? (*z >= p) : (*z < p));
R_DBG_printf(" do_search(y=%g, z=%15.10g %s p = %15.10g) --> search to %s",
y, *z, (lower_tail ? ">=" : "<"), p, (left ? "left" : "right"));
if(incr > 1)
R_DBG_printf(", incr = %.0f\n", incr);
else R_DBG_printf("\n");
if(left) { // (lower_tail, *z >= p) or (upper tail, *z < p): search to the __left__
for(int iter = 0; ; iter++) {
double newz = -1.; // -Wall
#ifndef MATHLIB_STANDALONE
if(iter % 10000 == 0) R_CheckUserInterrupt();// have seen inf.loops
#endif
if(y > 0)
newz = P_DIST(y - incr, _dist_PARS_);
else if(y < 0)
y = 0;
// note that newz may be NaN because of remaining border line bugs in _pDIST_() {bug from pbeta()}
if(y == 0 || ISNAN(newz) || (lower_tail ? (newz < p) : (newz >= p))) {
R_DBG_printf(" new y=%.15g, " AS_CHAR(_pDIST_) "(y-incr,*) %s;"
" ==> search() returns previous z=%g after %d iter.\n", y,
ISNAN(newz) ? "is NaN" : (lower_tail ? "< p" : ">= p"), *z, iter);
return y; // and previous *z
}
y = fmax2(0, y - incr);
*z = newz;
}
}
else { // (lower_tail, *z < p) or (upper tail, *z >= p): search to the __right__
for(int iter = 0; ; iter++) {
#ifndef MATHLIB_STANDALONE
if(iter % 10000 == 0) R_CheckUserInterrupt();
#endif
y += incr;
#ifdef _dist_MAX_y
if(y < _dist_MAX_y)
*z = P_DIST(y, _dist_PARS_);
else if(y > _dist_MAX_y)
y = _dist_MAX_y;
#else
*z = P_DIST(y, _dist_PARS_);
#endif
if(
#ifdef _dist_MAX_y
y == _dist_MAX_y ||
#endif
ISNAN(*z) || (lower_tail ? (*z >= p) : (*z < p)))
{
R_DBG_printf(" new y=%.15g, z=%g = " AS_CHAR(_pDIST_) "(y,*) %s;"
" ==> search() returns after %d iter.\n", y, *z,
ISNAN(*z) ? "is NaN" : (lower_tail ? ">= p" : "< p"), iter);
return y;
}
}
}
} // do_search()
/*
* Note : "same" code in qbinom.c, qnbinom.c __FIXME__ NOT YET for qpois() ??
* FIXME: This is far from optimal [cancellation for p ~= 1, etc]:
*/
#define q_DISCRETE_01_CHECKS() do { \
double p_n; /* p in the "normal" (lower_tail=TRUE, log.p=FALSE) scale */ \
if(!lower_tail || log_p) { \
p_n = R_DT_qIv(p); /* need check again (cancellation!): */ \
R_DBG_printf(" upper tail or log_p: (p_n, 1-p_n) = (%.15g, %.15g)\n", p_n, 1-p_n); \
/* _OLD_ (R <= 4.0.x): */ \
/* if (p_n == 0) return 0; */ \
/* if (p_n == 1) return ML_POSINF; */ \
/* end{_OLD_} */ \
if (p_n == 0) R_DBG_printf("** p_n == 0: _NO LONGER_ returning 0"); \
if (p_n == 1) R_DBG_printf("** p_n == 1: _NO LONGER_ returning +Inf"); \
} else \
p_n = p; \
/* temporary hack : __New: On 2020-08-26, only give message but do *NOT* early return: */ \
/* seen (only @sfs, not 'nb-mm' (???): infinite loop for chkQnbinom(1e-19, k.max=1e4) */ \
if (p_n + 1.01*DBL_EPSILON >= 1.) { \
R_DBG_printf("p_n + 1.01*eps >= 1 ; (1-p_n = %g): for now *NO LONGER* returning Inf\n", \
1-p_n); \
/* return ML_POSINF; */ \
} \
} while(0)
#ifdef _dist_MAX_y
# define q_DISCR_CHECK_BOUNDARY(Y_) do { \
if(Y_ > _dist_MAX_y) /* way off */ Y_ = _dist_MAX_y; \
else if(Y_ < 0) Y_ = 0.; \
} while(0)
#else
# define q_DISCR_CHECK_BOUNDARY(Y_) if(Y_ < 0) Y_ = 0.
/* e.g., for qnbinom(0.5, mu = 3, size = 1e-10) */
#endif
#define q_DISCRETE_BODY() do { \
/* y := approx.value (Cornish-Fisher expansion) : */ \
double \
z = qnorm(p, 0., 1., lower_tail, log_p), \
y = R_forceint(mu + sigma * (z + gamma * (z*z - 1) / 6)); \
R_DBG_printf(" Cornish-Fisher: initial z = qnorm(p, l.t, log)= %g, y = %g;\n", z,y); \
\
q_DISCR_CHECK_BOUNDARY(y); \
\
z = P_DIST(y, _dist_PARS_); \
\
/* Algorithmic "tuning parameters", used to be hardwired; changed for speed &| precision */ \
const double \
_pf_n_ = 8, /* was hardwired to 64 */ \
_pf_L_ = 2, /* was hardwired to 64 */ \
_yLarge_ = 4096, /* was hardwired to 1e5 */ \
_incF_ = (1./64),/* was hardwired to 0.001 (= 1./1000 ) */ \
_iShrink_ = 8, /* was hardwired to 100 */ \
_relTol_ = 1e-15,/* was hardwired to 1e-15 */ \
_xf_ = 4; /* extra factor, *must* be >= 1 (new anyway) */ \
\
R_DBG_printf(" algo. tuning: fuzz factors _pf_{n,L}: {%.0f, %.0f}; yLarge = %g\n" \
" large case: _incF_=%g, _iShrink_=%g; _relTol_=%g, _xf_=%g\n", \
_pf_n_, _pf_L_, _yLarge_, _incF_, _iShrink_, _relTol_, _xf_); \
\
/* fuzz to ensure left continuity: do not loose too much (=> error in upper tail) */ \
if(log_p) { /* <==> p \in [-Inf, 0] different adjustment: "other sign" */ \
double e = _pf_L_ * DBL_EPSILON; \
if(lower_tail && p > - DBL_MAX) /* prevent underflow to -Inf */ \
p *= 1 + e; \
else /* if(p < - DBL_MIN) // not too close to -0 */ \
p *= 1 - e; \
\
} else { /* not log scale */ \
double e = _pf_n_ * DBL_EPSILON; \
if(lower_tail) \
p *= 1 - e; \
else if(1 - p > _xf_*e) /* otherwise get p > 1 */ \
p *= 1 + e; \
} \
R_DBG_printf(" new z := " AS_CHAR(_pDIST_) "(y, *) = %.11g; left-cont. fuzz => p = %.11g\n", z, p); \
\
/* If the C-F value y is not too large a simple search is OK */ \
if(y < _yLarge_) return DO_SEARCH_(y, 1, _dist_PARS_); \
/* Otherwise be a bit cleverer in the search: use larger increments, notably initially: */ \
{ /* y >= _yLarge_ */ \
double oldincr, incr = floor(y * _incF_); \
int qIt = 0; \
\
R_DBG_printf(" large y: --> use larger increments than 1: incr=%.0f\n", incr); \
do { \
oldincr = incr; \
y = DO_SEARCH_(y, incr, _dist_PARS_); /* also updating *z */ \
if(++qIt % 10000 == 0) MAYBE_R_CheckUserInterrupt(); \
incr = fmax2(1, floor(incr / _iShrink_)); \
} while(oldincr > 1 && incr > y * _relTol_); \
R_DBG_printf(" \\--> oldincr=%.0f, after %d \"outer\" search() iterations\n", \
oldincr, qIt); \
return y; \
} \
} while(0)