diff --git a/docs/release-notes/0.16.0.md b/docs/release-notes/0.16.0.md index 4e92893b..d409138b 100644 --- a/docs/release-notes/0.16.0.md +++ b/docs/release-notes/0.16.0.md @@ -15,6 +15,7 @@ ``` * {func}`~rapids_singlecell.pp.neighbors` with ``method="jaccard"`` no longer crashes with ``CUDA_ERROR_ILLEGAL_ADDRESS`` on large datasets where ``n_obs * (n_neighbors - 1)**2`` exceeds ``2**31`` {pr}`700` {smaller}`S Dicks` * {func}`~rapids_singlecell.pp.pca` no longer imports a removed cuML internal, fixing ``ImportError`` on recent cuML builds {pr}`708` {smaller}`S Dicks` +* Promote dense integer inputs in {func}`~rapids_singlecell.pp.normalize_total` and {func}`~rapids_singlecell.pp.scale` to floating point {pr}`718` {smaller}`S Dicks` * Fix {func}`~rapids_singlecell.pp.scale` with ``obsm`` and metadata changes from {func}`~rapids_singlecell.pp.log1p` with ``inplace=False`` {pr}`719` {smaller}`S Dicks` * Reject Dask arrays chunked along the feature axis {pr}`717` {smaller}`S Dicks` diff --git a/src/rapids_singlecell/preprocessing/_normalize.py b/src/rapids_singlecell/preprocessing/_normalize.py index 2b18a29f..a551172f 100644 --- a/src/rapids_singlecell/preprocessing/_normalize.py +++ b/src/rapids_singlecell/preprocessing/_normalize.py @@ -80,7 +80,9 @@ def normalize_total( _check_gpu_X(X, allow_dask=True) - if not inplace: + if X.dtype.kind in "iu": + X = X.astype(cp.float32) + elif not inplace: X = X.copy() if sparse.isspmatrix_csc(X): diff --git a/src/rapids_singlecell/preprocessing/_scale.py b/src/rapids_singlecell/preprocessing/_scale.py index e4f12700..35773e66 100644 --- a/src/rapids_singlecell/preprocessing/_scale.py +++ b/src/rapids_singlecell/preprocessing/_scale.py @@ -137,6 +137,8 @@ def scale( def _scale_dispatch(X, *, mask_obs, zero_center, inplace, max_value): + if X.dtype.kind in "iu": + X = X.astype(cp.float64) if isinstance(X, DaskArray): return _scale_dask( X, diff --git a/tests/test_normalization.py b/tests/test_normalization.py index ad877686..b0ae7323 100644 --- a/tests/test_normalization.py +++ b/tests/test_normalization.py @@ -27,6 +27,18 @@ def test_normalize_total(dtype, sparse): ) +@pytest.mark.parametrize("dtype", [np.int32, np.int64]) +def test_normalize_total_promotes_dense_integers(dtype): + cudata = AnnData(cp.array([[1, 1], [2, 4]], dtype=dtype)) + + rsc.pp.normalize_total(cudata, target_sum=10) + + assert cudata.X.dtype == cp.float32 + cp.testing.assert_allclose( + cudata.X.sum(axis=1), cp.full(cudata.n_obs, 10, dtype=cp.float32) + ) + + @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_normalize_total_layers(dtype): cudata = AnnData(csr_matrix(X_total, dtype=dtype)) diff --git a/tests/test_scaling.py b/tests/test_scaling.py index a099a91c..305c32ea 100644 --- a/tests/test_scaling.py +++ b/tests/test_scaling.py @@ -113,6 +113,16 @@ def test_scale_simple(dtype): ) +@pytest.mark.parametrize("dtype", [np.int32, np.int64]) +def test_scale_promotes_dense_integers(dtype): + adata = AnnData(cp.array(X_original, dtype=dtype)) + + rsc.pp.scale(adata) + + assert adata.X.dtype == cp.float64 + cp.testing.assert_allclose(adata.X, X_centered_original) + + def test_scale_obsm_does_not_write_var_statistics(): adata = AnnData(cp.ones((3, 4), dtype=cp.float32)) adata.obsm["X_embedding"] = cp.array([[1, 2], [2, 4], [3, 6]], dtype=cp.float32)