code: def bracket(
      value_and_gradients_function,
      search_interval,
      f_lim,
      max_iterations,
      expansion_param=5.0,
  ):
      """Brackets the minimum given an initial starting point.
  
      Applies the Hager Zhang bracketing algorithm to find an interval containing
      a region with points satisfying Wolfe conditions. Uses the supplied initial
      step size 'c', the right end point of the provided search interval, to find
      such an interval. The only condition on 'c' is that it should be positive.
      For more details see steps B0-B3 in [Hager and Zhang (2006)][2].
  
      Args:
        value_and_gradients_function: A Python callable that accepts a real scalar
          tensor and returns a namedtuple containing the value filed `f` of the
          function and its derivative value field `df` at that point.
          Alternatively, the function may representthe batching of `n` such line
          functions (e.g. projecting a single multivariate objective function along
          `n` distinct directions at once) accepting n points as input, i.e. a
          tensor of shape [n], and return a tuple of two tensors of shape [n], the
          function values and the corresponding derivatives at the input points.
        search_interval: A namedtuple describing the current search interval,
          must include the fields:
          - converged: Boolean `Tensor` of shape [n], indicating batch members
              where search has already converged. Interval for these batch members
              wont be modified.
          - failed: Boolean `Tensor` of shape [n], indicating batch members
              where search has already failed. Interval for these batch members
              wont be modified.
          - iterations: int32 `Tensor` of shape [n]. Number of line search
              iterations so far.
          - func_evals: Scalar int32 `Tensor`. Number of function evaluations
              so far.
          - left: A namedtuple, as returned by value_and_gradients_function
              evaluated at 0, the left end point of the current interval.
          - right: A namedtuple, as returned by value_and_gradients_function,
              of the right end point of the current interval (labelled 'c' above).
        f_lim: real `Tensor` of shape [n]. The function value threshold for
          the approximate Wolfe conditions to be checked for each batch member.
        max_iterations: Int32 scalar `Tensor`. The maximum number of iterations
          permitted. The limit applies equally to all batch members.
        expansion_param: Scalar positive `Tensor` of real dtype. Must be greater
          than `1.`. Used to expand the initial interval in case it does not bracket
          a minimum.
  
      Returns:
        A namedtuple with the following fields.
          iteration: An int32 `Tensor` of shape [n]. The number of iterations
            performed. Bounded above by `max_iterations` parameter.
          stopped: A boolean `Tensor` of shape [n]. True for those batch members
            where the algorithm terminated before reaching `max_iterations`.
          failed: A boolean `Tensor` of shape [n]. True for those batch members
            where an error was encountered during bracketing.
          num_evals: An int32 scalar `Tensor`. The number of times the objective
            function was evaluated.
          left: Return value of value_and_gradients_function at the updated left
            end point of the interval found.
          right: Return value of value_and_gradients_function at the updated right
            end point of the interval found.
      """
      already_stopped = (
          search_interval.failed | search_interval.converged
      )
  
      # We fail if there is a nan at the right end-point that prevents us from
      # making a decision, i.e., either a nan value or a finite value and a nan
      # derivative.
      failed = _bad_nan(search_interval.right)
  
      # If the right end is -inf, we have already found a minimum.  Passing along to
      # `_bisect` in this state will do the right thing, so just mark those as
      # stopped.
      finished = _is_negative_inf(search_interval.right.f)
  
      # If the value at the right end is finite and the slope at right end point is
      # positive, step B1 in [2], then the given initial points already bracket a
      # minimum.
      bracketed = _is_rising(search_interval.right)
  
      # Bisection is needed, step B2, if right end point almost works as a new left
      # end point but the objective value is too high.
      needs_bisect = _needs_bisect(search_interval.right, f_lim)
  
      # In these three cases bracketing is already `stopped` and there is no need
      # to perform further evaluations. Otherwise, assuming there are no `nan`s,
      # - The right-end point has a finite value,
      # - Whose derivative is non-positive, and
      # - Which is below `f_lim`.
      # This is then a valid left end-point, and the bracketing loop is needed to
      # expand the interval to the right, step B3, until the conditions are met.
      initial_args = _IntermediateResult(
          iteration=search_interval.iterations,
          stopped=already_stopped
          | failed
          | finished
          | bracketed
          | needs_bisect,
          failed=search_interval.failed | failed,
          num_evals=search_interval.func_evals,
          left=search_interval.left,
          right=search_interval.right,
      )
  
      def _loop_cond(curr):
          return tf.reduce_any(
              (curr.iteration < max_iterations) & ~curr.stopped
          )
  
      def _loop_body(curr):
          """Main body of bracketing loop."""
          # The loop maintains the invariant that curr.stopped is true if we have
          # either: failed, successfully bracketed, or not yet bracketed but need to
          # bisect. On the only remaining case, step B3 in [2], `curr.right` is
          # actually a valid left end-point, and we need to expand to guess another
          # right end-point.
          new_right = value_and_gradients_function(
              expansion_param * curr.right.x
          )
          left = val_where(curr.stopped, curr.left, curr.right)
          right = val_where(curr.stopped, curr.right, new_right)
  
          # Updated the failed, bracketed, and needs_bisect conditions.
          failed = curr.failed | _bad_nan(right)
          finished = _is_negative_inf(right.f)
          bracketed = _is_rising(right)
          needs_bisect = _needs_bisect(right, f_lim)
          return [
              _IntermediateResult(
                  iteration=curr.iteration
                  + tf.cast(~curr.stopped, tf.int32),
                  stopped=curr.stopped
                  | failed
                  | finished
                  | bracketed
                  | needs_bisect,
                  failed=failed,
                  num_evals=curr.num_evals + 1,
                  left=left,
                  right=right,
              )
          ]
  
      bracket_result = tf.while_loop(
          cond=_loop_cond, body=_loop_body, loop_vars=[initial_args]
      )[0]
  
      # Reset the input for the _bisect loop.
      # - `stopped` is now whether `_bisect` should feel already stopped
      # - `left` is adjusted
      #   - to equal `right` if we already found a minimum (i.e., -inf)
      #   - to the starting point if we're actually bisecting, per step B2
      finished = _is_negative_inf(bracket_result.right.f)
      bracketed = _is_rising(bracket_result.right)
      needs_bisect = _needs_bisect(bracket_result.right, f_lim)
      stopped = (
          already_stopped | bracket_result.failed | finished | bracketed
      )
      left = val_where(
          finished,
          bracket_result.right,
          val_where(
              needs_bisect, search_interval.left, bracket_result.left
          ),
      )
      bisect_args = bracket_result._replace(stopped=stopped, left=left)
      return _bisect(value_and_gradients_function, bisect_args, f_lim)
  
file_path: probability/tensorflow_probability/python/optimizer/linesearch/internal/hager_zhang_lib.py
imports: import collections
  import tensorflow.compat.v2
  from tensorflow_probability.python.internal import broadcast_util
  from tensorflow_probability.python.internal import prefer_static
label_desc: Use image node_id 6 for calling a global function with example usage: bracket(value_and_gradients_function, search_interval, f_lim, max_iterations, expansion_param) and returns: _bisect, tf, list