
    2Vh<                     P   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  G d
 de      Z ed      d        Z G d de      Z ed      dWd       Z G d de      Z ed      dXd       Z G d de      Z ed      d        Z G d de      Z ed      d        Z G d de      Z ed       d!        Z G d" d#e      Z ed$      d%        Z G d& d'e      Z ed(      d)        Z  G d* d+e      Z! ed,      	 dYd-       Z" G d. d/e      Z# ed0      d1        Z$ G d2 d3e      Z% ed4      d5        Z& G d6 d7e      Z' ed8      dZd9       Z( ed:      d;        Z) ed<      d=        Z* G d> d?e      Z+ ed@      dA        Z, G dB dCe      Z- edD      dE        Z.dYdFZ/ G dG dHe      Z0 edI      d[dJ       Z1 edK      dL        Z2 G dM dNe      Z3 edO      dP        Z4 edQ      dR        Z5 edS      dT        Z6 edU      dV        Z7y)\    N)backend)tree)keras_export)KerasTensor)any_symbolic_tensors)slice_along_axis)	Operation)traceback_utilsc                   *     e Zd Z fdZd Zd Z xZS )Mapc                 "    t         |           y Nsuper__init__self	__class__s    B/home/dcms/DCMS/lib/python3.12/site-packages/keras/src/ops/core.pyr   zMap.__init__           c                 B    t         j                  j                  ||      S r   )r   coremap)r   fxss      r   callzMap.call   s    ||2&&r   c                     |d   }|j                   d   t        j                  ||      }fd}t        j                  ||      }|S )Nr   c                 d    t        f| j                  z   | j                  | j                        S )Nshapedtypesparser   r!   r"   r#   )xns    r   append_batch_axisz2Map.compute_output_spec.<locals>.append_batch_axis   s)    dQWWnAGGAHH r   )r!   r   compute_output_specr   map_structure)r   r   r   r%   yr'   r&   s         @r   r(   zMap.compute_output_spec   sJ    qEHHQK''1-	
 0!4r   __name__
__module____qualname__r   r   r(   __classcell__r   s   @r   r   r      s    'r   r   zkeras.ops.mapc                     t        |f      rt               j                  | |      S t        j                  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   s     r   r   r   #   s;    P RE"u""1b))<<Ar""r   c                   ,     e Zd Zd fd	Zd Zd Z xZS )Scanc                 >    t         |           || _        || _        y r   )r   r   reverseunroll)r   r6   r7   r   s      r   r   zScan.__init__Q   s    r   c                 t    t         j                  j                  ||||| j                  | j                        S )Nr6   r7   )r   r   scanr6   r7   )r   r   initr   lengths        r   r   z	Scan.callV   s3    ||  tRdkk ! 
 	
r   c                 ,   |t        |      }d }n7|t        |      n$t        j                  |      d   j                  d   }|d   }t	        j
                  |||      \  }}t        |f|j                  z   |j                  |j                        }||fS Nr   r    )	intr   flattenr!   r   r(   r   r"   r#   )	r   r   r;   r   r<   r&   r%   carryr*   s	            r   r(   zScan.compute_output_spec[   s    :FAA % F\\"%a(..q1 
 1A..q$:qqdQWWnAGGAHHMaxr   )F   r+   r0   s   @r   r4   r4   P   s    


r   r4   zkeras.ops.scanc                     t        ||f      rt        ||      j                  | |||      S t        j                  j                  | |||||      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]
    r9   )r   r4   r2   r   r   r:   )r   r;   r   r<   r6   r7   s         r   r:   r:   l   s_    L T2J'GF3AAtR
 	
 <<	4VWV   r   c                   .     e Zd Zd fd	ZddZd Z xZS )AssociativeScanc                 0    t         |           || _        y r   )r   r   r6   )r   r6   r   s     r   r   zAssociativeScan.__init__   s    r   c                 \    t         j                  j                  ||| j                  |      S )Nr6   axis)r   r   associative_scanr6   )r   r   elemsrI   s       r   r   zAssociativeScan.call   s+    ||,,udll - 
 	
r   c                   	 t        j                  |      		D cg c]  }|j                  |    }}t        t	        |            dk7  r2t        dj                  	D cg c]  }|j                   c}            t        j                  |	D cg c]  }t        |dd|       c}      }t        j                  |||      }	fd}t        j                  ||      }|S c c}w c c}w c c}w )NrB   zNArray inputs to associative_scan must have the same first dimension. (saw: {})r   )rI   c                 b    t        d   j                  | j                  | j                        S r>   r$   )r%   
elems_flats    r   _restore_shapez;AssociativeScan.compute_output_spec.<locals>._restore_shape   s)     m)) r   )r   r@   r!   lenset
ValueErrorformatpack_sequence_asr   r   r(   r)   )
r   r   rK   rI   elemlensr%   y_specrO   rN   s
            @r   r(   z#AssociativeScan.compute_output_spec   s    \\%(
-78T

4 88s4y>Q--3V,67DTZZ7.  !!*MQ$Q148M
 ,,Q15	
 ##NF;) 9
 8
 Ns   C!C&C+
)F)r   r+   r0   s   @r   rE   rE      s    

r   rE   zkeras.ops.associative_scanc                     t        |f      rt        |      j                  | ||      S t        j                  j                  | |||      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]]
    )r6   rH   )r   rE   r2   r   r   rJ   )r   rK   r6   rI   s       r   rJ   rJ      sG    l UH%w/==aMM<<((E7(NNr   c                       e Zd Zd Zd Zy)Scatterc                 D    t         j                  j                  |||      S r   )r   r   scatterr   indicesvaluesr!   s       r   r   zScatter.call  s    ||##GVU;;r   c                 0    t        ||j                        S Nr"   r   r"   r]   s       r   r(   zScatter.compute_output_spec      555r   Nr,   r-   r.   r   r(    r   r   rZ   rZ     s    <6r   rZ   zkeras.ops.scatterc                     t        | ||f      rt               j                  | ||      S t        j                  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.]])
    )r   rZ   r2   r   r   r\   )r^   r_   r!   s      r   r\   r\   !  sC    4 Wfe45y&&w>><<77r   c                       e Zd Zd Zd Zy)ScatterUpdatec                 D    t         j                  j                  |||      S r   )r   r   scatter_updater   inputsr^   updatess       r   r   zScatterUpdate.callA  s    ||**67GDDr   c                 D    t        |j                  |j                        S ra   r   r!   r"   rl   s       r   r(   z!ScatterUpdate.compute_output_specD      6<<v||<<r   Nre   rf   r   r   ri   ri   @  s    E=r   ri   zkeras.ops.scatter_updatec                     t        | ||f      rt               j                  | ||      S t        j                  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   ri   r2   r   r   rk   )rm   r^   rn   s      r   rk   rk   H  sD    ^ VWg67,,VWgFF<<&&vw@@r   c                       e Zd Zd Zd Zy)Slicec                 D    t         j                  j                  |||      S r   )r   r   slicer   rm   start_indicesr!   s       r   r   z
Slice.call}  s    ||!!&-??r   c                 0    t        ||j                        S ra   rc   rw   s       r   r(   zSlice.compute_output_spec  rd   r   Nre   rf   r   r   rt   rt   |  s    @6r   rt   zkeras.ops.slicec                     t        | ||f      rt               j                  | ||      S t        j                  j                  | ||      S )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`.
    )r   rt   r2   r   r   rv   )rm   rx   r!   s      r   rv   rv     sC    4 V]E:;w$$V]EBB<<fmU;;r   c                       e Zd Zd Zd Zy)SliceUpdatec                 D    t         j                  j                  |||      S r   )r   r   slice_updater   rm   rx   rn   s       r   r   zSliceUpdate.call  s    ||((HHr   c                 D    t        |j                  |j                        S ra   rp   r   s       r   r(   zSliceUpdate.compute_output_spec  rq   r   Nre   rf   r   r   r|   r|     s    I=r   r|   zkeras.ops.slice_updatec                     t        | ||f      rt               j                  | ||      S t        j                  j                  | ||      S )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~   )rm   rx   rn   s      r   r~   r~     sC    > V]G<=}**6='JJ<<$$V]GDDr   c                       e Zd Zd Zd Zy)Switchc                 D    t        j                  j                  ||g| S r   )r   r   switch)r   indexbranchesoperandss       r   r   zSwitch.call  s    ||""5(>X>>r   c                 8    t        j                  |d   g| }|S Nr   )r   r(   )r   r   r   r   specs        r   r(   zSwitch.compute_output_spec  s     **8A;BBr   Nre   rf   r   r   r   r     s    ?r   r   zkeras.ops.switchc                     t        |      r t               j                  | |g| S t        j                  j
                  | |g| 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   s      r   r   r     sE    H H%%vx%%eXAAA<<uh:::r   c                   *     e Zd Z fdZd Zd Z xZS )	WhileLoopc                 L    t         |           || _        || _        || _        y r   )r   r   condbodymaximum_iterations)r   r   r   r   r   s       r   r   zWhileLoop.__init__  s$    		"4r   c                     t         j                  j                  | j                  | j                  || j
                        S )Nr   )r   r   
while_loopr   r   r   )r   	loop_varss     r   r   zWhileLoop.call	  s8    ||&&IIII#66	 ' 
 	
r   c                 j    |D cg c]#  }t        |j                  |j                        % c}S c c}w ra   rp   )r   r   vs      r   r(   zWhileLoop.compute_output_spec  s%    =FGAGG1773GGGs   (0r+   r0   s   @r   r   r     s    5
Hr   r   zkeras.ops.while_loopc                 H    t         j                  j                  | |||      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   r   )r   r   r   r   s       r   r   r     s-    T <<""-	 #  r   c                   *     e Zd Z fdZd Zd Z xZS )StopGradientc                 "    t         |           y r   r   r   s    r   r   zStopGradient.__init__H  r   r   c                 @    t         j                  j                  |      S r   )r   r   stop_gradientr   variables     r   r   zStopGradient.callK  s    ||))(33r   c                 D    t        |j                  |j                        S ra   rp   r   s     r   r(   z StopGradient.compute_output_specN      8>>@@r   r+   r0   s   @r   r   r   G  s    4Ar   r   zkeras.ops.stop_gradientc                     t        | f      rt               j                  |       S t        j                  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   s    r   r   r   R  s6    & XK(~++H55<<%%h//r   c                   *     e Zd Z fdZd Zd Z xZS )ForiLoopc                 L    t         |           || _        || _        || _        y r   )r   r   lowerupperbody_fun)r   r   r   r   r   s       r   r   zForiLoop.__init__k  s#    

 r   c                     t         j                  j                  | j                  | j                  | j
                  |      S r   )r   r   	fori_loopr   r   r   r   init_vals     r   r   zForiLoop.callq  s1    ||%%JJJJMM	
 	
r   c                 D    t        |j                  |j                        S ra   rp   r   s     r   r(   zForiLoop.compute_output_specy  r   r   r+   r0   s   @r   r   r   j  s    !
Ar   r   zkeras.ops.fori_loopc                     t        | ||f      rt        | ||      j                  |      S t        j                  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   s       r   r   r   }  sH    0 UE845uh/==hGG<<!!%(CCr   c                   ,     e Zd Zd fd	Zd Zd Z xZS )Unstackc                 >    t         |           || _        || _        y r   )r   r   numrI   )r   r   rI   r   s      r   r   zUnstack.__init__  s    	r   c                 l    t         j                  j                  || j                  | j                        S r   )r   r   unstackr   rI   r   r%   s     r   r   zUnstack.call  s#    ||##Atxx;;r   c                 p   | j                   }|dk  rt        |j                        |z   }|j                  d | |j                  |dz   d  z   }| j                  }||j                  |   }|t	        d|j                   d      t        |      D cg c]  }t        ||j                         }}|S c c}w )Nr   rB   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.r!   r"   )rI   rP   r!   r   rR   ranger   r"   )r   r%   rI   output_shapesr   _outputs          r   r(   zUnstack.compute_output_spec  s    yy!8qww<$&D)<<hh;''$-C;977) 66  FK3Z
@AKm177;
 
 
s   B3r   r+   r0   s   @r   r   r     s    
<r   r   zkeras.ops.unstackc                     t        | f      rt        ||      j                  |       S t        j                  j                  | ||      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   rI   )r   r   r2   r   r   r   )r%   r   rI   s      r   r   r     sB    & QD!sD!//22<<s66r   zkeras.ops.shapec                 p    t        | f      r| j                  S t        j                  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   r%   s    r   r!   r!     s,    , QD!ww<<a  r   zkeras.ops.dtypec                 @    t        j                  | 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"   r   s    r   r"   r"     s    * $$QWW--r   c                   *     e Zd Z fdZd Zd Z xZS )Castc                 V    t         |           t        j                  |      | _        y r   r   r   r   r   r"   r   r"   r   s     r   r   zCast.__init__       ..u5
r   c                 V    t         j                  j                  || j                        S r   )r   r   castr"   r   s     r   r   z	Cast.call  s    ||  DJJ//r   c                 X    t        j                  |j                  | j                        S Nr   r   r   r!   r"   r   s     r   r(   zCast.compute_output_spec      ""

CCr   r+   r0   s   @r   r   r     s    60Dr   r   zkeras.ops.castc                     t        j                  |      }t        | f      r t        |      |       S t         j                  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")
    rb   )r   r   r   r   r   r   r%   r"   s     r   r   r     sG      %%e,EQD! t% ##<<Q&&r   c                   *     e Zd Z fdZd Zd Z xZS )SaturateCastc                 V    t         |           t        j                  |      | _        y r   r   r   s     r   r   zSaturateCast.__init__'  r   r   c                 .    t        || j                        S r   )_saturate_castr"   r   s     r   r   zSaturateCast.call+  s    a,,r   c                 X    t        j                  |j                  | j                        S r   r   r   s     r   r(   z SaturateCast.compute_output_spec.  r   r   r+   r0   s   @r   r   r   &  s    6-Dr   r   zkeras.ops.saturate_castc                     t        j                  |      }t        | f      r t        |      |       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]]

    rb   )r   r   r   r   r   r   s     r   saturate_castr   2  s>    ^ %%e,EQD!(|%(++!U##r   c                    |xs t         }d }t        j                  |      }t        j                  | j                        } ||      \  }} ||      \  }}t        j                  ||      j                  |      }	|	|k  rt        j                  |	d|      }	t        j                  ||      j                  |      }
|
|kD  rt        j                  |
d|      }
|j                  j                  | |	|
      } |j                  | |      S )Nc                 ,   d| k(  rd}d}||fS d| v rBt        j                  |       j                  }t        j                  |       j                  }||fS t        j                  |       j                  }t        j                  |       j                  }||fS )Nboolr   rB   r?   )	ml_dtypesiinfominmaxfinfo)r"   	dtype_min	dtype_maxs      r   get_dtype_min_maxz)_saturate_cast.<locals>.get_dtype_min_maxk  s    U?II )## e^!.22I!.22I )## ".22I!.22I)##r   r   rb   )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_limits              r   r   r   h  s    #.wN
$ %%e,E((1H&x0NFF(/GW 

67+228<I7LLAX>	

67+228<I7LLAX>	 	!!!Y	:Aq%((r   c                   *     e Zd Z fdZd Zd Z xZS )ConvertToTensorc                 d    t         |           t        j                  |      | _        || _        y r   )r   r   r   r   r"   r#   )r   r"   r#   r   s      r   r   zConvertToTensor.__init__  s'    ..u5
r   c                 n    t         j                  j                  || j                  | j                        S )Nr"   r#   )r   r   convert_to_tensorr"   r#   r   s     r   r   zConvertToTensor.call  s-    ||--TZZ . 
 	
r   c                     | j                   |j                   n| j                   }| j                  | j                  sdn|j                  }t        j                  |j                  ||      S )NFr    )r"   r#   r   r   r!   )r   r%   r"   r#   s       r   r(   z#ConvertToTensor.compute_output_spec  sR    ::-4::[[,T[[Eahh 	 ""fMMr   r+   r0   s   @r   r   r     s    


Nr   r   zkeras.ops.convert_to_tensorc                     t        | f      r t        ||      |       S t        j                  j	                  | |||      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#   ragged)r   r   r   r   r   )r%   r"   r#   r   s       r   r   r     sG    2 QD!:U6:1==<<))	vf *  r   zkeras.ops.convert_to_numpyc                 n    t        | f      rt        j                  |       S t        j                  |       S )zlConvert a tensor to a NumPy array.

    Args:
        x: A tensor.

    Returns:
        A NumPy array.
    )r   r   arrayr   convert_to_numpyr   s    r   r   r     s/     QD! xx{##A&&r   c                   B    e Zd Zej                  d        Zd Zd Zd Zy)Condc                       fd}t        j                         r6t        j                  | j                  j                   d      } ||i |S  ||i |S )Nc                  d    t        | |      r j                  | i |S  j                  | i |S r   )r   r2   r   )argskwargsr   s     r   call_fnzCond.__call__.<locals>.call_fn  s;    #D&1)t))4:6:: tyy$1&11r   z.call())object_name)r
   is_traceback_filtering_enabled!inject_argument_info_in_tracebackr   r,   )r   r  r  r  s   `   r   __call__zCond.__call__  sb    	2 99;%GG $ 7 78@G D+F++ '''r   c                 D    t         j                  j                  |||      S r   )r   r   r   )r   predtrue_fnfalse_fns       r   r   z	Cond.call  s    ||  w99r   c                     t        j                  |      }t        j                  |      }| j                  ||      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_specrR   )r   r
  r  r  true_fn_specfalse_fn_specs         r   r(   zCond.compute_output_spec  s_    227;33H=&&|]C#nE-	C 
 r   c                     	 t        j                  ||       d }t        j                  |||      }t        t        j                  |            S #  Y yxY w)NFc                     | |
| d u xr |d u S | j                   |j                   k(  xr | j                  |j                  k(  S r   r   )t_specf_specs     r   
check_leafz+Cond._check_output_spec.<locals>.check_leaf  sC    ~~8&D.8<<6<</PFLLFLL4PPr   )r   assert_same_structurer)   allr@   )r   r  r  r  sames        r   r  zCond._check_output_spec  sQ    	&&|]C	Q
 !!*lMJ4<<%&&	s   A AN)	r,   r-   r.   r
   filter_tracebackr  r   r(   r  rf   r   r   r   r     s)    %%( &($:	'r   r   zkeras.ops.condc                 &     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  s      r   r   r     s     46$**r   zkeras.ops.vectorized_mapc                 B    t         j                  j                  | |      S )a5  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 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   vectorized_map)functionelementss     r   r  r    s    < <<&&x::r   zkeras.ops.is_tensorc                 @    t         j                  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_tensorr   s    r   r   r   6  s     <<!!!$$r   zkeras.ops.custom_gradientc                 @    t         j                  j                  |       S )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   s    r   r"  r"  F  s    b <<''**r   )NNFrB   )Fr   r   r   )NNN)8r   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.utilsr
   r   r   r4   r:   rE   rJ   rZ   r\   ri   rk   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"  rf   r   r   <module>r)     sL       - ) 2 C - +) * o)# )#X9 8 K  K\ i  F *+7O ,7Ot6i 6 !"8 #8<=I = ()0A *0Af6I 6  < !<<=) = &' E ( EFY   !%; "%;PH	 H& $%
 	. &.bA9 A '(0 )0.Ay A& #$D %D8i < !"7 #7.  ! !!4  . !..	D9 	D '  ',	D9 	D '(2$ )2$j%)PNi N& +, -> *+' ,' .'9 .'b +  + (); *;@ #$% %% )*P+ +P+r   