"""Utility functions to use Python Array API compatible libraries.

For the context about the Array API see:
https://data-apis.org/array-api/latest/purpose_and_scope.html

The SciPy use case of the Array API is described on the following page:
https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy
"""

"""SciPy-specific replacement for `np.asarray` with `order`, `check_finite`, and
    `subok`.

    Memory layout parameter `order` is not exposed in the Array API standard.
    `order` is only enforced if the input array implementation
    is NumPy based, otherwise `order` is just silently ignored.

    `check_finite` is also not a keyword in the array API standard; included
    here for convenience rather than that having to be a separate function
    call inside SciPy functions.

    `subok` is included to allow this function to preserve the behaviour of
    `np.asanyarray` for NumPy based inputs.
    """

"""
    Copies an array.

    Parameters
    ----------
    x : array

    xp : array_namespace

    Returns
    -------
    copy : array
        Copied array

    Notes
    -----
    This copy function does not offer all the semantics of `np.copy`, i.e. the
    `subok` and `order` keywords are not used.
    """

    # Note: for older NumPy versions, `np.asarray` did not support the `copy` kwarg,
    # so this uses our other helper `_asarray`.
    
"""Copies a possibly on device array to a NumPy array.

    This function is intended only for converting alternative backend
    arrays to numpy arrays within test code, to make it easier for use
    of the alternative backend to be isolated only to the function being
    tested. `_xp_copy_to_numpy` should NEVER be used except in test code
    for the specific purpose mentioned above. In production code, attempts
    to copy device arrays to NumPy arrays should fail, or else functions
    may appear to be working on the GPU when they actually aren't.

    x : array

    Returns
    -------
    ndarray
    """

        # array api strict supports multiple devices, so need to
        # ensure x is on the cpu before copying to NumPy.
        
    # Fall back to np.asarray. This works for dask.array. It
    # currently works for jax.numpy, but hopefully JAX will make
    # the transfer guard workable enough for use in scipy tests, in
    # which case, JAX will have to be handled explicitly.
    # If new backends are added, they may require explicit handling as
    # well.
    
"""In all ``xp_assert_*`` and ``assert_*`` function calls executed within this
    context manager, test by default that the array namespace is
    the provided across all arrays, unless one explicitly passes the ``xp=``
    parameter or ``check_namespace=False``.

    Without this context manager, the default value for `xp` is the namespace
    for the desired array (the second parameter of the tests).
    """

"""pytest.warns context manager if arrays of specified namespace are always eager.

    Otherwise, context manager that *ignores* specified warning.
    """

    # only NumPy distinguishes between scalars and arrays; we do if check_0d=True.
    # do this first so we can then cast to array (and thus use the array API) below.
    
        # PyTorch recommends using `rtol=0, atol=0` like this
        # to test for exact equality
        
        # multiplier of 4 is used as for `np.float64` this puts the default `rtol`
        # roughly half way between sqrt(eps) and the default for
        # `numpy.testing.assert_allclose`, 1e-7
        
"""Backwards compatible replacement. In new code, use xp_assert_close instead.
    """

"""Return name for native namespace (without array_api_compat prefix)."""

"""Return the `scipy`-like namespace of a non-NumPy backend

    That is, return the namespace corresponding with backend `xp` that contains
    `scipy` sub-namespaces like `linalg` and `special`. If no such namespace
    exists, return ``None``. Useful for dispatching.
    """

# maybe use `scipy.linalg` if/when array API support is added

            # return (x @ x)**0.5
            # or to get the right behavior with nd, complex arrays
            
    # Equivalent of np.ravel written in terms of array API
    # Even though it's one line, it comes up so often that it's worth having
    # this function for readability
    
    # Equivalent of np.swapaxes written in terms of array API
    
"""
    Returns the dtype that results from applying type promotion rules
    (see Array API Standard Type Promotion Rules) to the arguments. Augments
    standard `result_type` in a few ways:

    - There is a `force_floating` argument that ensures that the result type
      is floating point, even when all args are integer.
    - When a TypeError is raised (e.g. due to an unsupported promotion)
      and `force_floating=True`, we define a custom rule: use the result type
      of the default float and any other floats passed. See
      https://github.com/scipy/scipy/pull/22695/files#r1997905891
      for rationale.
    - This function accepts array-like iterables, which are immediately converted
      to the namespace's arrays before result type calculation. Consequently, the
      result dtype may be different when an argument is `1.` vs `[1.]`.

    Typically, this function will be called shortly after `array_namespace`
    on a subset of the arguments passed to `array_namespace`.
    """

    # prevent double conversion of iterable to array
    # avoid `np.iterable` for torch arrays due to pytorch/pytorch#143334
    # don't use `array_api_compat.is_array_api_obj` as it returns True for NumPy scalars
    
        # use `result_type` of default floating point type and any floats present
        # This can be revisited, but right now, the only backends that get here
        # are array-api-strict (which is not for production use) and PyTorch
        # (due to data-apis/array-api-compat#279).
        
"""
    Promotes elements of *args to result dtype, ignoring `None`s.
    Includes options for forcing promotion to floating point and
    broadcasting the arrays, again ignoring `None`s.
    Type promotion rules follow `xp_result_type` instead of `xp.result_type`.

    This function accepts array-like iterables, which are immediately converted
    to the namespace's arrays before result type calculation. Consequently, the
    result dtype may be different when an argument is `1.` vs `[1.]`.

    See Also
    --------
    xp_result_type
    """

        # broadcast only if needed
        # Even if two arguments need broadcasting, this is faster than
        # `broadcast_arrays`, especially since we've already determined `shape`
        
        # This is much faster than xp.astype(arg, dtype, copy=False)
        
    # The standard float dtypes are float32 and float64.
    # Convert float32 to complex64,
    # and float64 (and non-standard real dtypes) to complex128
    
"""Query the namespace-dependent default floating-point dtype.
    """

        # historically, we allow pytorch to keep its default of float32
        
"""Return the device of an array in `args`, for the purpose of
    input-output device propagation.
    If there are multiple devices, return an arbitrary one.
    If there are no arrays, return None (this typically happens only on NumPy).
    """

        # Do not do a duck-type test for the .device attribute, as many backends today
        # don't have it yet. See workarouunds in array_api_compat.device().
        
"""A replacement for `np.r_` as `xp.concat` does not accept python scalars
       or 0-D arrays.
    """

"""Returns True if `xp` is an MArray namespace; False otherwise."""

            # compact way to deal with axis tuples or ints
            
        # Note: Dask+CuPy is currently untested and unsupported
        
        # It will be better to link to a section of the dev-arrayapi docs
        # that explains what is and isn't in-scope, but such a section
        # doesn't exist yet. Using :ref:`dev-arrayapi` as a placeholder.
        
` is not in-scope for support of Python Array API Standard compatible
        backends other than NumPy.

        See :ref:`dev-arrayapi` for more information.
        
` has experimental support for Python Array API Standard compatible
    backends in addition to NumPy. Please consider testing these features
    by setting an environment variable ``SCIPY_ARRAY_API=1`` and providing
    CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following
    combinations of backend and device (or other capability) are supported.

    ====================  ====================  ====================
    Library               CPU                   GPU
    NumPy                 

    # Alternative capabilities table.
    # Used only for testing this decorator.
    
    # Generate pytest.mark.skip/xfail_xp_backends.
    # See documentation in conftest.py.
    # lists of tuples [(module name, reason), ...]
    
    # xpx.testing.lazy_xp_function kwargs.
    # Refer to array-api-extra documentation.
    
"""Decorator for a function that states its support among various
    Array API compatible backends.

    This decorator has two effects:
    1. It allows tagging tests with ``@make_xp_test_case`` or
       ``make_xp_pytest_param`` (see below) to automatically generate
       SKIP/XFAIL markers and perform additional backend-specific
       testing, such as extra validation for Dask and JAX;
    2. It automatically adds a note to the function's docstring, containing
       a table matching what has been tested.

    See Also
    --------
    make_xp_test_case
    make_xp_pytest_param
    array_api_extra.testing.lazy_xp_function
    """

        # Don't use a wrapper, as in some cases @xp_capabilities is
        # applied to a ufunc
        
            # Can't update __doc__ on ufuncs if SciPy
            # was compiled against NumPy < 2.2.
            
"""Generate pytest decorator for a test function that tests functionality
    of one or more Array API compatible functions.

    Read the parameters of the ``@xp_capabilities`` decorator applied to the
    listed functions and:

    - Generate the ``@pytest.mark.skip_xp_backends`` and
      ``@pytest.mark.xfail_xp_backends`` decorators
      for the decorated test function
    - Tag the function with `xpx.testing.lazy_xp_function`

    Example::

        @make_xp_test_case(f1)
        def test_f1(xp):
            ...

        @make_xp_test_case(f2)
        def test_f2(xp):
            ...

        @make_xp_test_case(f1, f2)
        def test_f1_and_f2(xp):
            ...

    The above is equivalent to::
        @pytest.mark.skip_xp_backends(...)
        @pytest.mark.skip_xp_backends(...)        
        @pytest.mark.xfail_xp_backends(...)
            ...

    etc., where the arguments of ``skip_xp_backends`` and ``xfail_xp_backends`` are
    determined by the ``@xp_capabilities`` decorator applied to the functions.

    See Also
    --------
    xp_capabilities
    make_xp_pytest_marks
    """

"""Variant of ``make_xp_test_case`` that returns a pytest.param for a function,
    with all necessary skip_xp_backends and xfail_xp_backends marks applied::

        @pytest.mark.parametrize(
            "func", [make_xp_pytest_param(f1), make_xp_pytest_param(f2)]
        )
        def test(func, xp):
            ...

            "func", [
                pytest.param(f1, marks=[
                    pytest.mark.skip_xp_backends(...),
                    pytest.mark.xfail_xp_backends(...), ...]),
                pytest.param(f2, marks=[
        )
            ...

    func : Callable
        Function to be tested. It must be decorated with ``@xp_capabilities``.
    *args : Any, optional
        Extra pytest parameters for the use case, e.g.::

        @pytest.mark.parametrize("func,verb", [
            make_xp_pytest_param(f1, "hello"),
            make_xp_pytest_param(f2, "world")])
        def test(func, verb, xp):
            # iterates on (func=f1, verb="hello")
            # and (func=f2, verb="world")

    See Also
    --------
    """

"""Variant of ``make_xp_test_case`` that returns a list of pytest marks,
    which can be used with the module-level `pytestmark = ...` variable::

        pytestmark = make_xp_pytest_marks(f1, f2)

        def test(xp):
            ...

    In this example, the whole test module is dedicated to testing `f1` or `f2`,
    and the two functions have the same capabilities, so it's unnecessary to
    cherry-pick which test tests which function.

        pytestmark = [
            pytest.mark.skip_xp_backends(...),
            pytest.mark.xfail_xp_backends(...), ...]),
        ]

            ...
    
    See Also
    --------
    """

# Is it OK to have a dictionary that is mutated (once upon import) in many places?

        # TODO this can return other backends e.g. tpu but they're unsupported in scipy
        
    # array-api-strict is a stand-in for unknown libraries; don't special-case it
    
from scipy._lib.array_api_compat.common._helpers import _compat_module_name

from scipy._lib.array_api_extra.testing import lazy_xp_function

    '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal',

    'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch',

    'xp_unsupported_param_msg', 'xp_vector_norm', 'xp_capabilities',

    'make_xp_test_case', 'make_xp_pytest_marks', 'make_xp_pytest_param',

Array: TypeAlias = Any  # To be changed to a Protocol later (see array-api#589)

    """SciPy-specific replacement for `np.asarray` with `order`, `check_finite`, and

            array = np.array(array, order=order, dtype=dtype, subok=subok)

            array = np.asanyarray(array, order=order, dtype=dtype)

            array = np.asarray(array, order=order, dtype=dtype)

            array = coerced_xp.asarray(array, dtype=dtype, copy=copy)

def xp_copy(x: Array, *, xp: ModuleType | None = None) -> Array:

            xp.asarray(x, device=xp.Device("CPU_DEVICE")), copy=True

_default_xp_ctxvar: ContextVar[ModuleType] = ContextVar("_default_xp")

    """In all ``xp_assert_*`` and ``assert_*`` function calls executed within this

    """pytest.warns context manager if arrays of specified namespace are always eager.

    return ignore_warns(warning_type, match='' if match is None else match)

                  check_namespace=True, check_dtype=True, check_shape=True,

                or (not xp.isscalar(actual) and not xp.isscalar(desired))), _msg

        _msg = f"dtypes do not match.\nActual: {actual.dtype}\nDesired: {desired.dtype}"

        _msg = f"Shapes do not match.\nActual: {actual.shape}\nDesired: {desired.shape}"

    _msg = ("Namespace of desired array does not match expectations "

            "set by the `default_xp` context manager or by the `xp`"

            f"Desired array's space: {desired_arr_space.__name__}\n"

    _msg = ("Namespace of actual and desired arrays do not match.\n"

def xp_assert_equal(actual, desired, *, check_namespace=True, check_dtype=True,

                    check_shape=True, check_0d=True, err_msg='', xp=None):

        return xp.testing.assert_array_equal(actual, desired, err_msg=err_msg)

        return xp.testing.assert_close(actual, desired, rtol=0, atol=0, equal_nan=True,

                                       check_dtype=False, msg=err_msg)

    return np.testing.assert_array_equal(actual, desired, err_msg=err_msg)

def xp_assert_close(actual, desired, *, rtol=None, atol=0, check_namespace=True,

                    check_dtype=True, check_shape=True, check_0d=True,

        check_namespace=check_namespace, check_dtype=check_dtype,

    floating = xp.isdtype(actual.dtype, ('real floating', 'complex floating'))

        return xp.testing.assert_allclose(actual, desired, rtol=rtol,

                                          atol=atol, err_msg=err_msg)

        return xp.testing.assert_close(actual, desired, rtol=rtol, atol=atol,

                                       equal_nan=True, check_dtype=False, msg=err_msg)

    return np.testing.assert_allclose(actual, desired, rtol=rtol,

                                      atol=atol, err_msg=err_msg)

def xp_assert_close_nulp(actual, desired, *, nulp=1, check_namespace=True,

                         check_dtype=True, check_shape=True, check_0d=True,

    actual, desired = map(_xp_copy_to_numpy, (actual, desired))

    return np.testing.assert_array_almost_equal_nulp(actual, desired, nulp=nulp)

def xp_assert_less(actual, desired, *, check_namespace=True, check_dtype=True,

                   check_shape=True, check_0d=True, err_msg='', verbose=True, xp=None):

                                            err_msg=err_msg, verbose=verbose)

                                        err_msg=err_msg, verbose=verbose)

def assert_array_almost_equal(actual, desired, decimal=6, *args, **kwds):

    """Backwards compatible replacement. In new code, use xp_assert_close instead.

                           atol=atol, rtol=rtol, check_dtype=False, check_shape=False,

def assert_almost_equal(actual, desired, decimal=7, *args, **kwds):

    return f'Providing {param!r} is only supported for numpy arrays.'

    """Return name for native namespace (without array_api_compat prefix)."""

    """Return the `scipy`-like namespace of a non-NumPy backend

        import cupyx  # type: ignore[import-not-found,import-untyped]

            return xp.linalg.vector_norm(x, axis=axis, keepdims=keepdims, ord=ord)

                    "only the Euclidean norm (`ord=2`) is currently supported in "

                    "`xp_vector_norm` for backends not implementing the `linalg` "

            return xp.sum(xp.conj(x) * x, axis=axis, keepdims=keepdims)**0.5

        return np.linalg.norm(x, ord=ord, axis=axis, keepdims=keepdims)

def xp_ravel(x: Array, /, *, xp: ModuleType | None = None) -> Array:

    args = [(_asarray(arg, subok=True, xp=xp) if is_torch_array(arg) or np.iterable(arg)

        args_not_none = [arg.dtype if getattr(arg, 'size', 0) == 1 else arg

            arg_array = xp.asarray(arg) if np.isscalar(arg) else arg

            if xp.isdtype(dtype, ('real floating', 'complex floating')):

        return xp.result_type(*float_args, xp_default_dtype(xp))

def xp_promote(*args, broadcast=False, force_floating=False, xp):

    dtype = xp_result_type(*args, force_floating=force_floating, xp=xp)

    args = [(_asarray(arg, dtype=dtype, subok=True, xp=xp) if arg is not None else arg)

        shape = (np.broadcast_shapes(*shapes) if len(shapes) != 1

        message = "Array shapes are incompatible for broadcasting."

def xp_float_to_complex(arr: Array, xp: ModuleType | None = None) -> Array:

    """Query the namespace-dependent default floating-point dtype.

    """Return the device of an array in `args`, for the purpose of

def concat_1d(xp: ModuleType | None, *arrays: Iterable[ArrayLike]) -> Array:

    """A replacement for `np.r_` as `xp.concat` does not accept python scalars

    arys = [xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) for a in arrays]

    """Returns True if `xp` is an MArray namespace; False otherwise."""

            message = '`axis` must be an integer or None for use with `MArray`.'

        return xp.astype(xp.count(x, axis=axis, keepdims=keepdims), x.dtype)

            int(np.prod(np.asarray(x.shape)[np.asarray(axis)])))

        mask = functools.reduce(operator.or_, (arg.mask for arg in args))

        args = [xp.asarray(arg.data, mask=mask) for arg in args]

    warnings: list[str] = dataclasses.field(default_factory=list)

    cpu_only=False, np_only=False, out_of_scope=False, exceptions=(),

        "array_api_strict": _XPSphinxCapability(cpu=True, gpu=None),

            warnings=["computes graph"] if allow_dask_compute else []),

    for module, _ in list(skip_backends) + list(xfail_backends):

        elif cpu_only and module not in exceptions and backend.gpu is not None:

def _make_capabilities_note(fun_name, capabilities, extra_note=None):

        `{fun_name}` is not in-scope for support of Python Array API Standard compatible

    `{fun_name}` has experimental support for Python Array API Standard compatible

    NumPy                 {capabilities['numpy']                   }

    CuPy                  {capabilities['cupy']                    }

    PyTorch               {capabilities['torch']                   }

    JAX                   {capabilities['jax.numpy']               }

    Dask                  {capabilities['dask.array']              }

    """ + (extra_note or "") + "    See :ref:`dev-arrayapi` for more information."

    """Decorator for a function that states its support among various

    capabilities_table = (xp_capabilities_table if capabilities_table is None

    sphinx_capabilities = _make_sphinx_capabilities(**capabilities)

        note = _make_capabilities_note(f.__name__, sphinx_capabilities, extra_note)

        doc = str(doc).split("\n", 1)[1].lstrip(" \n")  # remove signature

    """Generate pytest decorator for a test function that tests functionality

    marks = make_xp_pytest_marks(*funcs, capabilities_table=capabilities_table)

    return lambda func: functools.reduce(lambda f, g: g(f), marks, func)

def make_xp_pytest_param(func, *args, capabilities_table=None):

    """Variant of ``make_xp_test_case`` that returns a pytest.param for a function,

    marks = make_xp_pytest_marks(func, capabilities_table=capabilities_table)

    return pytest.param(func, *args, marks=marks, id=func.__name__)

    """Variant of ``make_xp_test_case`` that returns a list of pytest marks,

                cpu_only=True, exceptions=exceptions, reason=reason))

                np_only=True, exceptions=exceptions, reason=reason))

            marks.append(pytest.mark.skip_xp_backends(mod_name, reason=reason))

        for mod_name, reason in capabilities['xfail_backends']:

            marks.append(pytest.mark.xfail_xp_backends(mod_name, reason=reason))

                       for k in ('allow_dask_compute', 'jax_jit')}

        return "cuda" if (p := a.device.platform) == "gpu" else p