class_docstr: The acquisition function for the multi-objective 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 Pareto optimal input-output pairs.
  
  See [Tu2022]_ for a discussion on the estimation procedure.
  
  NOTES:
  (i) The estimated acquisition value could be negative.
  
  (ii) The lower bound batch acquisition function might not be monotone in the
  sense that adding more elements to the batch does not necessarily increase the
  acquisition value. Specifically, the acquisition value can become smaller when
  more inputs are added.
class_docstr_tok: ["The","acquisition","function","for","the","multi-objective","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","Pareto","optimal","input-output","pairs",".","See","[","Tu2022","]","_","for","a","discussion","on","the","estimation","procedure",".","NOTES",":","(","i",")","The","estimated","acquisition","value","could","be","negative",".","(","ii",")","The","lower","bound","batch","acquisition","function","might","not","be","monotone","in","the","sense","that","adding","more","elements","to","the","batch","does","not","necessarily","increase","the","acquisition","value",".","Specifically",",","the","acquisition","value","can","become","smaller","when","more","inputs","are","added","."]
docstr: Compute the posterior statistics.
  Args:
      X: A `batch_shape x q x d`-dim Tensor of inputs.
  
  Returns:
      A dictionary containing the posterior variables used to estimate the
      entropy.
  
      - "initial_entropy": A `batch_shape`-dim Tensor containing the entropy of
          the Gaussian random variable `p(Y| X, D_n)`.
      - "posterior_mean": A `batch_shape x num_pareto_samples x q x 1 x M`-dim
          Tensor containing the posterior mean at the input `X`.
      - "posterior_variance": A `batch_shape x num_pareto_samples x q x 1 x M`
          -dim Tensor containing the posterior variance at the input `X`
          excluding the observation noise.
      - "observation_noise": A `batch_shape x num_pareto_samples x q x 1 x M`
          -dim Tensor containing the observation noise at the input `X`.
      - "posterior_with_noise": The posterior distribution at `X` which
          includes the observation noise. This is used to compute the marginal
          log-probabilities with respect to `p(y| x, D_n)` for `x` in `X`.
docstr_tok: ["Compute","the","posterior","statistics",".","Args",":","X",":","A","`","batch_shape","x","q","x","d","`","-dim","Tensor","of","inputs",".","Returns",":","A","dictionary","containing","the","posterior","variables","used","to","estimate","the","entropy",".","-","``","initial_entropy","''",":","A","`","batch_shape","`","-dim","Tensor","containing","the","entropy","of","the","Gaussian","random","variable","`","p","(","Y|","X",",","D_n",")","`",".","-","``","posterior_mean","''",":","A","`","batch_shape","x","num_pareto_samples","x","q","x","1","x","M","`","-dim","Tensor","containing","the","posterior","mean","at","the","input","`","X","`",".","-","``","posterior_variance","''",":","A","`","batch_shape","x","num_pareto_samples","x","q","x","1","x","M","`","-dim","Tensor","containing","the","posterior","variance","at","the","input","`","X","`","excluding","the","observation","noise",".","-","``","observation_noise","''",":","A","`","batch_shape","x","num_pareto_samples","x","q","x","1","x","M","`","-dim","Tensor","containing","the","observation","noise","at","the","input","`","X","`",".","-","``","posterior_with_noise","''",":","The","posterior","distribution","at","`","X","`","which","includes","the","observation","noise",".","This","is","used","to","compute","the","marginal","log-probabilities","with","respect","to","`","p","(","y|","x",",","D_n",")","`","for","`","x","`","in","`","X","`","."]
code: def _compute_posterior_statistics(
      self, X: Tensor
  ) -> dict[str, Union[Tensor, GPyTorchPosterior]]:
      r"""Compute the posterior statistics.
      Args:
          X: A `batch_shape x q x d`-dim Tensor of inputs.
  
      Returns:
          A dictionary containing the posterior variables used to estimate the
          entropy.
  
          - "initial_entropy": A `batch_shape`-dim Tensor containing the entropy of
              the Gaussian random variable `p(Y| X, D_n)`.
          - "posterior_mean": A `batch_shape x num_pareto_samples x q x 1 x M`-dim
              Tensor containing the posterior mean at the input `X`.
          - "posterior_variance": A `batch_shape x num_pareto_samples x q x 1 x M`
              -dim Tensor containing the posterior variance at the input `X`
              excluding the observation noise.
          - "observation_noise": A `batch_shape x num_pareto_samples x q x 1 x M`
              -dim Tensor containing the observation noise at the input `X`.
          - "posterior_with_noise": The posterior distribution at `X` which
              includes the observation noise. This is used to compute the marginal
              log-probabilities with respect to `p(y| x, D_n)` for `x` in `X`.
      """
      tkwargs = {"dtype": X.dtype, "device": X.device}
      CLAMP_LB = torch.finfo(tkwargs["dtype"]).eps
  
      # Compute the prior entropy term depending on `X`.
      initial_posterior_plus_noise = self.initial_model.posterior(
          X, observation_noise=True
      )
  
      # Additional constant term.
      add_term = (
          0.5
          * self.model.num_outputs
          * (1 + torch.log(2 * pi * torch.ones(1, **tkwargs)))
      )
      # The variance initially has shape `batch_shape x (q*M) x (q*M)`
      # prior_entropy has shape `batch_shape`.
      initial_entropy = add_term + 0.5 * torch.logdet(
          initial_posterior_plus_noise.mvn.covariance_matrix
      )
  
      posterior_statistics = {"initial_entropy": initial_entropy}
  
      # Compute the posterior entropy term.
      conditional_posterior_with_noise = (
          self.conditional_model.posterior(
              X.unsqueeze(-2).unsqueeze(-3), observation_noise=True
          )
      )
  
      # `batch_shape x num_pareto_samples x q x 1 x M`
      post_mean = conditional_posterior_with_noise.mean.swapaxes(-4, -3)
      post_var_with_noise = (
          conditional_posterior_with_noise.variance.clamp_min(
              CLAMP_LB
          ).swapaxes(-4, -3)
      )
  
      # TODO: This computes the observation noise via a second evaluation of the
      #   posterior. This step could be done better.
      conditional_posterior = self.conditional_model.posterior(
          X.unsqueeze(-2).unsqueeze(-3), observation_noise=False
      )
  
      # `batch_shape x num_pareto_samples x q x 1 x M`
      post_var = conditional_posterior.variance.clamp_min(
          CLAMP_LB
      ).swapaxes(-4, -3)
      obs_noise = (post_var_with_noise - post_var).clamp_min(CLAMP_LB)
  
      posterior_statistics["posterior_mean"] = post_mean
      posterior_statistics["posterior_variance"] = post_var
      posterior_statistics["observation_noise"] = obs_noise
      posterior_statistics[
          "posterior_with_noise"
      ] = conditional_posterior_with_noise
  
      return posterior_statistics
  
imports: from __future__ import annotations
  from abc import abstractmethod
  from math import pi
  from typing import Any, Optional, Tuple, Union
  import torch
  from botorch import settings
  from botorch.acquisition.acquisition import AcquisitionFunction, MCSamplerMixin
  from botorch.exceptions.errors import UnsupportedError
  from botorch.models.model import Model
  from botorch.models.model_list_gp_regression import ModelListGP
  from botorch.models.utils import fantasize
  from botorch.posteriors.gpytorch import GPyTorchPosterior
  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 qLowerBoundMultiObjectiveJointEntropySearch obj's underlying member method code with example usage: obj._compute_posterior_statistics(X) and returns: posterior_statistics