1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.statistics.distribution;
18
19 /**
20 * Utility class used by various distributions to accurately compute their
21 * respective probability mass functions. The implementation for this class is
22 * based on the Catherine Loader's
23 * <a href="http://www.herine.net/stat/software/dbinom.html">dbinom</a> routines.
24 *
25 * This class is not intended to be called directly.
26 */
27 final class SaddlePointExpansionUtils {
28 /** 2 π. */
29 private static final double TWO_PI = 2 * Math.PI;
30 /** 1/10. */
31 private static final double ONE_TENTH = 0.1;
32 /** The threshold value for switching the method to compute th Stirling error. */
33 private static final int STIRLING_ERROR_THRESHOLD = 15;
34
35 /** Exact Stirling expansion error for certain values. */
36 private static final double[] EXACT_STIRLING_ERRORS = {
37 0.0, /* 0.0 */
38 0.1534264097200273452913848, /* 0.5 */
39 0.0810614667953272582196702, /* 1.0 */
40 0.0548141210519176538961390, /* 1.5 */
41 0.0413406959554092940938221, /* 2.0 */
42 0.03316287351993628748511048, /* 2.5 */
43 0.02767792568499833914878929, /* 3.0 */
44 0.02374616365629749597132920, /* 3.5 */
45 0.02079067210376509311152277, /* 4.0 */
46 0.01848845053267318523077934, /* 4.5 */
47 0.01664469118982119216319487, /* 5.0 */
48 0.01513497322191737887351255, /* 5.5 */
49 0.01387612882307074799874573, /* 6.0 */
50 0.01281046524292022692424986, /* 6.5 */
51 0.01189670994589177009505572, /* 7.0 */
52 0.01110455975820691732662991, /* 7.5 */
53 0.010411265261972096497478567, /* 8.0 */
54 0.009799416126158803298389475, /* 8.5 */
55 0.009255462182712732917728637, /* 9.0 */
56 0.008768700134139385462952823, /* 9.5 */
57 0.008330563433362871256469318, /* 10.0 */
58 0.007934114564314020547248100, /* 10.5 */
59 0.007573675487951840794972024, /* 11.0 */
60 0.007244554301320383179543912, /* 11.5 */
61 0.006942840107209529865664152, /* 12.0 */
62 0.006665247032707682442354394, /* 12.5 */
63 0.006408994188004207068439631, /* 13.0 */
64 0.006171712263039457647532867, /* 13.5 */
65 0.005951370112758847735624416, /* 14.0 */
66 0.005746216513010115682023589, /* 14.5 */
67 0.005554733551962801371038690 /* 15.0 */
68 };
69
70 /**
71 * Forbid construction.
72 */
73 private SaddlePointExpansionUtils() {}
74
75 /**
76 * Compute the error of Stirling's series at the given value.
77 * <p>
78 * References:
79 * <ol>
80 * <li>Eric W. Weisstein. "Stirling's Series." From MathWorld--A Wolfram Web
81 * Resource. <a target="_blank"
82 * href="https://mathworld.wolfram.com/StirlingsSeries.html">
83 * https://mathworld.wolfram.com/StirlingsSeries.html</a></li>
84 * </ol>
85 *
86 * <p>Note: This function has been modified for integer {@code z}.</p>
87 *
88 * @param z Value at which the function is evaluated.
89 * @return the Stirling's series error.
90 */
91 static double getStirlingError(int z) {
92 if (z <= STIRLING_ERROR_THRESHOLD) {
93 return EXACT_STIRLING_ERRORS[2 * z];
94 }
95 final double z2 = (double) z * z;
96 return (0.083333333333333333333 -
97 (0.00277777777777777777778 -
98 (0.00079365079365079365079365 -
99 (0.000595238095238095238095238 -
100 0.0008417508417508417508417508 /
101 z2) / z2) / z2) / z2) / z;
102 }
103
104 /**
105 * A part of the deviance portion of the saddle point approximation.
106 * <p>
107 * References:
108 * <ol>
109 * <li>Catherine Loader (2000). "Fast and Accurate Computation of Binomial
110 * Probabilities.". <a target="_blank"
111 * href="http://www.herine.net/stat/papers/dbinom.pdf">
112 * http://www.herine.net/stat/papers/dbinom.pdf</a></li>
113 * </ol>
114 *
115 * <p>Note: This function has been modified for integer {@code x}.</p>
116 *
117 * @param x Value at which the function is evaluated.
118 * @param mu Average.
119 * @return a part of the deviance.
120 */
121 static double getDeviancePart(int x, double mu) {
122 if (Math.abs(x - mu) < 0.1 * (x + mu)) {
123 final double d = x - mu;
124 double v = d / (x + mu);
125 double s1 = v * d;
126 double s = Double.NaN;
127 double ej = 2.0 * x * v;
128 v *= v;
129 int j = 1;
130 while (s1 != s) {
131 s = s1;
132 ej *= v;
133 s1 = s + ej / ((j * 2) + 1);
134 ++j;
135 }
136 return s1;
137 } else if (x == 0) {
138 return mu;
139 }
140 return x * Math.log(x / mu) + mu - x;
141 }
142
143 /**
144 * Compute the logarithm of the PMF for a binomial distribution
145 * using the saddle point expansion.
146 *
147 * @param x Value at which the probability is evaluated.
148 * @param n Number of trials.
149 * @param p Probability of success.
150 * @param q Probability of failure (1 - p).
151 * @return log(p(x)).
152 */
153 static double logBinomialProbability(int x, int n, double p, double q) {
154 if (x == 0) {
155 if (p < ONE_TENTH) {
156 // Subtract from 0 avoids returning -0.0 for p=0.0
157 return 0.0 - getDeviancePart(n, n * q) - n * p;
158 } else if (n == 0) {
159 return 0;
160 }
161 return n * Math.log(q);
162 } else if (x == n) {
163 if (q < ONE_TENTH) {
164 // Subtract from 0 avoids returning -0.0 for p=1.0
165 return 0.0 - getDeviancePart(n, n * p) - n * q;
166 }
167 return n * Math.log(p);
168 }
169 final int nMx = n - x;
170 final double ret = getStirlingError(n) - getStirlingError(x) -
171 getStirlingError(nMx) - getDeviancePart(x, n * p) -
172 getDeviancePart(nMx, n * q);
173 final double f = (TWO_PI * x * nMx) / n;
174 return -0.5 * Math.log(f) + ret;
175 }
176 }