Skip to content

feat: add Array API support via array-api-compat#4179

Draft
amalia-k510 wants to merge 17 commits into
scverse:mainfrom
amalia-k510:array-api-compat-integration
Draft

feat: add Array API support via array-api-compat#4179
amalia-k510 wants to merge 17 commits into
scverse:mainfrom
amalia-k510:array-api-compat-integration

Conversation

@amalia-k510

Copy link
Copy Markdown
Contributor

This adds Array API support to scanpy's preprocessing pipeline, where I am planning to use JAX as the test case. The goal is for someone to put a JAX array into adata.X and run the usual pipeline without the array getting silently pulled to CPU partway through. It builds on the recent fast-array-utils work, which means a lot of the heavy lifting (sums, means, variances) already works across backends.

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

⚠️ JUnit XML file not found

The CLI was unable to find any JUnit XML files to upload.
For more help, visit our troubleshooting guide.

@flying-sheep
flying-sheep self-requested a review June 25, 2026 14:58

@flying-sheep flying-sheep left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, please remember the comments from the fast-array-utils PR, e.g. we agreed to handle singledispatch functions like this: https://github.com/scverse/fast-array-utils/blob/febaf245ecd6da849c2331638219f649935d5a21/src/fast_array_utils/stats/_power.py#L38-L39

We also need tests. There’s MAP_ARRAY_TYPES which should gain an entry, which will make many many tests run with the type you add. We can discuss how to best exclude certain tests, for starters you can just add it and make some of the tests fail – that’ll show you where things aren’t working yet (and might or might not be fixed in this PR)

Please also add a release note (hatch run towncrier:create 4179.feat.md)

@flying-sheep flying-sheep changed the title Add Array API support via array-api-compat feat: add Array API support via array-api-compat Jul 17, 2026
Comment on lines +793 to +797
@axis_nnz.register(HasArrayNamespace)
def _(x: HasArrayNamespace, /, axis: Literal[0, 1]) -> Any:

xp = get_namespace(x)
return xp.count_nonzero(x, axis=axis)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this handles numpy with equal performace too no?

Comment on lines +800 to 815
if pkg_version("scipy") >= Version("1.15"):
# newer scipy versions support the `axis` argument for count_nonzero
@axis_nnz.register(CSBase)
def _(x: CSBase, /, axis: Literal[0, 1]) -> np.ndarray:
return x.count_nonzero(axis=axis)

else:
# older scipy versions don’t have any way to get the nnz of a sparse array
@axis_nnz.register(CSBase)
def _(x: CSBase, /, axis: Literal[0, 1]) -> np.ndarray:
if isinstance(x, _CSArray):
from scipy.sparse import csc_array, csr_array # noqa: TID251

x = (csr_array if x.format == "csr" else csc_array)(x)
return x.getnnz(axis=axis)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like you got a merge wrong, this change reverts an intentional simplification.

Comment on lines +833 to +834
@check_nonnegative_integers.register(HasArrayNamespace)
def _check_nonnegative_integers_array_api(x: HasArrayNamespace, /) -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this go below the np.ndarray case? I think _check_nonnegative_integers_array_api.dispatch(np.ndarray) will return the first match, no?

Comment on lines +100 to +103
@_resolve_vals.register(HasArrayNamespace)
def _resolve_vals_array_api(val: HasArrayNamespace) -> NDArray:
# Moran's I / Geary's C use numba kernels, so convert at the boundary
return np.asarray(val)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, should go below the np.ndarray case right?

if n_removed:
x = x[:, filt].copy()

from .._compat import get_namespace ### double check

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does ### double check mean?

counts = counts.compute()

counts_greater_than_zero = counts[counts > 0]
median = np.median(counts_greater_than_zero)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no median for array api?

Comment on lines +203 to +207
logg.info(
"... as scaling leads to float results, integer "
"input is cast to float, returning copy."
)
x = xp.astype(x, xp.float64)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assign the message before this if/else block so it’s not duplicated

return log1p_array(data, copy=copy, base=base)


@log1p.register(HasArrayNamespace)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again specificity/order. please check all of these in case I forgot one!

Comment thread src/scanpy/_compat.py
return metadata(package)


def get_namespace(x):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the appropriate return type

if find_spec("jax"):
import jax

jax.config.update("jax_enable_x64", True) # noqa: FBT003

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a short comment what that does?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants