|
13 | 13 | from anndata.tests.helpers import asarray |
14 | 14 | from scipy import sparse |
15 | 15 |
|
| 16 | +import scanpy._utils |
16 | 17 | from scanpy._compat import CSBase, DaskArray |
17 | 18 | from scanpy._utils import ( |
18 | 19 | _numba_thread_limit, |
19 | 20 | axis_mul_or_truediv, |
| 21 | + axis_nnz, |
20 | 22 | check_nonnegative_integers, |
21 | 23 | descend_classes_and_funcs, |
22 | 24 | ) |
|
26 | 28 | random_k_tuples, |
27 | 29 | random_str, |
28 | 30 | ) |
| 31 | +from testing.scanpy._pytest.marks import needs |
29 | 32 | from testing.scanpy._pytest.params import ( |
30 | 33 | ARRAY_TYPES, |
31 | 34 | ARRAY_TYPES_DASK, |
@@ -182,6 +185,45 @@ def test_check_nonnegative_integers(array_type, array_value, expected): |
182 | 185 | assert received is expected |
183 | 186 |
|
184 | 187 |
|
| 188 | +@needs.dask |
| 189 | +@pytest.mark.parametrize( |
| 190 | + ("func", "kwargs"), |
| 191 | + [ |
| 192 | + pytest.param(check_nonnegative_integers, {}, id="check_nonnegative_integers"), |
| 193 | + pytest.param(axis_nnz, dict(axis=0), id="axis_nnz-0"), |
| 194 | + pytest.param(axis_nnz, dict(axis=1), id="axis_nnz-1"), |
| 195 | + ], |
| 196 | +) |
| 197 | +@pytest.mark.parametrize( |
| 198 | + "block_type", |
| 199 | + [np.asarray, sparse.csr_matrix], # noqa: TID251 |
| 200 | + ids=["dense_dask", "csr_dask"], |
| 201 | +) |
| 202 | +def test_dask_reductions_blockwise( |
| 203 | + monkeypatch: pytest.MonkeyPatch, func: Callable, kwargs: dict, block_type |
| 204 | +): |
| 205 | + """Reductions over dask arrays must see one block at a time, not all blocks concatenated.""" |
| 206 | + import dask.array as da |
| 207 | + |
| 208 | + x_np = np.random.default_rng(0).poisson(size=(100, 20)).astype(np.float32) |
| 209 | + x = da.from_array(block_type(x_np), chunks=(10, 20), asarray=False) |
| 210 | + |
| 211 | + # the dask implementation calls the module-level function on each block |
| 212 | + seen_shapes = [] |
| 213 | + |
| 214 | + def spy(block, **kw): |
| 215 | + seen_shapes.append(block.shape) |
| 216 | + return func(block, **kw) |
| 217 | + |
| 218 | + monkeypatch.setattr(scanpy._utils, func.__name__, spy) |
| 219 | + lazy = func(x, **kwargs) |
| 220 | + seen_shapes.clear() # drop dask’s metadata probe, made while building the graph |
| 221 | + received = lazy.compute() |
| 222 | + |
| 223 | + assert seen_shapes == [(10, 20)] * 10 |
| 224 | + np.testing.assert_array_equal(received, func(x_np, **kwargs)) |
| 225 | + |
| 226 | + |
185 | 227 | @pytest.mark.parametrize("seed", [0, 1, 1256712675]) |
186 | 228 | @pytest.mark.parametrize("pass_seed", [True, False], ids=["pass_seed", "set_seed"]) |
187 | 229 | @pytest.mark.parametrize("func", ["choice"]) |
|
0 commit comments