python - Wolfram Alpha and scipy.integrate.quad give me different answers for the same integral -
consider following function:
import numpy np scipy.special import erf def my_func(x): return np.exp(x ** 2) * (1 + erf(x)) when evaluate integral of function -14 -4 using scipy's quad function, following result:
in [3]: scipy import integrate in [4]: integrate.quad(my_func, -14, -4) /usr/local/lib/python2.7/dist-packages/scipy/integrate/quadpack.py:289: userwarning: maximum number of subdivisions (50) has been achieved. if increasing limit yields no improvement advised analyze integrand in order determine difficulties. if position of local difficulty can determined (singularity, discontinuity) 1 gain splitting interval , calling integrator on subranges. perhaps special-purpose integrator should used. warnings.warn(msg) out[4]: (0.21896647054443383, 0.00014334175850538866) that is, 0.22.
however, when submit integral wolfram alpha, different result:
-5.29326 x 10 ^ 69. what's deal? i'm guessing has warning scipy has given me. what's best way evaluate integral in python?
note: increasing limit changes warning leaves scipy result unchanged:
in [5]: integrate.quad(my_func, -14, -4, limit=10000) /usr/local/lib/python2.7/dist-packages/scipy/integrate/quadpack.py:289: userwarning: occurrence of roundoff error detected, prevents requested tolerance being achieved. error may underestimated. warnings.warn(msg) out[5]: (0.21894780966717864, 1.989164129832358e-05)
tl;dr: integrand equivalent erfcx(-x), , implementation of erfcx @ scipy.special.erfcx takes care of numerical issues:
in [10]: scipy.integrate import quad in [11]: scipy.special import erfcx in [12]: quad(lambda x: erfcx(-x), -14, -4) out[12]: (0.6990732491815446, 1.4463494884581349e-13) in [13]: quad(lambda x: erfcx(-x), -150, -50) out[13]: (0.6197754761443759, 4.165648376274775e-14) you can avoid lambda expression changing sign of integration argument , limits:
in [14]: quad(erfcx, 4, 14) out[14]: (0.6990732491815446, 1.4463494884581349e-13) the problem numerical evaluation of 1 + erf(x) negative values of x. x decreases, erf(x) approaches -1. when add 1, catastrophic loss of precision, , sufficiently negative x (specifically x < -5.87), 1 + erf(x) numerically 0.
note default behavior @ wolfram alpha suffers same problem. had click on "more digits" twice reasonable answer.
the fix reformulate function. can express 1+erf(x) 2*ndtr(x*sqrt(2)), ndtr normal cumulative distribution function, available scipy.special.ndtr (see, example, https://en.wikipedia.org/wiki/error_function). here's alternative version of function, , result of integrating scipy.integrate.quad:
in [133]: def func2(x): .....: return np.exp(x**2) * 2 * ndtr(x * np.sqrt(2)) .....: in [134]: my_func(-5) out[134]: 0.1107029852258767 in [135]: func2(-5) out[135]: 0.11070463773306743 in [136]: integrate.quad(func2, -14, -4) out[136]: (0.6990732491815298, 1.4469372263470424e-13) the answer @ wolfram alpha after clicking on "more digits" twice 0.6990732491815446...
this plot of function looks when use numerically stable version:
to avoid overflow or underflow arguments large magnitudes, can part of computation in log-space:
from scipy.special import log_ndtr def func3(x): t = x**2 + np.log(2) + log_ndtr(x * np.sqrt(2)) y = np.exp(t) return y e.g.
in [20]: quad(func3, -150, -50) out[20]: (0.6197754761435517, 4.6850379059597266e-14) (looks @ali_m beat me in new question: tricking numpy/python representing large , small numbers.)
finally, simon byrne pointed out in answer on @ tricking numpy/python representing large , small numbers, function integrated can expressed erfcx(-x), erfcx scaled complementary error function. available scipy.special.erfcx.
for example,
in [10]: scipy.integrate import quad in [11]: scipy.special import erfcx in [12]: quad(lambda x: erfcx(-x), -14, -4) out[12]: (0.6990732491815446, 1.4463494884581349e-13) in [13]: quad(lambda x: erfcx(-x), -150, -50) out[13]: (0.6197754761443759, 4.165648376274775e-14) 
Comments
Post a Comment