/// <summary> /// Computes <c>int_0^infinity t^n N(t;x,1) dt / (n! N(x;0,1))</c> /// </summary> /// <param name="n">The exponent</param> /// <param name="x">A real number > -1</param> /// <returns></returns> private static double NormalCdfMomentRatioRecurrence(int n, double x) { double rPrev = MMath.NormalCdfRatio(x); if (n == 0) { return(rPrev); } double r = x * rPrev + 1; for (int i = 1; i < n; i++) { double rNew = (x * r + rPrev) / (i + 1); rPrev = r; r = rNew; } return(r); }
// Helper function for NormalCdfRatioConFrac private static double NormalCdfRatioConFracNumer(double x, double y, double r, double scale, double sqrtomr2, double diff, double Rdiff) { double delta = (1 + r) * (y - x) / sqrtomr2; double numer; if (Math.Abs(delta) > 0.5) { numer = scale * r * Rdiff; double diffy = (y - r * x) / sqrtomr2; if (scale == 1) { numer += MMath.NormalCdfRatio(diffy); } else // this assumes scale = N((y-rx)/sqrt(1-r^2);0,1) { numer += MMath.NormalCdf(diffy); } } else { numer = scale * (NormalCdfRatioDiff(diff, delta) + (1 + r) * Rdiff); } return(numer); }
/// <summary> /// Returns NormalCdfMomentRatio(i,x) for i=n,n+1,n+2,... /// </summary> /// <param name="n">A starting index >= 0</param> /// <param name="x">A real number</param> /// <param name="useConFrac">If true, do not use the lookup table</param> /// <returns></returns> private static IEnumerator <double> NormalCdfMomentRatioSequence(int n, double x, bool useConFrac = false) { if (n < 0) { throw new ArgumentException("n < 0"); } if (x > -1) { // Use the upward recurrence. double rPrev = MMath.NormalCdfRatio(x); if (n == 0) { yield return(rPrev); } double r = x * rPrev + 1; if (n <= 1) { yield return(r); } for (int i = 1; ; i++) { double rNew = (x * r + rPrev) / (i + 1); rPrev = r; r = rNew; if (n <= i + 1) { yield return(r); } } } else { // Use the downward recurrence. // Each batch of tableSize items is generated in advance. // If tableSize is larger than 50, the results will lose accuracy. // If tableSize is too small, then the recurrence is used less often, requiring more computation. int maxTableSize = 30; // rtable[tableStart-i] = R_i double[] rtable = new double[maxTableSize]; int tableStart = -1; int tableSize = 2; for (int i = n; ; i++) { if (i > tableStart) { // build the table tableStart = i + tableSize - 1; if (useConFrac) { rtable[0] = MMath.NormalCdfMomentRatioConFrac(tableStart, x); rtable[1] = MMath.NormalCdfMomentRatioConFrac(tableStart - 1, x); } else { rtable[0] = MMath.NormalCdfMomentRatio(tableStart, x); rtable[1] = MMath.NormalCdfMomentRatio(tableStart - 1, x); } for (int j = 2; j < tableSize; j++) { int nj = tableStart - j + 1; if (rtable[j - 2] == 0 && rtable[j - 1] == 0) { rtable[j] = MMath.NormalCdfMomentRatio(nj - 1, x); } else { rtable[j] = (nj + 1) * rtable[j - 2] - x * rtable[j - 1]; } } // Increase tableSize up to the maximum. tableSize = Math.Min(maxTableSize, 2 * tableSize); } yield return(rtable[tableStart - i]); } } }
// Returns NormalCdf divided by N(x;0,1) N((y-rx)/sqrt(1-r^2);0,1) // requires -1 < x < 0, abs(r) <= 0.6, and x-r*y <= 0 (or equivalently y < -x). // Uses Taylor series at r=0. private static double NormalCdfRatioTaylor(double x, double y, double r) { if (Math.Abs(x) > 5 || Math.Abs(y) > 5) { throw new ArgumentOutOfRangeException(); } // First term of the Taylor series double sum = MMath.NormalCdfRatio(x) * MMath.NormalCdfRatio(y); double Halfx2y2 = (x * x + y * y) / 2; double xy = x * y; // Q = NormalCdf(x,y,r)/dNormalCdf(x,y,r) // where dNormalCdf(x,y,r) = N(x;0,1) N(y; rx, 1-r^2) List <double> Qderivs = new List <double>(); Qderivs.Add(sum); List <double> logphiDerivs = new List <double>(); double dlogphi = xy; logphiDerivs.Add(dlogphi); // dQ(0) = 1 - Q(0) d(log(dNormalCdf)) double Qderiv = 1 - sum * dlogphi; Qderivs.Add(Qderiv); double rPowerN = r; // Second term of the Taylor series sum += Qderiv * rPowerN; double sumOld = sum; for (int n = 2; n <= 100; n++) { if (n == 100) { throw new Exception($"not converging for x={x}, y={y}, r={r}"); } //Console.WriteLine($"n = {n - 1} sum = {sum:r}"); double dlogphiOverFactorial; if (n % 2 == 0) { dlogphiOverFactorial = 1.0 / n - Halfx2y2; } else { dlogphiOverFactorial = xy; } logphiDerivs.Add(dlogphiOverFactorial); // ddQ = -dQ d(log(dNormalCdf)) - Q dd(log(dNormalCdf)), and so on double QderivOverFactorial = 0; for (int i = 0; i < n; i++) { QderivOverFactorial -= Qderivs[i] * logphiDerivs[n - i - 1] * (n - i) / n; } Qderivs.Add(QderivOverFactorial); rPowerN *= r; sum += QderivOverFactorial * rPowerN; if ((sum > double.MaxValue) || double.IsNaN(sum)) { throw new Exception($"not converging for x={x}, y={y}, r={r}"); } if (AreEqual(sum, sumOld)) { break; } sumOld = sum; } double omr2 = (1 - r) * (1 + r); // more accurate than 1-r*r return(sum / Math.Sqrt(omr2)); }