// Start from definition C_r = < (x-M1)^r > and use the binomial formula to write (x - M1)^r in terms of powers of x and M1. // C_r = \sum_{k=0}^{n} {n \choose k} (-\mu)^k M_{r-k} internal static double RawToCentral(double[] M, int r) { Debug.Assert(M.Length > 1); Debug.Assert(0 <= r && r < M.Length); Debug.Assert(M[0] == 1.0); double mmu = -M[1]; IEnumerator <double> B = AdvancedIntegerMath.BinomialCoefficients(r).GetEnumerator(); // k = 0 term is M_r B.MoveNext(); int i = r; // tracks r - k double mmuk = 1.0; // tracks (-\mu)^k double C = M[r]; while (B.MoveNext()) { i--; mmuk *= mmu; C += mmuk * B.Current * M[i]; } // final term is (-\mu)^r {r \choose r} M_0 = (-\mu)^r; this will be wrong if M[0] != 1 return(C); }
// M_r = \sum_{k=0}^{r} {r \choose k} \mu^k C_{r-k} internal static double CentralToRaw(double mu, double[] C, int r) { Debug.Assert(C.Length > 0); Debug.Assert(0 <= r && r < C.Length); Debug.Assert(C[0] == 1.0); //Debug.Assert(C[1] == 0.0); IEnumerator <double> B = AdvancedIntegerMath.BinomialCoefficients(r).GetEnumerator(); // k = 0 term B.MoveNext(); int kp = r; // tracks k' = k - r double muk = 1.0; // tracks \mu^k double M = C[r]; while (B.MoveNext()) { kp--; muk *= mu; M += B.Current * muk * C[kp]; } // final term is \mu^r {r \choose r} C_0 = \mu^r; this will be wrong if C[0] != 1 // second to final term is 0; this will be wrong if C[1] != 0 return(M); }
public void BinomialCoefficientAgreement() { foreach (int n in TestUtilities.GenerateIntegerValues(1, 100, 4)) { int m = -1; IEnumerator B = AdvancedIntegerMath.BinomialCoefficients(n).GetEnumerator(); while (B.MoveNext()) { m++; Assert.IsTrue(TestUtilities.IsNearlyEqual((double)B.Current, AdvancedIntegerMath.BinomialCoefficient(n, m))); } } }
// Privault, "Generalized Bell polynomials and the combinatorics of Poisson central moments", // The Electronic Jounrnal of Combinatorics 2011 // (http://www.ntu.edu.sg/home/nprivault/papers/central_moments.pdf), // derives the recurrence // C_{r+1} = \mu \sum{s=0}^{r-1} { r \choose s } C_s // for central moments of the Poisson distribution, directly from the definition. // For the record, here is the derivation, which I have not found elsewhere. By // Poisson PMF, mean, and definition of central moment: // C_{r+1} = e^{-\mu} \sum_{k=0}^{\infty} \frac{\mu^k}{k!} (k - \mu)^{r+1} // Expand one factor of (k - \mu) to get // C_{r+1} = e^{-\mu} \sum_{k=1}^{\infty} \frac{\mu^k (k - \mu)^{r}}{(k-1)!} // -e^{-\mu} \sum_{k=0}^{\infty} \frac{\mu^{k+1} (k - \mu)^{r}}{k!} // Note k=0 does not contribute to first term because of multiplication by k. // Now in first term, redefine dummy summation variable k -> k + 1 to get // C_{r+1} = e^{-\mu} \sum_{k=0}^{\infty} \frac{\mu^{k+1}}{k!} // \left[ (k + 1 - \mu)^{r} - (k - \mu)^{r} \right] // Now use the binomial theorem to expand (k - \mu + 1) in factors of (k -\mu) and 1. // C_{r+1} = e^{-\mu} \sum_{k=0}^{\infty} \frac{\mu^{k+1}}{k!} // \sum_{s=0}^{r-1} { r \choose s } (k - \mu)^{s} // The s=r term is canceled by the subtracted (k - \mu)^{r}, so the binomial sum only goes to s=r-1. // Switch the order of the sums and notice \sum_{k} \frac{\mu^{k}}{k!} (k - \mu)^{s} = C_{s} // to obtain the result. Wow. // This is almost, but not quite, the same recurrence as for the raw moments. // For the raw moment recursion, the bottom binomial argument runs over its full range. // For the central moment recursion, the final value of the bottom binomial argument is left out. // Also, for raw moments start the recursion with M_0 = 1, M_1 = \mu. For central moments, // start the recursion with C_0 = 1, C_1 = 0. private void ComputePoissonCentralMoments(double[] C) { for (int r = 2; r < C.Length; r++) { IEnumerator <double> binomial = AdvancedIntegerMath.BinomialCoefficients(r - 1).GetEnumerator(); double t = 0.0; for (int s = 0; s < r - 1; s++) { binomial.MoveNext(); t += binomial.Current * C[s]; } C[r] = mu * t; } }
public void BellNumberRecurrence() { // B_{n+1} = \sum_{k=0}^{n} {n \choose k} B_k foreach (int r in TestUtilities.GenerateIntegerValues(50, 100, 2)) { double s = 0.0; IEnumerator <double> b = AdvancedIntegerMath.BinomialCoefficients(r).GetEnumerator(); for (int k = 0; k <= r; k++) { b.MoveNext(); s += b.Current * AdvancedIntegerMath.BellNumber(k); } Assert.IsTrue(TestUtilities.IsNearlyEqual(s, AdvancedIntegerMath.BellNumber(r + 1))); } }
public void BinomialCoefficientSums() { foreach (int n in TestUtilities.GenerateIntegerValues(1, 100, 8)) { double S0 = 0.0; double S1 = 0.0; double S2 = 0.0; int k = 0; foreach (double B in AdvancedIntegerMath.BinomialCoefficients(n)) { S0 += B; S1 += k * B; S2 += k * k * B; k += 1; } Assert.IsTrue(TestUtilities.IsNearlyEqual(S0, MoreMath.Pow(2, n))); Assert.IsTrue(TestUtilities.IsNearlyEqual(S1, MoreMath.Pow(2, n - 1) * n)); Assert.IsTrue(TestUtilities.IsNearlyEqual(S2, MoreMath.Pow(2, n - 2) * n * (n + 1))); } }
internal override double[] CentralMoments(int rMax) { double[] C = new double[rMax + 1]; C[0] = 1.0; if (rMax == 0) { return(C); } C[1] = 0.0; for (int r = 2; r <= rMax; r++) { double s = 0.0; IEnumerator <double> B = AdvancedIntegerMath.BinomialCoefficients(r - 1).GetEnumerator(); for (int k = 0; k <= r - 2; k++) { B.MoveNext(); s += B.Current * (n * q * C[k] - C[k + 1]); } C[r] = p * s; } return(C); }
/// <inheritdoc /> public override int InverseLeftProbability(double P) { if ((P < 0.0) || (P > 1.0)) { throw new ArgumentOutOfRangeException(nameof(P)); } if (n < 16) { // for small m, just add up probabilities directly // here PP is p^k q^(m-k), PS is the sum of PPs up to k, and r = p / q is used to increment PP double r = p / q; double PP = MoreMath.Pow(q, n); double PS = 0.0; int k = -1; foreach (double B in AdvancedIntegerMath.BinomialCoefficients(n)) { k++; PS += B * PP; if (PS >= P) { return(k); } PP *= r; } // we "shouldn't" reach here, but if floating point jitter makes all the values add up to 0.999999 instead of 1, // and we have P = 0.99999999, it could happen, so in this case we return m return(n); } else { double Q = 1.0 - P; if (Q == 0.0) { return(n); } double mu = n * p; int kmin, kmax; if (P < 0.5) { kmin = 0; kmax = (int)Math.Ceiling(mu); int kChernov = (int)Math.Floor(mu - Math.Sqrt(-2.0 * mu * Math.Log(P))); if (kChernov > kmin) { kmin = kChernov; } } else { kmin = (int)Math.Floor(mu); kmax = n; int kMarkov = (int)Math.Ceiling(mu / Q); if (kMarkov < kmax) { kmax = kMarkov; } int kCantelli = (int)Math.Ceiling(mu + Math.Sqrt(mu * q * P / Q)); if (kCantelli < kmax) { kmax = kCantelli; } int kChernov = (int)Math.Ceiling(mu + Math.Sqrt(-2.0 * mu * Math.Log(Q))); if (kChernov < kmax) { kmax = kChernov; } } return(InverseLeftProbability(kmin, kmax, P)); } }
internal double[] Cumulants(int rMax) { double[] K = new double[rMax + 1]; K[0] = 0.0; if (rMax == 0) { return(K); } K[1] = this.Mean; if (rMax == 1) { return(K); } // C. L. Mallows & J. Riordan, "The Inversion Enumerator for Labeled Trees" derives a // recurrence for lognormal cumulants. // K_r = (M_1)^r (e^x - 1)^{r-1} J_{r-1}(x) // Here x = e^{\sigma^2} and J_{n}(x) is a polynomial with all positive coefficients. // J_{n+1} = \sum_{k=0}^{n} {n \choose k} (1 + x + \cdots x^k) J_{k} J_{n - k} // with J_0 = J_1 = 1. // Thus the first few cumulants are // K_0 = 1 // K_1 = M_1 // K_2 = (M_1)^2 (x - 1) // K_3 = (M_1)^3 (x - 1)^2 (2 + x) // K_4 = (M_1)^4 (x - 1)^3 (6 + 6 x^2 + 3 x^2 + x^3) // K_5 = (M_1)^5 (x - 1)^4 (24 + 36 x + 30 x^2 + 20 x^3 + 4 x^4 + x^5) double x = Math.Exp(sigma * sigma); // Form L_k = 1 + x + \cdots + x^{k} double[] L = new double[rMax]; double xk = 1.0; L[0] = 1.0; for (int i = 1; i < L.Length; i++) { xk *= x; L[i] = L[i - 1] + xk; } double y = MoreMath.ExpMinusOne(sigma * sigma) * K[1]; double yk = K[1]; double[] J = new double[rMax]; J[0] = 1.0; for (int i = 1; i < rMax; i++) { J[i] = 0.0; IEnumerator <double> B = AdvancedIntegerMath.BinomialCoefficients(i - 1).GetEnumerator(); for (int j = 0; j < i; j++) { B.MoveNext(); J[i] += B.Current * L[j] * J[j] * J[(i - 1) - j]; } yk *= y; K[i + 1] = yk * J[i]; } return(K); }
/// <inheritdoc /> public override int InverseLeftProbability(double P) { if ((P < 0.0) || (P > 1.0)) { throw new ArgumentOutOfRangeException("P"); } if (n < 16) { // for small m, just add up probabilities directly // here PP is p^k q^(m-k), PS is the sum of PPs up to k, and r = p / q is used to increment PP double r = p / q; double PP = MoreMath.Pow(q, n); double PS = 0.0; /* * using (IEnumerator<double> B = AdvancedIntegerMath.BinomialCoefficients(m).GetEnumerator()) { * // by ending this loop at k = m - 1 instead of k = m, we not only avoid evaluating P(m), but we also * // ensure that if PS = 0.999999 due to floating point noise, and we have P = 0.99999999, we dont * // return m+1 when we should return m * for (int k = 0; k < m; k++) { * B.MoveNext(); * PS += B.Current * PP; * if (PS >= P) return(k); * PP *= r; * } * return (m); * } */ int k = -1; foreach (double B in AdvancedIntegerMath.BinomialCoefficients(n)) { k++; PS += B * PP; if (PS >= P) { return(k); } PP *= r; } // we "shouldn't" reach here, but if floating point jitter makes all the values add up to 0.999999 instead of 1, // and we have P = 0.99999999, it could happen, so in this case we return m return(n); /* * // for small distributions, just add probabilities directly * int k = 0; * double P0 = MoreMath.Pow(q, m); * double PP = P0; * while (k < m) { * if (P <= PP) break; * k++; * P0 *= (m + 1 - k) / k * p / q; * PP += P0; * } * return (k); */ } else { // for larger distributions, use bisection // this will require log_{2}(m) CDF evaluations, which is at most 31 int ka = 0; int kb = n; while (ka != kb) { int k = (ka + kb) / 2; if (P > LeftInclusiveProbability(k)) { ka = k + 1; } else { kb = k; } } return(ka); } }