|
|
|
@ -1,4 +1,4 @@ |
|
|
|
// <copyright file="Combinatorics.cs" company="Math.NET">
|
|
|
|
// <copyright file="Erf.cs" company="Math.NET">
|
|
|
|
// Math.NET Numerics, part of the Math.NET Project
|
|
|
|
// http://mathnet.opensourcedotnet.info
|
|
|
|
//
|
|
|
|
@ -30,11 +30,14 @@ namespace MathNet.Numerics |
|
|
|
{ |
|
|
|
using System; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// This partial implementation of the SpecialFunctions class contains all methods related to the error function.
|
|
|
|
/// </summary>
|
|
|
|
public partial class SpecialFunctions |
|
|
|
{ |
|
|
|
/// <summary>Calculates the error function.</summary>
|
|
|
|
/// <param name="x">The value to evaluate.</param>
|
|
|
|
/// <returns>The error function evaluated at given value.</returns>
|
|
|
|
/// <returns>the error function evaluated at given value.</returns>
|
|
|
|
/// <remarks>
|
|
|
|
/// <list type="bullet">
|
|
|
|
/// <item>returns 1 if <c>x == Double.PositiveInfinity</c>.</item>
|
|
|
|
@ -47,14 +50,17 @@ namespace MathNet.Numerics |
|
|
|
{ |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
if (Double.IsPositiveInfinity(x)) |
|
|
|
{ |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (Double.IsNegativeInfinity(x)) |
|
|
|
{ |
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
if (Double.IsNaN(x) || Double.IsNaN(x)) |
|
|
|
{ |
|
|
|
return Double.NaN; |
|
|
|
@ -65,7 +71,7 @@ namespace MathNet.Numerics |
|
|
|
|
|
|
|
/// <summary>Calculates the complementary error function.</summary>
|
|
|
|
/// <param name="x">The value to evaluate.</param>
|
|
|
|
/// <returns>The complementary error function evaluated at given value.</returns>
|
|
|
|
/// <returns>the complementary error function evaluated at given value.</returns>
|
|
|
|
/// <remarks>
|
|
|
|
/// <list type="bullet">
|
|
|
|
/// <item>returns 0 if <c>x == Double.PositiveInfinity</c>.</item>
|
|
|
|
@ -78,14 +84,17 @@ namespace MathNet.Numerics |
|
|
|
{ |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (Double.IsPositiveInfinity(x)) |
|
|
|
{ |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
if (Double.IsNegativeInfinity(x)) |
|
|
|
{ |
|
|
|
return 2; |
|
|
|
} |
|
|
|
|
|
|
|
if (Double.IsNaN(x) || Double.IsNaN(x)) |
|
|
|
{ |
|
|
|
return Double.NaN; |
|
|
|
@ -94,7 +103,12 @@ namespace MathNet.Numerics |
|
|
|
return ErfImp(x,true); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Implementation of the error function.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="z">Where to evaluate the error function.</param>
|
|
|
|
/// <param name="invert">Whether to compute 1 - the error function.</param>
|
|
|
|
/// <returns>the error function.</returns>
|
|
|
|
private static double ErfImp(double z, bool invert) |
|
|
|
{ |
|
|
|
if (z < 0) |
|
|
|
@ -103,10 +117,12 @@ namespace MathNet.Numerics |
|
|
|
{ |
|
|
|
return -ErfImp(-z, invert); |
|
|
|
} |
|
|
|
|
|
|
|
if (z < -0.5) |
|
|
|
{ |
|
|
|
return 2 - ErfImp((-z), invert); |
|
|
|
} |
|
|
|
|
|
|
|
return 1 + ErfImp(-z, false); |
|
|
|
} |
|
|
|
|
|
|
|
@ -124,7 +140,7 @@ namespace MathNet.Numerics |
|
|
|
//
|
|
|
|
if (z < 1e-10) |
|
|
|
{ |
|
|
|
result = z * 1.125 + z * 0.003379167095512573896158903121545171688; |
|
|
|
result = (z * 1.125) + (z * 0.003379167095512573896158903121545171688); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
@ -132,7 +148,7 @@ namespace MathNet.Numerics |
|
|
|
double[] n = new double[] { 0.00337916709551257388990745, -0.00073695653048167948530905, -0.374732337392919607868241, 0.0817442448733587196071743, -0.0421089319936548595203468, 0.0070165709512095756344528, -0.00495091255982435110337458, 0.000871646599037922480317225 }; |
|
|
|
double[] d = new double[] { 1, -0.218088218087924645390535, 0.412542972725442099083918, -0.0841891147873106755410271, 0.0655338856400241519690695, -0.0120019604454941768171266, 0.00408165558926174048329689, -0.000615900721557769691924509 }; |
|
|
|
|
|
|
|
result = z * 1.125 + z * evaluate_polynomial(n, z) / evaluate_polynomial(d, z); |
|
|
|
result = (z * 1.125) + (z * evaluate_polynomial(n, z) / evaluate_polynomial(d, z)); |
|
|
|
} |
|
|
|
} |
|
|
|
else if ((z < 110) || ((z < 110) && invert)) |
|
|
|
@ -246,8 +262,9 @@ namespace MathNet.Numerics |
|
|
|
r = evaluate_polynomial(n, z - 85) / evaluate_polynomial(d, z - 85); |
|
|
|
b = 0.5641584396F; |
|
|
|
} |
|
|
|
|
|
|
|
double g = System.Math.Exp(-z * z) / z; |
|
|
|
result = g * b + g * r; |
|
|
|
result = (g * b) + (g * r); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
@ -265,5 +282,196 @@ namespace MathNet.Numerics |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
///<summary>Calculates the complementary inverse error function evaluated at z.</summary>
|
|
|
|
/// <returns>The complementary inverse error function evaluated at given value.</returns>
|
|
|
|
/// <remarks> We have tested this implementation against the arbitrary precision mpmath library
|
|
|
|
/// and found cases where we can only guarantee 9 significant figures correct.
|
|
|
|
/// <list type="bullet">
|
|
|
|
/// <item>returns Double.PositiveInfinity if <c>z <= 0.0</c>.</item>
|
|
|
|
/// <item>returns Double.NegativeInfinity if <c>z >= 2.0</c>.</item>
|
|
|
|
/// </list>
|
|
|
|
/// </remarks>
|
|
|
|
///<summary>Calculates the complementary inverse error function evaluated at z.</summary>
|
|
|
|
///<param name="z">value to evaluate.</param>
|
|
|
|
///<returns>the complementary inverse error function evaluated at Z.</returns>
|
|
|
|
public static double ErfcInv(double z) |
|
|
|
{ |
|
|
|
if (z <= 0.0) |
|
|
|
{ |
|
|
|
return double.PositiveInfinity; |
|
|
|
} |
|
|
|
|
|
|
|
if (z >= 2.0) |
|
|
|
{ |
|
|
|
return double.NegativeInfinity; |
|
|
|
} |
|
|
|
|
|
|
|
double p, q, s; |
|
|
|
if (z > 1) |
|
|
|
{ |
|
|
|
q = 2 - z; |
|
|
|
p = 1 - q; |
|
|
|
s = -1; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
p = 1 - z; |
|
|
|
q = z; |
|
|
|
s = 1; |
|
|
|
} |
|
|
|
|
|
|
|
return ErfInvImpl(p, q, s); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The implementation of the inverse error function.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="p">First intermediate parameter.</param>
|
|
|
|
/// <param name="q">Second intermediate parameter.</param>
|
|
|
|
/// <param name="s">Third intermediate parameter.</param>
|
|
|
|
/// <returns>the inverse error function.</returns>
|
|
|
|
private static double ErfInvImpl(double p, double q, double s) |
|
|
|
{ |
|
|
|
double result; |
|
|
|
|
|
|
|
if (p <= 0.5) |
|
|
|
{ |
|
|
|
//
|
|
|
|
// Evaluate inverse erf using the rational approximation:
|
|
|
|
//
|
|
|
|
// x = p(p+10)(Y+R(p))
|
|
|
|
//
|
|
|
|
// Where Y is a constant, and R(p) is optimized for a low
|
|
|
|
// absolute error compared to |Y|.
|
|
|
|
//
|
|
|
|
// double: Max error found: 2.001849e-18
|
|
|
|
// long double: Max error found: 1.017064e-20
|
|
|
|
// Maximum Deviation Found (actual error term at infinite precision) 8.030e-21
|
|
|
|
//
|
|
|
|
float Y = 0.0891314744949340820313f; |
|
|
|
double[] P = new double[] { -0.000508781949658280665617, -0.00836874819741736770379, 0.0334806625409744615033, -0.0126926147662974029034, -0.0365637971411762664006, 0.0219878681111168899165, 0.00822687874676915743155, -0.00538772965071242932965 }; |
|
|
|
double[] Q = new double[] { 1, -0.970005043303290640362, -1.56574558234175846809, 1.56221558398423026363, 0.662328840472002992063, -0.71228902341542847553, -0.0527396382340099713954, 0.0795283687341571680018, -0.00233393759374190016776, 0.000886216390456424707504 }; |
|
|
|
double g = p * (p + 10); |
|
|
|
double r = evaluate_polynomial(P, p) / evaluate_polynomial(Q, p); |
|
|
|
result = (g * Y) + (g * r); |
|
|
|
} |
|
|
|
else if (q >= 0.25) |
|
|
|
{ |
|
|
|
//
|
|
|
|
// Rational approximation for 0.5 > q >= 0.25
|
|
|
|
//
|
|
|
|
// x = sqrt(-2*log(q)) / (Y + R(q))
|
|
|
|
//
|
|
|
|
// Where Y is a constant, and R(q) is optimized for a low
|
|
|
|
// absolute error compared to Y.
|
|
|
|
//
|
|
|
|
// double : Max error found: 7.403372e-17
|
|
|
|
// long double : Max error found: 6.084616e-20
|
|
|
|
// Maximum Deviation Found (error term) 4.811e-20
|
|
|
|
//
|
|
|
|
float Y = 2.249481201171875f; |
|
|
|
double[] P = new double[] { -0.202433508355938759655, 0.105264680699391713268, 8.37050328343119927838, 17.6447298408374015486, -18.8510648058714251895, -44.6382324441786960818, 17.445385985570866523, 21.1294655448340526258, -3.67192254707729348546 }; |
|
|
|
double[] Q = new double[] { 1, 6.24264124854247537712, 3.9713437953343869095, -28.6608180499800029974, -20.1432634680485188801, 48.5609213108739935468, 10.8268667355460159008, -22.6436933413139721736, 1.72114765761200282724 }; |
|
|
|
double g = System.Math.Sqrt(-2 * System.Math.Log(q)); |
|
|
|
double xs = q - 0.25; |
|
|
|
double r = evaluate_polynomial(P, xs) / evaluate_polynomial(Q, xs); |
|
|
|
result = g / (Y + r); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
//
|
|
|
|
// For q < 0.25 we have a series of rational approximations all
|
|
|
|
// of the general form:
|
|
|
|
//
|
|
|
|
// let: x = sqrt(-log(q))
|
|
|
|
//
|
|
|
|
// Then the result is given by:
|
|
|
|
//
|
|
|
|
// x(Y+R(x-B))
|
|
|
|
//
|
|
|
|
// where Y is a constant, B is the lowest value of x for which
|
|
|
|
// the approximation is valid, and R(x-B) is optimized for a low
|
|
|
|
// absolute error compared to Y.
|
|
|
|
//
|
|
|
|
// Note that almost all code will really go through the first
|
|
|
|
// or maybe second approximation. After than we're dealing with very
|
|
|
|
// small input values indeed: 80 and 128 bit long double's go all the
|
|
|
|
// way down to ~ 1e-5000 so the "tail" is rather long...
|
|
|
|
//
|
|
|
|
double x = System.Math.Sqrt(-System.Math.Log(q)); |
|
|
|
if (x < 3) |
|
|
|
{ |
|
|
|
// Max error found: 1.089051e-20
|
|
|
|
float Y = 0.807220458984375f; |
|
|
|
double[] P = new double[] { -0.131102781679951906451, -0.163794047193317060787, 0.117030156341995252019, 0.387079738972604337464, 0.337785538912035898924, 0.142869534408157156766, 0.0290157910005329060432, 0.00214558995388805277169, -0.679465575181126350155e-6, 0.285225331782217055858e-7, -0.681149956853776992068e-9 }; |
|
|
|
double[] Q = new double[] { 1, 3.46625407242567245975, 5.38168345707006855425, 4.77846592945843778382, 2.59301921623620271374, 0.848854343457902036425, 0.152264338295331783612, 0.01105924229346489121 }; |
|
|
|
double xs = x - 1.125; |
|
|
|
double R = evaluate_polynomial(P, xs) / evaluate_polynomial(Q, xs); |
|
|
|
result = (Y * x) + (R * x); |
|
|
|
} |
|
|
|
else if (x < 6) |
|
|
|
{ |
|
|
|
// Max error found: 8.389174e-21
|
|
|
|
float Y = 0.93995571136474609375f; |
|
|
|
double[] P = new double[] { -0.0350353787183177984712, -0.00222426529213447927281, 0.0185573306514231072324, 0.00950804701325919603619, 0.00187123492819559223345, 0.000157544617424960554631, 0.460469890584317994083e-5, -0.230404776911882601748e-9, 0.266339227425782031962e-11 }; |
|
|
|
double[] Q = new double[] { 1, 1.3653349817554063097, 0.762059164553623404043, 0.220091105764131249824, 0.0341589143670947727934, 0.00263861676657015992959, 0.764675292302794483503e-4 }; |
|
|
|
double xs = x - 3; |
|
|
|
double R = evaluate_polynomial(P, xs) / evaluate_polynomial(Q, xs); |
|
|
|
result = (Y * x) + (R * x); |
|
|
|
} |
|
|
|
else if (x < 18) |
|
|
|
{ |
|
|
|
// Max error found: 1.481312e-19
|
|
|
|
float Y = 0.98362827301025390625f; |
|
|
|
double[] P = new double[] { -0.0167431005076633737133, -0.00112951438745580278863, 0.00105628862152492910091, 0.000209386317487588078668, 0.149624783758342370182e-4, 0.449696789927706453732e-6, 0.462596163522878599135e-8, -0.281128735628831791805e-13, 0.99055709973310326855e-16 }; |
|
|
|
double[] Q = new double[] { 1, 0.591429344886417493481, 0.138151865749083321638, 0.0160746087093676504695, 0.000964011807005165528527, 0.275335474764726041141e-4, 0.282243172016108031869e-6 }; |
|
|
|
double xs = x - 6; |
|
|
|
double R = evaluate_polynomial(P, xs) / evaluate_polynomial(Q, xs); |
|
|
|
result = (Y * x) + (R * x); |
|
|
|
} |
|
|
|
else if (x < 44) |
|
|
|
{ |
|
|
|
// Max error found: 5.697761e-20
|
|
|
|
float Y = 0.99714565277099609375f; |
|
|
|
double[] P = new double[] { -0.0024978212791898131227, -0.779190719229053954292e-5, 0.254723037413027451751e-4, 0.162397777342510920873e-5, 0.396341011304801168516e-7, 0.411632831190944208473e-9, 0.145596286718675035587e-11, -0.116765012397184275695e-17 }; |
|
|
|
double[] Q = new double[] { 1, 0.207123112214422517181, 0.0169410838120975906478, 0.000690538265622684595676, 0.145007359818232637924e-4, 0.144437756628144157666e-6, 0.509761276599778486139e-9 }; |
|
|
|
double xs = x - 18; |
|
|
|
double R = evaluate_polynomial(P, xs) / evaluate_polynomial(Q, xs); |
|
|
|
result = (Y * x) + (R * x); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Max error found: 1.279746e-20
|
|
|
|
float Y = 0.99941349029541015625f; |
|
|
|
double[] P = new double[] { -0.000539042911019078575891, -0.28398759004727721098e-6, 0.899465114892291446442e-6, 0.229345859265920864296e-7, 0.225561444863500149219e-9, 0.947846627503022684216e-12, 0.135880130108924861008e-14, -0.348890393399948882918e-21 }; |
|
|
|
double[] Q = new double[] { 1, 0.0845746234001899436914, 0.00282092984726264681981, 0.468292921940894236786e-4, 0.399968812193862100054e-6, 0.161809290887904476097e-8, 0.231558608310259605225e-11 }; |
|
|
|
double xs = x - 44; |
|
|
|
double R = evaluate_polynomial(P, xs) / evaluate_polynomial(Q, xs); |
|
|
|
result = (Y * x) + (R * x); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return s * result; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A helper function to evaluate polynomials fast.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="poly">The coefficients of the polynomial.</param>
|
|
|
|
/// <param name="z">The location where to evaluate the polynomial at.</param>
|
|
|
|
/// <returns>the evaluation of the polynomial.</returns>
|
|
|
|
private static double evaluate_polynomial(double[] poly, double z) |
|
|
|
{ |
|
|
|
int count = poly.Length; |
|
|
|
double sum = poly[count - 1]; |
|
|
|
for (int i = count - 2; i >= 0; --i) |
|
|
|
{ |
|
|
|
sum *= z; |
|
|
|
sum += poly[i]; |
|
|
|
} |
|
|
|
|
|
|
|
return sum; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|