Repository: pydata/sparse
Pull request #836 (2025-01-27 08:24:26)

fix: Fix Array API failures.
<!--
# Thanks for contributing a pull request!
## Please make sure you see our contribution guidelines: https://github.com/pydata/sparse/blob/main/docs/contributing.md

Your PR title should start with any of these abbreviatons: `build`, `chore`, `ci`, `depr`, `docs`, `feat`, `fix`, `perf`, `refactor`, `release`, `test`. Add a `!`at the end, if it is a breaking change.
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] 🪄 Feature
- [x] 🐞 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📚 Documentation
- [ ] 🧪 Test
- [ ] 🛠️ Other

## Related issues

- Related issue #
- Closes #

## Checklist

- [x] Code follows style guide
- [x] Tests added
- [x] Documented the changes

***

## Please explain your changes below.
- **Fix Array API tests for Numba backend.**
- **Fix Array API tests for Finch backend.**


Patch:
diff --git a/ci/Finch-array-api-xfails.txt b/ci/Finch-array-api-xfails.txt
index 56bd583..eee7a1c 100644
--- a/ci/Finch-array-api-xfails.txt
+++ b/ci/Finch-array-api-xfails.txt
@@ -65,14 +65,9 @@ array_api_tests/test_signatures.py::test_array_method_signature[__dlpack__]
 array_api_tests/test_signatures.py::test_array_method_signature[__dlpack_device__]
 array_api_tests/test_signatures.py::test_array_method_signature[__setitem__]
 
-# test_creation_functions
-
 # not implemented
 array_api_tests/test_creation_functions.py::test_meshgrid
 
-# buggy
-array_api_tests/test_creation_functions.py::test_full_like
-
 # test_array_object
 
 array_api_tests/test_array_object.py::test_getitem
@@ -179,8 +174,6 @@ array_api_tests/test_indexing_functions.py::test_take
 # not implemented
 array_api_tests/test_linalg.py::test_matrix_transpose
 array_api_tests/test_linalg.py::test_vecdot
-# test suite draws two scalars
-array_api_tests/test_linalg.py::test_tensordot
 
 # test_manipulation_functions
 
diff --git a/ci/Numba-array-api-xfails.txt b/ci/Numba-array-api-xfails.txt
index 8e2421a..585c3bd 100644
--- a/ci/Numba-array-api-xfails.txt
+++ b/ci/Numba-array-api-xfails.txt
@@ -42,8 +42,6 @@ array_api_tests/test_has_names.py::test_has_names[array_method-__dlpack_device__
 array_api_tests/test_has_names.py::test_has_names[array_method-__setitem__]
 array_api_tests/test_indexing_functions.py::test_take
 array_api_tests/test_linalg.py::test_vecdot
-array_api_tests/test_operators_and_elementwise_functions.py::test_ceil
-array_api_tests/test_operators_and_elementwise_functions.py::test_trunc
 array_api_tests/test_set_functions.py::test_unique_all
 array_api_tests/test_set_functions.py::test_unique_inverse
 array_api_tests/test_signatures.py::test_func_signature[unique_all]
diff --git a/sparse/numba_backend/__init__.py b/sparse/numba_backend/__init__.py
index 4658109..9f731cc 100644
--- a/sparse/numba_backend/__init__.py
+++ b/sparse/numba_backend/__init__.py
@@ -4,7 +4,6 @@ from numpy import (
     bitwise_not,
     bitwise_or,
     bitwise_xor,
-    can_cast,
     ceil,
     complex64,
     complex128,
@@ -86,6 +85,7 @@ from ._common import (
     astype,
     broadcast_arrays,
     broadcast_to,
+    can_cast,
     concat,
     concatenate,
     dot,
diff --git a/sparse/numba_backend/_common.py b/sparse/numba_backend/_common.py
index 69c33b3..d952a0b 100644
--- a/sparse/numba_backend/_common.py
+++ b/sparse/numba_backend/_common.py
@@ -1601,13 +1601,18 @@ def eye(N, M=None, k=0, dtype=float, format="coo", *, device=None, **kwargs):
     k = int(k)
 
     data_length = builtins.min(N, M)
-
     if k > 0:
         data_length = builtins.max(builtins.min(data_length, M - k), 0)
+    elif k < 0:
+        data_length = builtins.max(builtins.min(data_length, N + k), 0)
+
+    if data_length == 0:
+        return zeros((N, M), dtype=dtype, format=format, device=device)
+
+    if k > 0:
         n_coords = np.arange(data_length, dtype=np.intp)
         m_coords = n_coords + k
     elif k < 0:
-        data_length = builtins.max(builtins.min(data_length, N + k), 0)
         m_coords = np.arange(data_length, dtype=np.intp)
         n_coords = m_coords - k
     else:
@@ -1854,6 +1859,38 @@ def empty_like(a, dtype=None, shape=None, format=None, *, device=None, **kwargs)
 empty_like.__doc__ = zeros_like.__doc__
 
 
+def can_cast(from_: SparseArray, to: np.dtype, /, *, casting: str = "safe") -> bool:
+    """Determines if one data type can be cast to another data type
+
+    Parameters
+    ----------
+    from_ : dtype or SparseArray
+        Source array or dtype.
+    to : dtype
+        Destination dtype.
+    casting: str
+        Casting kind
+
+    Returns
+    -------
+    out : bool
+        Whether or not a cast is possible.
+
+    Examples
+    --------
+    >>> x = sparse.ones((2, 3), dtype=sparse.int8)
+    >>> sparse.can_cast(x, sparse.float64)
+    True
+
+    See Also
+    --------
+    - [`numpy.can_cast`][] : NumPy equivalent function
+    """
+    from_ = np.dtype(from_)
+
+    return np.can_cast(from_, to, casting=casting)
+
+
 def outer(a, b, out=None):
     """
     Return outer product of two sparse arrays.


Test patch:
diff --git a/ci/test_array_api.sh b/ci/test_array_api.sh
index 4704300..90864ef 100755
--- a/ci/test_array_api.sh
+++ b/ci/test_array_api.sh
@@ -6,4 +6,4 @@ source ci/clone_array_api_tests.sh
 if [ "${SPARSE_BACKEND}" = "Finch" ]; then
     python -c 'import finch'
 fi
-ARRAY_API_TESTS_MODULE="sparse" pytest "$ARRAY_API_TESTS_DIR/array_api_tests/" -v -c "$ARRAY_API_TESTS_DIR/pytest.ini" --ci --max-examples=2 --derandomize --disable-deadline -o xfail_strict=True -n auto --xfails-file ../sparse/ci/${SPARSE_BACKEND}-array-api-xfails.txt --skips-file ../sparse/ci/${SPARSE_BACKEND}-array-api-skips.txt
+ARRAY_API_TESTS_MODULE="sparse" pytest "$ARRAY_API_TESTS_DIR/array_api_tests/" -v -c "$ARRAY_API_TESTS_DIR/pytest.ini" --ci --max-examples=2 --derandomize --disable-deadline --disable-warnings -o xfail_strict=True -n auto --xfails-file ../sparse/ci/${SPARSE_BACKEND}-array-api-xfails.txt --skips-file ../sparse/ci/${SPARSE_BACKEND}-array-api-skips.txt
diff --git a/sparse/numba_backend/tests/test_coo.py b/sparse/numba_backend/tests/test_coo.py
index 58df49d..efd7f77 100644
--- a/sparse/numba_backend/tests/test_coo.py
+++ b/sparse/numba_backend/tests/test_coo.py
@@ -1203,11 +1203,21 @@ def test_initialization(ndim, rng):
 @pytest.mark.parametrize("N, M", [(4, None), (4, 10), (10, 4), (0, 10)])
 def test_eye(N, M):
     m = M or N
-    for k in [0, N - 2, N + 2, m - 2, m + 2]:
+    for k in [0, N - 2, N + 2, m - 2, m + 2, np.iinfo(np.intp).min]:
         assert_eq(sparse.eye(N, M=M, k=k), np.eye(N, M=M, k=k))
         assert_eq(sparse.eye(N, M=M, k=k, dtype="i4"), np.eye(N, M=M, k=k, dtype="i4"))
 
 
+@pytest.mark.parametrize("from_", [np.int8, np.int64, np.float32, np.float64, np.complex64, np.complex128])
+@pytest.mark.parametrize("to", [np.int8, np.int64, np.float32, np.float64, np.complex64, np.complex128])
+@pytest.mark.parametrize("casting", ["no", "safe", "same_kind"])
+def test_can_cast(from_, to, casting):
+    assert sparse.can_cast(sparse.zeros((2, 2), dtype=from_), to, casting=casting) == np.can_cast(
+        np.zeros((2, 2), dtype=from_), to, casting=casting
+    )
+    assert sparse.can_cast(from_, to, casting=casting) == np.can_cast(from_, to, casting=casting)
+
+
 @pytest.mark.parametrize("funcname", ["ones", "zeros"])
 def test_ones_zeros(funcname):
     sp_func = getattr(sparse, funcname)