_quadrature.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360
  1. from __future__ import annotations
  2. from typing import TYPE_CHECKING, Callable, Dict, Tuple, Any, cast
  3. import functools
  4. import numpy as np
  5. import math
  6. import types
  7. import warnings
  8. from collections import namedtuple
  9. from scipy.special import roots_legendre
  10. from scipy.special import gammaln, logsumexp
  11. from scipy._lib._util import _rng_spawn
  12. __all__ = ['fixed_quad', 'quadrature', 'romberg', 'romb',
  13. 'trapezoid', 'trapz', 'simps', 'simpson',
  14. 'cumulative_trapezoid', 'cumtrapz', 'newton_cotes',
  15. 'AccuracyWarning']
  16. def trapezoid(y, x=None, dx=1.0, axis=-1):
  17. r"""
  18. Integrate along the given axis using the composite trapezoidal rule.
  19. If `x` is provided, the integration happens in sequence along its
  20. elements - they are not sorted.
  21. Integrate `y` (`x`) along each 1d slice on the given axis, compute
  22. :math:`\int y(x) dx`.
  23. When `x` is specified, this integrates along the parametric curve,
  24. computing :math:`\int_t y(t) dt =
  25. \int_t y(t) \left.\frac{dx}{dt}\right|_{x=x(t)} dt`.
  26. Parameters
  27. ----------
  28. y : array_like
  29. Input array to integrate.
  30. x : array_like, optional
  31. The sample points corresponding to the `y` values. If `x` is None,
  32. the sample points are assumed to be evenly spaced `dx` apart. The
  33. default is None.
  34. dx : scalar, optional
  35. The spacing between sample points when `x` is None. The default is 1.
  36. axis : int, optional
  37. The axis along which to integrate.
  38. Returns
  39. -------
  40. trapezoid : float or ndarray
  41. Definite integral of `y` = n-dimensional array as approximated along
  42. a single axis by the trapezoidal rule. If `y` is a 1-dimensional array,
  43. then the result is a float. If `n` is greater than 1, then the result
  44. is an `n`-1 dimensional array.
  45. See Also
  46. --------
  47. cumulative_trapezoid, simpson, romb
  48. Notes
  49. -----
  50. Image [2]_ illustrates trapezoidal rule -- y-axis locations of points
  51. will be taken from `y` array, by default x-axis distances between
  52. points will be 1.0, alternatively they can be provided with `x` array
  53. or with `dx` scalar. Return value will be equal to combined area under
  54. the red lines.
  55. References
  56. ----------
  57. .. [1] Wikipedia page: https://en.wikipedia.org/wiki/Trapezoidal_rule
  58. .. [2] Illustration image:
  59. https://en.wikipedia.org/wiki/File:Composite_trapezoidal_rule_illustration.png
  60. Examples
  61. --------
  62. Use the trapezoidal rule on evenly spaced points:
  63. >>> import numpy as np
  64. >>> from scipy import integrate
  65. >>> integrate.trapezoid([1, 2, 3])
  66. 4.0
  67. The spacing between sample points can be selected by either the
  68. ``x`` or ``dx`` arguments:
  69. >>> integrate.trapezoid([1, 2, 3], x=[4, 6, 8])
  70. 8.0
  71. >>> integrate.trapezoid([1, 2, 3], dx=2)
  72. 8.0
  73. Using a decreasing ``x`` corresponds to integrating in reverse:
  74. >>> integrate.trapezoid([1, 2, 3], x=[8, 6, 4])
  75. -8.0
  76. More generally ``x`` is used to integrate along a parametric curve. We can
  77. estimate the integral :math:`\int_0^1 x^2 = 1/3` using:
  78. >>> x = np.linspace(0, 1, num=50)
  79. >>> y = x**2
  80. >>> integrate.trapezoid(y, x)
  81. 0.33340274885464394
  82. Or estimate the area of a circle, noting we repeat the sample which closes
  83. the curve:
  84. >>> theta = np.linspace(0, 2 * np.pi, num=1000, endpoint=True)
  85. >>> integrate.trapezoid(np.cos(theta), x=np.sin(theta))
  86. 3.141571941375841
  87. ``trapezoid`` can be applied along a specified axis to do multiple
  88. computations in one call:
  89. >>> a = np.arange(6).reshape(2, 3)
  90. >>> a
  91. array([[0, 1, 2],
  92. [3, 4, 5]])
  93. >>> integrate.trapezoid(a, axis=0)
  94. array([1.5, 2.5, 3.5])
  95. >>> integrate.trapezoid(a, axis=1)
  96. array([2., 8.])
  97. """
  98. # Future-proofing, in case NumPy moves from trapz to trapezoid for the same
  99. # reasons as SciPy
  100. if hasattr(np, 'trapezoid'):
  101. return np.trapezoid(y, x=x, dx=dx, axis=axis)
  102. else:
  103. return np.trapz(y, x=x, dx=dx, axis=axis)
  104. # Note: alias kept for backwards compatibility. Rename was done
  105. # because trapz is a slur in colloquial English (see gh-12924).
  106. def trapz(y, x=None, dx=1.0, axis=-1):
  107. """An alias of `trapezoid`.
  108. `trapz` is kept for backwards compatibility. For new code, prefer
  109. `trapezoid` instead.
  110. """
  111. return trapezoid(y, x=x, dx=dx, axis=axis)
  112. class AccuracyWarning(Warning):
  113. pass
  114. if TYPE_CHECKING:
  115. # workaround for mypy function attributes see:
  116. # https://github.com/python/mypy/issues/2087#issuecomment-462726600
  117. from typing import Protocol
  118. class CacheAttributes(Protocol):
  119. cache: Dict[int, Tuple[Any, Any]]
  120. else:
  121. CacheAttributes = Callable
  122. def cache_decorator(func: Callable) -> CacheAttributes:
  123. return cast(CacheAttributes, func)
  124. @cache_decorator
  125. def _cached_roots_legendre(n):
  126. """
  127. Cache roots_legendre results to speed up calls of the fixed_quad
  128. function.
  129. """
  130. if n in _cached_roots_legendre.cache:
  131. return _cached_roots_legendre.cache[n]
  132. _cached_roots_legendre.cache[n] = roots_legendre(n)
  133. return _cached_roots_legendre.cache[n]
  134. _cached_roots_legendre.cache = dict()
  135. def fixed_quad(func, a, b, args=(), n=5):
  136. """
  137. Compute a definite integral using fixed-order Gaussian quadrature.
  138. Integrate `func` from `a` to `b` using Gaussian quadrature of
  139. order `n`.
  140. Parameters
  141. ----------
  142. func : callable
  143. A Python function or method to integrate (must accept vector inputs).
  144. If integrating a vector-valued function, the returned array must have
  145. shape ``(..., len(x))``.
  146. a : float
  147. Lower limit of integration.
  148. b : float
  149. Upper limit of integration.
  150. args : tuple, optional
  151. Extra arguments to pass to function, if any.
  152. n : int, optional
  153. Order of quadrature integration. Default is 5.
  154. Returns
  155. -------
  156. val : float
  157. Gaussian quadrature approximation to the integral
  158. none : None
  159. Statically returned value of None
  160. See Also
  161. --------
  162. quad : adaptive quadrature using QUADPACK
  163. dblquad : double integrals
  164. tplquad : triple integrals
  165. romberg : adaptive Romberg quadrature
  166. quadrature : adaptive Gaussian quadrature
  167. romb : integrators for sampled data
  168. simpson : integrators for sampled data
  169. cumulative_trapezoid : cumulative integration for sampled data
  170. ode : ODE integrator
  171. odeint : ODE integrator
  172. Examples
  173. --------
  174. >>> from scipy import integrate
  175. >>> import numpy as np
  176. >>> f = lambda x: x**8
  177. >>> integrate.fixed_quad(f, 0.0, 1.0, n=4)
  178. (0.1110884353741496, None)
  179. >>> integrate.fixed_quad(f, 0.0, 1.0, n=5)
  180. (0.11111111111111102, None)
  181. >>> print(1/9.0) # analytical result
  182. 0.1111111111111111
  183. >>> integrate.fixed_quad(np.cos, 0.0, np.pi/2, n=4)
  184. (0.9999999771971152, None)
  185. >>> integrate.fixed_quad(np.cos, 0.0, np.pi/2, n=5)
  186. (1.000000000039565, None)
  187. >>> np.sin(np.pi/2)-np.sin(0) # analytical result
  188. 1.0
  189. """
  190. x, w = _cached_roots_legendre(n)
  191. x = np.real(x)
  192. if np.isinf(a) or np.isinf(b):
  193. raise ValueError("Gaussian quadrature is only available for "
  194. "finite limits.")
  195. y = (b-a)*(x+1)/2.0 + a
  196. return (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1), None
  197. def vectorize1(func, args=(), vec_func=False):
  198. """Vectorize the call to a function.
  199. This is an internal utility function used by `romberg` and
  200. `quadrature` to create a vectorized version of a function.
  201. If `vec_func` is True, the function `func` is assumed to take vector
  202. arguments.
  203. Parameters
  204. ----------
  205. func : callable
  206. User defined function.
  207. args : tuple, optional
  208. Extra arguments for the function.
  209. vec_func : bool, optional
  210. True if the function func takes vector arguments.
  211. Returns
  212. -------
  213. vfunc : callable
  214. A function that will take a vector argument and return the
  215. result.
  216. """
  217. if vec_func:
  218. def vfunc(x):
  219. return func(x, *args)
  220. else:
  221. def vfunc(x):
  222. if np.isscalar(x):
  223. return func(x, *args)
  224. x = np.asarray(x)
  225. # call with first point to get output type
  226. y0 = func(x[0], *args)
  227. n = len(x)
  228. dtype = getattr(y0, 'dtype', type(y0))
  229. output = np.empty((n,), dtype=dtype)
  230. output[0] = y0
  231. for i in range(1, n):
  232. output[i] = func(x[i], *args)
  233. return output
  234. return vfunc
  235. def quadrature(func, a, b, args=(), tol=1.49e-8, rtol=1.49e-8, maxiter=50,
  236. vec_func=True, miniter=1):
  237. """
  238. Compute a definite integral using fixed-tolerance Gaussian quadrature.
  239. Integrate `func` from `a` to `b` using Gaussian quadrature
  240. with absolute tolerance `tol`.
  241. Parameters
  242. ----------
  243. func : function
  244. A Python function or method to integrate.
  245. a : float
  246. Lower limit of integration.
  247. b : float
  248. Upper limit of integration.
  249. args : tuple, optional
  250. Extra arguments to pass to function.
  251. tol, rtol : float, optional
  252. Iteration stops when error between last two iterates is less than
  253. `tol` OR the relative change is less than `rtol`.
  254. maxiter : int, optional
  255. Maximum order of Gaussian quadrature.
  256. vec_func : bool, optional
  257. True or False if func handles arrays as arguments (is
  258. a "vector" function). Default is True.
  259. miniter : int, optional
  260. Minimum order of Gaussian quadrature.
  261. Returns
  262. -------
  263. val : float
  264. Gaussian quadrature approximation (within tolerance) to integral.
  265. err : float
  266. Difference between last two estimates of the integral.
  267. See Also
  268. --------
  269. romberg : adaptive Romberg quadrature
  270. fixed_quad : fixed-order Gaussian quadrature
  271. quad : adaptive quadrature using QUADPACK
  272. dblquad : double integrals
  273. tplquad : triple integrals
  274. romb : integrator for sampled data
  275. simpson : integrator for sampled data
  276. cumulative_trapezoid : cumulative integration for sampled data
  277. ode : ODE integrator
  278. odeint : ODE integrator
  279. Examples
  280. --------
  281. >>> from scipy import integrate
  282. >>> import numpy as np
  283. >>> f = lambda x: x**8
  284. >>> integrate.quadrature(f, 0.0, 1.0)
  285. (0.11111111111111106, 4.163336342344337e-17)
  286. >>> print(1/9.0) # analytical result
  287. 0.1111111111111111
  288. >>> integrate.quadrature(np.cos, 0.0, np.pi/2)
  289. (0.9999999999999536, 3.9611425250996035e-11)
  290. >>> np.sin(np.pi/2)-np.sin(0) # analytical result
  291. 1.0
  292. """
  293. if not isinstance(args, tuple):
  294. args = (args,)
  295. vfunc = vectorize1(func, args, vec_func=vec_func)
  296. val = np.inf
  297. err = np.inf
  298. maxiter = max(miniter+1, maxiter)
  299. for n in range(miniter, maxiter+1):
  300. newval = fixed_quad(vfunc, a, b, (), n)[0]
  301. err = abs(newval-val)
  302. val = newval
  303. if err < tol or err < rtol*abs(val):
  304. break
  305. else:
  306. warnings.warn(
  307. "maxiter (%d) exceeded. Latest difference = %e" % (maxiter, err),
  308. AccuracyWarning)
  309. return val, err
  310. def tupleset(t, i, value):
  311. l = list(t)
  312. l[i] = value
  313. return tuple(l)
  314. # Note: alias kept for backwards compatibility. Rename was done
  315. # because cumtrapz is a slur in colloquial English (see gh-12924).
  316. def cumtrapz(y, x=None, dx=1.0, axis=-1, initial=None):
  317. """An alias of `cumulative_trapezoid`.
  318. `cumtrapz` is kept for backwards compatibility. For new code, prefer
  319. `cumulative_trapezoid` instead.
  320. """
  321. return cumulative_trapezoid(y, x=x, dx=dx, axis=axis, initial=initial)
  322. def cumulative_trapezoid(y, x=None, dx=1.0, axis=-1, initial=None):
  323. """
  324. Cumulatively integrate y(x) using the composite trapezoidal rule.
  325. Parameters
  326. ----------
  327. y : array_like
  328. Values to integrate.
  329. x : array_like, optional
  330. The coordinate to integrate along. If None (default), use spacing `dx`
  331. between consecutive elements in `y`.
  332. dx : float, optional
  333. Spacing between elements of `y`. Only used if `x` is None.
  334. axis : int, optional
  335. Specifies the axis to cumulate. Default is -1 (last axis).
  336. initial : scalar, optional
  337. If given, insert this value at the beginning of the returned result.
  338. Typically this value should be 0. Default is None, which means no
  339. value at ``x[0]`` is returned and `res` has one element less than `y`
  340. along the axis of integration.
  341. Returns
  342. -------
  343. res : ndarray
  344. The result of cumulative integration of `y` along `axis`.
  345. If `initial` is None, the shape is such that the axis of integration
  346. has one less value than `y`. If `initial` is given, the shape is equal
  347. to that of `y`.
  348. See Also
  349. --------
  350. numpy.cumsum, numpy.cumprod
  351. quad : adaptive quadrature using QUADPACK
  352. romberg : adaptive Romberg quadrature
  353. quadrature : adaptive Gaussian quadrature
  354. fixed_quad : fixed-order Gaussian quadrature
  355. dblquad : double integrals
  356. tplquad : triple integrals
  357. romb : integrators for sampled data
  358. ode : ODE integrators
  359. odeint : ODE integrators
  360. Examples
  361. --------
  362. >>> from scipy import integrate
  363. >>> import numpy as np
  364. >>> import matplotlib.pyplot as plt
  365. >>> x = np.linspace(-2, 2, num=20)
  366. >>> y = x
  367. >>> y_int = integrate.cumulative_trapezoid(y, x, initial=0)
  368. >>> plt.plot(x, y_int, 'ro', x, y[0] + 0.5 * x**2, 'b-')
  369. >>> plt.show()
  370. """
  371. y = np.asarray(y)
  372. if x is None:
  373. d = dx
  374. else:
  375. x = np.asarray(x)
  376. if x.ndim == 1:
  377. d = np.diff(x)
  378. # reshape to correct shape
  379. shape = [1] * y.ndim
  380. shape[axis] = -1
  381. d = d.reshape(shape)
  382. elif len(x.shape) != len(y.shape):
  383. raise ValueError("If given, shape of x must be 1-D or the "
  384. "same as y.")
  385. else:
  386. d = np.diff(x, axis=axis)
  387. if d.shape[axis] != y.shape[axis] - 1:
  388. raise ValueError("If given, length of x along axis must be the "
  389. "same as y.")
  390. nd = len(y.shape)
  391. slice1 = tupleset((slice(None),)*nd, axis, slice(1, None))
  392. slice2 = tupleset((slice(None),)*nd, axis, slice(None, -1))
  393. res = np.cumsum(d * (y[slice1] + y[slice2]) / 2.0, axis=axis)
  394. if initial is not None:
  395. if not np.isscalar(initial):
  396. raise ValueError("`initial` parameter should be a scalar.")
  397. shape = list(res.shape)
  398. shape[axis] = 1
  399. res = np.concatenate([np.full(shape, initial, dtype=res.dtype), res],
  400. axis=axis)
  401. return res
  402. def _basic_simpson(y, start, stop, x, dx, axis):
  403. nd = len(y.shape)
  404. if start is None:
  405. start = 0
  406. step = 2
  407. slice_all = (slice(None),)*nd
  408. slice0 = tupleset(slice_all, axis, slice(start, stop, step))
  409. slice1 = tupleset(slice_all, axis, slice(start+1, stop+1, step))
  410. slice2 = tupleset(slice_all, axis, slice(start+2, stop+2, step))
  411. if x is None: # Even-spaced Simpson's rule.
  412. result = np.sum(y[slice0] + 4.0*y[slice1] + y[slice2], axis=axis)
  413. result *= dx / 3.0
  414. else:
  415. # Account for possibly different spacings.
  416. # Simpson's rule changes a bit.
  417. h = np.diff(x, axis=axis)
  418. sl0 = tupleset(slice_all, axis, slice(start, stop, step))
  419. sl1 = tupleset(slice_all, axis, slice(start+1, stop+1, step))
  420. h0 = np.float64(h[sl0])
  421. h1 = np.float64(h[sl1])
  422. hsum = h0 + h1
  423. hprod = h0 * h1
  424. h0divh1 = np.true_divide(h0, h1, out=np.zeros_like(h0), where=h1 != 0)
  425. tmp = hsum/6.0 * (y[slice0] *
  426. (2.0 - np.true_divide(1.0, h0divh1,
  427. out=np.zeros_like(h0divh1),
  428. where=h0divh1 != 0)) +
  429. y[slice1] * (hsum *
  430. np.true_divide(hsum, hprod,
  431. out=np.zeros_like(hsum),
  432. where=hprod != 0)) +
  433. y[slice2] * (2.0 - h0divh1))
  434. result = np.sum(tmp, axis=axis)
  435. return result
  436. # Note: alias kept for backwards compatibility. simps was renamed to simpson
  437. # because the former is a slur in colloquial English (see gh-12924).
  438. def simps(y, x=None, dx=1.0, axis=-1, even='avg'):
  439. """An alias of `simpson`.
  440. `simps` is kept for backwards compatibility. For new code, prefer
  441. `simpson` instead.
  442. """
  443. return simpson(y, x=x, dx=dx, axis=axis, even=even)
  444. def simpson(y, x=None, dx=1.0, axis=-1, even='avg'):
  445. """
  446. Integrate y(x) using samples along the given axis and the composite
  447. Simpson's rule. If x is None, spacing of dx is assumed.
  448. If there are an even number of samples, N, then there are an odd
  449. number of intervals (N-1), but Simpson's rule requires an even number
  450. of intervals. The parameter 'even' controls how this is handled.
  451. Parameters
  452. ----------
  453. y : array_like
  454. Array to be integrated.
  455. x : array_like, optional
  456. If given, the points at which `y` is sampled.
  457. dx : float, optional
  458. Spacing of integration points along axis of `x`. Only used when
  459. `x` is None. Default is 1.
  460. axis : int, optional
  461. Axis along which to integrate. Default is the last axis.
  462. even : str {'avg', 'first', 'last'}, optional
  463. 'avg' : Average two results:1) use the first N-2 intervals with
  464. a trapezoidal rule on the last interval and 2) use the last
  465. N-2 intervals with a trapezoidal rule on the first interval.
  466. 'first' : Use Simpson's rule for the first N-2 intervals with
  467. a trapezoidal rule on the last interval.
  468. 'last' : Use Simpson's rule for the last N-2 intervals with a
  469. trapezoidal rule on the first interval.
  470. Returns
  471. -------
  472. float
  473. The estimated integral computed with the composite Simpson's rule.
  474. See Also
  475. --------
  476. quad : adaptive quadrature using QUADPACK
  477. romberg : adaptive Romberg quadrature
  478. quadrature : adaptive Gaussian quadrature
  479. fixed_quad : fixed-order Gaussian quadrature
  480. dblquad : double integrals
  481. tplquad : triple integrals
  482. romb : integrators for sampled data
  483. cumulative_trapezoid : cumulative integration for sampled data
  484. ode : ODE integrators
  485. odeint : ODE integrators
  486. Notes
  487. -----
  488. For an odd number of samples that are equally spaced the result is
  489. exact if the function is a polynomial of order 3 or less. If
  490. the samples are not equally spaced, then the result is exact only
  491. if the function is a polynomial of order 2 or less.
  492. Examples
  493. --------
  494. >>> from scipy import integrate
  495. >>> import numpy as np
  496. >>> x = np.arange(0, 10)
  497. >>> y = np.arange(0, 10)
  498. >>> integrate.simpson(y, x)
  499. 40.5
  500. >>> y = np.power(x, 3)
  501. >>> integrate.simpson(y, x)
  502. 1642.5
  503. >>> integrate.quad(lambda x: x**3, 0, 9)[0]
  504. 1640.25
  505. >>> integrate.simpson(y, x, even='first')
  506. 1644.5
  507. """
  508. y = np.asarray(y)
  509. nd = len(y.shape)
  510. N = y.shape[axis]
  511. last_dx = dx
  512. first_dx = dx
  513. returnshape = 0
  514. if x is not None:
  515. x = np.asarray(x)
  516. if len(x.shape) == 1:
  517. shapex = [1] * nd
  518. shapex[axis] = x.shape[0]
  519. saveshape = x.shape
  520. returnshape = 1
  521. x = x.reshape(tuple(shapex))
  522. elif len(x.shape) != len(y.shape):
  523. raise ValueError("If given, shape of x must be 1-D or the "
  524. "same as y.")
  525. if x.shape[axis] != N:
  526. raise ValueError("If given, length of x along axis must be the "
  527. "same as y.")
  528. if N % 2 == 0:
  529. val = 0.0
  530. result = 0.0
  531. slice1 = (slice(None),)*nd
  532. slice2 = (slice(None),)*nd
  533. if even not in ['avg', 'last', 'first']:
  534. raise ValueError("Parameter 'even' must be "
  535. "'avg', 'last', or 'first'.")
  536. # Compute using Simpson's rule on first intervals
  537. if even in ['avg', 'first']:
  538. slice1 = tupleset(slice1, axis, -1)
  539. slice2 = tupleset(slice2, axis, -2)
  540. if x is not None:
  541. last_dx = x[slice1] - x[slice2]
  542. val += 0.5*last_dx*(y[slice1]+y[slice2])
  543. result = _basic_simpson(y, 0, N-3, x, dx, axis)
  544. # Compute using Simpson's rule on last set of intervals
  545. if even in ['avg', 'last']:
  546. slice1 = tupleset(slice1, axis, 0)
  547. slice2 = tupleset(slice2, axis, 1)
  548. if x is not None:
  549. first_dx = x[tuple(slice2)] - x[tuple(slice1)]
  550. val += 0.5*first_dx*(y[slice2]+y[slice1])
  551. result += _basic_simpson(y, 1, N-2, x, dx, axis)
  552. if even == 'avg':
  553. val /= 2.0
  554. result /= 2.0
  555. result = result + val
  556. else:
  557. result = _basic_simpson(y, 0, N-2, x, dx, axis)
  558. if returnshape:
  559. x = x.reshape(saveshape)
  560. return result
  561. def romb(y, dx=1.0, axis=-1, show=False):
  562. """
  563. Romberg integration using samples of a function.
  564. Parameters
  565. ----------
  566. y : array_like
  567. A vector of ``2**k + 1`` equally-spaced samples of a function.
  568. dx : float, optional
  569. The sample spacing. Default is 1.
  570. axis : int, optional
  571. The axis along which to integrate. Default is -1 (last axis).
  572. show : bool, optional
  573. When `y` is a single 1-D array, then if this argument is True
  574. print the table showing Richardson extrapolation from the
  575. samples. Default is False.
  576. Returns
  577. -------
  578. romb : ndarray
  579. The integrated result for `axis`.
  580. See Also
  581. --------
  582. quad : adaptive quadrature using QUADPACK
  583. romberg : adaptive Romberg quadrature
  584. quadrature : adaptive Gaussian quadrature
  585. fixed_quad : fixed-order Gaussian quadrature
  586. dblquad : double integrals
  587. tplquad : triple integrals
  588. simpson : integrators for sampled data
  589. cumulative_trapezoid : cumulative integration for sampled data
  590. ode : ODE integrators
  591. odeint : ODE integrators
  592. Examples
  593. --------
  594. >>> from scipy import integrate
  595. >>> import numpy as np
  596. >>> x = np.arange(10, 14.25, 0.25)
  597. >>> y = np.arange(3, 12)
  598. >>> integrate.romb(y)
  599. 56.0
  600. >>> y = np.sin(np.power(x, 2.5))
  601. >>> integrate.romb(y)
  602. -0.742561336672229
  603. >>> integrate.romb(y, show=True)
  604. Richardson Extrapolation Table for Romberg Integration
  605. ======================================================
  606. -0.81576
  607. 4.63862 6.45674
  608. -1.10581 -3.02062 -3.65245
  609. -2.57379 -3.06311 -3.06595 -3.05664
  610. -1.34093 -0.92997 -0.78776 -0.75160 -0.74256
  611. ======================================================
  612. -0.742561336672229 # may vary
  613. """
  614. y = np.asarray(y)
  615. nd = len(y.shape)
  616. Nsamps = y.shape[axis]
  617. Ninterv = Nsamps-1
  618. n = 1
  619. k = 0
  620. while n < Ninterv:
  621. n <<= 1
  622. k += 1
  623. if n != Ninterv:
  624. raise ValueError("Number of samples must be one plus a "
  625. "non-negative power of 2.")
  626. R = {}
  627. slice_all = (slice(None),) * nd
  628. slice0 = tupleset(slice_all, axis, 0)
  629. slicem1 = tupleset(slice_all, axis, -1)
  630. h = Ninterv * np.asarray(dx, dtype=float)
  631. R[(0, 0)] = (y[slice0] + y[slicem1])/2.0*h
  632. slice_R = slice_all
  633. start = stop = step = Ninterv
  634. for i in range(1, k+1):
  635. start >>= 1
  636. slice_R = tupleset(slice_R, axis, slice(start, stop, step))
  637. step >>= 1
  638. R[(i, 0)] = 0.5*(R[(i-1, 0)] + h*y[slice_R].sum(axis=axis))
  639. for j in range(1, i+1):
  640. prev = R[(i, j-1)]
  641. R[(i, j)] = prev + (prev-R[(i-1, j-1)]) / ((1 << (2*j))-1)
  642. h /= 2.0
  643. if show:
  644. if not np.isscalar(R[(0, 0)]):
  645. print("*** Printing table only supported for integrals" +
  646. " of a single data set.")
  647. else:
  648. try:
  649. precis = show[0]
  650. except (TypeError, IndexError):
  651. precis = 5
  652. try:
  653. width = show[1]
  654. except (TypeError, IndexError):
  655. width = 8
  656. formstr = "%%%d.%df" % (width, precis)
  657. title = "Richardson Extrapolation Table for Romberg Integration"
  658. print(title, "=" * len(title), sep="\n", end="\n")
  659. for i in range(k+1):
  660. for j in range(i+1):
  661. print(formstr % R[(i, j)], end=" ")
  662. print()
  663. print("=" * len(title))
  664. return R[(k, k)]
  665. # Romberg quadratures for numeric integration.
  666. #
  667. # Written by Scott M. Ransom <ransom@cfa.harvard.edu>
  668. # last revision: 14 Nov 98
  669. #
  670. # Cosmetic changes by Konrad Hinsen <hinsen@cnrs-orleans.fr>
  671. # last revision: 1999-7-21
  672. #
  673. # Adapted to SciPy by Travis Oliphant <oliphant.travis@ieee.org>
  674. # last revision: Dec 2001
  675. def _difftrap(function, interval, numtraps):
  676. """
  677. Perform part of the trapezoidal rule to integrate a function.
  678. Assume that we had called difftrap with all lower powers-of-2
  679. starting with 1. Calling difftrap only returns the summation
  680. of the new ordinates. It does _not_ multiply by the width
  681. of the trapezoids. This must be performed by the caller.
  682. 'function' is the function to evaluate (must accept vector arguments).
  683. 'interval' is a sequence with lower and upper limits
  684. of integration.
  685. 'numtraps' is the number of trapezoids to use (must be a
  686. power-of-2).
  687. """
  688. if numtraps <= 0:
  689. raise ValueError("numtraps must be > 0 in difftrap().")
  690. elif numtraps == 1:
  691. return 0.5*(function(interval[0])+function(interval[1]))
  692. else:
  693. numtosum = numtraps/2
  694. h = float(interval[1]-interval[0])/numtosum
  695. lox = interval[0] + 0.5 * h
  696. points = lox + h * np.arange(numtosum)
  697. s = np.sum(function(points), axis=0)
  698. return s
  699. def _romberg_diff(b, c, k):
  700. """
  701. Compute the differences for the Romberg quadrature corrections.
  702. See Forman Acton's "Real Computing Made Real," p 143.
  703. """
  704. tmp = 4.0**k
  705. return (tmp * c - b)/(tmp - 1.0)
  706. def _printresmat(function, interval, resmat):
  707. # Print the Romberg result matrix.
  708. i = j = 0
  709. print('Romberg integration of', repr(function), end=' ')
  710. print('from', interval)
  711. print('')
  712. print('%6s %9s %9s' % ('Steps', 'StepSize', 'Results'))
  713. for i in range(len(resmat)):
  714. print('%6d %9f' % (2**i, (interval[1]-interval[0])/(2.**i)), end=' ')
  715. for j in range(i+1):
  716. print('%9f' % (resmat[i][j]), end=' ')
  717. print('')
  718. print('')
  719. print('The final result is', resmat[i][j], end=' ')
  720. print('after', 2**(len(resmat)-1)+1, 'function evaluations.')
  721. def romberg(function, a, b, args=(), tol=1.48e-8, rtol=1.48e-8, show=False,
  722. divmax=10, vec_func=False):
  723. """
  724. Romberg integration of a callable function or method.
  725. Returns the integral of `function` (a function of one variable)
  726. over the interval (`a`, `b`).
  727. If `show` is 1, the triangular array of the intermediate results
  728. will be printed. If `vec_func` is True (default is False), then
  729. `function` is assumed to support vector arguments.
  730. Parameters
  731. ----------
  732. function : callable
  733. Function to be integrated.
  734. a : float
  735. Lower limit of integration.
  736. b : float
  737. Upper limit of integration.
  738. Returns
  739. -------
  740. results : float
  741. Result of the integration.
  742. Other Parameters
  743. ----------------
  744. args : tuple, optional
  745. Extra arguments to pass to function. Each element of `args` will
  746. be passed as a single argument to `func`. Default is to pass no
  747. extra arguments.
  748. tol, rtol : float, optional
  749. The desired absolute and relative tolerances. Defaults are 1.48e-8.
  750. show : bool, optional
  751. Whether to print the results. Default is False.
  752. divmax : int, optional
  753. Maximum order of extrapolation. Default is 10.
  754. vec_func : bool, optional
  755. Whether `func` handles arrays as arguments (i.e., whether it is a
  756. "vector" function). Default is False.
  757. See Also
  758. --------
  759. fixed_quad : Fixed-order Gaussian quadrature.
  760. quad : Adaptive quadrature using QUADPACK.
  761. dblquad : Double integrals.
  762. tplquad : Triple integrals.
  763. romb : Integrators for sampled data.
  764. simpson : Integrators for sampled data.
  765. cumulative_trapezoid : Cumulative integration for sampled data.
  766. ode : ODE integrator.
  767. odeint : ODE integrator.
  768. References
  769. ----------
  770. .. [1] 'Romberg's method' https://en.wikipedia.org/wiki/Romberg%27s_method
  771. Examples
  772. --------
  773. Integrate a gaussian from 0 to 1 and compare to the error function.
  774. >>> from scipy import integrate
  775. >>> from scipy.special import erf
  776. >>> import numpy as np
  777. >>> gaussian = lambda x: 1/np.sqrt(np.pi) * np.exp(-x**2)
  778. >>> result = integrate.romberg(gaussian, 0, 1, show=True)
  779. Romberg integration of <function vfunc at ...> from [0, 1]
  780. ::
  781. Steps StepSize Results
  782. 1 1.000000 0.385872
  783. 2 0.500000 0.412631 0.421551
  784. 4 0.250000 0.419184 0.421368 0.421356
  785. 8 0.125000 0.420810 0.421352 0.421350 0.421350
  786. 16 0.062500 0.421215 0.421350 0.421350 0.421350 0.421350
  787. 32 0.031250 0.421317 0.421350 0.421350 0.421350 0.421350 0.421350
  788. The final result is 0.421350396475 after 33 function evaluations.
  789. >>> print("%g %g" % (2*result, erf(1)))
  790. 0.842701 0.842701
  791. """
  792. if np.isinf(a) or np.isinf(b):
  793. raise ValueError("Romberg integration only available "
  794. "for finite limits.")
  795. vfunc = vectorize1(function, args, vec_func=vec_func)
  796. n = 1
  797. interval = [a, b]
  798. intrange = b - a
  799. ordsum = _difftrap(vfunc, interval, n)
  800. result = intrange * ordsum
  801. resmat = [[result]]
  802. err = np.inf
  803. last_row = resmat[0]
  804. for i in range(1, divmax+1):
  805. n *= 2
  806. ordsum += _difftrap(vfunc, interval, n)
  807. row = [intrange * ordsum / n]
  808. for k in range(i):
  809. row.append(_romberg_diff(last_row[k], row[k], k+1))
  810. result = row[i]
  811. lastresult = last_row[i-1]
  812. if show:
  813. resmat.append(row)
  814. err = abs(result - lastresult)
  815. if err < tol or err < rtol * abs(result):
  816. break
  817. last_row = row
  818. else:
  819. warnings.warn(
  820. "divmax (%d) exceeded. Latest difference = %e" % (divmax, err),
  821. AccuracyWarning)
  822. if show:
  823. _printresmat(vfunc, interval, resmat)
  824. return result
  825. # Coefficients for Newton-Cotes quadrature
  826. #
  827. # These are the points being used
  828. # to construct the local interpolating polynomial
  829. # a are the weights for Newton-Cotes integration
  830. # B is the error coefficient.
  831. # error in these coefficients grows as N gets larger.
  832. # or as samples are closer and closer together
  833. # You can use maxima to find these rational coefficients
  834. # for equally spaced data using the commands
  835. # a(i,N) := integrate(product(r-j,j,0,i-1) * product(r-j,j,i+1,N),r,0,N) / ((N-i)! * i!) * (-1)^(N-i);
  836. # Be(N) := N^(N+2)/(N+2)! * (N/(N+3) - sum((i/N)^(N+2)*a(i,N),i,0,N));
  837. # Bo(N) := N^(N+1)/(N+1)! * (N/(N+2) - sum((i/N)^(N+1)*a(i,N),i,0,N));
  838. # B(N) := (if (mod(N,2)=0) then Be(N) else Bo(N));
  839. #
  840. # pre-computed for equally-spaced weights
  841. #
  842. # num_a, den_a, int_a, num_B, den_B = _builtincoeffs[N]
  843. #
  844. # a = num_a*array(int_a)/den_a
  845. # B = num_B*1.0 / den_B
  846. #
  847. # integrate(f(x),x,x_0,x_N) = dx*sum(a*f(x_i)) + B*(dx)^(2k+3) f^(2k+2)(x*)
  848. # where k = N // 2
  849. #
  850. _builtincoeffs = {
  851. 1: (1,2,[1,1],-1,12),
  852. 2: (1,3,[1,4,1],-1,90),
  853. 3: (3,8,[1,3,3,1],-3,80),
  854. 4: (2,45,[7,32,12,32,7],-8,945),
  855. 5: (5,288,[19,75,50,50,75,19],-275,12096),
  856. 6: (1,140,[41,216,27,272,27,216,41],-9,1400),
  857. 7: (7,17280,[751,3577,1323,2989,2989,1323,3577,751],-8183,518400),
  858. 8: (4,14175,[989,5888,-928,10496,-4540,10496,-928,5888,989],
  859. -2368,467775),
  860. 9: (9,89600,[2857,15741,1080,19344,5778,5778,19344,1080,
  861. 15741,2857], -4671, 394240),
  862. 10: (5,299376,[16067,106300,-48525,272400,-260550,427368,
  863. -260550,272400,-48525,106300,16067],
  864. -673175, 163459296),
  865. 11: (11,87091200,[2171465,13486539,-3237113, 25226685,-9595542,
  866. 15493566,15493566,-9595542,25226685,-3237113,
  867. 13486539,2171465], -2224234463, 237758976000),
  868. 12: (1, 5255250, [1364651,9903168,-7587864,35725120,-51491295,
  869. 87516288,-87797136,87516288,-51491295,35725120,
  870. -7587864,9903168,1364651], -3012, 875875),
  871. 13: (13, 402361344000,[8181904909, 56280729661, -31268252574,
  872. 156074417954,-151659573325,206683437987,
  873. -43111992612,-43111992612,206683437987,
  874. -151659573325,156074417954,-31268252574,
  875. 56280729661,8181904909], -2639651053,
  876. 344881152000),
  877. 14: (7, 2501928000, [90241897,710986864,-770720657,3501442784,
  878. -6625093363,12630121616,-16802270373,19534438464,
  879. -16802270373,12630121616,-6625093363,3501442784,
  880. -770720657,710986864,90241897], -3740727473,
  881. 1275983280000)
  882. }
  883. def newton_cotes(rn, equal=0):
  884. r"""
  885. Return weights and error coefficient for Newton-Cotes integration.
  886. Suppose we have (N+1) samples of f at the positions
  887. x_0, x_1, ..., x_N. Then an N-point Newton-Cotes formula for the
  888. integral between x_0 and x_N is:
  889. :math:`\int_{x_0}^{x_N} f(x)dx = \Delta x \sum_{i=0}^{N} a_i f(x_i)
  890. + B_N (\Delta x)^{N+2} f^{N+1} (\xi)`
  891. where :math:`\xi \in [x_0,x_N]`
  892. and :math:`\Delta x = \frac{x_N-x_0}{N}` is the average samples spacing.
  893. If the samples are equally-spaced and N is even, then the error
  894. term is :math:`B_N (\Delta x)^{N+3} f^{N+2}(\xi)`.
  895. Parameters
  896. ----------
  897. rn : int
  898. The integer order for equally-spaced data or the relative positions of
  899. the samples with the first sample at 0 and the last at N, where N+1 is
  900. the length of `rn`. N is the order of the Newton-Cotes integration.
  901. equal : int, optional
  902. Set to 1 to enforce equally spaced data.
  903. Returns
  904. -------
  905. an : ndarray
  906. 1-D array of weights to apply to the function at the provided sample
  907. positions.
  908. B : float
  909. Error coefficient.
  910. Notes
  911. -----
  912. Normally, the Newton-Cotes rules are used on smaller integration
  913. regions and a composite rule is used to return the total integral.
  914. Examples
  915. --------
  916. Compute the integral of sin(x) in [0, :math:`\pi`]:
  917. >>> from scipy.integrate import newton_cotes
  918. >>> import numpy as np
  919. >>> def f(x):
  920. ... return np.sin(x)
  921. >>> a = 0
  922. >>> b = np.pi
  923. >>> exact = 2
  924. >>> for N in [2, 4, 6, 8, 10]:
  925. ... x = np.linspace(a, b, N + 1)
  926. ... an, B = newton_cotes(N, 1)
  927. ... dx = (b - a) / N
  928. ... quad = dx * np.sum(an * f(x))
  929. ... error = abs(quad - exact)
  930. ... print('{:2d} {:10.9f} {:.5e}'.format(N, quad, error))
  931. ...
  932. 2 2.094395102 9.43951e-02
  933. 4 1.998570732 1.42927e-03
  934. 6 2.000017814 1.78136e-05
  935. 8 1.999999835 1.64725e-07
  936. 10 2.000000001 1.14677e-09
  937. """
  938. try:
  939. N = len(rn)-1
  940. if equal:
  941. rn = np.arange(N+1)
  942. elif np.all(np.diff(rn) == 1):
  943. equal = 1
  944. except Exception:
  945. N = rn
  946. rn = np.arange(N+1)
  947. equal = 1
  948. if equal and N in _builtincoeffs:
  949. na, da, vi, nb, db = _builtincoeffs[N]
  950. an = na * np.array(vi, dtype=float) / da
  951. return an, float(nb)/db
  952. if (rn[0] != 0) or (rn[-1] != N):
  953. raise ValueError("The sample positions must start at 0"
  954. " and end at N")
  955. yi = rn / float(N)
  956. ti = 2 * yi - 1
  957. nvec = np.arange(N+1)
  958. C = ti ** nvec[:, np.newaxis]
  959. Cinv = np.linalg.inv(C)
  960. # improve precision of result
  961. for i in range(2):
  962. Cinv = 2*Cinv - Cinv.dot(C).dot(Cinv)
  963. vec = 2.0 / (nvec[::2]+1)
  964. ai = Cinv[:, ::2].dot(vec) * (N / 2.)
  965. if (N % 2 == 0) and equal:
  966. BN = N/(N+3.)
  967. power = N+2
  968. else:
  969. BN = N/(N+2.)
  970. power = N+1
  971. BN = BN - np.dot(yi**power, ai)
  972. p1 = power+1
  973. fac = power*math.log(N) - gammaln(p1)
  974. fac = math.exp(fac)
  975. return ai, BN*fac
  976. def _qmc_quad_iv(func, a, b, n_points, n_estimates, qrng, log):
  977. # lazy import to avoid issues with partially-initialized submodule
  978. if not hasattr(_qmc_quad, 'qmc'):
  979. from scipy import stats
  980. _qmc_quad.stats = stats
  981. else:
  982. stats = _qmc_quad.stats
  983. if not callable(func):
  984. message = "`func` must be callable."
  985. raise TypeError(message)
  986. # a, b will be modified, so copy. Oh well if it's copied twice.
  987. a = np.atleast_1d(a).copy()
  988. b = np.atleast_1d(b).copy()
  989. a, b = np.broadcast_arrays(a, b)
  990. dim = a.shape[0]
  991. try:
  992. func((a + b) / 2)
  993. except Exception as e:
  994. message = ("`func` must evaluate the integrand at points within "
  995. "the integration range; e.g. `func( (a + b) / 2)` "
  996. "must return the integrand at the centroid of the "
  997. "integration volume.")
  998. raise ValueError(message) from e
  999. try:
  1000. func(np.array([a, b]))
  1001. vfunc = func
  1002. except Exception as e:
  1003. message = ("Exception encountered when attempting vectorized call to "
  1004. f"`func`: {e}. `func` should accept two-dimensional array "
  1005. "with shape `(n_points, len(a))` and return an array with "
  1006. "the integrand value at each of the `n_points` for better "
  1007. "performance.")
  1008. warnings.warn(message, stacklevel=3)
  1009. def vfunc(x):
  1010. return np.apply_along_axis(func, axis=-1, arr=x)
  1011. n_points_int = np.int64(n_points)
  1012. if n_points != n_points_int:
  1013. message = "`n_points` must be an integer."
  1014. raise TypeError(message)
  1015. n_estimates_int = np.int64(n_estimates)
  1016. if n_estimates != n_estimates_int:
  1017. message = "`n_estimates` must be an integer."
  1018. raise TypeError(message)
  1019. if qrng is None:
  1020. qrng = stats.qmc.Halton(dim)
  1021. elif not isinstance(qrng, stats.qmc.QMCEngine):
  1022. message = "`qrng` must be an instance of scipy.stats.qmc.QMCEngine."
  1023. raise TypeError(message)
  1024. if qrng.d != a.shape[0]:
  1025. message = ("`qrng` must be initialized with dimensionality equal to "
  1026. "the number of variables in `a`, i.e., "
  1027. "`qrng.random().shape[-1]` must equal `a.shape[0]`.")
  1028. raise ValueError(message)
  1029. rng_seed = getattr(qrng, 'rng_seed', None)
  1030. rng = stats._qmc.check_random_state(rng_seed)
  1031. if log not in {True, False}:
  1032. message = "`log` must be boolean (`True` or `False`)."
  1033. raise TypeError(message)
  1034. return (vfunc, a, b, n_points_int, n_estimates_int, qrng, rng, log, stats)
  1035. QMCQuadResult = namedtuple('QMCQuadResult', ['integral', 'standard_error'])
  1036. def _qmc_quad(func, a, b, *, n_points=1024, n_estimates=8, qrng=None,
  1037. log=False, args=None):
  1038. """
  1039. Compute an integral in N-dimensions using Quasi-Monte Carlo quadrature.
  1040. Parameters
  1041. ----------
  1042. func : callable
  1043. The integrand. Must accept a single arguments `x`, an array which
  1044. specifies the point at which to evaluate the integrand. For efficiency,
  1045. the function should be vectorized to compute the integrand for each
  1046. element an array of shape ``(n_points, n)``, where ``n`` is number of
  1047. variables.
  1048. a, b : array-like
  1049. One-dimensional arrays specifying the lower and upper integration
  1050. limits, respectively, of each of the ``n`` variables.
  1051. n_points, n_estimates : int, optional
  1052. One QMC sample of `n_points` (default: 256) points will be generated
  1053. by `qrng`, and `n_estimates` (default: 8) statistically independent
  1054. estimates of the integral will be produced. The total number of points
  1055. at which the integrand `func` will be evaluated is
  1056. ``n_points * n_estimates``. See Notes for details.
  1057. qrng : `~scipy.stats.qmc.QMCEngine`, optional
  1058. An instance of the QMCEngine from which to sample QMC points.
  1059. The QMCEngine must be initialized to a number of dimensions
  1060. corresponding with the number of variables ``x0, ..., xn`` passed to
  1061. `func`.
  1062. The provided QMCEngine is used to produce the first integral estimate.
  1063. If `n_estimates` is greater than one, additional QMCEngines are
  1064. spawned from the first (with scrambling enabled, if it is an option.)
  1065. If a QMCEngine is not provided, the default `scipy.stats.qmc.Halton`
  1066. will be initialized with the number of dimensions determine from
  1067. `a`.
  1068. log : boolean, default: False
  1069. When set to True, `func` returns the log of the integrand, and
  1070. the result object contains the log of the integral.
  1071. Returns
  1072. -------
  1073. result : object
  1074. A result object with attributes:
  1075. integral : float
  1076. The estimate of the integral.
  1077. standard_error :
  1078. The error estimate. See Notes for interpretation.
  1079. Notes
  1080. -----
  1081. Values of the integrand at each of the `n_points` points of a QMC sample
  1082. are used to produce an estimate of the integral. This estimate is drawn
  1083. from a population of possible estimates of the integral, the value of
  1084. which we obtain depends on the particular points at which the integral
  1085. was evaluated. We perform this process `n_estimates` times, each time
  1086. evaluating the integrand at different scrambled QMC points, effectively
  1087. drawing i.i.d. random samples from the population of integral estimates.
  1088. The sample mean :math:`m` of these integral estimates is an
  1089. unbiased estimator of the true value of the integral, and the standard
  1090. error of the mean :math:`s` of these estimates may be used to generate
  1091. confidence intervals using the t distribution with ``n_estimates - 1``
  1092. degrees of freedom. Perhaps counter-intuitively, increasing `n_points`
  1093. while keeping the total number of function evaluation points
  1094. ``n_points * n_estimates`` fixed tends to reduce the actual error, whereas
  1095. increasing `n_estimates` tends to decrease the error estimate.
  1096. Examples
  1097. --------
  1098. QMC quadrature is particularly useful for computing integrals in higher
  1099. dimensions. An example integrand is the probability density function
  1100. of a multivariate normal distribution.
  1101. >>> import numpy as np
  1102. >>> from scipy import stats
  1103. >>> dim = 8
  1104. >>> mean = np.zeros(dim)
  1105. >>> cov = np.eye(dim)
  1106. >>> def func(x):
  1107. ... return stats.multivariate_normal.pdf(x, mean, cov)
  1108. To compute the integral over the unit hypercube:
  1109. >>> from scipy.integrate import qmc_quad
  1110. >>> a = np.zeros(dim)
  1111. >>> b = np.ones(dim)
  1112. >>> rng = np.random.default_rng()
  1113. >>> qrng = stats.qmc.Halton(d=dim, seed=rng)
  1114. >>> n_estimates = 8
  1115. >>> res = qmc_quad(func, a, b, n_estimates=n_estimates, qrng=qrng)
  1116. >>> res.integral, res.standard_error
  1117. (0.00018441088533413305, 1.1255608140911588e-07)
  1118. A two-sided, 99% confidence interval for the integral may be estimated
  1119. as:
  1120. >>> t = stats.t(df=n_estimates-1, loc=res.integral,
  1121. ... scale=res.standard_error)
  1122. >>> t.interval(0.99)
  1123. (0.00018401699720722663, 0.00018480477346103947)
  1124. Indeed, the value reported by `scipy.stats.multivariate_normal` is
  1125. within this range.
  1126. >>> stats.multivariate_normal.cdf(b, mean, cov, lower_limit=a)
  1127. 0.00018430867675187443
  1128. """
  1129. args = _qmc_quad_iv(func, a, b, n_points, n_estimates, qrng, log)
  1130. func, a, b, n_points, n_estimates, qrng, rng, log, stats = args
  1131. # The sign of the integral depends on the order of the limits. Fix this by
  1132. # ensuring that lower bounds are indeed lower and setting sign of resulting
  1133. # integral manually
  1134. if np.any(a == b):
  1135. message = ("A lower limit was equal to an upper limit, so the value "
  1136. "of the integral is zero by definition.")
  1137. warnings.warn(message, stacklevel=2)
  1138. return QMCQuadResult(-np.inf if log else 0, 0)
  1139. i_swap = b < a
  1140. sign = (-1)**(i_swap.sum(axis=-1)) # odd # of swaps -> negative
  1141. a[i_swap], b[i_swap] = b[i_swap], a[i_swap]
  1142. A = np.prod(b - a)
  1143. dA = A / n_points
  1144. estimates = np.zeros(n_estimates)
  1145. rngs = _rng_spawn(qrng.rng, n_estimates)
  1146. for i in range(n_estimates):
  1147. # Generate integral estimate
  1148. sample = qrng.random(n_points)
  1149. x = stats.qmc.scale(sample, a, b)
  1150. integrands = func(x)
  1151. if log:
  1152. estimate = logsumexp(integrands) + np.log(dA)
  1153. else:
  1154. estimate = np.sum(integrands * dA)
  1155. estimates[i] = estimate
  1156. # Get a new, independently-scrambled QRNG for next time
  1157. qrng = type(qrng)(seed=rngs[i], **qrng._init_quad)
  1158. integral = np.mean(estimates)
  1159. integral = integral + np.pi*1j if (log and sign < 0) else integral*sign
  1160. standard_error = stats.sem(estimates)
  1161. return QMCQuadResult(integral, standard_error)