/* *
* Title:
* Description:
* Copyright: Copyright (c)
* Company:
* @author
* @version 1.0
*/
package umontreal.iro.lecuyer.probdist;
import umontreal.iro.lecuyer.util.*;
import umontreal.iro.lecuyer.functions.MathFunction;
/**
* Extends the class {@link ContinuousDistribution} for the
* Kolmogorov-Smirnov+ distribution (see).
* Given a sample of n independent uniforms Ui over [0, 1],
* the Kolmogorov-Smirnov+ statistic Dn+ and the Kolmogorov-Smirnov- statistic Dn-,
* are defined by
*
*
*
*
* Dn+
* =
* max1 <= j <= n(j/n - U(j)),
*
*
* Dn-
* =
* max1 <= j <= n(U(j) - (j - 1)/n),
*
*
*
*
*
* where the U(j) are the Ui sorted in increasing order. Both statistics
* follows the same distribution function, i.e.
*
* Fn(x) = P[Dn+ <= x] = P[Dn- <= x].
*
*/
public class KolmogorovSmirnovPlusDist extends ContinuousDistribution {
protected int n;
private static class Function implements MathFunction {
protected int n;
protected double u;
public Function (int n, double u) {
this.n = n;
this.u = u;
}
public double evaluate (double x) {
return u - cdf(n,x);
}
}
/**
* Constructs an Kolmogorov-Smirnov+ distribution for a sample of size n.
*
*/
public KolmogorovSmirnovPlusDist (int n) {
setN (n);
}
public double density (double x) {
return density (n, x);
}
public double cdf (double x) {
return cdf (n, x);
}
public double barF (double x) {
return barF (n, x);
}
public double inverseF (double u) {
return inverseF (n, u);
}
private static double dclem (int n, double x, double EPS) {
return (cdf(n, x + EPS) - cdf(n, x - EPS)) / (2.0 * EPS);
}
/**
* Computes the density of the Kolmogorov-Smirnov+ distribution with parameter n.
*
*/
public static double density (int n, double x) {
if (n <= 0)
throw new IllegalArgumentException (
"Calling kolmogorovSmirnovPlus with n < 1");
if (x <= 0.0 || x >= 1.0)
return 0.0;
if (n == 1)
return 1.0;
final double EPS = 1.0 / 100.0;
final double D1 = dclem(n, x, EPS);
final double D2 = dclem(n, x, 2.0 * EPS);
final double RES = D1 + (D1 - D2) / 3.0;
if (RES < 0.0)
return 0.0;
return RES;
}
/**
* Computes the Kolmogorov-Smirnov+ distribution function Fn(x) with parameter n.
* The relative error on
* Fn(x) = P[Dn+ <= x] is always less than
* 10-5.
*/
public static double cdf (int n, double x) {
if (n <= 0)
throw new IllegalArgumentException (
"Calling kolmogorovSmirnovPlus with n < 1");
if (x <= 0.0)
return 0.0;
if ((x >= 1.0) || (n*x*x >= 25.0))
return 1.0;
if (n == 1)
return x;
final double NXPARAM = 6.5; // frontier: alternating series
final int NPARAM = 4000; // frontier: non-alternating series
double q;
double Sum = 0.0;
double term;
double Njreal;
double jreal;
double LogCom = Math.log ((double)n);
int j;
int jmax;
//--------------------------------------------------------------
// the alternating series is stable and fast for n*x very small
//--------------------------------------------------------------
if (n*x <= NXPARAM) {
final double EPSILON = Num.DBL_MIN;
jmax = (int)(n*x);
int Sign = -1;
for (j = 1; j <= jmax; j++) {
jreal = j;
Njreal = n - j;
q = jreal/n - x;
// we must avoid log (0.0) for j = jmax and n*x near an integer
if (-q > EPSILON) {
term = LogCom + jreal*Math.log(-q) + (Njreal - 1.0)
*Math.log1p (-q);
Sum += Sign*Math.exp (term);
}
Sign = -Sign;
LogCom += Math.log (Njreal/(j + 1));
}
// add the term j = 0
Sum += Math.exp ((n - 1)*Math.log1p (x));
return Sum*x;
}
//-----------------------------------------------------------
// For nx > NxParam, we use the other exact series for small
// n, and the asymptotic form for n larger than NPARAM
//-----------------------------------------------------------
if (n <= NPARAM) {
jmax = (int)(n*(1.0 - x));
if (1.0 - x - (double) jmax/n <= 0.0)
--jmax;
for (j = 1; j <= jmax; j++) {
jreal = j;
Njreal = n - j;
q = jreal/n + x;
term = LogCom+(jreal - 1.0)*Math.log(q) + Njreal*Math.log1p (-q);
Sum += Math.exp (term);
LogCom += Math.log (Njreal/(jreal + 1.0));
}
Sum *= x;
// add the term j = 0; avoid log (0.0)
if (1.0 > x)
Sum += Math.exp (n*Math.log1p (-x));
return 1.0 - Sum;
}
//--------------------------
// Use an asymptotic formula
//--------------------------
term = 2.0/3.0;
q = x*x*n;
Sum = 1.0 - Math.exp (-2.0*q)*(1.0 - term*x*(1.0 - x*(1.0 - term*q)
- term/n*(0.2 - 19.0/15.0*q + term*q*q)));
return Sum;
}
static double KSPlusbarAsymp (int n, double x) {
/* Compute the probability of the KSPlus distribution using
an asymptotic formula */
double t = (6.0*n*x + 1);
double z = t*t/(18.0*n);
double v = 1.0 - (2.0*z*z - 4.0*z - 1.0)/(18.0*n);
if (v <= 0.0)
return 0.0;
v = v*Math.exp(-z);
if (v >= 1.0)
return 1.0;
return v;
}
//-------------------------------------------------------------------------
static double KSPlusbarUpper (int n, double x) {
// Compute the probability in the upper tail using Smirnov stable formula
final double EPSILON = 1.0E-10;
int jmax = (int) (n* (1.0 - x));
// Avoid log(0) for j = jmax and q ~ 1.0
if ((1.0 - x - (double)jmax/n) <= 0.0)
jmax--;
int jdiv;
if (n > 3000)
jdiv = 2;
else
jdiv = 3;
int j = jmax / jdiv + 1;
double LogCom = Num.lnFactorial(n) - Num.lnFactorial(j) - Num.lnFactorial(n-j);
final double LOGJM = LogCom;
double q;
double term;
double t;
double Sum = 0.0;
while (j <= jmax) {
q = (double)j / n + x;
term = LogCom + (j - 1)*Math.log(q) + (n-j)*Math.log1p(-q);
t = Math.exp (term);
Sum += t;
LogCom += Math.log ((double)(n - j)/(j + 1));
if (t <= Sum*EPSILON)
break;
j++;
}
j = jmax / jdiv;
LogCom = LOGJM + Math.log ((double)(j+1)/(n - j));
while (j > 0) {
q = (double)j / n + x;
term = LogCom+(j - 1)*Math.log (q)+ (n - j)*Math.log1p (-q);
t = Math.exp (term);
Sum += t;
LogCom += Math.log ((double)j/(n - j + 1));
if (t <= Sum*EPSILON)
break;
j--;
}
Sum *= x;
// add the term j = 0
Sum += Math.exp (n*Math.log1p (-x));
return Sum;
}
/**
* Computes the complementary distribution function
* bar(F)n(x)
* with parameter n.
*
*/
public static double barF (int n, double x) {
if (n <= 0)
throw new IllegalArgumentException (
"Calling kolmogorovSmirnovPlus with n < 1");
if (x <= 0.0)
return 1.0;
if ((x >= 1.0) || (n*x*x >= 365.0))
return 0.0;
if (n == 1)
return 1.0 - x;
final double NXPARAM = 6.5; // frontier: alternating series
final int NPARAM = 4000; // frontier: non-alternating series
final int NASYMP = 200000; // frontier: asymptotic
// the alternating series is stable and fast for n*x very small
if (n*x <= NXPARAM)
return 1.0 - cdf(n, x);
if (n >= NASYMP)
return KSPlusbarAsymp (n, x);
if ((n <= NPARAM) || (n*x*x > 1.0))
return KSPlusbarUpper(n,x);
return KSPlusbarAsymp (n, x);
// return (1.0 - 2.0*x/3.0)*Math.exp(-2.0*n*x*x);
}
/**
* Computes the inverse
* x = F-1(u) of the distribution with parameter n.
*
*/
public static double inverseF (int n, double u) {
if (n <= 0)
throw new IllegalArgumentException ("n <= 0");
if (u < 0.0 || u > 1.0)
throw new IllegalArgumentException ("u must be in [0,1]");
if (u == 1.0)
return 1.0;
if (u == 0.0)
return 0.0;
Function f = new Function (n,u);
return RootFinder.brentDekker (0.0, 1.0, f, 1e-8);
}
/**
* Returns the parameter n of this object.
*
*/
public int getN() {
return n;
}
/**
* Sets the parameter n of this object.
*
*/
public void setN (int n) {
if (n <= 0)
throw new IllegalArgumentException ("n <= 0");
this.n = n;
supportA = 0.0;
supportB = 1.0;
}
/**
* Returns an array containing the parameter n of this object.
*
*
*/
public double[] getParams () {
double[] retour = {n};
return retour;
}
public String toString () {
return getClass().getSimpleName() + " : n = " + n;
}
}
Ads help maintain this website.