# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/31_arrs.ipynb.

# %% auto 0
__all__ = ['detach', 'clone', 'cpu', 'numpy', 'todense', 'tolist', 'toarray', 'arrchain', 'ann2arr', 'asnp', 'asarr', 'as01']

# %% ../nbs/31_arrs.ipynb 6
from functools import wraps

# %% ../nbs/31_arrs.ipynb 8
from typing import Any, Union, TypeAlias, ParamSpec

# %% ../nbs/31_arrs.ipynb 10
try: import numpy as np
except ModuleNotFoundError: np = None

# %% ../nbs/31_arrs.ipynb 12
from nlit import (
    CPU, TOARRAY, TODENSE, TOLIST, DETACH, CLONE, NUMPY, LAYERS,
    __DOC__, __NAME__, __MODULE__, __ANNOTATIONS__
)
from chck import isad, isarr, issparse, istrc, isnpmatrix
from sigr import attrfunc, applyfns
from quac import (strq, nparray, npmatrix, tensor, adata, sparray, spmatrix, devcpu)

# %% ../nbs/31_arrs.ipynb 14
from .atyp import P

# %% ../nbs/31_arrs.ipynb 17
def ndarray(a: Any, *args: P.args, **kwargs: P.kwargs) -> nparray:
    '''Wrapper for `np.array(a, ...)`'''
    return np.array(a, *args, **kwargs)

# %% ../nbs/31_arrs.ipynb 19
@attrfunc(DETACH)
def detach(x: tensor) -> tensor: '''try: x.detach() except: x'''

@attrfunc(CLONE)
def clone(x: tensor) -> tensor: '''try: x.clone() except: x'''

@attrfunc(CPU)
def cpu(x: tensor) -> tensor[devcpu]: '''try: x.cpu() except: x'''

@attrfunc(NUMPY)
def numpy(x: tensor) -> nparray: '''try: x.numpy() except: x'''

@attrfunc(TODENSE)
def todense(x: tensor) -> nparray: '''try: x.todense() except: x'''

@attrfunc(TOLIST)
def tolist(x: tensor) -> list: '''try: x.tolist() except: x'''

@attrfunc(TOARRAY)
def toarray(x: tensor) -> nparray: '''try: x.toarray() except: x'''

# %% ../nbs/31_arrs.ipynb 21
def arrchain(x, funcs, *args: P.args, **kwargs: P.kwargs):
    '''Applies a sequence of functions (`funcs`) to an initial value `x`, 
    optionally stopping when the typeguard function `isarr` returns `True`.
    
    See Also
    --------
    sigr.applyfns
    ''' 
    return applyfns(x, funcs, *args, check=isarr, **kwargs)

# %% ../nbs/31_arrs.ipynb 22
def try2arr(a) -> nparray:
    '''Attempts to convert an input to an ndarray, falling back to the input if conversion fails.
    
    Parameters
    ----------
    a : nparray | npmatrix | sparray | spmatrix | tensor | anndata | list | tuple 
        The input to convert.
        
    Returns
    -------
    np.array
        The resulting numpy array.
    
    '''
    try: return a if isarr(a) else ndarray(a)
    except: return a

# %% ../nbs/31_arrs.ipynb 24
def trc2arr(a: tensor) -> nparray:
    '''torch.tensor --> np.array
    
    Converts a PyTorch tensor to a NumPy array by detaching, cloning, moving to CPU, and converting.
    
    Parameters
    ----------
    a : tensor
        The tensor to convert.
        
    Returns
    -------
    np.array
        The resulting array.

    Examples
    --------
    >>> t = torch.tensor([...])
    ... t.detach().clone().cpu().numpy()
    np.array([...])
    '''
    if isarr(a) or not istrc(a): return a
    return arrchain(a, (detach, clone, cpu, numpy, ))

# %% ../nbs/31_arrs.ipynb 26
def mtx2arr(a: npmatrix) -> nparray:
    '''np.matrix --> np.array
    
    Converts an np.matrix to an np.array using the `tolist` and `ndarray` methods.
    
    Parameters
    ----------
    a : npmatrix
        The numpy.matrix object to convert from.
        
    Returns
    -------
    np.array
        The resulting array.
    
    Examples
    --------
    >>> m = np.matrix([...])
    ... np.array(m.tolist())
    np.array([...])
    '''
    if isarr(a) or not isnpmatrix(a): return a
    return arrchain(a, (tolist, ndarray, ))

# %% ../nbs/31_arrs.ipynb 28
def spr2arr(a: Union[spmatrix, sparray]) -> nparray:
    '''scipy.sparse --> np.array
    
    Converts a scipy.sparse matrix to an np.array using the `toarray` method.
    
    Parameters
    ----------
    a : spmatrix | sparray
        The scipy sparse object to convert.
        
    Returns
    -------
    np.array
        The resulting array.
    
    Examples
    --------
    >>> m = scipy.sparse.csr_matrix([...])
    ... m.toarray()
    np.array([...])
    '''
    if isarr(a) or not issparse(a): return a
    return arrchain(a, (toarray, ))

# %% ../nbs/31_arrs.ipynb 30
def dns2arr(a: Union[spmatrix, sparray]) -> nparray:
    '''scipy.sparse --> np.array
    
    Converts a scipy.sparse matrix to a dense np.array using the `todense` and `tolist` methods.
    
    Parameters
    ----------
    a : spmatrix | sparray
        The scipy sparse object to convert.
        
    Returns
    -------
    np.array
        The resulting dense array.
    
    Examples
    --------
    >>> m = scipy.sparse.csr_matrix([...])
    ... np.array(m.todense().tolist())
    np.array([...])
    '''
    if isarr(a) or not issparse(a): return a
    return arrchain(a, (todense, tolist, ndarray, ))

# %% ../nbs/31_arrs.ipynb 32
def ann2arr(a: adata, layer: strq = None) -> nparray:
    '''anndata.AnnData --> np.array
    
    Extracts an array from an AnnData object, optionally from a specified layer.

    Parameters
    ----------
    a : adata
        The AnnData object to extract from.
    
    layer : str, optional
        The layer to extract from. If None, the X attribute is used.
        
    Returns
    -------
    np.array
        The extracted array.

    Examples
    --------
    >>> a = anndata.AnnData(X=[...], ...)
    ... a.X
    np.array([...])
    
    >>> a = anndata.AnnData(X=[...], ..., layers=dict(...))
    >>> a.layers('layer', a.X)
    np.array([...])
    '''
    if isarr(a) or not isad(a) or not hasattr(a, LAYERS): return a
    arr = a.layers.get(layer, a.X)
    return arrchain(arr, (toarray, ndarray))

# %% ../nbs/31_arrs.ipynb 34
def obj2arr(a, *args: P.args, **kwargs: P.kwargs) -> nparray:
    '''object --> np.array
    
    Attempts to convert any object to an ndarray using a series of conversion functions.
    
    Parameters
    ----------
    a : nparray | npmatrix | sparray | spmatrix | tensor | anndata | list | tuple 
        The object to convert.
    *args : Any
        Additional arguments passed to conversion functions.
    **kwargs : Any
        Additional keyword arguments passed to conversion functions.
        
    Other Parameters
    ----------------
    layer : str, optional
        The layer to extract from an AnnData object if `a` is an AnnData object.
        
    Returns
    -------
    nparray
        The resulting ndarray, or the original object if all conversions fail.
        
    Examples
    --------
    >>> import numpy as np, torch
    ... ten = torch.tensor(np.random.randint(0, 5, size=(2, 3)))
    ... ten, asnp(ten)
    (
    ... tensor([[4, 4, 3],
    ...         [3, 2, 1]]),
    ... array([[4, 4, 3],
    ...        [3, 2, 1]])
    )
    
    >>> asnp(np.matrix([1,2,3]))
    array([[1, 2, 3]])
    
    >>> # invoke spr2arr
    ... a = scipy.sparse.csr_matrix([...])
    ... a.toarray()
    np.array([...])
    
    >>> # invoke dns2arr
    ... a = scipy.sparse.csr_matrix([...])
    ... np.array(a.todense().tolist())
    np.array([...])
    
    >>> # invoke trc2arr
    ... a = torch.tensor([...])
    ... a.detach().clone().cpu().numpy()
    np.array([...])
    
    >>> # invoke mtx2arr
    ... a = np.matrix([...])
    ... np.array(a.tolist())
    np.array([...])
    
    >>> # invoke ann2arr
    ... a = anndata.AnnData(X=[...], ...)
    ... a.X
    np.array([...])
    
    >>> # invoke try2arr
    ... a = [...]
    ... np.array(a)
    '''
    if isarr(a): return a
    return arrchain(a, (
            spr2arr, # `scipy.sparse.csr_matrix([...]).toarray()`
            trc2arr, # `torch.tensor([...]).detach().clone().cpu().numpy()`
            dns2arr, # `np.array(scipy.sparse.csr_matrix([...]).todense().tolist())`
            mtx2arr, # `np.array(np.matrix([...]).tolist())`
            ann2arr, # `AnnData([...]).layers.get(layer, a.X).toarray()`
            try2arr, # `np.array`
        ), *args, **kwargs
    )

# %% ../nbs/31_arrs.ipynb 35
@wraps(obj2arr)
def mth2arr(a, *args: P.args, **kwargs: P.kwargs) -> nparray:
    '''Wrapper for obj2arr to maintain backwards compatibility with the `mth2arr` function.'''
    return obj2arr(a, *args, **kwargs)

# %% ../nbs/31_arrs.ipynb 37
@wraps(obj2arr, assigned=(__DOC__, __MODULE__, __ANNOTATIONS__))
def asnp(a, *args, **kwargs) -> nparray: 
    '''Ensures an input is converted to an np.array, applying a series of potential conversions.'''
    return obj2arr(a, *args, **kwargs)

# %% ../nbs/31_arrs.ipynb 38
@wraps(asnp)
def asarr(a, *args: P.args, **kwargs: P.kwargs) -> nparray:
    '''Wrapper for asnp to maintain backwards compatibility with the `asarr` function.'''
    return obj2arr(a, *args, **kwargs)

# %% ../nbs/31_arrs.ipynb 43
def as01(a: list, cutoff: float = 0.5) -> nparray:
    '''Binarizes an input array based on a cutoff threshold.
    
    Parameters
    ----------
    a : list
        The input list to binarize.
        
    cutoff : float, default: 0.5
        The threshold above which values are set to 1, and below to 0. 
        Default is 0.5.
        
    Returns
    -------
    nparray
        The binarized array.
    '''
    return (asnp(a) > cutoff).astype(int)
