1 // ******************************************************************************
2 //
3 // Title: Force Field X.
4 // Description: Force Field X - Software for Molecular Biophysics.
5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025.
6 //
7 // This file is part of Force Field X.
8 //
9 // Force Field X is free software; you can redistribute it and/or modify it
10 // under the terms of the GNU General Public License version 3 as published by
11 // the Free Software Foundation.
12 //
13 // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 // details.
17 //
18 // You should have received a copy of the GNU General Public License along with
19 // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20 // Place, Suite 330, Boston, MA 02111-1307 USA
21 //
22 // Linking this library statically or dynamically with other modules is making a
23 // combined work based on this library. Thus, the terms and conditions of the
24 // GNU General Public License cover the whole combination.
25 //
26 // As a special exception, the copyright holders of this library give you
27 // permission to link this library with independent modules to produce an
28 // executable, regardless of the license terms of these independent modules, and
29 // to copy and distribute the resulting executable under terms of your choice,
30 // provided that you also meet, for each linked independent module, the terms
31 // and conditions of the license of that module. An independent module is a
32 // module which is not derived from or based on this library. If you modify this
33 // library, you may extend this exception to your version of the library, but
34 // you are not obligated to do so. If you do not wish to do so, delete this
35 // exception statement from your version.
36 //
37 // ******************************************************************************
38 package ffx.numerics.special;
39
40 import static org.apache.commons.math3.util.FastMath.PI;
41 import static org.apache.commons.math3.util.FastMath.abs;
42 import static org.apache.commons.math3.util.FastMath.exp;
43 import static org.apache.commons.math3.util.FastMath.floor;
44 import static org.apache.commons.math3.util.FastMath.sqrt;
45
46 /**
47 * Static methods to evaluate erf(x) and erfc(x) for a real argument x. Rational functions are used
48 * that approximate erf(x) and erfc(x) to machine precision (approximately 15 decimal digits).
49 *
50 * <p>The error function erf(x) is defined as:
51 * erf(x) = (2/sqrt(pi)) * integral from 0 to x of exp(-t^2) dt
52 *
53 * <p>The complementary error function erfc(x) is defined as:
54 * erfc(x) = 1 - erf(x) = (2/sqrt(pi)) * integral from x to infinity of exp(-t^2) dt
55 *
56 * <p>This implementation uses different approximation formulas for different ranges of the input:
57 * <ul>
58 * <li>|x| <= 0.46875: Uses a rational function approximation.</li>
59 * <li>0.46875 < |x| <= 4.0: Uses a rational function approximation for erfc.</li>
60 * <li>|x| > 4.0: Uses a rational function approximation for erfc with additional scaling.</li>
61 * </ul>
62 *
63 * <p>Adapted from an original program written by W. J. Cody, Mathematics and Computer Science
64 * Division, Argonne National Laboratory, Argonne, IL 60439
65 *
66 * <p>Reference: W. J. Cody, "Rational Chebyshev Approximations for the Error Function,"
67 * Mathematics of Computation, Vol. 23, No. 107 (Jul., 1969), pp. 631-637.
68 *
69 * @author Michael J. Schnieders
70 * @since 1.0
71 */
72 public class Erf {
73
74 // Mathematical and machine-dependent constants.
75
76 /**
77 * The reciprocal of the square root of PI, used in the error function calculations.
78 */
79 private static final double sqrtPI = 1.0 / sqrt(PI);
80
81 /**
82 * One-sixteenth (1/16), used in the computation of exp(-y*y) for large y.
83 */
84 private static final double oneSixteenth = 1.0 / 16.0;
85
86 /**
87 * The threshold value that determines which approximation formula to use.
88 * For |x| <= thresh, a direct approximation of erf(x) is used.
89 * For |x| > thresh, the complementary error function erfc(x) is computed first.
90 */
91 private static final double thresh = 0.46875;
92
93 /**
94 * The xSmall argument below which erf(x) may be represented by 2*x/sqrt(pi) and above which x*x won't underflow.
95 * <p>
96 * A conservative value is the largest machine number X such that 1.0 + X = 1.0 to machine precision.
97 * This is approximately the square root of the double precision machine epsilon.
98 */
99 private static final double xSmall = 1.11e-16;
100
101 /**
102 * xBig is the largest argument acceptable for erfc.
103 * <p>
104 * Solution to the equation:
105 * <p>
106 * W(x) * (1-0.5/x**2) = xMin, where
107 * <p>
108 * W(x) = exp(-x*x)/[x*sqrt(pi)]
109 * <p>
110 * For x > xBig, erfc(x) is effectively 0 in double precision.
111 */
112 private static final double xBig = 26.543;
113
114 private Erf() {
115 }
116
117 /**
118 * Evaluates erf(x) for a real argument x.
119 * <p>
120 * The error function erf(x) is defined as:
121 * erf(x) = (2/sqrt(pi)) * integral from 0 to x of exp(-t^2) dt
122 * <p>
123 * Special cases:
124 * <ul>
125 * <li>If arg is NaN, then the result is NaN.</li>
126 * <li>If arg is +infinity, then the result is 1.0.</li>
127 * <li>If arg is -infinity, then the result is -1.0.</li>
128 * <li>If arg is 0, then the result is 0.</li>
129 * </ul>
130 *
131 * @param arg the value to evaluate erf at.
132 * @return erf of the argument.
133 * @since 1.0
134 */
135 public static double erf(final double arg) {
136 // Handle special cases
137 if (Double.isNaN(arg)) {
138 return Double.NaN;
139 }
140 if (Double.isInfinite(arg)) {
141 return arg > 0 ? 1.0 : -1.0;
142 }
143
144 return erfCore(arg, false);
145 }
146
147 /**
148 * Evaluate erfc(x) for a real argument x.
149 * <p>
150 * The complementary error function erfc(x) is defined as:
151 * erfc(x) = 1 - erf(x) = (2/sqrt(pi)) * integral from x to infinity of exp(-t^2) dt
152 * <p>
153 * Special cases:
154 * <ul>
155 * <li>If arg is NaN, then the result is NaN.</li>
156 * <li>If arg is +infinity, then the result is 0.0.</li>
157 * <li>If arg is -infinity, then the result is 2.0.</li>
158 * <li>If arg is 0, then the result is 1.0.</li>
159 * </ul>
160 *
161 * @param arg the value to evaluate erfc at.
162 * @return erfc of the argument.
163 * @since 1.0
164 */
165 public static double erfc(final double arg) {
166 // Handle special cases
167 if (Double.isNaN(arg)) {
168 return Double.NaN;
169 }
170 if (Double.isInfinite(arg)) {
171 return arg > 0 ? 0.0 : 2.0;
172 }
173
174 return erfCore(arg, true);
175 }
176
177 /**
178 * Evaluates erf(x) or erfc(x) for a real argument x. When called with mode = false, erf is
179 * returned, while with mode = true, erfc is returned.
180 * <p>
181 * This method implements the core algorithm for both erf and erfc functions using rational
182 * function approximations for different ranges of the input value. The implementation is based
183 * on the algorithm developed by W. J. Cody.
184 * <p>
185 * The algorithm uses three different approximation formulas:
186 * <ul>
187 * <li>For |x| <= 0.46875: Direct rational approximation of erf(x)</li>
188 * <li>For 0.46875 < |x| <= 4.0: Rational approximation of erfc(x)</li>
189 * <li>For |x| > 4.0: Continued fraction approximation of erfc(x)</li>
190 * </ul>
191 *
192 * @param x the value to evaluate erf or erfc at.
193 * @param mode if mode is true, evaluate erfc, otherwise evaluate erf.
194 * @return if (!mode) erf(arg), else erfc(arg)
195 * @since 1.0
196 */
197 private static double erfCore(final double x, final boolean mode) {
198
199 // Store the argument and its absolute value.
200 final double y = abs(x);
201 double result = 0.0;
202
203 // Evaluate error function for |x| less than 0.46875.
204 if (y <= thresh) {
205 // For very small values, avoid underflow in y^2 calculation
206 double ysq = 0.0;
207 if (y > xSmall) {
208 ysq = y * y;
209 }
210
211 // Calculate rational approximation for erf(x)
212 // P(y^2)/Q(y^2) where P and Q are polynomials
213 // These coefficients are from Cody's Chebyshev approximation
214 double xNum = 1.85777706184603153e-1 * ysq;
215 double xDen = ysq;
216
217 // Build up the polynomials term by term
218 xNum = (xNum + 3.16112374387056560e0) * ysq;
219 xDen = (xDen + 2.36012909523441209e1) * ysq;
220
221 xNum = (xNum + 1.13864154151050156e2) * ysq;
222 xDen = (xDen + 2.44024637934444173e2) * ysq;
223
224 xNum = (xNum + 3.77485237685302021e2) * ysq;
225 xDen = (xDen + 1.28261652607737228e3) * ysq;
226
227 // Final term and calculation
228 // Multiply by x to get erf(x)
229 result = x * (xNum + 3.20937758913846947e3) / (xDen + 2.84423683343917062e3);
230
231 // If calculating erfc, return 1 - erf(x)
232 if (mode) {
233 result = 1.0 - result;
234 }
235 } else if (y <= 4.0) {
236 // Get complementary error function for 0.46875 <= |x| <= 4.0.
237 // For this range, we use a rational approximation for erfc(y)
238 // These coefficients are from Cody's Chebyshev approximation
239
240 // Calculate rational approximation for erfc(y)
241 // R(y) = P(y)/Q(y) where P and Q are polynomials in y
242 double xNum = 2.15311535474403846e-8 * y;
243 double xDen = y;
244
245 // Build up the polynomials term by term
246 xNum = (xNum + 5.64188496988670089e-1) * y;
247 xDen = (xDen + 1.57449261107098347e1) * y;
248
249 xNum = (xNum + 8.88314979438837594e0) * y;
250 xDen = (xDen + 1.17693950891312499e2) * y;
251
252 xNum = (xNum + 6.61191906371416295e1) * y;
253 xDen = (xDen + 5.37181101862009858e2) * y;
254
255 xNum = (xNum + 2.98635138197400131e2) * y;
256 xDen = (xDen + 1.62138957456669019e3) * y;
257
258 xNum = (xNum + 8.81952221241769090e2) * y;
259 xDen = (xDen + 3.29079923573345963e3) * y;
260
261 xNum = (xNum + 1.71204761263407058e3) * y;
262 xDen = (xDen + 4.36261909014324716e3) * y;
263
264 xNum = (xNum + 2.05107837782607147e3) * y;
265 xDen = (xDen + 3.43936767414372164e3) * y;
266
267 // Final term and calculation
268 result = (xNum + 1.23033935479799725e3) / (xDen + 1.23033935480374942e3);
269
270 // Compute exp(-y*y) efficiently for large y
271 // Break y into integer and fractional parts to avoid overflow
272 double ysq = floor(16.0 * y) * oneSixteenth; // Integer part divided by 16
273 double del = (y - ysq) * (y + ysq); // Compute remainder using difference of squares
274
275 // Multiply by exp(-y*y) to get erfc(y)
276 result = exp(-ysq * ysq - del) * result;
277 if (!mode) {
278 result = 1.0 - result;
279 if (x < 0.0) {
280 result = -result;
281 }
282 } else if (x < 0.0) {
283 result = 2.0 - result;
284 }
285 } else {
286 // Get complementary error function for |x| greater than 4.0.
287 // For large |x|, we use a continued fraction approximation for erfc
288 if (y < xBig) {
289 // For very large y, erfc(y) approaches 0, but we need to compute it accurately
290 // Use a rational approximation in 1/y and 1/y^2
291
292 double iy = 1.0 / y; // Inverse of y
293 double ysq = iy * iy; // 1/y^2
294
295 // Calculate rational approximation for erfc(y) * exp(y^2) * y * sqrt(pi)
296 // These coefficients are from Cody's Chebyshev approximation
297 double xNum = 1.63153871373020978e-2 * ysq;
298 double xDen = ysq;
299
300 // Build up the polynomials term by term
301 xNum = (xNum + 3.05326634961232344e-1) * ysq;
302 xDen = (xDen + 2.56852019228982242e0) * ysq;
303
304 xNum = (xNum + 3.60344899949804439e-1) * ysq;
305 xDen = (xDen + 1.87295284992346047e0) * ysq;
306
307 xNum = (xNum + 1.25781726111229246e-1) * ysq;
308 xDen = (xDen + 5.27905102951428412e-1) * ysq;
309
310 xNum = (xNum + 1.60837851487422766e-2) * ysq;
311 xDen = (xDen + 6.05183413124413191e-2) * ysq;
312
313 // Final term and calculation
314 result = ysq * (xNum + 6.58749161529837803e-4) / (xDen + 2.33520497626869185e-3);
315
316 // Complete the calculation of erfc(y)
317 result = (sqrtPI - result) * iy;
318
319 // Compute exp(-y*y) efficiently for large y
320 // Break y into integer and fractional parts to avoid overflow
321 ysq = floor(16.0 * y) * oneSixteenth; // Integer part divided by 16
322 double del = (y - ysq) * (y + ysq); // Compute remainder using difference of squares
323
324 // Multiply by exp(-y*y) to get erfc(y)
325 result = exp(-ysq * ysq - del) * result;
326 }
327 // For y >= xBig, erfc(y) is effectively 0 in double precision
328 // result is already initialized to 0.0
329 // Convert erfc result to erf result if needed
330 if (!mode) {
331 // For erf, use the relationship erf(x) = 1 - erfc(x)
332 result = 1.0 - result;
333
334 // erf is an odd function: erf(-x) = -erf(x)
335 if (x < 0.0) {
336 result = -result;
337 }
338 } else {
339 // erfc is neither odd nor even: erfc(-x) = 2 - erfc(x)
340 if (x < 0.0) {
341 result = 2.0 - result;
342 }
343 }
344 }
345 return result;
346 }
347 }