trueskill/Skills/Numerics/GaussianDistribution.cs

240 lines
8.8 KiB
C#
Raw Normal View History

using System;
namespace Moserware.Numerics
{
public class GaussianDistribution
{
// Intentionally, we're not going to derive related things, but set them all at once
// to get around some NaN issues
private GaussianDistribution()
{
}
public GaussianDistribution(double mean, double standardDeviation)
{
Mean = mean;
StandardDeviation = standardDeviation;
Variance = Square(StandardDeviation);
Precision = 1.0/Variance;
PrecisionMean = Precision*Mean;
}
public double Mean { get; private set; }
public double StandardDeviation { get; private set; }
// Precision and PrecisionMean are used because they make multiplying and dividing simpler
// (the the accompanying math paper for more details)
public double Precision { get; private set; }
public double PrecisionMean { get; private set; }
private double Variance { get; set; }
public double NormalizationConstant
{
get
{
// Great derivation of this is at http://www.astro.psu.edu/~mce/A451_2/A451/downloads/notes0.pdf
return 1.0/(Math.Sqrt(2*Math.PI)*StandardDeviation);
}
}
public GaussianDistribution Clone()
{
var result = new GaussianDistribution();
result.Mean = Mean;
result.StandardDeviation = StandardDeviation;
result.Variance = Variance;
result.Precision = Precision;
result.PrecisionMean = PrecisionMean;
return result;
}
public static GaussianDistribution FromPrecisionMean(double precisionMean, double precision)
{
var gaussianDistribution = new GaussianDistribution();
gaussianDistribution.Precision = precision;
gaussianDistribution.PrecisionMean = precisionMean;
gaussianDistribution.Variance = 1.0/precision;
gaussianDistribution.StandardDeviation = Math.Sqrt(gaussianDistribution.Variance);
gaussianDistribution.Mean = gaussianDistribution.PrecisionMean/gaussianDistribution.Precision;
return gaussianDistribution;
}
// Although we could use equations from // For details, see http://www.tina-vision.net/tina-knoppix/tina-memo/2003-003.pdf
// for multiplication, the precision mean ones are easier to write :)
public static GaussianDistribution operator *(GaussianDistribution left, GaussianDistribution right)
{
return FromPrecisionMean(left.PrecisionMean + right.PrecisionMean, left.Precision + right.Precision);
}
/// Computes the absolute difference between two Gaussians
public static double AbsoluteDifference(GaussianDistribution left, GaussianDistribution right)
{
return Math.Max(
Math.Abs(left.PrecisionMean - right.PrecisionMean),
Math.Sqrt(Math.Abs(left.Precision - right.Precision)));
}
/// Computes the absolute difference between two Gaussians
public static double operator -(GaussianDistribution left, GaussianDistribution right)
{
return AbsoluteDifference(left, right);
}
public static double LogProductNormalization(GaussianDistribution left, GaussianDistribution right)
{
if ((left.Precision == 0) || (right.Precision == 0))
{
return 0;
}
double varianceSum = left.Variance + right.Variance;
double meanDifference = left.Mean - right.Mean;
double logSqrt2Pi = Math.Log(Math.Sqrt(2*Math.PI));
return -logSqrt2Pi - (Math.Log(varianceSum)/2.0) - (Square(meanDifference)/(2.0*varianceSum));
}
public static GaussianDistribution operator /(GaussianDistribution numerator, GaussianDistribution denominator)
{
return FromPrecisionMean(numerator.PrecisionMean - denominator.PrecisionMean,
numerator.Precision - denominator.Precision);
}
public static double LogRatioNormalization(GaussianDistribution numerator, GaussianDistribution denominator)
{
if ((numerator.Precision == 0) || (denominator.Precision == 0))
{
return 0;
}
double varianceDifference = denominator.Variance - numerator.Variance;
double meanDifference = numerator.Mean - denominator.Mean;
double logSqrt2Pi = Math.Log(Math.Sqrt(2*Math.PI));
return Math.Log(denominator.Variance) + logSqrt2Pi - Math.Log(varianceDifference)/2.0 +
Square(meanDifference)/(2*varianceDifference);
}
private static double Square(double x)
{
return x*x;
}
public static double At(double x)
{
return At(x, 0, 1);
}
public static double At(double x, double mean, double standardDeviation)
{
// See http://mathworld.wolfram.com/NormalDistribution.html
// 1 -(x-mean)^2 / (2*stdDev^2)
// P(x) = ------------------- * e
// stdDev * sqrt(2*pi)
double multiplier = 1.0/(standardDeviation*Math.Sqrt(2*Math.PI));
double expPart = Math.Exp((-1.0*Math.Pow(x - mean, 2.0))/(2*(standardDeviation*standardDeviation)));
double result = multiplier*expPart;
return result;
}
public static double CumulativeTo(double x, double mean, double standardDeviation)
{
double invsqrt2 = -0.707106781186547524400844362104;
double result = ErrorFunctionCumulativeTo(invsqrt2*x);
return 0.5*result;
}
public static double CumulativeTo(double x)
{
return CumulativeTo(x, 0, 1);
}
private static double ErrorFunctionCumulativeTo(double x)
{
// Derived from page 265 of Numerical Recipes 3rd Edition
double z = Math.Abs(x);
double t = 2.0/(2.0 + z);
double ty = 4*t - 2;
double[] coefficients = {
-1.3026537197817094, 6.4196979235649026e-1,
1.9476473204185836e-2, -9.561514786808631e-3, -9.46595344482036e-4,
3.66839497852761e-4, 4.2523324806907e-5, -2.0278578112534e-5,
-1.624290004647e-6, 1.303655835580e-6, 1.5626441722e-8, -8.5238095915e-8,
6.529054439e-9, 5.059343495e-9, -9.91364156e-10, -2.27365122e-10,
9.6467911e-11, 2.394038e-12, -6.886027e-12, 8.94487e-13, 3.13092e-13,
-1.12708e-13, 3.81e-16, 7.106e-15, -1.523e-15, -9.4e-17, 1.21e-16, -2.8e-17
};
int ncof = coefficients.Length;
double d = 0.0;
double dd = 0.0;
for (int j = ncof - 1; j > 0; j--)
{
double tmp = d;
d = ty*d - dd + coefficients[j];
dd = tmp;
}
double ans = t*Math.Exp(-z*z + 0.5*(coefficients[0] + ty*d) - dd);
return x >= 0.0 ? ans : (2.0 - ans);
}
private static double InverseErrorFunctionCumulativeTo(double p)
{
// From page 265 of numerical recipes
if (p >= 2.0)
{
return -100;
}
if (p <= 0.0)
{
return 100;
}
double pp = (p < 1.0) ? p : 2 - p;
double t = Math.Sqrt(-2*Math.Log(pp/2.0)); // Initial guess
double x = -0.70711*((2.30753 + t*0.27061)/(1.0 + t*(0.99229 + t*0.04481)) - t);
for (int j = 0; j < 2; j++)
{
double err = ErrorFunctionCumulativeTo(x) - pp;
x += err/(1.12837916709551257*Math.Exp(-(x*x)) - x*err); // Halley
}
return p < 1.0 ? x : -x;
}
public static double InverseCumulativeTo(double x, double mean, double standardDeviation)
{
// From numerical recipes, page 320
return mean - Math.Sqrt(2)*standardDeviation*InverseErrorFunctionCumulativeTo(2*x);
}
public static double InverseCumulativeTo(double x)
{
return InverseCumulativeTo(x, 0, 1);
}
public override string ToString()
{
// Debug help
return String.Format("μ={0:0.0000}, σ={1:0.0000}",
Mean,
StandardDeviation);
}
}
}