scijit.integrate.nquad

scijit.integrate.nquad(func, ranges, args=None, opts=None, full_output=False)

Integration over n variables.

ranges[0] is the INNERMOST integral and ranges[-1] the outermost.

Parameters:
func@njit function

Integrand, in either of two shapes. The scalar shape takes one scalar per axis, innermost first, and then one argument per entry of args: f(x0, x1, ..., a0, a1). The array shape takes the coordinates as one array and the same args after it, f(x, a0, a1) with x[0] innermost. The shape is resolved when the call compiles, from the function’s arity and from whether it types against a coordinate array.

rangestuple

One entry per axis, innermost first. Each is a (lo, hi) pair, or a plain @njit callable RETURNING such a pair. A pair’s members are each a float or a callback. A callback for axis i receives the already-fixed OUTER coordinates x_{i+1} ... x_{n-1}, innermost of those first, and then the args entries; or, in the array shape, one coords array holding them outermost first, followed by the same entries. The tuple’s LENGTH sets the nesting depth, so inside @njit it must be a tuple rather than a list or an array.

argstuple, optional

Extra parameters, passed to func and to every range callback after their coordinates. Each entry is a real number or an array of real numbers. A value that is not a tuple becomes a one-item tuple.

optsdict, tuple of dicts, or None, optional

Per-axis settings, innermost first, or one dict applied to every axis. An opts entry is quad()’s own keyword arguments: epsabs, epsrel, limit, points, weight, wvar, wopts, maxp1, limlst, complex_func. wopts is accepted and ignored. A (epsabs, epsrel, limit) triple is accepted in place of a dict.

weight and points pick the QUADPACK routine for their axis, so they are read when the call compiles. Inside @njit that needs a dict whose values are of MIXED kinds, which is the one numba carries its keys on: {'weight': 'cos', 'wvar': 1.0} works, {'weight': 'cos'} alone does not. A dict holding numbers only needs nothing, since those settings are read at run time.

full_outputbool, optional

Return the diagnostics as a third value. Must be known when the call compiles, since it picks the number of return values.

Returns:
valuefloat

The integral.

abserrfloat

The LARGEST error estimate over every level.

out_dictdict

Only when full_output. One key, neval, counting the innermost level’s evaluations.

Raises:
TypeError

func or a limit callback is not a plain @njit function; an opts key is not one of the ten above; an opts points is not iterable; func returns a complex value. TypingError inside @njit for the first two, which are compile-time questions.

ValueError

ranges is empty; an opts sequence’s length is not the number of ranges; a range entry is not a (lo, hi) pair; an args entry is not a real number or an array of them; or QUADPACK refuses the settings of some level, which is quad()’s own set of conditions.

See also

scipy.integrate.nquad

The scipy routine this mirrors.

Notes

Prange-safe. Each call works in its own buffer.

An opts entry is a dict or a triple, where scipy also accepts a callable returning a dict. weight and points pick the QUADPACK routine for their axis and are read when the call compiles, so a value produced at run time cannot reach them.

A per-axis opts carrying a weight cannot be passed from inside @njit. One dict applied to every axis works, and so does every per-axis spelling when nquad is called from python.

An opts sequence of the wrong length raises, and an empty ranges raises ValueError. scipy validates neither: it indexes opts from the end, so three entries against two ranges uses the last two and drops the first, and an empty ranges reaches IndexError: list index out of range.

A (lo, hi) pair whose members are individually callbacks, a length-3 sequence of scalars read as one (epsabs, epsrel, limit) triple for every axis, and the array integrand shape are additive. No scipy-shaped call reaches them.

An opts dict whose values are all numbers cannot carry points. A dict literal holding a tuple carries it, opts={'points': (0.5,), 'epsabs': 1.49e-8}.

complex_func is accepted only as false. scipy accepts the key and then compares a complex abserr against an int, so complex_func=True raises TypeError there for every integrand, real or complex.

abserr is a float64 on every route. scipy returns the int 0 on an integral whose range is degenerate.

Examples

>>> import numpy as np
>>> from numba import njit
>>> import scijit.integrate as si
>>> @njit
... def f(x0, x1):                    # scipy's shape, x0 innermost
...     return x0 * x1
>>> @njit
... def run():
...     return si.nquad(f, ((0.0, 1.0), (0.0, 2.0)))
>>> run()
(0.9999999999999999, 1.1102230246251564e-14)

A limit that depends on the axis outside it, and scipy’s other range spelling side by side:

>>> @njit
... def hi0(x1):                      # axis 0's upper limit
...     return 1.0 - x1
>>> @njit
... def rng0(x1):                     # the same, as one callable
...     return 0.0, 1.0 - x1
>>> @njit
... def tri():
...     a = si.nquad(f, ((0.0, hi0), (0.0, 1.0)))
...     b = si.nquad(f, (rng0, (0.0, 1.0)))
...     return a[0], b[0]
>>> tri()
(0.04166666666666667, 0.04166666666666667)

Extra parameters travel as scipy’s tuple and reach the integrand and the range callbacks alike:

>>> @njit
... def g(x0, x1, c):
...     return c * x0 * x1
>>> @njit
... def lo0(x1, c):
...     return 0.0 * c
>>> @njit
... def scaled():
...     return si.nquad(g, ((lo0, 1.0), (0.0, 2.0)), (3.0,))[0]
>>> scaled()
3.0