Repository: scipy/scipy
Pull request: DOC: `integrate.quad_vec`: Add example when using `workers`
#### Reference issue
An attempt to close #18879

#### What does this implement/fix?
Write a second code example where the argument ``workers`` is used.


Problem statement:
BUG: `integrate.quad_vec`: Fatal error when using `workers` kwarg
### Describe your issue.

When using the workers keyword argument in integrate.quad_vec, a fatal error occurs and the threads are not properly closed, resulting in an infinite loop of overlapping tracebacks.

Part of the error is below, although multiple tracebacks end up overlapping one another so I am unsure if it was completely finished where I cut it off. Happy to run it again if necessary and provide more of the output

### Reproducing Code Example

```python
import scipy.integrate as integrate
import numpy as np

alpha = np.linspace(0,10,20)
def vector_func(x):
    return x**alpha

integrate.quad_vec(vector_func, 0, 2, workers = 2)
```


### Error message

```shell
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 120, in spawn_main
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 120, in spawn_main
    exitcode = _main(fd, parent_sentinel)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 129, in _main
    exitcode = _main(fd, parent_sentinel)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 129, in _main
    prepare(preparation_data)
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 240, in prepare
    prepare(preparation_data)
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 240, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 291, in _fixup_main_from_path
    _fixup_main_from_path(data['init_main_from_path'])
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 291, in _fixup_main_from_path
    main_content = runpy.run_path(main_path,
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen runpy>", line 291, in run_path
  File "<frozen runpy>", line 98, in _run_module_code
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Users\BenDavisonPetch\OneDrive - Alloyed\Documents\High Throughput AM Modelling\Scan_Path_High_Throughput_BDP_Rewrite\scipybugtest.py", line 8, in <module>
    main_content = runpy.run_path(main_path,
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen runpy>", line 291, in run_path
  File "<frozen runpy>", line 98, in _run_module_code
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Users\BenDavisonPetch\OneDrive - Alloyed\Documents\High Throughput AM Modelling\Scan_Path_High_Throughput_BDP_Rewrite\scipybugtest.py", line 8, in <module>
    integrate.quad_vec(vector_func, 0, 2, workers = 2)
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\site-packages\scipy\integrate\_quad_vec.py", line 358, in quad_vec
    integrate.quad_vec(vector_func, 0, 2, workers = 2)
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\site-packages\scipy\integrate\_quad_vec.py", line 358, in quad_vec
    with MapWrapper(workers) as mapwrapper:
         ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\site-packages\scipy\_lib\_util.py", line 399, in __init__
    with MapWrapper(workers) as mapwrapper:
         ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\site-packages\scipy\_lib\_util.py", line 399, in __init__
    self.pool = Pool(processes=int(pool))
                ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\context.py", line 119, in Pool
    self.pool = Pool(processes=int(pool))
                ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\context.py", line 119, in Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\pool.py", line 215, in __init__
    return Pool(processes, initializer, initargs, maxtasksperchild,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\pool.py", line 215, in __init__
    self._repopulate_pool()
    self._repopulate_pool()
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\pool.py", line 306, in _repopulate_pool
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\pool.py", line 306, in _repopulate_pool
    return self._repopulate_pool_static(self._ctx, self.Process,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\pool.py", line 329, in _repopulate_pool_static
    return self._repopulate_pool_static(self._ctx, self.Process,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\pool.py", line 329, in _repopulate_pool_static
    w.start()
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\process.py", line 121, in start
    w.start()
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\process.py", line 121, in start
    self._popen = self._Popen(self)
                  ^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\context.py", line 336, in _Popen
    self._popen = self._Popen(self)
                  ^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\context.py", line 336, in _Popen
    return Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^
    return Popen(process_obj)
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\popen_spawn_win32.py", line 45, in __init__
           ^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\popen_spawn_win32.py", line 45, in __init__
    prep_data = spawn.get_preparation_data(process_obj._name)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 158, in get_preparation_data
    prep_data = spawn.get_preparation_data(process_obj._name)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 158, in get_preparation_data
    _check_not_importing_main()
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 138, in _check_not_importing_main
    _check_not_importing_main()
  File "C:\Users\BenDavisonPetch\miniconda3\envs\solver_platform\Lib\multiprocessing\spawn.py", line 138, in _check_not_importing_main
    raise RuntimeError('''
RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
    raise RuntimeError('''
RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
```


### SciPy/NumPy/Python version and system information

```shell
1.11.1 1.25.1 sys.version_info(major=3, minor=11, micro=4, releaselevel='final', serial=0)
lapack_armpl_info:
  NOT AVAILABLE
lapack_mkl_info:
  NOT AVAILABLE
openblas_lapack_info:
  NOT AVAILABLE
openblas_clapack_info:
  NOT AVAILABLE
flame_info:
  NOT AVAILABLE
accelerate_info:
  NOT AVAILABLE
atlas_3_10_threads_info:
  NOT AVAILABLE
atlas_3_10_info:
  NOT AVAILABLE
atlas_threads_info:
  NOT AVAILABLE
atlas_info:
  NOT AVAILABLE
lapack_info:
    libraries = ['lapack', 'blas', 'lapack', 'blas']
    library_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\lib']
    language = f77
blas_armpl_info:
  NOT AVAILABLE
blas_mkl_info:
  NOT AVAILABLE
blis_info:
  NOT AVAILABLE
openblas_info:
  NOT AVAILABLE
atlas_3_10_blas_threads_info:
  NOT AVAILABLE
atlas_3_10_blas_info:
  NOT AVAILABLE
atlas_blas_threads_info:
  NOT AVAILABLE
atlas_blas_info:
  NOT AVAILABLE
blas_info:
    libraries = ['cblas', 'blas', 'cblas', 'blas', 'cblas', 'blas']
    library_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\lib']
    include_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\include']
    language = f77
    define_macros = [('HAVE_CBLAS', None)]
blas_opt_info:
    define_macros = [('NO_ATLAS_INFO', 1), ('HAVE_CBLAS', None)]
    libraries = ['cblas', 'blas', 'cblas', 'blas', 'cblas', 'blas']
    library_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\lib']
    include_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\include']
    language = f77
lapack_opt_info:
    libraries = ['lapack', 'blas', 'lapack', 'blas', 'cblas', 'blas', 'cblas', 'blas', 'cblas', 'blas']
    library_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\lib']
    language = f77
    define_macros = [('NO_ATLAS_INFO', 1), ('HAVE_CBLAS', None)]
    include_dirs = ['C:/Users/BenDavisonPetch/miniconda3/envs/solver_platform\\Library\\include']
Supported SIMD extensions in this NumPy install:
    baseline = SSE,SSE2,SSE3
    found = SSSE3,SSE41,POPCNT,SSE42,AVX,F16C,FMA3,AVX2
    not found = AVX512F,AVX512CD,AVX512_SKX,AVX512_CLX,AVX512_CNL,AVX512_ICL
```

Hints from issue discussion:
Looks like Windows platform based on the paths? I was, however, able to reproduce on MacOS with latest `main` as well.
The default parallelisation used by `workers` is `multiprocessing.Pool`. Remember that a few things are necessary with `multiprocessing`, https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods, see 'Safe importing of main module'.
The example, as given, won't run correctly from a notebook when `spawn` is used for starting the `Pool`. `spawn` is the default process starter used by on macOS and win. I think the example from the OP should work on Linux as the default start method is `fork`.

The following works from a standalone python script started from a CLI:

```python
import scipy.integrate as integrate
import numpy as np

alpha = np.linspace(0, 10, 20)

def vector_func(x, *args):
    alpha = args[0]
    return x**alpha
   
if __name__ == '__main__':
    print(integrate.quad_vec(vector_func, 0, 2, workers=2, args=(alpha,)))
```

If you want to run the example from a notebook cell you need to do:

```python
import scipy.integrate as integrate
import numpy as np
from vf import vector_func     # import vector_func from a separate python file

alpha = np.linspace(0, 10, 20)
print(integrate.quad_vec(vector_func, 0, 2, workers=2, args=(alpha,)))
```

The underlying issue is probably documentation. There are certain things required for `multiprocessing.Pool` that aren't demonstrated in the docstring.

Commit message: DOC: Attempt to fix refguide-check error.

[docs only]

Patch to review:
diff --git a/scipy/integrate/_quad_vec.py b/scipy/integrate/_quad_vec.py
index 19218d196eb3..20362be84063 100644
--- a/scipy/integrate/_quad_vec.py
+++ b/scipy/integrate/_quad_vec.py
@@ -221,6 +221,27 @@ def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6,
     >>> plt.ylabel(r"$\int_{0}^{2} x^\alpha dx$")
     >>> plt.show()
 
+    When using the argument ``workers``, one should ensure
+    the 'Safe importing of main module', for instance
+    by rewriting the example above as:
+
+    .. code-block:: python
+
+        from scipy.integrate import quad_vec
+        import numpy as np
+        import matplotlib.pyplot as plt
+
+        alpha = np.linspace(0.0, 2.0, num=30)
+        x0, x1 = 0, 2
+        def f(x):
+            return x**alpha
+
+        if __name__ == "__main__":
+            y, err = quad_vec(f, x0, x1, workers=2)
+            plt.plot(alpha, y)
+            plt.xlabel(r"$\alpha$")
+            plt.ylabel(r"$\int_{0}^{2} x^\alpha dx$")
+            plt.show()
     """
     a = float(a)
     b = float(b)


Code review comments:
diff_hunk: @@ -221,6 +221,27 @@ def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6,
     >>> plt.ylabel(r"$\int_{0}^{2} x^\alpha dx$")
     >>> plt.show()
 
+    When using the argument ``workers``, one should ensure
+    the 'Safe importing of main module', for instance
+    by rewriting the example above as:
+
+    .. code-block:: python
+
+        from scipy.integrate import quad_vec
+        import numpy as np
+        import matplotlib.pyplot as plt
+
+        alpha = np.linspace(0.0, 2.0, num=30)
+        x0, x1 = 0, 2
+        def f(x):
+            return x**alpha
+
+        if __name__ == "__main__":
+            y, err = quad_vec(f, x0, x1, workers=2)
+            plt.plot(alpha, y)
+            plt.xlabel(r"$\alpha$")
+            plt.ylabel(r"$\int_{0}^{2} x^\alpha dx$")
+            plt.show()
path: scipy/integrate/_quad_vec.py
text: @user1:
```suggestion
```

@user1:
Lets keep this minimal since we aren't showing a plot