code: def permed_lsdds(
      k_all_c: tf.Tensor,
      x_perms: List[tf.Tensor],
      y_perms: List[tf.Tensor],
      H: tf.Tensor,
      H_lam_inv: Optional[tf.Tensor] = None,
      lam_rd_max: float = 0.2,
      return_unpermed: bool = False,
  ) -> Union[
      Tuple[tf.Tensor, tf.Tensor],
      Tuple[tf.Tensor, tf.Tensor, tf.Tensor],
  ]:
      """
      Compute LSDD estimates from kernel matrix across various ref and test window samples
  
      Parameters
      ----------
      k_all_c
          Kernel matrix of similarities between all samples and the kernel centers.
      x_perms
          List of B reference window index vectors
      y_perms
          List of B test window index vectors
      H
          Special (scaled) kernel matrix of similarities between kernel centers
      H_lam_inv
          Function of H corresponding to a particular regulariation parameter lambda.
          See Eqn 11 of Bu et al. (2017)
      lam_rd_max
          The maximum relative difference between two estimates of LSDD that the regularization parameter
          lambda is allowed to cause. Defaults to 0.2. Only relavent if H_lam_inv is not supplied.
      return_unpermed
          Whether or not to return value corresponding to unpermed order defined by k_all_c
  
      Returns
      -------
      Vector of B LSDD estimates for each permutation, H_lam_inv which may have been inferred, and optionally \
      the unpermed LSDD estimate.
      """
  
      # Compute (for each bootstrap) the average distance to each kernel center (Eqn 7)
      k_xc_perms = tf.stack(
          [tf.gather(k_all_c, x_inds) for x_inds in x_perms], axis=0
      )
      k_yc_perms = tf.stack(
          [tf.gather(k_all_c, y_inds) for y_inds in y_perms], axis=0
      )
      h_perms = tf.reduce_mean(k_xc_perms, axis=1) - tf.reduce_mean(
          k_yc_perms, axis=1
      )
  
      if H_lam_inv is None:
          # We perform the initialisation for multiple candidate lambda values and pick the largest
          # one for which the relative difference (RD) between two difference estimates is below lambda_rd_max.
          # See Appendix A
          candidate_lambdas = [
              1 / (4**i) for i in range(10)
          ]  # TODO: More principled selection
          H_plus_lams = tf.stack(
              [
                  H + tf.eye(H.shape[0], dtype=H.dtype) * can_lam
                  for can_lam in candidate_lambdas
              ],
              axis=0,
          )
          H_plus_lam_invs = tf.transpose(
              tf.linalg.inv(H_plus_lams), [1, 2, 0]
          )  # lambdas last
          omegas = tf.einsum(
              "jkl,bk->bjl", H_plus_lam_invs, h_perms
          )  # (Eqn 8)
          h_omegas = tf.einsum("bj,bjl->bl", h_perms, omegas)
          omega_H_omegas = tf.einsum(
              "bkl,bkl->bl", tf.einsum("bjl,jk->bkl", omegas, H), omegas
          )
          rds = tf.reduce_mean(1 - (omega_H_omegas / h_omegas), axis=0)
          less_than_rd_inds = tf.where(rds < lam_rd_max)
          if len(less_than_rd_inds) == 0:
              repeats = (
                  k_all_c.shape[0] - np.unique(k_all_c, axis=0).shape[0]
              )
              if repeats > 0:
                  msg = "Too many repeat instances for LSDD-based detection. \
                  Try using MMD-based detection instead"
              else:
                  msg = "Unknown error. Try using MMD-based detection instead"
              raise ValueError(msg)
          lambda_index = int(less_than_rd_inds[0])
          lam = candidate_lambdas[lambda_index]
          logger.info(
              f"Using lambda value of {lam:.2g} with RD of {float(rds[lambda_index]):.2g}"
          )
          H_plus_lam_inv = tf.linalg.inv(
              H + lam * tf.eye(H.shape[0], dtype=H.dtype)
          )
          H_lam_inv = 2 * H_plus_lam_inv - (
              tf.transpose(H_plus_lam_inv, [1, 0]) @ H @ H_plus_lam_inv
          )  # (blw Eqn 11)
  
      # Now to compute an LSDD estimate for each permutation
      lsdd_perms = tf.reduce_sum(
          h_perms
          * tf.transpose(
              H_lam_inv @ tf.transpose(h_perms, [1, 0]), [1, 0]
          ),
          axis=1,
      )  # (Eqn 11)
  
      if return_unpermed:
          n_x = x_perms[0].shape[0]
          h = tf.reduce_mean(k_all_c[:n_x], axis=0) - tf.reduce_mean(
              k_all_c[n_x:], axis=0
          )
          lsdd_unpermed = tf.reduce_sum(
              h[None, :] * tf.transpose(H_lam_inv @ h[:, None], [1, 0])
          )
          return lsdd_perms, H_lam_inv, lsdd_unpermed
      else:
          return lsdd_perms, H_lam_inv
  
file_path: alibi-detect/alibi_detect/utils/tensorflow/distance.py
imports: import logging
  import numpy
  import tensorflow
  from typing import Callable, Tuple, List, Optional, Union
label_desc: Use image node_id 6 for calling a global function with example usage: permed_lsdds(k_all_c, x_perms, y_perms, H, H_lam_inv, lam_rd_max, return_unpermed) and returns: lsdd_perms,  H_lam_inv,  lsdd_unpermed, lsdd_perms,  H_lam_inv