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 }