code: def update(
      value_and_gradients_function,
      val_left,
      val_right,
      val_trial,
      f_lim,
      active=None,
  ):
      """Squeezes a bracketing interval containing the minimum.
  
      Given an interval which brackets a minimum and a point in that interval,
      finds a smaller nested interval which also brackets the minimum. If the
      supplied point does not lie in the bracketing interval, the current interval
      is returned.
  
      The following description is given in terms of individual points evaluated on
      a line function to be minimized. Note, however, the implementation also
      accepts batches of points allowing to minimize multiple line functions at
      once. See details on the docstring of `value_and_gradients_function` below.
  
      The requirement of the interval bracketing a minimum is expressed through the
      opposite slope conditions. Assume the left end point is 'a', the right
      end point is 'b', the function to be minimized is 'f' and the derivative is
      'df'. The update procedure relies on the following conditions being satisfied:
  
      '''
        f(a) <= f(0) + epsilon   (1)
        df(a) < 0                (2)
        df(b) > 0                (3)
      '''
  
      In the first condition, epsilon is a small positive constant. The condition
      demands that the function at the left end point be not much bigger than the
      starting point (i.e. 0). This is an easy to satisfy condition because by
      assumption, we are in a direction where the function value is decreasing.
      The second and third conditions together demand that there is at least one
      zero of the derivative in between a and b.
  
      In addition to the interval, the update algorithm requires a third point to
      be supplied. Usually, this point would lie within the interval [a, b]. If the
      point is outside this interval, the current interval is returned. If the
      point lies within the interval, the behaviour of the function and derivative
      value at this point is used to squeeze the original interval in a manner that
      preserves the opposite slope conditions.
  
      For further details of this component, see the procedure U0-U3 on page 123 of
      the [Hager and Zhang (2006)][2] article.
  
      Note that this function does not explicitly verify whether the opposite slope
      conditions are satisfied for the supplied interval. It is assumed that this
      is so.
  
      Args:
        value_and_gradients_function: A Python callable that accepts a real scalar
          tensor and returns an object that can be converted to a namedtuple.
          The namedtuple should have fields 'f' and 'df' that correspond to scalar
          tensors of real dtype containing the value of the function and its
          derivative at that point. The other namedtuple fields, if present,
          should be tensors or sequences (possibly nested) of tensors.
          In usual optimization application, this function would be generated by
          projecting the multivariate objective function along some specific
          direction. The direction is determined by some other procedure but should
          be a descent direction (i.e. the derivative of the projected univariate
          function must be negative at 0.).
          Alternatively, the function may represent the 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 the fields 'f' and 'df' in the returned
          namedtuple should each be a tensor of shape [n], with the corresponding
          function values and derivatives at the input points.
        val_left: Return value of value_and_gradients_function at the left
          end point of the bracketing interval (labelles 'a' above).
        val_right: Return value of value_and_gradients_function at the right
          end point of the bracketing interval (labelles 'b' above).
        val_trial: Return value of value_and_gradients_function at the trial point
          to be used to shrink the 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.
        active: optional boolean `Tensor` of shape [n]. Relevant in batching mode
          only, indicates batch members on which the update procedure should be
          applied. On non-active members the current left/right interval is returned
          unmodified.
  
      Returns:
        A namedtuple containing the following fields:
          iteration: An int32 scalar `Tensor`. The number of iterations performed
            by the bisect algorithm.
          stopped: A boolean `Tensor` of shape [n]. True for those batch members
            where the bisection algorithm terminated.
          failed: A boolean `Tensor` of shape [n]. True for those batch members
            where an error was encountered.
          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.
      """
      # We should only update if the trial point is within the interval.
      within_range = (val_left.x < val_trial.x) & (
          val_trial.x < val_right.x
      )
      if active is not None:
          within_range = within_range & active
  
      # The new point is a valid left end point if it has negative slope
      # and the value at the point is not too large.
      valid_left = (val_trial.df < 0) & (val_trial.f <= f_lim)
  
      # If the trial point has a negative slope but the value at that point
      # is too high, bisect can narrow down an interval between the current left
      # and the trial point.
      needs_bisect = (
          within_range & (val_trial.df < 0) & (val_trial.f > f_lim)
      )
  
      # Note that if `~valid_left` it is because either:
      # - the slope at the trial point is positive, so it is a valid right
      #   point, or
      # - the needs_bisect condition is true.
      # In both cases we want to keep the current left and replace right
      # with the trial point.
      left = val_where(within_range & valid_left, val_trial, val_left)
      right = val_where(
          within_range & ~valid_left, val_trial, val_right
      )
  
      bisect_args = _IntermediateResult(
          iteration=tf.convert_to_tensor(0),
          stopped=~needs_bisect,
          failed=tf.zeros_like(within_range),  # i.e. all false.
          num_evals=tf.convert_to_tensor(0),
          left=left,
          right=right,
      )
      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 5 for calling a global function with example usage: update(value_and_gradients_function, val_left, val_right, val_trial, f_lim, active) and returns: _bisect