o
    bi                     @   s  d dl Z d dlZd dlmZ d dlmZ d dlmZ d dlm	Z	 d dlm
Z
 d dlmZ d dlmZ d d	lmZ d d
lmZ G dd deZeddd ZG dd deZeddsddZG dd deZeddtddZG dd deZeddd  ZG d!d" d"eZed#d$d% ZG d&d' d'eZed(d)d* ZG d+d, d,eZed-d.d/ Z G d0d1 d1eZ!ed2d3d4 Z"G d5d6 d6eZ#ed7	dud8d9Z$G d:d; d;eZ%ed<d=d> Z&G d?d@ d@eZ'edAdBdC Z(G dDdE dEeZ)edFdvdGdHZ*edIdJdK Z+edLdMdN Z,G dOdP dPeZ-edQdRdS Z.G dTdU dUeZ/edVdWdX Z0dudYdZZ1G d[d\ d\eZ2ed]dwd^d_Z3ed`dadb Z4G dcdd ddeZ5ededfdg Z6G dhdi dieZ7edjdkdl Z8edmdndo Z9edpdqdr Z:dS )x    N)backend)tree)keras_export)KerasTensor)any_symbolic_tensors)slice_along_axis)	Operation)serialization_lib)traceback_utilsc                   @      e Zd Zdd Zdd ZdS )Mapc                 C   s   t j||S N)r   coremap)selffxs r   F/home/ubuntu/.local/lib/python3.10/site-packages/keras/src/ops/core.pycall   s   zMap.callc                    sL   t dd |}t |d jd  t||} fdd}t ||}|S )Nc                 S      | d S Nr   r   tr   r   r   <lambda>       z)Map.compute_output_spec.<locals>.<lambda>r   c                       t  f| j | j| j| jdS Nshapedtypesparseraggedr   r   r    r!   r"   r   nr   r   append_batch_axis      
z2Map.compute_output_spec.<locals>.append_batch_axis)r   map_structureflattenr   r   compute_output_spec)r   r   r   xyr&   r   r$   r   r*      s   zMap.compute_output_specN__name__
__module____qualname__r   r*   r   r   r   r   r          r   zkeras.ops.mapc                 C   s&   t |frt | |S tj| |S )u  Map a function over leading array axes.

    Like Python’s builtin map, except inputs and outputs are in the form of
    stacked arrays. Consider using the `vectorized_map()` transform instead,
    unless you need to apply a function element by element for reduced memory
    usage or heterogeneous computation with other control flow primitives.

    When `xs` is an array type, the semantics of `map()` are given by this
    Python implementation:

    ```python
    def map(f, xs):
        return np.stack([f(x) for x in xs])
    ```

    Args:
        f: Callable defines the function to apply element-wise over the first
            axis or axes of `xs`.
        xs: Values over which to map along the leading axis.

    Returns:
        Mapped values.

    Examples:

    >>> f = lambda x: x**2
    >>> xs = keras.ops.arange(10)
    >>> ys = keras.ops.map(f, xs)
    >>> ys
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

    >>> f = lambda x: {"y1": x**2, "y2": x * 10}  # Can have nested outputs
    >>> ys = keras.ops.map(f, xs)
    >>> ys["y1"]
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> ys["y2"]
    [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
    )r   r   symbolic_callr   r   r   )r   r   r   r   r   r   $   s   
(r   c                       s8   e Zd Zddd fddZdddZdd	d
Z  ZS )ScanNF   namec                   $   t  j|d || _|| _|| _d S Nr5   )super__init__lengthreverseunroll)r   r;   r<   r=   r6   	__class__r   r   r:   R      
zScan.__init__c                 C   s   t jj|||| j| j| jdS )Nr;   r<   r=   )r   r   scanr;   r<   r=   )r   r   initr   r   r   r   r   X   s   z	Scan.callc                 C   s|   |d u rt | j}d }n| jd urt | jn	t|d jd }|d }t|||\}}t|f|j |j|j	d}||fS Nr   )r   r    r!   )
intr;   r   r)   r   r   r*   r   r    r!   )r   r   rC   r   r%   r+   carryr,   r   r   r   r*   b   s   


zScan.compute_output_spec)NFr4   r   r.   r/   r0   r:   r   r*   __classcell__r   r   r>   r   r3   Q   s    

r3   zkeras.ops.scanFr4   c                 C   s<   t ||frt|||d| ||S tjj| |||||dS )a  Scan a function over leading array axes while carrying along state.

    When the type of `xs` is an array type or `None`, and the type of `ys` is an
    array type, the semantics of `scan()` are given roughly by this Python
    implementation:

    ```python
    def scan(f, init, xs, length=None):
        if xs is None:
            xs = [None] * length
        carry = init
        ys = []
        for x in xs:
            carry, y = f(carry, x)
            ys.append(y)
        return carry, np.stack(ys)
    ```

    The loop-carried value `carry` (`init`) must hold a fixed shape and dtype
    across all iterations.

    In TensorFlow, `y` must match `carry` in shape and dtype. This is not
    required in other backends.

    Args:
        f: Callable defines the logic for each loop iteration. This accepts two
            arguments where the first is a value of the loop carry and the
            second is a slice of `xs` along its leading axis.
            This callable returns a pair where the first represents a new value
            for the loop carry and the second represents a slice of the output.
        init: The initial loop carry value. This can be a scalar, tensor, or any
            nested structure. It must match the structure of the first element
            returned by `f`.
        xs: Optional value to scan along its leading axis. This can be a tensor
            or any nested structure. If `xs` is not provided, you must specify
            `length` to define the number of loop iterations.
            Defaults to `None`.
        length: Optional integer specifying the number of loop iterations.
            If `length` is not provided, it defaults to the sizes of leading
            axis of the arrays in `xs`. Defaults to `None`.
        reverse: Optional boolean specifying whether to run the scan iteration
            forward or in reverse, equivalent to reversing the leading axes of
            the arrays in both `xs` and in `ys`.
        unroll: Optional positive integer or boolean specifying how many scan
            iterations to unroll within a single iteration of a loop. If an
            integer is provided, it determines how many unrolled loop iterations
            to run within a single rolled iteration of the loop. If a boolean is
            provided, it will determine if the loop is completely unrolled
            (`unroll=True`) or left completely unrolled (`unroll=False`).
            Note that unrolling is only supported by JAX and TensorFlow
            backends.

    Returns:
        A pair where the first element represents the final loop carry value and
        the second element represents the stacked outputs of `f` when scanned
        over the leading axis of the inputs.

    Examples:

    >>> sum_fn = lambda c, x: (c + x, c + x)
    >>> init = keras.ops.array(0)
    >>> xs = keras.ops.array([1, 2, 3, 4, 5])
    >>> carry, result = keras.ops.scan(sum_fn, init, xs)
    >>> carry
    15
    >>> result
    [1, 3, 6, 10, 15]
    rA   )r<   r=   )r   r3   r2   r   r   rB   )r   rC   r   r;   r<   r=   r   r   r   rB   s   s   F
rB   c                       s4   e Zd Zddd fddZdd Zd	d
 Z  ZS )AssociativeScanFr   Nr5   c                      t  j|d || _|| _d S r8   )r9   r:   r<   axis)r   r<   rK   r6   r>   r   r   r:         
zAssociativeScan.__init__c                 C   s   t jj||| j| jdS )Nr<   rK   )r   r   associative_scanr<   rK   )r   r   elemsr   r   r   r      s   zAssociativeScan.callc                    s   t | fdd D }tt|dkr"tddd  D t |fdd D }t|||} fdd}t 	||}|S )	Nc                    s   g | ]}|j  j qS r   )r   rK   .0elemr   r   r   
<listcomp>   s    z7AssociativeScan.compute_output_spec.<locals>.<listcomp>r4   zNArray inputs to associative_scan must have the same first dimension. (saw: {})c                 S   s   g | ]}|j qS r   r   rP   r   r   r   rT      s    c                    s   g | ]}t |d d jdqS )r   r4   )rK   )r   rK   )rQ   r+   rS   r   r   rT      s    c                    s   t  d j| j| jdS rD   )r   r   r    r!   r+   )
elems_flatr   r   _restore_shape   s   z;AssociativeScan.compute_output_spec.<locals>._restore_shape)
r   r)   lenset
ValueErrorformatpack_sequence_asr   r*   r(   )r   r   rO   lensr+   y_specrX   r   )rW   r   r   r*      s"   
z#AssociativeScan.compute_output_specFr   rG   r   r   r>   r   rI      s    rI   zkeras.ops.associative_scanc                 C   s2   t |frt||d| |S tjj| |||dS )aL	  Performs a scan with an associative binary operation, in parallel.

    This operation his similar to `scan`, with the key difference that
    `associative_scan` is a parallel implementation with
    potentially significant performance benefits, especially when jit compiled.
    The catch is that it can only be used when `f` is a binary associative
    operation (i.e. it must verify `f(a, f(b, c)) == f(f(a, b), c)`).

    For an introduction to associative scans, refer to this paper:
    Blelloch, Guy E. 1990.
    [Prefix Sums and Their Applications](
        https://www.cs.cmu.edu/~guyb/papers/Ble93.pdf).

    Args:
        f: A Python callable implementing an associative binary operation with
            signature `r = f(a, b)`. Function `f` must be associative, i.e.,
            it must satisfy the equation
            `f(a, f(b, c)) == f(f(a, b), c)`.
            The inputs and result are (possibly nested Python tree structures
            of) array(s) matching `elems`. Each array has a dimension in place
            of the `axis` dimension. `f` should be applied elementwise over
            the `axis` dimension.
            The result `r` has the same shape (and structure) as the
            two inputs `a` and `b`.
        elems: A (possibly nested Python tree structure of) array(s), each with
            an `axis` dimension of size `num_elems`.
        reverse: A boolean stating if the scan should be reversed with respect
            to the `axis` dimension.
        axis: an integer identifying the axis over which the scan should occur.

    Returns:
        A (possibly nested Python tree structure of) array(s) of the same shape
        and structure as `elems`, in which the `k`'th element of `axis` is
        the result of recursively applying `f` to combine the first `k`
        elements of `elems` along `axis`. For example, given
        `elems = [a, b, c, ...]`, the result would be
        `[a, f(a, b), f(f(a, b), c), ...]`.

    Examples:

    >>> sum_fn = lambda x, y: x + y
    >>> xs = keras.ops.arange(5)
    >>> ys = keras.ops.associative_scan(sum_fn, xs, axis=0)
    >>> ys
    [0, 1, 3, 6, 10]

    >>> sum_fn = lambda x, y: [x[0] + y[0], x[1] + y[1], x[2] + y[2]]
    >>> xs = [keras.ops.array([[1, 2]]) for _ in range(3)]
    >>> ys = keras.ops.associative_scan(sum_fn, xs, axis=0)
    >>> ys
    [[1, 3], [1, 3], [1, 3]]
    rM   )r   rI   r2   r   r   rN   )r   rO   r<   rK   r   r   r   rN      s
   
6rN   c                       2   e Zd Zdd fdd
Zdd Zdd Z  ZS )	ScatterNr5   c                      t  j|d || _d S r8   r9   r:   r   r   r   r6   r>   r   r   r:   %     
zScatter.__init__c                 C      t j||| jS r   )r   r   scatterr   r   indicesvaluesr   r   r   r   )     zScatter.callc                 C   s   t | j|jdS Nr    r   r   r    ri   r   r   r   r*   ,     zScatter.compute_output_specrG   r   r   r>   r   rb   $      rb   zkeras.ops.scatterc                 C   .   t | |frt|d| |S tj| ||S )a  Returns a tensor of shape `shape` where `indices` are set to `values`.

    At a high level, this operation does `zeros[indices] = updates` and
    returns the output. It is equivalent to:

    ```python
    zeros = keras.ops.zeros(shape)
    output = keras.ops.scatter_update(zeros, indices, values)
    ```

    Args:
        indices: A tensor or list/tuple specifying
            indices for the values in `values`.
        values: A tensor, the values to be set at `indices`.
        shape: Shape of the output tensor.

    Example:

    >>> indices = [[0, 1], [1, 1]]
    >>> values = np.array([1., 1.])
    >>> keras.ops.scatter(indices, values, shape=(2, 2))
    array([[0., 1.],
           [0., 1.]])
    rU   )r   rb   r2   r   r   rh   )rj   rk   r   r   r   r   rh   0     rh   c                   @   r   )ScatterUpdatec                 C      t j|||S r   )r   r   scatter_updater   inputsrj   updatesr   r   r   r   P  rp   zScatterUpdate.callc                 C      t |j|jdS rm   ro   rw   r   r   r   r*   S  rp   z!ScatterUpdate.compute_output_specNr-   r   r   r   r   rt   O  r1   rt   zkeras.ops.scatter_updatec                 C   .   t | ||frt | ||S tj| ||S )a  Update inputs via updates at scattered (sparse) indices.

    At a high level, this operation does `inputs[indices] = updates`.
    Assume `inputs` is a tensor of shape `(D0, D1, ..., Dn)`, there are 2 main
    usages of `scatter_update`.

    1. `indices` is a 2D tensor of shape `(num_updates, n)`, where `num_updates`
        is the number of updates to perform, and `updates` is a 1D tensor of
        shape `(num_updates,)`. For example, if `inputs` is `zeros((4, 4, 4))`,
        and we want to update `inputs[1, 2, 3]` and `inputs[0, 1, 3]` as 1, then
        we can use:

    ```python
    inputs = np.zeros((4, 4, 4))
    indices = [[1, 2, 3], [0, 1, 3]]
    updates = np.array([1., 1.])
    inputs = keras.ops.scatter_update(inputs, indices, updates)
    ```

    2 `indices` is a 2D tensor of shape `(num_updates, k)`, where `num_updates`
        is the number of updates to perform, and `k` (`k < n`) is the size of
        each index in `indices`. `updates` is a `n - k`-D tensor of shape
        `(num_updates, inputs.shape[k:])`. For example, if
        `inputs = np.zeros((4, 4, 4))`, and we want to update `inputs[1, 2, :]`
        and `inputs[2, 3, :]` as `[1, 1, 1, 1]`, then `indices` would have shape
        `(num_updates, 2)` (`k = 2`), and `updates` would have shape
        `(num_updates, 4)` (`inputs.shape[2:] = 4`). See the code below:

    ```python
    inputs = np.zeros((4, 4, 4))
    indices = [[1, 2], [2, 3]]
    updates = np.array([[1., 1., 1, 1,], [1., 1., 1, 1,])
    inputs = keras.ops.scatter_update(inputs, indices, updates)
    ```

    Args:
        inputs: A tensor, the tensor to be updated.
        indices: A tensor or list/tuple of shape `(N, inputs.ndim)`, specifying
            indices to update. `N` is the number of indices to update, must be
            equal to the first dimension of `updates`.
        updates: A tensor, the new values to be put to `inputs` at `indices`.

    Returns:
        A tensor, has the same shape and dtype as `inputs`.
    )r   rt   r2   r   r   rv   )rx   rj   ry   r   r   r   rv   W  s   /rv   c                       ra   )	SliceNr5   c                   rc   r8   rd   re   r>   r   r   r:     rf   zSlice.__init__c                 C   rg   r   )r   r   slicer   )r   rx   start_indicesr   r   r   r     rl   z
Slice.callc                    sR   t dd | jD rttrtdt fddt| jD }t| jdS )Nc                 s   s    | ]}|d kV  qdS Nr   )rQ   sr   r   r   	<genexpr>  s    z,Slice.compute_output_spec.<locals>.<genexpr>zGWhen using -1 in `shape`, `start_indices` should not be a KerasTensor. c                 3   s2    | ]\}}|d kr j | |  n|V  qdS r   rU   )rQ   ir   rx   r~   r   r   r     s
    
rn   )anyr   
isinstancer   r[   tuple	enumerater    )r   rx   r~   final_shaper   r   r   r*     s   zSlice.compute_output_specrG   r   r   r>   r   r|     rq   r|   zkeras.ops.slicec                 C   rr   )aT  Return a slice of an input tensor.

    At a high level, this operation is an explicit replacement for array slicing
    e.g. `inputs[start_indices: start_indices + shape]`.
    Unlike slicing via brackets, this operation will accept tensor start
    indices on all backends, which is useful when indices dynamically computed
    via other tensor operations.

    ```python
    inputs = np.zeros((5, 5))
    start_indices = np.array([3, 3])
    shape = np.array([2, 2])
    inputs = keras.ops.slice(inputs, start_indices, shape)
    ```

    Args:
        inputs: A tensor, the tensor to be updated.
        start_indices: A list/tuple of shape `(inputs.ndim,)`, specifying
            the starting indices for updating.
        shape: The full shape of the returned slice.

    Returns:
        A tensor, has the same shape and dtype as `inputs`.
    rU   )r   r|   r2   r   r   r}   )rx   r~   r   r   r   r   r}     rs   r}   c                   @   r   )SliceUpdatec                 C   ru   r   )r   r   slice_updater   rx   r~   ry   r   r   r   r     rp   zSliceUpdate.callc                 C   rz   rm   ro   r   r   r   r   r*     rp   zSliceUpdate.compute_output_specNr-   r   r   r   r   r     r1   r   zkeras.ops.slice_updatec                 C   r{   )a  Update an input by slicing in a tensor of updated values.

    At a high level, this operation does
    `inputs[start_indices: start_indices + updates.shape] = updates`.
    Assume inputs is a tensor of shape `(D0, D1, ..., Dn)`,
    `start_indices` must be a list/tuple of n integers, specifying the starting
    indices. `updates` must have the same rank as `inputs`, and the size of each
    dim must not exceed `Di - start_indices[i]`. For example, if we have 2D
    inputs `inputs = np.zeros((5, 5))`, and we want to update the intersection
    of last 2 rows and last 2 columns as 1, i.e.,
    `inputs[3:, 3:] = np.ones((2, 2))`, then we can use the code below:

    ```python
    inputs = np.zeros((5, 5))
    start_indices = [3, 3]
    updates = np.ones((2, 2))
    inputs = keras.ops.slice_update(inputs, start_indices, updates)
    ```

    Args:
        inputs: A tensor, the tensor to be updated.
        start_indices: A list/tuple of shape `(inputs.ndim,)`, specifying
            the starting indices for updating.
        updates: A tensor, the new values to be put to `inputs` at `indices`.
            `updates` must have the same rank as `inputs`.

    Returns:
        A tensor, has the same shape and dtype as `inputs`.
    )r   r   r2   r   r   r   )rx   r~   ry   r   r   r   r     s   r   c                   @   r   )Switchc                 G   s   t jj||g|R  S r   )r   r   switch)r   indexbranchesoperandsr   r   r   r     s   zSwitch.callc                 G   s   t j|d g|R  }|S r   )r   r*   )r   r   r   r   specr   r   r   r*     s   zSwitch.compute_output_specNr-   r   r   r   r   r     r1   r   zkeras.ops.switchc                 G   s4   t |rt j| |g|R  S tjj| |g|R  S )a  Apply exactly one of the `branches` given by `index`.

    If `index` is out of bounds, it is clamped to within bounds.

    The semantics of `switch` are given roughly by this Python implementation:

    ```python
    def switch(index, branches, *operands):
        index = clamp(0, index, len(branches) - 1)
        return branches[index](*operands)
    ```

    Args:
        index: An integer scalar indicating which branch function to apply.
        branches: A sequence of functions to be applied based on `index`.
        operands: Inputs to whichever branch is applied.

    Returns:
        The outputs of `branch(*operands)` for the branch that was selected
        based on `index`.

    Examples:

    >>> add_fn = lambda x, y: x + y
    >>> subtract_fn = lambda x, y: x - y
    >>> x = keras.ops.array(2.0)
    >>> y = keras.ops.array(0.5)
    >>> branches = [add_fn, subtract_fn]
    >>> keras.ops.switch(0, branches, x, y)
    2.5

    >>> keras.ops.switch(1, branches, x, y)
    1.5
    )r   r   r2   r   r   r   )r   r   r   r   r   r   r     s   $r   c                       4   e Zd Zd	dd fddZdd Zdd Z  ZS )
	WhileLoopNr5   c                   r7   r8   )r9   r:   condbodymaximum_iterations)r   r   r   r   r6   r>   r   r   r:   #  r@   zWhileLoop.__init__c                 C   s   t jj| j| j|| jdS )Nr   )r   r   
while_loopr   r   r   r   	loop_varsr   r   r   r   )  s   zWhileLoop.callc                 C   s   t dd |S )Nc                 S   s   t | j| jdS rm   ro   )vr   r   r   r   3  s    z/WhileLoop.compute_output_spec.<locals>.<lambda>)r   r(   r   r   r   r   r*   1  s   zWhileLoop.compute_output_specr   rG   r   r   r>   r   r   "  s    r   zkeras.ops.while_loopc                 C   s2   t |frt| ||d|S tjj| |||dS )a  While loop implementation.

    Args:
        cond: A callable that represents the termination condition of the loop.
            Must accept a `loop_vars` like structure as an argument. If
            `loop_vars` is a tuple or list, each element of `loop_vars` will be
            passed positionally to the callable.
        body: A callable that represents the loop body. Must accept a
            `loop_vars` like structure as an argument, and return update value
            with the same structure. If `loop_vars` is a tuple or list, each
            element of `loop_vars` will be passed positionally to the callable.
        loop_vars: An arbitrary nested structure of tensor state to persist
            across loop iterations.
        maximum_iterations: Optional maximum number of iterations of the while
            loop to run. If provided, the `cond` output is AND-ed with an
            additional condition ensuring the number of iterations executed is
            no greater than `maximum_iterations`.

    Returns:
        A list/tuple of tensors, has the same shape and dtype as `inputs`.

    Examples:

    >>> i = 0
    >>> cond = lambda i: i < 10
    >>> body = lambda i: i + 1
    >>> keras.ops.while_loop(cond, body, i)
    10

    >>> x, y = 0, 1
    >>> cond = lambda x, y: x < 10
    >>> body = lambda x, y: (x + 1, y + 1)
    >>> keras.ops.while_loop(cond, body, (x, y))
    10, 11
    r   )r   r   r2   r   r   r   )r   r   r   r   r   r   r   r   7  s   
*r   c                   @   r   )StopGradientc                 C   s   t j|S r   )r   r   stop_gradientr   variabler   r   r   r   n     zStopGradient.callc                 C   rz   rm   ro   r   r   r   r   r*   q  rp   z StopGradient.compute_output_specNr-   r   r   r   r   r   m  r1   r   zkeras.ops.stop_gradientc                 C   s"   t | frt | S tj| S )a  Stops gradient computation.

    Args:
        variable: A tensor variable for which the gradient
            computation is to be disabled.

    Returns:
        The variable with gradient computation disabled.

    Examples:

    >>> var = keras.backend.convert_to_tensor(
    ...     [1., 2., 3.],
    ...     dtype="float32"
    ... )
    >>> var = keras.ops.stop_gradient(var)
    )r   r   r2   r   r   r   )r   r   r   r   r   u  s   
r   c                       ra   )	ForiLoopNr5   c                   r7   r8   )r9   r:   lowerupperbody_fun)r   r   r   r   r6   r>   r   r   r:     r@   zForiLoop.__init__c                 C   s   t j| j| j| j|S r   )r   r   	fori_loopr   r   r   r   init_valr   r   r   r     s   zForiLoop.callc                 C   rz   rm   ro   r   r   r   r   r*     rp   zForiLoop.compute_output_specrG   r   r   r>   r   r     s    r   zkeras.ops.fori_loopc                 C   s2   t | ||frt| |||S tj| |||S )a  For loop implementation.

    Args:
        lower: The initial value of the loop variable.
        upper: The upper bound of the loop variable.
        body_fun: A callable that represents the loop body. Must take two
            arguments: the loop variable and the loop state. The loop state
            should be updated and returned by this function.
        init_val: The initial value of the loop state.

    Returns:
        The final state after the loop.

    Example:

    >>> lower = 0
    >>> upper = 10
    >>> body_fun = lambda i, s: (i + 1, s + i)
    >>> init_val = 0
    >>> keras.ops.fori_loop(lower, upper, body_fun, init_val)
    45
    )r   r   r2   r   r   r   )r   r   r   r   r   r   r   r     s   r   c                       s4   e Zd Zd
dd fddZdd Zdd	 Z  ZS )UnstackNr   r5   c                   rJ   r8   )r9   r:   numrK   )r   r   rK   r6   r>   r   r   r:     rL   zUnstack.__init__c                 C   s   t j|| j| jS r   )r   r   unstackr   rK   r   r+   r   r   r   r     s   zUnstack.callc                    s   | j }|dk rtj| }jd | j|d d    | j}|d u r*j| }|d u r7tdj d fddt|D }|S )Nr   r4   z'Cannot infer argument `num` from shape zn. Either provide a tensor with a concrete shape in the `axis` dimension or explicitly pass the `num` argument.c                    s   g | ]	}t  jd qS )r   r    )r   r    )rQ   _output_shapesr+   r   r   rT     s    z/Unstack.compute_output_spec.<locals>.<listcomp>)rK   rY   r   r   r[   range)r   r+   rK   r   outputr   r   r   r*     s"    
zUnstack.compute_output_specr   rG   r   r   r>   r   r     s    r   zkeras.ops.unstackc                 C   s,   t | frt||| S tjj| ||dS )a  Unpacks the given dimension of a rank-R tensor into rank-(R-1) tensors.

    Args:
        x: The input tensor.
        num: The length of the dimension axis. Automatically inferred
            if `None`.
        axis: The axis along which to unpack.

    Returns:
        A list of tensors unpacked along the given axis.

    Example:

    >>> x = keras.ops.array([[1, 2], [3, 4]])
    >>> keras.ops.unstack(x, axis=0)
    [array([1, 2]), array([3, 4])]
    )r   rK   )r   r   r2   r   r   r   )r+   r   rK   r   r   r   r     s   
r   zkeras.ops.shapec                 C   s   t | fr| jS tj| S )aK  Gets the shape of the tensor input.

    Note: On the TensorFlow backend, when `x` is a `tf.Tensor` with dynamic
    shape, dimensions which are dynamic in the context of a compiled function
    will have a `tf.Tensor` value instead of a static integer value.

    Args:
        x: A tensor. This function will try to access the `shape` attribute of
            the input tensor.

    Returns:
        A tuple of integers or None values, indicating the shape of the input
            tensor.

    Example:

    >>> x = keras.ops.zeros((8, 12))
    >>> keras.ops.shape(x)
    (8, 12)
    )r   r   r   r   rV   r   r   r   r     s   
r   zkeras.ops.dtypec                 C   s   t | jS )a  Return the dtype of the tensor input as a standardized string.

    Note that due to the standardization, the dtype will not compare equal
    to the backend-specific version of the dtype.

    Args:
        x: A tensor. This function will try to access the `dtype` attribute of
            the input tensor.

    Returns:
        A string indicating the dtype of the input tensor, e.g. `"float32"`.

    Example:

    >>> x = keras.ops.zeros((8, 12))
    >>> keras.ops.dtype(x)
    'float32'

    )r   standardize_dtyper    rV   r   r   r   r      s   r    c                       ra   )	CastNr5   c                      t  j|d t|| _d S r8   r9   r:   r   r   r    r   r    r6   r>   r   r   r:   '     zCast.__init__c                 C   s   t j|| jS r   )r   r   castr    r   r   r   r   r   +  rp   z	Cast.callc                 C      t j|j| jdS Nr   r   r   r   r    r   r   r   r   r*   .  rl   zCast.compute_output_specrG   r   r   r>   r   r   &  rq   r   zkeras.ops.castc                 C   s&   t | frt|d| S tj| |S )a  Cast a tensor to the desired dtype.

    Args:
        x: A tensor or variable.
        dtype: The target type.

    Returns:
        A tensor of the specified `dtype`.

    Example:

    >>> x = keras.ops.arange(4)
    >>> x = keras.ops.cast(x, dtype="float16")
    rn   )r   r   r   r   r   r+   r    r   r   r   r   2  s   
r   c                       ra   )	SaturateCastNr5   c                   r   r8   r   r   r>   r   r   r:   H  r   zSaturateCast.__init__c                 C   s   t || jS r   )_saturate_castr    r   r   r   r   r   L  r   zSaturateCast.callc                 C   r   r   r   r   r   r   r   r*   O  rl   z SaturateCast.compute_output_specrG   r   r   r>   r   r   G  rq   r   zkeras.ops.saturate_castc                 C   s"   t | frt|d| S t| |S )a  Performs a safe saturating cast to the desired dtype.

    Saturating cast prevents data type overflow when casting to `dtype` with
    smaller values range. E.g.
    `ops.cast(ops.cast([-1, 256], "float32"), "uint8")` returns `[255, 0]`,
    but `ops.saturate_cast(ops.cast([-1, 256], "float32"), "uint8")` returns
    `[0, 255]`.

    Args:
        x: A tensor or variable.
        dtype: The target type.

    Returns:
        A safely casted tensor of the specified `dtype`.

    Example:

    Image resizing with bicubic interpolation may produce values outside
    original range.
    >>> image2x2 = np.array([0, 1, 254, 255], dtype="uint8").reshape(1, 2, 2, 1)
    >>> image4x4 = tf.image.resize(image2x2, (4, 4), method="bicubic")
    >>> print(image4x4.numpy().squeeze())
    >>> # [[-22.500004 -22.204624 -21.618908 -21.32353 ]
    >>> #  [ 52.526054  52.82143   53.407146  53.70253 ]
    >>> #  [201.29752  201.59288  202.17859  202.47395 ]
    >>> #  [276.32355  276.61893  277.20465  277.50006 ]]

    Casting this resized image back to `uint8` will cause overflow.
    >>> image4x4_casted = ops.cast(image4x4, "uint8")
    >>> print(image4x4_casted.numpy().squeeze())
    >>> # [[234 234 235 235]
    >>> #  [ 52  52  53  53]
    >>> #  [201 201 202 202]
    >>> #  [ 20  20  21  21]]

    Saturate casting to `uint8` will clip values to `uint8` range before
    casting and will not cause overflow.
    >>> image4x4_saturate_casted = ops.saturate_cast(image4x4, "uint8")
    >>> print(image4x4_saturate_casted.numpy().squeeze())
    >>> # [[  0   0   0   0]
    >>> #  [ 52  52  53  53]
    >>> #  [201 201 202 202]
    >>> #  [255 255 255 255]]

    rn   )r   r   r   r   r   r   r   saturate_castS  s   
/
r   c                 C   s   |pt }dd }t |}t | j}||\}}||\}}t|||}	|	|k r4tj|	d|d}	t|||}
|
|krItj|
d|d}
|j	| |	|
} |
| |S )Nc                 S   s`   d| krd}d}||fS d| v r t | j}t | j}||fS t | j}t | j}||fS )Nboolr   r4   rE   )	ml_dtypesiinfominmaxfinfo)r    	dtype_min	dtype_maxr   r   r   get_dtype_min_max  s   z)_saturate_cast.<locals>.get_dtype_min_maxr   rn   )r   r   r    npmaximumastype	nextafterminimumnumpyclipr   )r+   r    backend_moduler   in_dtypein_minin_maxout_minout_max	min_limit	max_limitr   r   r   r     s   
	r   c                       r   )
ConvertToTensorNr5   c                   s6   t  j|d |d u rd nt|| _|| _|| _d S r8   )r9   r:   r   r   r    r!   r"   )r   r    r!   r"   r6   r>   r   r   r:     s   
zConvertToTensor.__init__c                 C   s   t jj|| j| j| jdS )Nr    r!   r"   )r   r   convert_to_tensorr    r!   r"   r   r   r   r   r     s   zConvertToTensor.callc                 C   sd   | j d u rt|j n| j }| jd ur| jsdn|j}| jd ur%| js%dn|j}tj|j|||dS )NFr   )r    r   r   r!   r"   r   r   )r   r+   r    r!   r"   r   r   r   r*     s   

z#ConvertToTensor.compute_output_specNNNrG   r   r   r>   r   r     s    r   zkeras.ops.convert_to_tensorc                 C   s0   t | frt|||d| S tjj| |||dS )a  Convert a NumPy array or Python array to a tensor.

    Native tensors for the current backend or left unchanged unless the `dtype`,
    `sparse` or `ragged` arguments are set.

    Args:
        x: A NumPy array, Python array (can be nested) or a backend tensor.
        dtype: The target type. If `None`, the type of `x` is used.
        sparse: Whether to keep sparse tensors. `False` will cause sparse
            tensors to be densified. The default value of `None` means that
            sparse tensors are kept only if the backend supports them.
        ragged: Whether to keep ragged tensors. `False` will cause ragged
            tensors to be densified. The default value of `None` means that
            ragged tensors are kept only if the backend supports them.

    Returns:
        A backend tensor of the specified `dtype` and sparseness.

    Example:

    >>> x = np.array([1, 2, 3])
    >>> y = keras.ops.convert_to_tensor(x)
    r   )r   r   r   r   r   )r+   r    r!   r"   r   r   r   r     s
   
r   zkeras.ops.convert_to_numpyc                 C   s   t | fr
t| S t| S )zlConvert a tensor to a NumPy array.

    Args:
        x: A tensor.

    Returns:
        A NumPy array.
    )r   r   arrayr   convert_to_numpyrV   r   r   r   r     s   



r   c                   @   s2   e Zd Zejdd Zdd Zdd Zdd Zd	S )
Condc                    sH    fdd}t  rt j| jj dd}||i |S ||i |S )Nc                     s*   t | |r j| i |S  j| i |S r   )r   r2   r   )argskwargsrS   r   r   call_fn   s   
zCond.__call__.<locals>.call_fnz.call())object_name)r
   is_traceback_filtering_enabled!inject_argument_info_in_tracebackr?   r.   )r   r   r   r   r   rS   r   __call__  s   zCond.__call__c                 C   ru   r   )r   r   r   )r   predtrue_fnfalse_fnr   r   r   r     rp   z	Cond.callc                 C   s:   t |}t |}| ||std| d| d|S )Nz_`true_fn` and `false_fn` should return outputs of the same kind (struct, dtype and shape). Got z and z	 instead.)r   r*   _check_output_specr[   )r   r   r   r   true_fn_specfalse_fn_specr   r   r   r*     s   

zCond.compute_output_specc                 C   sB   zt || W n   Y dS dd }t |||}tt |S )NFc                 S   s8   | d u s|d u r| d u o|d u S | j |j ko| j|jkS r   r   )t_specf_specr   r   r   
check_leaf%  s   z+Cond._check_output_spec.<locals>.check_leaf)r   assert_same_structurer(   allr)   )r   r   r   r   samer   r   r   r     s   zCond._check_output_specN)	r.   r/   r0   r
   filter_tracebackr   r   r*   r   r   r   r   r   r     s    
r   zkeras.ops.condc                 C   s   t  | ||S )aP  Conditionally applies `true_fn` or `false_fn`.

    Args:
        pred: Boolean scalar type
        true_fn: Callable returning the output for the `pred == True` case.
        false_fn: Callable returning the output for the `pred == False` case.

    Returns:
        The output of either `true_fn` or `false_fn` depending on pred.
    )r   )r   r   r   r   r   r   r   .  s   r   c                       sJ   e Zd Zdd fdd
Zdd Zdd Z fd	d
Zedd Z  Z	S )VectorizedMapNr5   c                   rc   r8   )r9   r:   function)r   r   r6   r>   r   r   r:   >  rf   zVectorizedMap.__init__c                 C   s   t j| j|S r   )r   r   vectorized_mapr   )r   elementsr   r   r   r   B  rp   zVectorizedMap.callc                    sN   t dd |}t |d jd  t| j|} fdd}t ||}|S )Nc                 S   r   r   r   r   r   r   r   r   F  r   z3VectorizedMap.compute_output_spec.<locals>.<lambda>r   c                    r   r   r#   r   r$   r   r   r&   J  r'   z<VectorizedMap.compute_output_spec.<locals>.append_batch_axis)r   r(   r)   r   r   r*   r   )r   r   r+   r,   r&   r   r$   r   r*   E  s   z!VectorizedMap.compute_output_specc                    s   t   }|d| ji |S )Nr   )r9   
get_configupdater   )r   configr>   r   r   r   U  s   
zVectorizedMap.get_configc                 C   s(   |  }t|d |d< | di |S )Nr   r   )copyr	   deserialize_keras_object)clsr   r   r   r   from_configZ  s
   zVectorizedMap.from_config)
r.   r/   r0   r:   r   r*   r   classmethodr   rH   r   r   r>   r   r   =  s    r   zkeras.ops.vectorized_mapc                 C   s$   t |frt| |S tj| |S )a:  Parallel map of `function` on axis 0 of tensor(s) `elements`.

    Schematically, `vectorized_map` implements the following,
    in the case of a single tensor input `elements`:

    ```python
    def vectorized_map(function, elements):
        outputs = []
        for e in elements:
            outputs.append(function(e))
        return np.stack(outputs)
    ```

    In the case of an iterable of tensors `elements`,
    it implements the following:

    ```python
    def vectorized_map(function, elements):
        batch_size = elements[0].shape[0]
        outputs = []
        for index in range(batch_size):
            outputs.append(function([e[index] for e in elements]))
        return np.stack(outputs)
    ```

    In this case, `function` is expected to take as input
    a single list of tensor arguments.
    )r   r   r   r   r   )r   r   r   r   r   r   c  s   
r   zkeras.ops.is_tensorc                 C      t j| S )a%  Check whether the given object is a tensor.

    Note: This checks for backend specific tensors so passing a TensorFlow
    tensor would return `False` if your backend is PyTorch or JAX.

    Args:
        x: A variable.

    Returns:
        `True` if `x` is a tensor, otherwise `False`.
    )r   r   	is_tensorrV   r   r   r   r    s   r  zkeras.ops.custom_gradientc                 C   r   )a
  Decorator to define a function with a custom gradient.

    This decorator allows fine grained control over the gradients of a sequence
    for operations. This may be useful for multiple reasons, including providing
    a more efficient or numerically stable gradient for a sequence of
    operations.

    Args:
        f: Function `f(*args)` that returns a tuple
            `(output, grad_fn)`, where:
            - `args` is a sequence of (nested structures of) tensor inputs to
                the function.
            - `output` is a (nested structure of) tensor outputs of applying
                operations in `forward_fn` to `args`.
            - `grad_fn` is a function with the signature `grad_fn(*args,
                upstream)` which returns a tuple of tensors the same size as
                (flattened) `args`: the derivatives of tensors in `output` with
                respect to the tensors in `args`. `upstream` is a tensor or
                sequence of tensors holding the initial value gradients for each
                tensor in `output`.

    Returns:
        A function `h(*args)` which returns the same value as
        `f(*args)[0]` and whose gradient is determined by
        `f(*args)[1]`.


    Examples:

    1. Backend-agnostic example.

    ```python
    @ops.custom_gradient
    def log1pexp(x):
        e = ops.exp(x)

        def grad(*args, upstream=None):
            if upstream is None:
                (upstream,) = args
            return ops.multiply(upstream, 1.0 - 1.0 / ops.add(1, e))

        return ops.log(1 + e), grad
    ```

    Note that the grad function that returns gradient computation
    requires `args` as well as an `upstream` keyword argument, depending
    on the backend being set. With the JAX and TensorFlow backends,
    it requires only one argument, whereas it might use the `upstream`
    argument in the case of the PyTorch backend.

    When working with TensorFlow/JAX backend, `grad(upstream)`
    is sufficient. With PyTorch, the `grad` function requires
    `*args` as well as `upstream`, e.g. `def grad(*args, upstream)`.
    Follow the previous example to use `@ops.custom_gradient` in
    a way that is compatible with all backends.

    2. Here's JAX & TensorFlow-specific example:

    ```python
    @ops.custom_gradient
    def log1pexp(x):
        e = ops.exp(x)
        def grad(upstream):
            return ops.multiply(upstream, 1.0 - 1.0 / ops.add(1, e))
        return ops.log(1 + e), grad
    ```

    3. Lastly, here's a PyTorch-specific example,
    using `*args` & `upstream`:

    ```python
    @ops.custom_gradient
    def log1pexp(x):
        e = ops.exp(x)
        def grad(*args, upstream):
            return ops.multiply(upstream, 1.0 - 1.0 / ops.add(1, e))
        return ops.log(1 + e), grad
    ```
    )r   r   custom_gradient)r   r   r   r   r    s   Qr  )NNFr4   r`   r   r   r   );r   r   r   	keras.srcr   r   keras.src.api_exportr   keras.src.backendr   r   &keras.src.backend.common.backend_utilsr   keras.src.ops.operationr   keras.src.savingr	   keras.src.utilsr
   r   r   r3   rB   rI   rN   rb   rh   rt   rv   r|   r}   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r   r   r   r   r   r   r   r   r   r   r   r   r  r  r   r   r   r   <module>   s    
,"N%<

3

#

(5






3(
1
&
"
