code: def compute_local_upper_bounds(
      U: Tensor, Z: Tensor, z: Tensor
  ) -> Tuple[Tensor, Tensor]:
      r"""Compute local upper bounds.
  
      Note: this assumes minimization.
  
      This uses the incremental algorithm (Alg. 1) from [Lacour17]_.
  
      Args:
          U: A `n x m`-dim tensor containing the local upper bounds.
          Z: A `n x m x m`-dim tensor containing the defining points.
          z: A `m`-dim tensor containing the new point.
  
      Returns:
          2-element tuple containing:
  
          - A new `n' x m`-dim tensor local upper bounds.
          - A `n' x m x m`-dim tensor containing the defining points.
      """
      num_outcomes = U.shape[-1]
      z_dominates_U = (U > z).all(dim=-1)
      # Select upper bounds that are dominated by z.
      # These are the search zones that contain z.
      if not z_dominates_U.any():
          return U, Z
      A = U[z_dominates_U]
      A_Z = Z[z_dominates_U]
      P = []
      P_Z = []
      mask = torch.ones(num_outcomes, dtype=torch.bool, device=U.device)
      for j in range(num_outcomes):
          mask[j] = 0
          z_uj_max = A_Z[:, mask, j].max(dim=-1).values.view(-1)
          add_z = z[j] >= z_uj_max
          if add_z.any():
              u_j = A[add_z].clone()
              u_j[:, j] = z[j]
              P.append(u_j)
              A_Z_filtered = A_Z[add_z]
              Z_ku = A_Z_filtered[:, mask]
              lt_zj = Z_ku[..., j] <= z[j]
              P_uj = torch.zeros(
                  u_j.shape[0],
                  num_outcomes,
                  num_outcomes,
                  dtype=U.dtype,
                  device=U.device,
              )
              P_uj[:, mask] = Z_ku[lt_zj].view(
                  P_uj.shape[0], num_outcomes - 1, -1
              )
              P_uj[:, ~mask] = z
              P_Z.append(P_uj)
          mask[j] = 1
      # filter out elements of U that are in A
      not_z_dominates_U = ~z_dominates_U
      U = U[not_z_dominates_U]
      # remaining indices
      Z = Z[not_z_dominates_U]
      if len(P) > 0:
          # add points from P_Z
          Z = torch.cat([Z, *P_Z], dim=0)
          # return elements in P or elements in (U that are not in A)
          U = torch.cat([U, *P], dim=-2)
      return U, Z
  
file_path: botorch/botorch/utils/multi_objective/box_decompositions/utils.py
imports: from typing import Optional, Tuple
  import torch
  from botorch.exceptions.errors import BotorchTensorDimensionError, UnsupportedError
  from botorch.utils.multi_objective.pareto import is_non_dominated
  from torch import Size, Tensor
label_desc: Use image node_id 3 for calling a global function with example usage: compute_local_upper_bounds(U, Z, z) and returns: U,  Z, U,  Z