
    Cd              	      ^   d dl mZ d dlZd dlZd dlmZmZmZ d dl	m
Z
mZmZmZmZ d dlmZ d dlmZ d dlmZ dZd	                    e          Zd
e dZd	                    e          Zd                    e          Zde de dZd Zd ZddddddddddZ G d d          ZddZ dS )    )annotationsN)concatmergeunique)Arrayapply_infer_dtypeasarray	blockwisegetitemmeta_from_array)flatten)HighLevelGraphz\w+z(?:{0:}(?:,{0:})*,?)?z\(z\)z{0:}(?:,{0:})*^->$c                   t          j        dd|           } t          j        t          |           st	          d|            |                     d          \  }}d t          j        t          |          D             }d t          j        t          |          D             }t          |          dk    r|d         d	k    r|d
         n|}||fS )a  
    Parse string signatures for a generalized universal function.

    Arguments
    ---------
    signature : string
        Generalized universal function signature, e.g., ``(m,n),(n,p)->(m,p)``
        for ``np.matmul``.

    Returns
    -------
    Tuple of input and output core dimensions parsed from the signature, each
    of the form List[Tuple[str, ...]], except for one output. For one output
    core dimension is not a list, but of the form Tuple[str, ...]
    \s+ zNot a valid gufunc signature: r   c                \    g | ])}t          t          j        t          |                    *S  tuplerefindall_DIMENSION_NAME.0args     1lib/python3.11/site-packages/dask/array/gufunc.py
<listcomp>z+_parse_gufunc_signature.<locals>.<listcomp>0   s9       47bj#..//      c                \    g | ])}t          t          j        t          |                    *S r   r   r   s     r    r!   z+_parse_gufunc_signature.<locals>.<listcomp>3   s<        	bj#..//  r"      ,r   )	r   submatch
_SIGNATURE
ValueErrorsplitr   	_ARGUMENTlen)	signaturein_txtout_txtinsoutss        r    _parse_gufunc_signaturer3      s      vr9--I8J	** GE)EEFFFood++OFG ;=:iQW;X;X  C :i11  D D		QWR[C-?-?477tD9r"   c                n   t          |          }t          |t                    sdnt          |          }| t          d          | r$t          | t                    st          d          |dk    r|n|g}t          t	          t           |                    }t          d |D                       }|r.|dk    rt          d          t          |          |d         gz  }||z   }	jt          t
                    st          d          |rD|d         }
t          |
          dk    rt          d	          |D ]}|
|k    rt          d
          | fd|	D             } n1d |	D             } n$t          | t                    st          d          d | D             } |dk    r)|t          |           k    r||z   t          |           k    s|dk    r%||z   t          |           k    rt          d          | |d         }|r|nd |D             }| d|         }t          t          ||                    D ]J\  }\  }}t          |          t          |          k    r"t          d                    |                    K|sit          t          ||                    D ]J\  }\  }}t          |          t          |          k    r"t          d                    |                    Kn:|r8|d         }|D ]}||k    rt          d          |d         fd|D             }||fS )a  
    Validates logic of `axes`/`axis`/`keepdims` arguments and normalize them.
    Refer to [1]_ for details

    Arguments
    ---------
    axes: List of tuples
    axis: int
    keepdims: bool
    input_coredimss: List of Tuple of dims
    output_coredimss: List of Tuple of dims

    Returns
    -------
    input_axes: List of tuple of int
    output_axes: List of tuple of int

    References
    ----------
    .. [1] https://docs.scipy.org/doc/numpy/reference/ufuncs.html#optional-keyword-arguments
    r$   Nz>Only one of `axis` or `axes` keyword arguments should be givenz`axes` has to be of type listc                8    g | ]}t          |          d k    dS )r   Tr-   r   xs     r    r!   z,_validate_normalize_axes.<locals>.<listcomp>]   s#    #Q#Q#QQc!ffqjjDjjjr"   r   z.`keepdims` can only be used for scalar outputsz*`axis` argument has to be an integer valuez9`axis` can be used only, if one core dimension is presentz3To use `axis`, all core dimensions have to be equalc                6    g | ]}|rfnt                      S r   )r   )r   cdaxiss     r    r!   z,_validate_normalize_axes.<locals>.<listcomp>w   s(    CCC2r.TGGuwwCCCr"   c           	     d    g | ]-}t          t          t          |           d                     .S r   r   ranger-   )r   icds     r    r!   z,_validate_normalize_axes.<locals>.<listcomp>y   s2    DDD3E%S	1--..DDDr"   z `axes` argument has to be a listc                B    g | ]}t          |t                    r|fn|S r   )
isinstanceintr   as     r    r!   z,_validate_normalize_axes.<locals>.<listcomp>|   s-    <<<!Jq#&&-QDDA<<<r"   zRThe number of `axes` entries is not equal the number of input and output argumentsc           	     d    g | ]-}t          t          t          |           d                     .S r=   r>   )r   ocds     r    r!   z,_validate_normalize_axes.<locals>.<listcomp>   s2    EEESeE3s88)Q''((EEEr"   zvThe number of `axes` entries for argument #{} is not equal the number of respective input core dimensions in signaturezwThe number of `axes` entries for argument #{} is not equal the number of respective output core dimensions in signaturez7To use `keepdims`, all core dimensions have to be equalc                    g | ]}S r   r   )r   _iax0s     r    r!   z,_validate_normalize_axes.<locals>.<listcomp>   s    :::A4:::r"   )	r-   rB   listr*   filterrC   	enumeratezipformat)axesr;   keepdimsinput_coredimssoutput_coredimssninnoutfiltered_core_dimsnr_outputs_with_coredims	core_dimscd0r:   output_axes
input_axesidxiaxr@   oaxrG   icd0rJ   s    `                  @r    _validate_normalize_axesr`   ;   s1   , o

C/66Q11C@P<Q<QDD,L
 
 	
  :JtT** :8999+/!88'':J9KfS/::;;"#Q#Q2B#Q#Q#QRR K#a''MNNN/004Fq4I3JJ"22I$$$ 	KIJJJ 
	$Q'C3xx1}} O   )  "99$M    |CCCCCCCDDDD)DDDDDd## =;<<<<<t<<<D 
"Q	&	&CII4Z3t99$$#a''cDjCII.E.E`
 
 	

 stt*K 	FEE4DEEE 
 dsdJ %S_%E%EFF  Zc3s88s3xxNNTfO O      ;([:J)K)KLL 	 	OC#s3xx3s88## SSYSYT T   $	  	;"1%D&  3;;$Q    a=D::::)9:::K{""r"   F)rP   r;   rQ   output_dtypesoutput_sizes	vectorizeallow_rechunkmetac          
       >?@ABCDEF t          |t                    st          d          t          j        dd|          }t          |          \  }}t          |t                    sdnt          |          D|	|t          d          |	|/|rt          j
        | |          }n| }t          ||
|ddD          }D7t          |t          t          f          rt          |          d	k    r|d
         }|
r|
d
         ndEDt          E|          }	nt          Efd|D                       }	t          |	          }	t          |	t                    rt          |	          }	DAt          |	t                    r+t          |	          d	k    r	|	d
         }	nqt          d          nat          |	t                    st          dD d          t          |	          Dk    r&t          dD dD dt          |	           d          |r@t          |	t                    rd |	D             n|	j        g}t          j
        | ||          } |i }t          |||||          \  }}d |
D             }
t          |          t          |
          k    r.t          dt          |          t          |
          fz            g }t!          |
|          D ]\  }A|j        Ft          FfdAD                       At          Afdt%          t          F           d
z   d
          D                       Az   }|                    |          }|                    |           |}
d |
D             }d |
D             }d t!          ||          D             }|rt+          |          ndCd t!          |||          D             }t-          | ??                    |           Cfd|D             }d t!          ||          D             }|rt+          |t                    nt                      }i }i }t!          |||          D ]~\  }F} t!          |F|           D ]f\  }!}"}#|                    |!g           }$|$                    |"           |$||!<   |                    |!g           }%|%                    |#           |%||!<   g|                                D ]\  }!}&t5          |&          d	hz  d	t+          |&          hk    rt          d |! d!          |s||!         } |!?v r:| d
         d
         ?|!         k     r"t          d"                    |!                    t          t9          d# t!          |&|           D                                 }'t          |'          d	k    rt          d |! d$          t          t;          t!          |
|                              }(t=          | |g|(R d%|	d&|})|)j        }*D0t          |*t          t          f          rJ d'|	 d(|*             |*f}*n]t          |*t          t          f          sJ d)|	 d(|*             t          |*          Dk    sJ d*D d+t          |*           d,            |)j        }+|)j         },t          tC          |)"                                                    }-|-d
         d
         #                    d-          \  }.}/D|g}g }0tI          t!          |||*                    D ]\  @\  }1}2}	t          ?fd.|1D                       }3t          |1          d/z  >|+|3z   }4|,|3z   }5d0|.@|/fz  B>@BDfd1|-D             }6tK          j&        B|6|)g2          }7t          |	t          |4                    }	tO          |7B|5|4|	3          }8|rKt          |8j                  tQ          d          fz  t          |2          t          j)        fz  z   }9|8|9         }8dgt          |8j                  z  }:t!          t%          t          |2           d
          |2          D ]
\  };}<|;|:|<<   d
}=t%          t          |:                    D ]};|:|;         
|=|:|;<   |=d	z  }=|8                    |:          }8|0                    |8           Drg |0R n|0d
         S )4aC  
    Apply a generalized ufunc or similar python function to arrays.

    ``signature`` determines if the function consumes or produces core
    dimensions. The remaining dimensions in given input arrays (``*args``)
    are considered loop dimensions and are required to broadcast
    naturally against each other.

    In other terms, this function is like ``np.vectorize``, but for
    the blocks of dask arrays. If the function itself shall also
    be vectorized use ``vectorize=True`` for convenience.

    Parameters
    ----------
    func : callable
        Function to call like ``func(*args, **kwargs)`` on input arrays
        (``*args``) that returns an array or tuple of arrays. If multiple
        arguments with non-matching dimensions are supplied, this function is
        expected to vectorize (broadcast) over axes of positional arguments in
        the style of NumPy universal functions [1]_ (if this is not the case,
        set ``vectorize=True``). If this function returns multiple outputs,
        ``output_core_dims`` has to be set as well.
    signature: string
        Specifies what core dimensions are consumed and produced by ``func``.
        According to the specification of numpy.gufunc signature [2]_
    *args : numeric
        Input arrays or scalars to the callable function.
    axes: List of tuples, optional, keyword only
        A list of tuples with indices of axes a generalized ufunc should operate on.
        For instance, for a signature of ``"(i,j),(j,k)->(i,k)"`` appropriate for
        matrix multiplication, the base elements are two-dimensional matrices
        and these are taken to be stored in the two last axes of each argument. The
        corresponding axes keyword would be ``[(-2, -1), (-2, -1), (-2, -1)]``.
        For simplicity, for generalized ufuncs that operate on 1-dimensional arrays
        (vectors), a single integer is accepted instead of a single-element tuple,
        and for generalized ufuncs for which all outputs are scalars, the output
        tuples can be omitted.
    axis: int, optional, keyword only
        A single axis over which a generalized ufunc should operate. This is a short-cut
        for ufuncs that operate over a single, shared core dimension, equivalent to passing
        in axes with entries of (axis,) for each single-core-dimension argument and ``()`` for
        all others. For instance, for a signature ``"(i),(i)->()"``, it is equivalent to passing
        in ``axes=[(axis,), (axis,), ()]``.
    keepdims: bool, optional, keyword only
        If this is set to True, axes which are reduced over will be left in the result as
        a dimension with size one, so that the result will broadcast correctly against the
        inputs. This option can only be used for generalized ufuncs that operate on inputs
        that all have the same number of core dimensions and with outputs that have no core
        dimensions , i.e., with signatures like ``"(i),(i)->()"`` or ``"(m,m)->()"``.
        If used, the location of the dimensions in the output can be controlled with axes
        and axis.
    output_dtypes : Optional, dtype or list of dtypes, keyword only
        Valid numpy dtype specification or list thereof.
        If not given, a call of ``func`` with a small set of data
        is performed in order to try to automatically determine the
        output dtypes.
    output_sizes : dict, optional, keyword only
        Optional mapping from dimension names to sizes for outputs. Only used if
        new core dimensions (not found on inputs) appear on outputs.
    vectorize: bool, keyword only
        If set to ``True``, ``np.vectorize`` is applied to ``func`` for
        convenience. Defaults to ``False``.
    allow_rechunk: Optional, bool, keyword only
        Allows rechunking, otherwise chunk sizes need to match and core
        dimensions are to consist only of one chunk.
        Warning: enabling this can increase memory usage significantly.
        Defaults to ``False``.
    meta: Optional, tuple, keyword only
        tuple of empty ndarrays describing the shape and dtype of the output of the gufunc.
        Defaults to ``None``.
    **kwargs : dict
        Extra keyword arguments to pass to `func`

    Returns
    -------
    Single dask.array.Array or tuple of dask.array.Array

    Examples
    --------
    >>> import dask.array as da
    >>> import numpy as np
    >>> def stats(x):
    ...     return np.mean(x, axis=-1), np.std(x, axis=-1)
    >>> a = da.random.normal(size=(10,20,30), chunks=(5, 10, 30))
    >>> mean, std = da.apply_gufunc(stats, "(i)->(),()", a)
    >>> mean.compute().shape
    (10, 20)


    >>> def outer_product(x, y):
    ...     return np.einsum("i,j->ij", x, y)
    >>> a = da.random.normal(size=(   20,30), chunks=(10, 30))
    >>> b = da.random.normal(size=(10, 1,40), chunks=(5, 1, 40))
    >>> c = da.apply_gufunc(outer_product, "(i),(j)->(i,j)", a, b, vectorize=True)
    >>> c.compute().shape
    (10, 20, 30, 40)

    References
    ----------
    .. [1] https://docs.scipy.org/doc/numpy/reference/ufuncs.html
    .. [2] https://docs.scipy.org/doc/numpy/reference/c-api/generalized-ufuncs.html
    z$`signature` has to be of type stringr   r   NzMOnly one of `meta` and `output_dtypes` should be given (`meta` is preferred).r.   apply_gufuncra   r$   r   dtypec              3  :   K   | ]}t          |           V  dS )ri   Nr   )r   odtsamples     r    	<genexpr>zapply_gufunc.<locals>.<genexpr>L  s0      UUs;;;UUUUUUr"   zhFor a function with one output, must give a single item for `output_dtypes`/`meta`, not a tuple or list.zFor a function with zR outputs, must give a tuple or list for `output_dtypes`/`meta`, not a single item.z' outputs, must give a tuple or list of z' items for `output_dtypes`/`meta`, not .c                    g | ]	}|j         
S r   ri   r7   s     r    r!   z apply_gufunc.<locals>.<listcomp>k  s    (((a!'(((r"   )r.   otypesc                ,    g | ]}t          |          S r   )r	   rD   s     r    r!   z apply_gufunc.<locals>.<listcomp>y  s    %%%1GAJJ%%%r"   zDAccording to `signature`, `func` requires %d arguments, but %s givenc              3  L   K   | ]}|d k     r|n|t                    z
  V  dS )r   Nr6   )r   rE   shapes     r    rn   zapply_gufunc.<locals>.<genexpr>  s9      @@qQAAAE

N@@@@@@r"   c              3  $   K   | ]
}|v|V  d S Nr   )r   ir]   s     r    rn   zapply_gufunc.<locals>.<genexpr>  s'      JJ1Qc\\Q\\\\JJr"   c                    g | ]	}|j         
S r   )rt   rD   s     r    r!   z apply_gufunc.<locals>.<listcomp>  s    ***AG***r"   c                    g | ]	}|j         
S r   )chunksrD   s     r    r!   z apply_gufunc.<locals>.<listcomp>  s    ,,,!QX,,,r"   c                R    g | ]$\  }}t          |          t          |          z
  %S r   r6   )r   sr:   s      r    r!   z apply_gufunc.<locals>.<listcomp>  s-    UUUBCFFSWW$UUUr"   c           
     `    g | ]+\  }}}t          t          |||d                              ,S rv   )dictrN   )r   r|   nr@   s       r    r!   z apply_gufunc.<locals>.<listcomp>  sF       Aq# 	Sae__  r"   c           	     d    g | ],}t          d  t          |z
            D                       -S )c              3      K   | ]	}d |z  V  
dS )z__loopdim%d__Nr   )r   ds     r    rn   z*apply_gufunc.<locals>.<listcomp>.<genexpr>  s'      QQao!QQQQQQr"   )r   r?   )r   r   max_loopdimss     r    r!   z apply_gufunc.<locals>.<listcomp>  sO        	QQ51A<+P+PQQQQQ  r"   c                    g | ]
\  }}||z   S r   r   )r   lcs      r    r!   z apply_gufunc.<locals>.<listcomp>  s     LLLTQ1q5LLLr"   )keyzDimension `'z#'` with different lengths in arrayszCore dimension `'{}'` consists of multiple chunks. To fix, rechunk into a single chunk along this dimension or set `allow_rechunk=True`, but beware that this may increase memory usage significantly.c              3  ,   K   | ]\  }}|d k    |V  dS r$   Nr   )r   r|   r   s      r    rn   zapply_gufunc.<locals>.<genexpr>  s*      EETQq1uuquuuuEEr"   z#'` with different chunksize presentT)concatenatere   zEmeta changed from single output to multiple output during blockwise: z -> zEmeta changed from multiple output to single output during blockwise: zNumber of outputs changed from z to z during blockwise-c              3  (   K   | ]}|         V  d S rv   r   )r   r   core_shapess     r    rn   zapply_gufunc.<locals>.<genexpr>  s'      !>!>Q+a.!>!>!>!>!>!>r"   r=   z%s_%d-%sc                L    i | ] }f|d d         z   z   r
t           |fn|!S r   )r   )r   r   core_chunkindsrw   	leaf_namerU   s     r    
<dictcomp>z apply_gufunc.<locals>.<dictcomp>  sY     
 
 
  L!""g48Aa00c
 
 
r"   )dependencies)rz   rt   re   )*rB   str	TypeErrorr   r'   r3   rK   r-   r*   nprc   r   r   r   rj   r`   rN   rt   r?   	transposeappendmaxr   updategetitemssetrO   r   r   r
   _metarz   r   __dask_keys__r+   rM   r   from_collectionsr   slicenewaxis)Gfuncr.   rP   r;   rQ   ra   rb   rc   rd   re   argskwargsrR   rS   tempfuncrq   r[   rZ   transposed_argsr   tidctransposed_arginput_shapesinput_chunkssnum_loopdimscore_input_shapesloop_input_dimssinput_dimssloop_output_dims	dimsizesschunksizessdims
chunksizesdimsize	chunksizedimsizeschunksizes_sizesrelevant_chunksizesargindstmpmetasloop_output_shapeloop_output_chunkskeysnametoken	leaf_arrsrG   r^   core_output_shapeoutput_shapeoutput_chunksleaf_dskgraphleaf_arrslicestidcsiioajr   r   rw   r]   r   r   rU   rm   rt   sG                                                                 @@@@@@@@@r    rh   rh      s
   l i%% @>??? vr9--I(?	(J(J%O% ""2D99T44sCS?T?TD M5[
 
 	
 |   <	BBB-$ M L=5$-88 M""a'')!,M *ad<"6???DDUUUU}UUUUUD 4  D$ T{{ |dE"" 	4yyA~~Aw +  		 $&& 	%t % % %   t99<t < <TX < </24yy< < <    F,6tU,C,CU((4(((($*|DIfEEE  7dHo/? J &%%%%D
?s4yy((R?##SYY/0
 
 	
 Oj)) / /S	@@@@C@@@@@JJJJs5zzkAoq 9 9JJJJJSPt,,~....D +*T***L,,t,,,MUU#lO2T2TUUUL(4>3|$$$$L \<II   *+K|$$$      MLS)9?%K%KLLLK9IVs+5555uww IK#&{L-#P#P + +eZ$'eZ$@$@ 	+ 	+ Cy }}S"--HOOD!!!%IcN%//#r22Ky)))*K	+  oo''  
Uu::3u::..TCTTTUUU 	$S)J{""Aq)9K<L)L)L     #'EES
%;%;EEEEE# # &''!++ K3KKK  
 6#dK001122G
 !( 6: IO C IE|D%=
 
 	e 	edSWdd]bdd	e 	e 	e D%=
 
 	e 	edSWdd]bdd	e 	e 	e JJ$TTTTs5zzTTT  	))++,,--Dq'!*""3''KD% |,- I(-={E)R)RSS  #  #Cd!!>!>!>!>#!>!>!>>>SD(+<<*->>$5!11	
 
 
 
 
 
 
 	
 
 
 /	8SVRWXXXtS%6%6779],T
 
 

  	(((E$KK>9CHH
}<TTF'HX^,,,%S	1--s33 	 	FBE"IIE

## 	 	BRy b	Q%%e,,"""" 2=Y===il2r"   c            
      :    e Zd ZdZdddddddddd	dZdddZdS )gufunca  
    Binds `pyfunc` into ``dask.array.apply_gufunc`` when called.

    Parameters
    ----------
    pyfunc : callable
        Function to call like ``func(*args, **kwargs)`` on input arrays
        (``*args``) that returns an array or tuple of arrays. If multiple
        arguments with non-matching dimensions are supplied, this function is
        expected to vectorize (broadcast) over axes of positional arguments in
        the style of NumPy universal functions [1]_ (if this is not the case,
        set ``vectorize=True``). If this function returns multiple outputs,
        ``output_core_dims`` has to be set as well.
    signature : String, keyword only
        Specifies what core dimensions are consumed and produced by ``func``.
        According to the specification of numpy.gufunc signature [2]_
    axes: List of tuples, optional, keyword only
        A list of tuples with indices of axes a generalized ufunc should operate on.
        For instance, for a signature of ``"(i,j),(j,k)->(i,k)"`` appropriate for
        matrix multiplication, the base elements are two-dimensional matrices
        and these are taken to be stored in the two last axes of each argument. The
        corresponding axes keyword would be ``[(-2, -1), (-2, -1), (-2, -1)]``.
        For simplicity, for generalized ufuncs that operate on 1-dimensional arrays
        (vectors), a single integer is accepted instead of a single-element tuple,
        and for generalized ufuncs for which all outputs are scalars, the output
        tuples can be omitted.
    axis: int, optional, keyword only
        A single axis over which a generalized ufunc should operate. This is a short-cut
        for ufuncs that operate over a single, shared core dimension, equivalent to passing
        in axes with entries of (axis,) for each single-core-dimension argument and ``()`` for
        all others. For instance, for a signature ``"(i),(i)->()"``, it is equivalent to passing
        in ``axes=[(axis,), (axis,), ()]``.
    keepdims: bool, optional, keyword only
        If this is set to True, axes which are reduced over will be left in the result as
        a dimension with size one, so that the result will broadcast correctly against the
        inputs. This option can only be used for generalized ufuncs that operate on inputs
        that all have the same number of core dimensions and with outputs that have no core
        dimensions , i.e., with signatures like ``"(i),(i)->()"`` or ``"(m,m)->()"``.
        If used, the location of the dimensions in the output can be controlled with axes
        and axis.
    output_dtypes : Optional, dtype or list of dtypes, keyword only
        Valid numpy dtype specification or list thereof.
        If not given, a call of ``func`` with a small set of data
        is performed in order to try to automatically determine the
        output dtypes.
    output_sizes : dict, optional, keyword only
        Optional mapping from dimension names to sizes for outputs. Only used if
        new core dimensions (not found on inputs) appear on outputs.
    vectorize: bool, keyword only
        If set to ``True``, ``np.vectorize`` is applied to ``func`` for
        convenience. Defaults to ``False``.
    allow_rechunk: Optional, bool, keyword only
        Allows rechunking, otherwise chunk sizes need to match and core
        dimensions are to consist only of one chunk.
        Warning: enabling this can increase memory usage significantly.
        Defaults to ``False``.
    meta: Optional, tuple, keyword only
        tuple of empty ndarrays describing the shape and dtype of the output of the gufunc.
        Defaults to ``None``.

    Returns
    -------
    Wrapped function

    Examples
    --------
    >>> import dask.array as da
    >>> import numpy as np
    >>> a = da.random.normal(size=(10,20,30), chunks=(5, 10, 30))
    >>> def stats(x):
    ...     return np.mean(x, axis=-1), np.std(x, axis=-1)
    >>> gustats = da.gufunc(stats, signature="(i)->(),()", output_dtypes=(float, float))
    >>> mean, std = gustats(a)
    >>> mean.compute().shape
    (10, 20)

    >>> a = da.random.normal(size=(   20,30), chunks=(10, 30))
    >>> b = da.random.normal(size=(10, 1,40), chunks=(5, 1, 40))
    >>> def outer_product(x, y):
    ...     return np.einsum("i,j->ij", x, y)
    >>> guouter_product = da.gufunc(outer_product, signature="(i),(j)->(i,j)", output_dtypes=float, vectorize=True)
    >>> c = guouter_product(a, b)
    >>> c.compute().shape
    (10, 20, 30, 40)

    >>> a = da.ones((1, 5, 10), chunks=(-1, -1, -1))
    >>> def stats(x):
    ...     return np.atleast_1d(x.mean()), np.atleast_1d(x.max())
    >>> meta = (np.array((), dtype=np.float64), np.array((), dtype=np.float64))
    >>> gustats = da.gufunc(stats, signature="(i,j)->(),()", meta=meta)
    >>> result = gustats(a)
    >>> result[0].compute().shape
    (1,)
    >>> result[1].compute().shape
    (1,)

    References
    ----------
    .. [1] https://docs.scipy.org/doc/numpy/reference/ufuncs.html
    .. [2] https://docs.scipy.org/doc/numpy/reference/c-api/generalized-ufuncs.html
    NF)	r.   rc   rP   r;   rQ   rb   ra   rd   re   c       	            || _         || _        || _        || _        || _        || _        || _        || _        |	| _        |
| _	        d
                    t          | j                   | j                  | _        d S )Na  
        Bound ``dask.array.gufunc``
        func: ``{func}``
        signature: ``'{signature}'``

        Parameters
        ----------
        *args : numpy/dask arrays or scalars
            Arrays to which to apply to ``func``. Core dimensions as specified in
            ``signature`` need to come last.
        **kwargs : dict
            Extra keyword arguments to pass to ``func``

        Returns
        -------
        Single dask.array.Array or tuple of dask.array.Array
        )r   r.   )pyfuncr.   rc   rP   r;   rQ   rb   ra   rd   re   rO   r   __doc__)selfr   r.   rc   rP   r;   rQ   rb   ra   rd   re   s              r    __init__zgufunc.__init__v  s     ""		 (**	  FT[!!T^  
 
! 	r"   )rd   c                   t          | j        | j        g|R | j        | j        | j        | j        | j        | j        | j	        p|| j
        d|S )N)rc   rP   r;   rQ   rb   ra   rd   re   )rh   r   r.   rc   rP   r;   rQ   rb   ra   rd   re   )r   rd   r   r   s       r    __call__zgufunc.__call__  ss    KN
 
 
 n]*,,=
 
 
 
 	
r"   )__name__
__module____qualname__r   r   r   r   r"   r    r   r     sv        d dT +
 +
 +
 +
 +
Z -2 
 
 
 
 
 
 
r"   r   c                     h d}                                 |z
  rt          d           fd}d                               |_        |S )a  
    Decorator for ``dask.array.gufunc``.

    Parameters
    ----------
    signature : String
        Specifies what core dimensions are consumed and produced by ``func``.
        According to the specification of numpy.gufunc signature [2]_
    axes: List of tuples, optional, keyword only
        A list of tuples with indices of axes a generalized ufunc should operate on.
        For instance, for a signature of ``"(i,j),(j,k)->(i,k)"`` appropriate for
        matrix multiplication, the base elements are two-dimensional matrices
        and these are taken to be stored in the two last axes of each argument. The
        corresponding axes keyword would be ``[(-2, -1), (-2, -1), (-2, -1)]``.
        For simplicity, for generalized ufuncs that operate on 1-dimensional arrays
        (vectors), a single integer is accepted instead of a single-element tuple,
        and for generalized ufuncs for which all outputs are scalars, the output
        tuples can be omitted.
    axis: int, optional, keyword only
        A single axis over which a generalized ufunc should operate. This is a short-cut
        for ufuncs that operate over a single, shared core dimension, equivalent to passing
        in axes with entries of (axis,) for each single-core-dimension argument and ``()`` for
        all others. For instance, for a signature ``"(i),(i)->()"``, it is equivalent to passing
        in ``axes=[(axis,), (axis,), ()]``.
    keepdims: bool, optional, keyword only
        If this is set to True, axes which are reduced over will be left in the result as
        a dimension with size one, so that the result will broadcast correctly against the
        inputs. This option can only be used for generalized ufuncs that operate on inputs
        that all have the same number of core dimensions and with outputs that have no core
        dimensions , i.e., with signatures like ``"(i),(i)->()"`` or ``"(m,m)->()"``.
        If used, the location of the dimensions in the output can be controlled with axes
        and axis.
    output_dtypes : Optional, dtype or list of dtypes, keyword only
        Valid numpy dtype specification or list thereof.
        If not given, a call of ``func`` with a small set of data
        is performed in order to try to automatically determine the
        output dtypes.
    output_sizes : dict, optional, keyword only
        Optional mapping from dimension names to sizes for outputs. Only used if
        new core dimensions (not found on inputs) appear on outputs.
    vectorize: bool, keyword only
        If set to ``True``, ``np.vectorize`` is applied to ``func`` for
        convenience. Defaults to ``False``.
    allow_rechunk: Optional, bool, keyword only
        Allows rechunking, otherwise chunk sizes need to match and core
        dimensions are to consist only of one chunk.
        Warning: enabling this can increase memory usage significantly.
        Defaults to ``False``.
    meta: Optional, tuple, keyword only
        tuple of empty ndarrays describing the shape and dtype of the output of the gufunc.
        Defaults to ``None``.

    Returns
    -------
    Decorator for `pyfunc` that itself returns a `gufunc`.

    Examples
    --------
    >>> import dask.array as da
    >>> import numpy as np
    >>> a = da.random.normal(size=(10,20,30), chunks=(5, 10, 30))
    >>> @da.as_gufunc("(i)->(),()", output_dtypes=(float, float))
    ... def stats(x):
    ...     return np.mean(x, axis=-1), np.std(x, axis=-1)
    >>> mean, std = stats(a)
    >>> mean.compute().shape
    (10, 20)

    >>> a = da.random.normal(size=(   20,30), chunks=(10, 30))
    >>> b = da.random.normal(size=(10, 1,40), chunks=(5, 1, 40))
    >>> @da.as_gufunc("(i),(j)->(i,j)", output_dtypes=float, vectorize=True)
    ... def outer_product(x, y):
    ...     return np.einsum("i,j->ij", x, y)
    >>> c = outer_product(a, b)
    >>> c.compute().shape
    (10, 20, 30, 40)

    References
    ----------
    .. [1] https://docs.scipy.org/doc/numpy/reference/ufuncs.html
    .. [2] https://docs.scipy.org/doc/numpy/reference/c-api/generalized-ufuncs.html
    >   rP   r;   re   rQ   rc   rb   rd   ra   z(Unsupported keyword argument(s) providedc                "    t          | fdiS )Nr.   )r   )r   r   r.   s    r    
_as_gufunczas_gufunc.<locals>._as_gufunc  s    f<<	<V<<<r"   a  
        Decorator to make ``dask.array.gufunc``
        signature: ``'{signature}'``

        Parameters
        ----------
        pyfunc : callable
            Function matching signature ``'{signature}'``.

        Returns
        -------
        ``dask.array.gufunc``
        rg   )r   r   rO   r   )r.   r   _allowedkeysr   s   ``  r    	as_gufuncr     s    f	 	 	L {{}}|# DBCCC= = = = = = F     r"   rv   )!
__future__r   r   numpyr   tlzr   r   r   dask.array.corer   r   r	   r
   r   dask.array.utilsr   	dask.corer   dask.highlevelgraphr   r   rO   _CORE_DIMENSION_LISTr,   _INPUT_ARGUMENTS_OUTPUT_ARGUMENTSr)   r3   r`   rh   r   r   r   r"   r    <module>r      s   " " " " " " 				     % % % % % % % % % % Q Q Q Q Q Q Q Q Q Q Q Q Q Q , , , , , ,       . . . . . . .55oFF *&***	*11)<< $++   :!99%6999
  >r# r# r#r 
		\3 \3 \3 \3 \3~
b
 b
 b
 b
 b
 b
 b
 b
Jr r r r r rr"   