class_docstr: MC-based batch Log Noisy Expected Improvement.
  
  This function does not assume a `best_f` is known (which would require
  noiseless observations). Instead, it uses samples from the joint posterior
  over the `q` test points and previously observed points. A smooth approximation
  to the canonical improvement over previously observed points is computed
  for each sample and the logarithm of the average is returned.
  
  See [Ament2023logei]_ for details. Formally,
  
  `qLogNEI(X) ~ log(qNEI(X)) = Log E(max(max Y - max Y_baseline, 0))`,
  
  where `(Y, Y_baseline) ~ f((X, X_baseline)), X = (x_1,...,x_q)`.
  
  Example:
      >>> model = SingleTaskGP(train_X, train_Y)
      >>> sampler = SobolQMCNormalSampler(1024)
      >>> qLogNEI = qLogNoisyExpectedImprovement(model, train_X, sampler)
      >>> acqval = qLogNEI(test_X)
class_docstr_tok: ["MC-based","batch","Log","Noisy","Expected","Improvement",".","This","function","does","not","assume","a","`","best_f","`","is","known","(","which","would","require","noiseless","observations",")",".","Instead",",","it","uses","samples","from","the","joint","posterior","over","the","`","q","`","test","points","and","previously","observed","points",".","A","smooth","approximation","to","the","canonical","improvement","over","previously","observed","points","is","computed","for","each","sample","and","the","logarithm","of","the","average","is","returned",".","See","[","Ament2023logei","]","_","for","details",".","Formally",",","`","qLogNEI","(","X",")","~","log","(","qNEI","(","X",")",")","=","Log","E","(","max","(","max","Y","-","max","Y_baseline",",","0",")",")","`",",","where","`","(","Y",",","Y_baseline",")","~","f","(","(","X",",","X_baseline",")",")",",","X","=","(","x_1",",","...",",","x_q",")","`",".","Example",":",">",">",">","model","=","SingleTaskGP","(","train_X",",","train_Y",")",">",">",">","sampler","=","SobolQMCNormalSampler","(","1024",")",">",">",">","qLogNEI","=","qLogNoisyExpectedImprovement","(","model",",","train_X",",","sampler",")",">",">",">","acqval","=","qLogNEI","(","test_X",")"]
docstr: Compute samples at new points, using the cached root decomposition.
  
  Args:
      X: A `batch_shape x q x d`-dim tensor of inputs.
  
  Returns:
      A two-tuple `(samples, obj)`, where `samples` is a tensor of posterior
      samples with shape `sample_shape x batch_shape x q x m`, and `obj` is a
      tensor of MC objective values with shape `sample_shape x batch_shape x q`.
docstr_tok: ["Compute","samples","at","new","points",",","using","the","cached","root","decomposition",".","Args",":","X",":","A","`","batch_shape","x","q","x","d","`","-dim","tensor","of","inputs",".","Returns",":","A","two-tuple","`","(","samples",",","obj",")","`",",","where","`","samples","`","is","a","tensor","of","posterior","samples","with","shape","`","sample_shape","x","batch_shape","x","q","x","m","`",",","and","`","obj","`","is","a","tensor","of","MC","objective","values","with","shape","`","sample_shape","x","batch_shape","x","q","`","."]
code: def _get_samples_and_objectives(
      self, X: Tensor
  ) -> Tuple[Tensor, Tensor]:
      r"""Compute samples at new points, using the cached root decomposition.
  
      Args:
          X: A `batch_shape x q x d`-dim tensor of inputs.
  
      Returns:
          A two-tuple `(samples, obj)`, where `samples` is a tensor of posterior
          samples with shape `sample_shape x batch_shape x q x m`, and `obj` is a
          tensor of MC objective values with shape `sample_shape x batch_shape x q`.
      """
      n_baseline, q = self.X_baseline.shape[-2], X.shape[-2]
      X_full = torch.cat(
          [match_batch_shape(self.X_baseline, X), X], dim=-2
      )
      # TODO: Implement more efficient way to compute posterior over both training and
      # test points in GPyTorch (https://github.com/cornellius-gp/gpytorch/issues/567)
      posterior = self.model.posterior(
          X_full, posterior_transform=self.posterior_transform
      )
      if not self._cache_root:
          samples_full = super().get_posterior_samples(posterior)
          obj_full = self.objective(samples_full, X=X_full)
          # assigning baseline buffers so `best_f` can be computed in _sample_forward
          self.baseline_samples, samples = samples_full.split(
              [n_baseline, q], dim=-2
          )
          self.baseline_obj, obj = obj_full.split(
              [n_baseline, q], dim=-1
          )
          return samples, obj
  
      # handle one-to-many input transforms
      n_plus_q = X_full.shape[-2]
      n_w = posterior._extended_shape()[-2] // n_plus_q
      q_in = q * n_w
      self._set_sampler(q_in=q_in, posterior=posterior)
      samples = self._get_f_X_samples(posterior=posterior, q_in=q_in)
      obj = self.objective(samples, X=X_full[..., -q:, :])
      return samples, obj
  
imports: from __future__ import annotations
  from copy import deepcopy
  from functools import partial
  from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union
  import torch
  from botorch.acquisition.cached_cholesky import CachedCholeskyMCSamplerMixin
  from botorch.acquisition.monte_carlo import SampleReducingMCAcquisitionFunction
  from botorch.acquisition.objective import ConstrainedMCObjective, MCAcquisitionObjective, PosteriorTransform
  from botorch.acquisition.utils import compute_best_feasible_objective, prune_inferior_points
  from botorch.exceptions.errors import BotorchError
  from botorch.models.model import Model
  from botorch.sampling.base import MCSampler
  from botorch.utils.safe_math import fatmax, log_fatplus, log_softplus, logmeanexp, smooth_amax
  from botorch.utils.transforms import match_batch_shape
  from torch import Tensor
label_desc: Use image node_id 5 for calling the qLogNoisyExpectedImprovement obj's underlying member method code with example usage: obj._get_samples_and_objectives(X) and returns: samples,  obj, samples,  obj