class_docstr: The acquisition function for the Joint Entropy Search, where the batches
  `q > 1` are supported through the lower bound formulation.
  
  This acquisition function computes the mutual information between the observation
  at a candidate point `X` and the optimal input-output pair.
  
  See [Tu2022joint]_ for a discussion on the estimation procedure.
class_docstr_tok: ["The","acquisition","function","for","the","Joint","Entropy","Search",",","where","the","batches","`","q",">","1","`","are","supported","through","the","lower","bound","formulation",".","This","acquisition","function","computes","the","mutual","information","between","the","observation","at","a","candidate","point","`","X","`","and","the","optimal","input-output","pair",".","See","[","Tu2022joint","]","_","for","a","discussion","on","the","estimation","procedure","."]
docstr: Evaluates qJointEntropySearch at the design points `X`.
  
  Args:
      X: A `batch_shape x q x d`-dim Tensor of `batch_shape` t-batches with `q`
      `d`-dim design points each.
  
  Returns:
      A `batch_shape`-dim Tensor of acquisition values at the given design
      points `X`.
docstr_tok: ["Evaluates","qJointEntropySearch","at","the","design","points","`","X","`",".","Args",":","X",":","A","`","batch_shape","x","q","x","d","`","-dim","Tensor","of","`","batch_shape","`","t-batches","with","`","q","`","`","d","`","-dim","design","points","each",".","Returns",":","A","`","batch_shape","`","-dim","Tensor","of","acquisition","values","at","the","given","design","points","`","X","`","."]
code: def forward(self, X: Tensor) -> Tensor:
      r"""Evaluates qJointEntropySearch at the design points `X`.
  
      Args:
          X: A `batch_shape x q x d`-dim Tensor of `batch_shape` t-batches with `q`
          `d`-dim design points each.
  
      Returns:
          A `batch_shape`-dim Tensor of acquisition values at the given design
          points `X`.
      """
      if self.estimation_type == "LB":
          res = self._compute_lower_bound_information_gain(X)
      elif self.estimation_type == "MC":
          res = self._compute_monte_carlo_information_gain(X)
      else:
          raise ValueError(
              f"Estimation type {self.estimation_type} is not valid. "
              f"Please specify any of {ESTIMATION_TYPES}"
          )
      return res
  
imports: from __future__ import annotations
  import warnings
  from math import log, pi
  from typing import Optional
  import torch
  from botorch import settings
  from botorch.acquisition.acquisition import AcquisitionFunction, MCSamplerMixin
  from botorch.acquisition.objective import PosteriorTransform
  from botorch.models.fully_bayesian import SaasFullyBayesianSingleTaskGP
  from botorch.models.gp_regression import MIN_INFERRED_NOISE_LEVEL
  from botorch.models.model import Model
  from botorch.models.utils import check_no_nans, fantasize
  from botorch.sampling.normal import SobolQMCNormalSampler
  from botorch.utils.transforms import concatenate_pending_points, t_batch_mode_transform
  from torch import Tensor
  from torch.distributions import Normal
label_desc: Use image node_id 2 for calling the qJointEntropySearch obj's underlying member method code with example usage: obj.forward(X) and returns: res