/*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* Copyright 2013 Aurelian Tutuianu
* Copyright 2014 Aurelian Tutuianu
* Copyright 2015 Aurelian Tutuianu
* Copyright 2016 Aurelian Tutuianu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package rapaio.core.distributions;
import rapaio.math.MathTools;
import rapaio.core.RandomSource;
import rapaio.sys.WS;
/**
* ChiSquare distribution.
* <p>
* Created by <a href="mailto:padreati@yahoo.com">Aurelian Tutuianu</a> on 4/29/15.
*/
public class ChiSquare implements Distribution {
private static final long serialVersionUID = 2967287812574824823L;
private final double df;
private final double b;
private final double vm;
private final double vd;
public ChiSquare(double df) {
if (df < 1) {
throw new IllegalArgumentException("degrees of freedom parameter must have value greater than zero");
}
this.df = df;
this.b = Math.sqrt(df - 1.0);
double vm1 = -0.6065306597 * (1.0 - 0.25 / (b * b + 1.0));
this.vm = (-b > vm1) ? -b : vm1;
this.vd = 0.6065306597 * (0.7071067812 + b) / (0.5 + b) - vm;
}
@Override
public String name() {
return "ChiSq(df=" + WS.formatFlex(df) + ")";
}
@Override
public boolean discrete() {
return false;
}
@Override
public double pdf(double x) {
if (x < 0.0)
return 0;
double logGamma = MathTools.lnGamma(df / 2.0);
return Math.exp((df / 2.0 - 1.0) * Math.log(x / 2.0) - x / 2.0 - logGamma) / 2.0;
}
@Override
public double cdf(double x) {
if (x < 0.0 || df < 1.0)
return 0.0;
return MathTools.incompleteGamma(df / 2.0, x / 2.0);
}
@Override
public double quantile(double p) {
// implement binary search
double low = 0;
double high = 1;
while (cdf(high) < p) {
high *= 2;
}
while (true) {
double mid = (low + high) / 2.0;
double v = cdf(mid);
if (v < p) {
low = mid;
} else {
high = mid;
}
if (Math.abs(p - v) < 1e-12) {
break;
}
}
return low;
}
@Override
public double min() {
return 0;
}
@Override
public double max() {
return Double.POSITIVE_INFINITY;
}
@Override
public double mean() {
return df;
}
@Override
public double mode() {
return Math.max(0, df - 2);
}
@Override
public double var() {
return 2 * df;
}
@Override
public double skewness() {
return Math.sqrt(8 / df);
}
@Override
public double kurtosis() {
return 12 / df;
}
@Override
public double entropy() {
throw new IllegalArgumentException("not implemented");
}
@Override
public double sampleNext() {
/***********************************************************************
* * Chi Distribution - Ratio of Uniforms with shift * *
* ***************************************************************** *
* FUNCTION : - chru samples a random number from the Chi * distribution
* with parameter a > 1. * REFERENCE : - J.F. Monahan (1987): An
* algorithm for * generating chi random variables, ACM Trans. * Math.
* Software 13, 168-172. * SUBPROGRAM : - anEngine ... pointer to a
* (0,1)-Uniform * engine * * Implemented by R. Kremer, 1990 *
**********************************************************************/
double u, v, z, zz, r;
// if( a < 1 ) return (-1.0); // Check for invalid input value
if (df == 1.0) {
for (; ; ) {
u = RandomSource.nextDouble();
v = RandomSource.nextDouble() * 0.857763884960707;
z = v / u;
if (z < 0)
continue;
zz = z * z;
r = 2.5 - zz;
if (z < 0.0)
r = r + zz * z / (3.0 * z);
if (u < r * 0.3894003915)
return (z * z);
if (zz > (1.036961043 / u + 1.4))
continue;
if (2.0 * Math.log(u) < (-zz * 0.5))
return (z * z);
}
} else {
for (; ; ) {
u = RandomSource.nextDouble();
v = RandomSource.nextDouble() * vd + vm;
z = v / u;
if (z < -b)
continue;
zz = z * z;
r = 2.5 - zz;
if (z < 0.0)
r = r + zz * z / (3.0 * (z + b));
if (u < r * 0.3894003915)
return ((z + b) * (z + b));
if (zz > (1.036961043 / u + 1.4))
continue;
if (2.0 * Math.log(u) < (Math.log(1.0 + z / b) * b * b - zz * 0.5 - z * b))
return ((z + b) * (z + b));
}
}
}
}