@@ -103,6 +103,33 @@ def _generate_hrf_matrix(self, n_scans):
103103 hrf_obj .generate_hrf (tr = self .tr , n_scans = n_scans )
104104 return hrf_obj .hrf_
105105
106+ def _dask_compute (self , futures ):
107+ """Run dask compute with the scheduler selected by ``n_jobs``.
108+
109+ Uses the synchronous scheduler when ``n_jobs=1`` (default, safe for
110+ all environments) and the threaded scheduler otherwise. Because the
111+ per-voxel solvers (LARS, FISTA) call into numpy/scipy C extensions
112+ that release the GIL, threads provide real parallelism here.
113+
114+ Parameters
115+ ----------
116+ futures : list of dask.delayed
117+ Delayed objects to compute.
118+
119+ Returns
120+ -------
121+ results : list
122+ Computed results in the same order as ``futures``.
123+ """
124+ if self .n_jobs == 0 or self .n_jobs < - 1 :
125+ raise ValueError (
126+ f"n_jobs must be -1 (use all CPUs) or a positive integer >= 1, got { self .n_jobs !r} "
127+ )
128+ if self .n_jobs == 1 :
129+ return compute (futures , scheduler = "synchronous" )[0 ]
130+ num_workers = None if self .n_jobs == - 1 else self .n_jobs
131+ return compute (futures , scheduler = "threads" , num_workers = num_workers )[0 ]
132+
106133 @abstractmethod
107134 def fit (self , X , y = None ):
108135 """Fit the deconvolution model.
@@ -378,7 +405,7 @@ def _fit_lars(self, X, n_scans, n_voxels):
378405 )
379406 futures .append (fut )
380407
381- results = compute (futures , scheduler = "synchronous" )[ 0 ]
408+ results = self . _dask_compute (futures )
382409
383410 for vox_idx in range (n_voxels ):
384411 self .coef_ [:, vox_idx ] = np .squeeze (results [vox_idx ][0 ])
@@ -403,7 +430,7 @@ def _fit_fista(self, X, n_scans, n_voxels):
403430 )
404431 futures .append (fut )
405432
406- results = compute (futures , scheduler = "synchronous" )[ 0 ]
433+ results = self . _dask_compute (futures )
407434
408435 for vox_idx in range (n_voxels ):
409436 self .coef_ [:, vox_idx ] = np .squeeze (results [vox_idx ][0 ])
@@ -628,7 +655,7 @@ def fit(self, X, y=None):
628655 )
629656 futures .append (fut )
630657
631- results = compute (futures , scheduler = "synchronous" )[ 0 ]
658+ results = self . _dask_compute (futures )
632659
633660 for vox_idx in range (n_voxels ):
634661 self .coef_ [:, vox_idx ] = np .squeeze (results [vox_idx ][0 ])
@@ -806,7 +833,7 @@ def fit(self, X, y=None):
806833 )
807834 futures .append (fut )
808835
809- results = compute (futures , scheduler = "synchronous" )[ 0 ]
836+ results = self . _dask_compute (futures )
810837
811838 for vox_idx in range (n_voxels ):
812839 self .selection_frequency_ [:, vox_idx ] = np .squeeze (results [vox_idx ])
0 commit comments