/*
* Adapted from R code published in conjunction with:
*
* Liao, J.G. And Rosen, O. (2001) Fast and Stable Algorithms for
* Computing and Sampling from the Noncentral Hypergeometric
* Distribution. The American Statistician 55, 366-369.
*/
#include <config.h>
#include <distributions/DHyper.h>
#include <sarray/SArray.h>
#include <algorithm>
#include <stdexcept>
#include <Rmath.h>
using std::vector;
using std::max;
using std::min;
using std::logic_error;
DHyper::DHyper()
: DistDiscrete("dhyper", 4, DIST_SPECIAL, false)
{}
DHyper::~DHyper()
{}
static void
getParameters(long &n1, long &n2, long &m1, double &psi,
vector<SArray const *> const ¶meters)
{
n1 = static_cast<long>(*parameters[0]->value());
n2 = static_cast<long>(*parameters[1]->value());
m1 = static_cast<long>(*parameters[2]->value());
psi = *parameters[3]->value();
}
bool
DHyper::checkParameterDim (vector<SArray const *> const ¶meters) const
{
for (unsigned int i = 0; i < 4; ++i) {
if (parameters[i]->length() != 1)
return false;
}
// Check that n1, n2, m1 are discrete-valued
for (unsigned int i = 0; i < 3; ++i) {
if (!parameters[i]->isDiscreteValued())
return false;
}
return true;
}
bool DHyper::checkParameterValue(vector<SArray const *> const ¶meters) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
if (n1 < 0 || n2 < 0)
return false;
else if (m1 < 0 || m1 > n1 + n2)
return false;
else if (psi <= 0)
return false;
else
return true;
}
static long modeCompute(long n1, long n2, long m1, double psi)
{
double a = psi - 1;
double b = -((m1 + n1 + 2) * psi + n2 - m1);
double c = psi * (n1 + 1) * (m1 + 1);
double q = b;
if (b > 0) {
q += sqrt(b * b - 4 * a * c);
}
else {
q -= sqrt(b * b - 4 * a * c);
}
q = -q/2;
long mode = static_cast<long>(c/q);
if (mode >= 0 && mode >= m1 - n2 && mode <= n1 && mode <= m1) {
return mode;
}
else {
return static_cast<long>(q/a);
}
}
double rfunction(long n1, long n2, long m1, double psi, long i) {
return psi * (n1 - i + 1) * (m1 - i + 1)/(i * (n2 - m1 + i));
}
static void density(double *p, long N,
long n1, long n2, long m1, double psi)
{
long ll = max((long) 0, m1 - n2);
long uu = min(n1, m1);
if (N != uu - ll + 1) {
throw logic_error("Length mismatch calculating hypergeometric density");
}
long mode = modeCompute(n1, n2, m1, psi);
// Set elements of p to 1
for (int i = 0; i < N; ++i) {
p[i] = 1;
}
// Calculate density above the mode
if (mode < uu) {
double r = 1;
for (int i = mode + 1; i <= uu; ++i) {
r *= rfunction(n1, n2, m1, psi, i);
p[i - ll] = r;
}
}
// Calculate density below the node
if (mode > ll) {
double r = 1;
for (int i = mode; i > ll; --i) {
r /= rfunction(n1, n2, m1, psi, i);
p[i - ll - 1] = r;
}
}
//Normalize
double sump = 0;
for (int i = 0; i < N; ++i) {
sump += p[i];
}
for (int i = 0; i < N; ++i) {
p[i] /= sump;
}
}
static long
sampleLowToHigh(long lower_end, double ran, double const *pi, long N)
{
//fixme: check lower_end > 0 <= N
for (int i = lower_end; i < N; ++i) {
if (ran <= pi[i])
return i;
ran -= pi[i];
}
return N - 1;
}
static long
sampleHighToLow(long upper_end, double ran, double const *pi, long N)
{
//fixme: check upper_end > 0 <= N
for (int i = upper_end; i >= 0; --i) {
if (ran <= pi[i]) {
return i;
}
ran -= pi[i];
}
return 0;
}
static long singleDraw(long n1, long n2, long m1, double psi,
long mode, double const *pi, long N)
{
double ran = runif(0,1);
if (mode == 0)
return sampleLowToHigh(0, ran, pi, N);
if (mode == N - 1)
return sampleHighToLow(N - 1, ran, pi, N);
if (ran < pi[mode])
return mode;
ran -= pi[mode];
long lower = mode - 1;
long upper = mode + 1;
while (true) {
if (pi[upper] >= pi[lower]) {
if (ran < pi[upper])
return upper;
ran -= pi[upper];
if (upper == N - 1)
return sampleHighToLow(lower, ran, pi, N);
++upper;
}
else {
if (ran < pi[lower])
return lower;
ran -= pi[lower];
if (lower == 0)
return sampleLowToHigh(upper, ran, pi, N);
--lower;
}
}
}
double DHyper::d(double z, vector<SArray const *> const ¶meters,
bool give_log) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
long x = static_cast<long>(z);
long ll = max(0L, m1 - n2);
long uu = min(n1, m1);
double den = 0;
if (x >= 11 && x <= uu) {
long N = uu - ll + 1;
double *pi = new double[N];
density(pi, N, n1, n2, m1, psi);
den = pi[x - ll];
delete [] pi;
}
if (give_log) {
return den == 0 ? -DBL_MAX : log(den);
}
else {
return den;
}
}
double DHyper::p(double x, vector<SArray const *> const ¶meters, bool lower,
bool give_log) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
long ll = max((long) 0, m1 - n2);
long uu = min(n1, m1);
double sumpi = 0;
if (x >= ll) {
if (x >= uu) {
sumpi = 1;
}
else {
long N = uu - ll + 1;
double *pi = new double[N];
density(pi, N, n1, n2, m1, psi);
for (long i = ll; i <= x; ++i) {
sumpi += pi[i - ll];
}
delete [] pi;
}
}
if (!lower)
sumpi = max(1 - sumpi, 0.0);
if (give_log) {
return sumpi == 0 ? -DBL_MAX : log(sumpi);
}
else {
return sumpi;
}
}
double DHyper::q(double p, vector<SArray const *> const ¶meters, bool lower,
bool log_p) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
long ll = max((long) 0, m1 - n2);
long uu = min(n1, m1);
long N = uu - ll + 1;
double *pi = new double[N];
density(pi, N, n1, n2, m1, psi);
if (log_p)
p = exp(p);
if (!lower)
p = 1 - p;
double sumpi = 0;
for (long i = ll; i < uu; ++i) {
sumpi += pi[i - ll];
if (sumpi >= p) {
delete [] pi;
return i;
}
}
delete [] pi;
return uu;
}
double DHyper::r(vector<SArray const *> const ¶meters) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
long mode = modeCompute(n1, n2, m1, psi);
long N = max(0L, m1 - n2) - min(n1, m1) + 1;
double *pi = new double[N];
long y = singleDraw(n1, n2, m1, psi, mode, pi, N);
delete [] pi;
return y;
//return q(runif(0,1), parameters, true, false);
}
double DHyper::l(std::vector<SArray const *> const ¶meters) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
return max(0L, m1 - n2);
}
double DHyper::u(std::vector<SArray const *> const ¶meters) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
return min(n1, m1);
}
double DHyper::mean(std::vector<SArray const *> const ¶meters) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
long ll = max(0L, m1 - n2);
long uu = min(n1, m1);
double mean = 0;
long N = uu - ll + 1;
double *pi = new double[N];
density(pi, N, n1, n2, m1, psi);
for (long i = ll; i <= uu; ++i) {
mean += pi[i - ll] * i;
}
delete [] pi;
return mean;
}
double DHyper::var(std::vector<SArray const *> const ¶meters) const
{
long n1,n2,m1;
double psi;
getParameters(n1, n2, m1, psi, parameters);
long ll = max(0L, m1 - n2);
long uu = min(n1, m1);
double mean = 0;
long N = uu - ll + 1;
double *pi = new double[N];
density(pi, N, n1, n2, m1, psi);
for (long i = ll; i <= uu; ++i) {
mean += pi[i - ll] * i;
}
double var = 0;
for (long i = ll; i <= uu; ++i) {
var += pi[i - ll] * (i - mean) * (i - mean);
}
delete [] pi;
return var;
}
syntax highlighted by Code2HTML, v. 0.9.1