Repository: scipy/scipy

Issue:
Extra function arguments to `scipy.integrate.quad_vec`
Hello, thanks for the work on scipy! 

I was looking at the docs/source for `scipy.integrate.quad_vec` and I noticed that there isn't a provision to provide arguments for the callable function we wish to integrate. For the `scipy.integrate.quadrature`, we have the `args` argument that we can pass to the function for such a behaviour. Is there a way to do that with `quad_vec`?

Thanks!


Discussion:
Thanks for the suggestion. In the meantime, you can pass the parameters by using [functools.partial](https://docs.python.org/3/library/functools.html#functools.partial) for instance.
Ah thanks! I forgot about partial, for some reason. I'll do that for now. But it'll be great to have this feature built-in too in future!

Resolving patch:
diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py
--- a/scipy/_lib/_util.py
+++ b/scipy/_lib/_util.py
@@ -401,6 +401,18 @@ def getfullargspec_no_self(func):
                        kwdefaults or None, annotations)
 
 
+class _FunctionWrapper:
+    """
+    Object to wrap user's function, allowing picklability
+    """
+    def __init__(self, f, args):
+        self.f = f
+        self.args = [] if args is None else args
+
+    def __call__(self, x):
+        return self.f(x, *self.args)
+
+
 class MapWrapper:
     """
     Parallelisation wrapper for working with map-like callables, such as
diff --git a/scipy/integrate/_quad_vec.py b/scipy/integrate/_quad_vec.py
--- a/scipy/integrate/_quad_vec.py
+++ b/scipy/integrate/_quad_vec.py
@@ -6,7 +6,7 @@
 
 import numpy as np
 
-from scipy._lib._util import MapWrapper
+from scipy._lib._util import MapWrapper, _FunctionWrapper
 
 
 class LRUDict(collections.OrderedDict):
@@ -102,7 +102,8 @@ def __repr__(self):
 
 
 def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, limit=10000,
-             workers=1, points=None, quadrature=None, full_output=False):
+             workers=1, points=None, quadrature=None, full_output=False,
+             *, args=()):
     r"""Adaptive integration of a vector-valued function.
 
     Parameters
@@ -140,6 +141,10 @@ def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, li
         Default: 'gk21' for finite intervals and 'gk15' for (semi-)infinite
     full_output : bool, optional
         Return an additional ``info`` dictionary.
+    args : tuple, optional
+        Extra arguments to pass to function, if any.
+
+        .. versionadded:: 1.8.0
 
     Returns
     -------
@@ -215,6 +220,13 @@ def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, li
     a = float(a)
     b = float(b)
 
+    if args:
+        if not isinstance(args, tuple):
+            args = (args,)
+
+        # create a wrapped function to allow the use of map and Pool.map
+        f = _FunctionWrapper(f, args)
+
     # Use simple transformations to deal with integrals over infinite
     # intervals.
     kwargs = dict(epsabs=epsabs,
diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py
--- a/scipy/optimize/_differentialevolution.py
+++ b/scipy/optimize/_differentialevolution.py
@@ -7,7 +7,7 @@
 import numpy as np
 from scipy.optimize import OptimizeResult, minimize
 from scipy.optimize.optimize import _status_message
-from scipy._lib._util import check_random_state, MapWrapper
+from scipy._lib._util import check_random_state, MapWrapper, _FunctionWrapper
 
 from scipy.optimize._constraints import (Bounds, new_bounds_to_old,
                                          NonlinearConstraint, LinearConstraint)
@@ -1334,18 +1334,6 @@ def _select_samples(self, candidate, number_samples):
         return idxs
 
 
-class _FunctionWrapper:
-    """
-    Object to wrap user cost function, allowing picklability
-    """
-    def __init__(self, f, args):
-        self.f = f
-        self.args = [] if args is None else args
-
-    def __call__(self, x):
-        return self.f(x, *self.args)
-
-
 class _ConstraintWrapper:
     """Object to wrap/evaluate user defined constraints.
 


Test patch:
diff --git a/scipy/integrate/tests/test__quad_vec.py b/scipy/integrate/tests/test__quad_vec.py
--- a/scipy/integrate/tests/test__quad_vec.py
+++ b/scipy/integrate/tests/test__quad_vec.py
@@ -5,6 +5,9 @@
 
 from scipy.integrate import quad_vec
 
+from multiprocessing.dummy import Pool
+
+
 quadrature_params = pytest.mark.parametrize(
     'quadrature', [None, "gk15", "gk21", "trapezoid"])
 
@@ -87,13 +90,20 @@ def test_quad_vec_simple_inf(quadrature):
     assert_allclose(res, exact, rtol=0, atol=max(epsabs, 1.5 * err))
 
 
+def test_quad_vec_args():
+    f = lambda x, a: x * (x + a) * np.arange(3)
+    a = 2
+    exact = np.array([0, 4/3, 8/3])
+
+    res, err = quad_vec(f, 0, 1, args=(a,))
+    assert_allclose(res, exact, rtol=0, atol=1e-4)
+
+
 def _lorenzian(x):
     return 1 / (1 + x**2)
 
 
 def test_quad_vec_pool():
-    from multiprocessing.dummy import Pool
-
     f = _lorenzian
     res, err = quad_vec(f, -np.inf, np.inf, norm='max', epsabs=1e-4, workers=4)
     assert_allclose(res, np.pi, rtol=0, atol=1e-4)
@@ -104,6 +114,24 @@ def test_quad_vec_pool():
         assert_allclose(res, np.pi, rtol=0, atol=1e-4)
 
 
+def _func_with_args(x, a):
+    return x * (x + a) * np.arange(3)
+
+
+@pytest.mark.parametrize('extra_args', [2, (2,)])
+@pytest.mark.parametrize('workers', [1, 10])
+def test_quad_vec_pool_args(extra_args, workers):
+    f = _func_with_args
+    exact = np.array([0, 4/3, 8/3])
+
+    res, err = quad_vec(f, 0, 1, args=extra_args, workers=workers)
+    assert_allclose(res, exact, rtol=0, atol=1e-4)
+
+    with Pool(workers) as pool:
+        res, err = quad_vec(f, 0, 1, args=extra_args, workers=pool.map)
+        assert_allclose(res, exact, rtol=0, atol=1e-4)
+
+
 @quadrature_params
 def test_num_eval(quadrature):
     def f(x):