Repository: scikit-learn/scikit-learn
File: sklearn/utils/_array_api.py (Python)

Code before:
    `array` may contain `None` entries, these are left unchanged.

    Sparse arrays are accepted if the reference namespace is Numpy, in which
    case they are returned unchanged. Otherwise a `TypeError` is raised.

    Parameters
    ----------
    *arrays : iterable of arrays
        Arrays to (potentially) move.

    xp_reference : namespace
        Array API namespace to move arrays to.

    device_reference : device
        Array API device to move arrays to.

    Returns
    -------
    arrays : list
        Arrays with the same namespace and device as reference.
    """
    sparse_mask = [sp.issparse(array) for array in arrays]
    none_mask = [array is None for array in arrays]
    if any(sparse_mask) and not _is_numpy_namespace(xp_reference):
        raise TypeError(
            "Sparse arrays are only supported when all dense arrays are Numpy arrays."
        )
    # Early return if all `arrays` are sparse or None (to numpy)
    if all(
        sparse_mask[i] or none_mask[i] for i in range(len(arrays))
    ) and _is_numpy_namespace(xp_reference):
        return arrays

    converted_arrays = []

    for array, is_sparse, is_none in zip(arrays, sparse_mask, none_mask):
        if is_none:
            converted_arrays.append(None)
        elif is_sparse:
            converted_arrays.append(array)
        else:
            xp_array, _, device_array = get_namespace_and_device(array)
            if xp_reference == xp_array and device_reference == device_array:
                converted_arrays.append(array)
            else:
                try:
                    # The dlpack protocol is the future proof and library agnostic
                    # method to transfer arrays across namespace and device boundaries
                    # hence this method is attempted first and going through NumPy is
                    # only used as fallback in case of failure.
                    # Note: copy=None is the default since 2023.12. Namespace libraries

Review comment (suggestion):
I find this error message doesn't describe what is happening anymore and is probably a remainder of a previous version of the code when we could pass reference arrays.

This is my attempt to improve it. I hope you find it helpful:

```suggestion
            "Sparse arrays are only accepted (as passhrough) when the target namespace is Numpy."
```

Code after:

    for array, is_sparse, is_none in zip(arrays, sparse_mask, none_mask):
        if is_none:
            converted_arrays.append(None)
        elif is_sparse:
            converted_arrays.append(array)
        else:
            xp_array, _, device_array = get_namespace_and_device(array)
            if xp == xp_array and device == device_array:
                converted_arrays.append(array)
            else:
                try:
                    # The dlpack protocol is the future proof and library agnostic
                    # method to transfer arrays across namespace and device boundaries
                    # hence this method is attempted first and going through NumPy is
                    # only used as fallback in case of failure.
                    # Note: copy=None is the default since array-api 2023.12. Namespace
                    # libraries should only trigger a copy automatically if needed.
                    array_converted = xp.from_dlpack(array, device=device)
                    # `AttributeError` occurs when `__dlpack__` and `__dlpack_device__`
                    # methods are not present on the input array
                    # `TypeError` and `NotImplementedError` for packages that do not
                    # yet support dlpack 1.0
                    # (i.e. the `device`/`copy` kwargs, e.g., torch <= 2.8.0)
                    # See https://github.com/data-apis/array-api/pull/741 for
                    # more details about the introduction of the `copy` and `device`
                    # kwargs in the from_dlpack method and their expected
                    # meaning by namespaces implementing the array API spec.
                    # TODO: try removing this once DLPack v1 more widely supported
                except (AttributeError, TypeError, NotImplementedError):
                    # Converting to numpy is tricky, handle this via dedicated function
                    if _is_numpy_namespace(xp):
                        array_converted = _convert_to_numpy(array, xp_array)
                    # Convert from numpy, all array libraries can do this
                    elif _is_numpy_namespace(xp_array):
                        array_converted = xp.asarray(array, device=device)
                    else:
                        # There is no generic way to convert from namespace A to B
                        # So we first convert from A to numpy and then from numpy to B
                        # The way to avoid this round trip is to lobby for DLpack
                        # support in libraries A and B
                        array_np = _convert_to_numpy(array, xp_array)
                        array_converted = xp.asarray(array_np, device=device)
                converted_arrays.append(array_converted)

    return (
        converted_arrays[0] if len(converted_arrays) == 1 else tuple(converted_arrays)
    )


def _expit(X, xp=None):