code: def permed_lsdds(
      k_all_c: torch.Tensor,
      x_perms: List[torch.Tensor],
      y_perms: List[torch.Tensor],
      H: torch.Tensor,
      H_lam_inv: Optional[torch.Tensor] = None,
      lam_rd_max: float = 0.2,
      return_unpermed: bool = False,
  ) -> Union[
      Tuple[torch.Tensor, torch.Tensor],
      Tuple[torch.Tensor, torch.Tensor, torch.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 = torch.stack(
          [k_all_c[x_inds] for x_inds in x_perms], 0
      )
      k_yc_perms = torch.stack(
          [k_all_c[y_inds] for y_inds in y_perms], 0
      )
      h_perms = k_xc_perms.mean(1) - k_yc_perms.mean(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 = torch.stack(
              [
                  H + torch.eye(H.shape[0], device=H.device) * can_lam
                  for can_lam in candidate_lambdas
              ],
              0,
          )
          H_plus_lam_invs = torch.inverse(H_plus_lams)
          H_plus_lam_invs = H_plus_lam_invs.permute(
              1, 2, 0
          )  # put lambdas in final axis
  
          omegas = torch.einsum(
              "jkl,bk->bjl", H_plus_lam_invs, h_perms
          )  # (Eqn 8)
          h_omegas = torch.einsum("bj,bjl->bl", h_perms, omegas)
          omega_H_omegas = torch.einsum(
              "bkl,bkl->bl",
              torch.einsum("bjl,jk->bkl", omegas, H),
              omegas,
          )
          rds = (1 - (omega_H_omegas / h_omegas)).mean(0)
          less_than_rd_inds = (rds < lam_rd_max).nonzero()
          if len(less_than_rd_inds) == 0:
              repeats = (
                  k_all_c.shape[0]
                  - torch.unique(k_all_c, dim=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)
          lam_index = less_than_rd_inds[0]
          lam = candidate_lambdas[lam_index]
          logger.info(
              f"Using lambda value of {lam:.2g} with RD of {float(rds[lam_index]):.2g}"
          )
          H_plus_lam_inv = H_plus_lam_invs[:, :, lam_index.item()]
          H_lam_inv = 2 * H_plus_lam_inv - (
              H_plus_lam_inv.transpose(0, 1) @ H @ H_plus_lam_inv
          )  # (below Eqn 11)
  
      # Now to compute an LSDD estimate for each permutation
      lsdd_perms = (
          h_perms
          * (H_lam_inv @ h_perms.transpose(0, 1)).transpose(0, 1)
      ).sum(
          -1
      )  # (Eqn 11)
  
      if return_unpermed:
          n_x = x_perms[0].shape[0]
          h = k_all_c[:n_x].mean(0) - k_all_c[n_x:].mean(0)
          lsdd_unpermed = (
              h[None, :] * (H_lam_inv @ h[:, None]).transpose(0, 1)
          ).sum()
          return lsdd_perms, H_lam_inv, lsdd_unpermed
      else:
          return lsdd_perms, H_lam_inv
  
file_path: alibi-detect/alibi_detect/utils/pytorch/distance.py
imports: import logging
  import torch
  from torch import nn
  import numpy
  from typing import Callable, List, Tuple, Optional, Union
label_desc: Use image node_id 5 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