View Javadoc
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| &lt;= 0.46875: Uses a rational function approximation.</li>
59   *   <li>0.46875 &lt; |x| &lt;= 4.0: Uses a rational function approximation for erfc.</li>
60   *   <li>|x| &gt; 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 }