Repository: mne-tools/mne-python

Issue:
make_forward_solution fails with negative n_jobs
On master, if I set a negative `n_jobs` in the call to `mne.make_forward_solution`, it fails. The same call works if I set it to a positive number > 2. (I have not thoroughly tested this - maybe it's specific to this computer.)

```
Source spaces are in head coordinates.
Checking that the sources are inside the bounding surface (will take a few...)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/home/jona/anaconda3/lib/python3.5/site-packages/numpy/lib/shape_base.py in array_split(ary, indices_or_sections, axis)
    403         # handle scalar case.
--> 404         Nsections = len(indices_or_sections) + 1
    405         div_points = [0] + list(indices_or_sections) + [Ntotal]

TypeError: object of type 'int' has no len()

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-8-ef05a42dcd32> in <module>()
      3                                 "/home/jona/tools/freesurfer/subjects/HA0710/bem/HA0710-oct-6-src.fif",
      4                                 "/home/jona/tools/freesurfer/subjects/HA0710/bem/HA0710_bem-sol.fif",
----> 5                                 eeg=False, n_jobs=-1,
      6                                )

/home/jona/tools/mne-python/mne/forward/_make_forward.py in make_forward_solution(info, trans, src, bem, meg, eeg, mindist, ignore_ref, n_jobs, verbose)

/home/jona/tools/mne-python/mne/utils.py in verbose(function, *args, **kwargs)
    705         with use_log_level(verbose_level):
    706             return function(*args, **kwargs)
--> 707     return function(*args, **kwargs)
    708 
    709 

/home/jona/tools/mne-python/mne/forward/_make_forward.py in make_forward_solution(info, trans, src, bem, meg, eeg, mindist, ignore_ref, n_jobs, verbose)
    573         update_kwargs, bem = _prepare_for_forward(
    574             src, mri_head_t, info, bem, mindist, n_jobs, bem_extra, trans,
--> 575             info_extra, meg, eeg, ignore_ref)
    576     del (src, mri_head_t, trans, info_extra, bem_extra, mindist,
    577          meg, eeg, ignore_ref)

/home/jona/tools/mne-python/mne/forward/_make_forward.py in _prepare_for_forward(src, mri_head_t, info, bem, mindist, n_jobs, bem_extra, trans, info_extra, meg, eeg, ignore_ref, verbose)

/home/jona/tools/mne-python/mne/utils.py in verbose(function, *args, **kwargs)
    705         with use_log_level(verbose_level):
    706             return function(*args, **kwargs)
--> 707     return function(*args, **kwargs)
    708 
    709 

/home/jona/tools/mne-python/mne/forward/_make_forward.py in _prepare_for_forward(src, mri_head_t, info, bem, mindist, n_jobs, bem_extra, trans, info_extra, meg, eeg, ignore_ref, verbose)
    463     if not bem['is_sphere']:
    464         inner_skull = _bem_find_surface(bem, 'inner_skull')
--> 465         _filter_source_spaces(inner_skull, mindist, mri_head_t, src, n_jobs)
    466         logger.info('')
    467 

/home/jona/tools/mne-python/mne/source_space.py in _filter_source_spaces(surf, limit, mri_head_t, src, n_jobs, verbose)

/home/jona/tools/mne-python/mne/utils.py in verbose(function, *args, **kwargs)
    705         with use_log_level(verbose_level):
    706             return function(*args, **kwargs)
--> 707     return function(*args, **kwargs)
    708 
    709 

/home/jona/tools/mne-python/mne/source_space.py in _filter_source_spaces(surf, limit, mri_head_t, src, n_jobs, verbose)
   2091 
   2092         # Check that the source is inside surface (often the inner skull)
-> 2093         outside = _points_outside_surface(r1s, surf, n_jobs)
   2094         omit_outside = np.sum(outside)
   2095 

/home/jona/tools/mne-python/mne/source_space.py in _points_outside_surface(rr, surf, n_jobs, verbose)

/home/jona/tools/mne-python/mne/utils.py in verbose(function, *args, **kwargs)
    705         with use_log_level(verbose_level):
    706             return function(*args, **kwargs)
--> 707     return function(*args, **kwargs)
    708 
    709 

/home/jona/tools/mne-python/mne/source_space.py in _points_outside_surface(rr, surf, n_jobs, verbose)
   2148     parallel, p_fun, _ = parallel_func(_get_solids, n_jobs)
   2149     tot_angles = parallel(p_fun(surf['rr'][tris], rr)
-> 2150                           for tris in np.array_split(surf['tris'], n_jobs))
   2151     return np.abs(np.sum(tot_angles, axis=0) / (2 * np.pi) - 1.0) > 1e-5
   2152 

/home/jona/anaconda3/lib/python3.5/site-packages/numpy/lib/shape_base.py in array_split(ary, indices_or_sections, axis)
    408         Nsections = int(indices_or_sections)
    409         if Nsections <= 0:
--> 410             raise ValueError('number sections must be larger than 0.')
    411         Neach_section, extras = divmod(Ntotal, Nsections)
    412         section_sizes = ([0] +

ValueError: number sections must be larger than 0.
```


Discussion:
It will probably also fail if `n_jobs=n_cpu`. It's an empty-slice sort of error. We need a `min` / `max` somewhere. I can try to look tomorrow if nobody beats me to it.
... actually there is probably just some bug here -- probably need to get `n_jobs` from our `cehck_n_jobs` or whatever we call it earlier in the stack to change it to the equivalent positive number.
I can't reproduce it easily. Can you try using `sample`, or even just do post-mortem debugging (`import pdb; pdb.pm()`) and look at what `surf['tris'].shape` and `n_jobs` are (issue one `u` command to move up the stack, then `p surf['tris'].shape` and `p n_jobs`)?

Resolving patch:
diff --git a/mne/forward/_make_forward.py b/mne/forward/_make_forward.py
--- a/mne/forward/_make_forward.py
+++ b/mne/forward/_make_forward.py
@@ -16,6 +16,7 @@
                           _get_trans, _print_coord_trans, _coord_frame_name,
                           Transform)
 from ..utils import logger, verbose, warn
+from ..parallel import check_n_jobs
 from ..source_space import (_ensure_src, _filter_source_spaces,
                             _make_discrete_source_space, SourceSpaces)
 from ..source_estimate import VolSourceEstimate
@@ -553,6 +554,7 @@ def make_forward_solution(info, trans, src, bem, meg=True, eeg=True,
         info = read_info(info, verbose=False)
     else:
         info_extra = 'instance of Info'
+    n_jobs = check_n_jobs(n_jobs)
 
     # Report the setup
     logger.info('Source space                 : %s' % src)
diff --git a/mne/source_space.py b/mne/source_space.py
--- a/mne/source_space.py
+++ b/mne/source_space.py
@@ -2145,6 +2145,7 @@ def _points_outside_surface(rr, surf, n_jobs=1, verbose=None):
     """
     rr = np.atleast_2d(rr)
     assert rr.shape[1] == 3
+    assert n_jobs > 0
     parallel, p_fun, _ = parallel_func(_get_solids, n_jobs)
     tot_angles = parallel(p_fun(surf['rr'][tris], rr)
                           for tris in np.array_split(surf['tris'], n_jobs))


Test patch:
diff --git a/mne/forward/tests/test_make_forward.py b/mne/forward/tests/test_make_forward.py
--- a/mne/forward/tests/test_make_forward.py
+++ b/mne/forward/tests/test_make_forward.py
@@ -207,7 +207,8 @@ def test_make_forward_solution_kit():
 def test_make_forward_solution():
     """Test making M-EEG forward solution from python."""
     fwd_py = make_forward_solution(fname_raw, fname_trans, fname_src,
-                                   fname_bem, mindist=5.0, eeg=True, meg=True)
+                                   fname_bem, mindist=5.0, eeg=True, meg=True,
+                                   n_jobs=-1)
     assert_true(isinstance(fwd_py, Forward))
     fwd = read_forward_solution(fname_meeg)
     assert_true(isinstance(fwd, Forward))