class_docstr: Sample from a set of points according to a tempered acquisition value.
  
  Given an acquisition function `acq_func`, this sampling strategies draws
  samples from a `batch_shape x N x d`-dim tensor `X` according to a multinomial
  distribution over its indices given by
  
      weight(X[..., i, :]) ~ exp(eta * standardize(acq_func(X[..., i, :])))
  
  where `standardize(Y)` standardizes `Y` to zero mean and unit variance. As the
  temperature parameter `eta -> 0`, this approaches uniform sampling, while as
  `eta -> infty`, this approaches selecting the maximizer(s) of the acquisition
  function `acq_func`.
  
  Example:
      >>> UCB = UpperConfidenceBound(model, beta=0.1)
      >>> BMUCB = BoltzmannSampling(UCB, eta=0.5)
      >>> X = torch.rand(2, 100, 3)
      >>> sampled_X = BMUCB(X, num_samples=5)
docstr: Sample from a tempered value of the acquisition function value.
  
  Args:
      X: A `batch_shape x N x d`-dim Tensor from which to sample (in the `N`
          dimension) according to the maximum posterior value under the objective.
          Note that if a batched model is used in the underlying acquisition
          function, then its batch shape must be broadcastable to `batch_shape`.
      num_samples: The number of samples to draw.
  
  Returns:
      A `batch_shape x num_samples x d`-dim Tensor of samples from `X`, where
      `X[..., i, :]` is the `i`-th sample.
docstr_tok: ["Sample","from","a","tempered","value","of","the","acquisition","function","value",".","Args",":","X",":","A","`","batch_shape","x","N","x","d","`","-dim","Tensor","from","which","to","sample","(","in","the","`","N","`","dimension",")","according","to","the","maximum","posterior","value","under","the","objective",".","Note","that","if","a","batched","model","is","used","in","the","underlying","acquisition","function",",","then","its","batch","shape","must","be","broadcastable","to","`","batch_shape","`",".","num_samples",":","The","number","of","samples","to","draw",".","Returns",":","A","`","batch_shape","x","num_samples","x","d","`","-dim","Tensor","of","samples","from","`","X","`",",","where","`","X","[","...",",","i",",",":","]","`","is","the","`","i","`","-th","sample","."]
code: def forward(self, X: Tensor, num_samples: int = 1) -> Tensor:
      r"""Sample from a tempered value of the acquisition function value.
  
      Args:
          X: A `batch_shape x N x d`-dim Tensor from which to sample (in the `N`
              dimension) according to the maximum posterior value under the objective.
              Note that if a batched model is used in the underlying acquisition
              function, then its batch shape must be broadcastable to `batch_shape`.
          num_samples: The number of samples to draw.
  
      Returns:
          A `batch_shape x num_samples x d`-dim Tensor of samples from `X`, where
          `X[..., i, :]` is the `i`-th sample.
      """
      # TODO: Can we get the model batch shape property from the model?
      # we move the `N` dimension to the front for evaluating the acquisition function
      # so that X_eval has shape `N x batch_shape x 1 x d`
      X_eval = X.permute(-2, *range(X.ndim - 2), -1).unsqueeze(-2)
      acqval = self.acq_func(X_eval)  # N x batch_shape
      # now move the `N` dimension back (this is the number of categories)
      acqval = acqval.permute(
          *range(1, X.ndim - 1), 0
      )  # batch_shape x N
      weights = torch.exp(
          self.eta * standardize(acqval)
      )  # batch_shape x N
      idcs = batched_multinomial(
          weights=weights,
          num_samples=num_samples,
          replacement=self.replacement,
      )
      # now do some gathering acrobatics to select the right elements from X
      return torch.gather(
          X, -2, idcs.unsqueeze(-1).expand(*idcs.shape, X.size(-1))
      )
  
imports: from __future__ import annotations
  from abc import ABC, abstractmethod
  from typing import Any, Optional, Union
  import torch
  from botorch.acquisition.acquisition import AcquisitionFunction
  from botorch.acquisition.objective import IdentityMCObjective, MCAcquisitionObjective, PosteriorTransform
  from botorch.generation.utils import _flip_sub_unique
  from botorch.models.model import Model
  from botorch.models.model_list_gp_regression import ModelListGP
  from botorch.models.multitask import MultiTaskGP
  from botorch.utils.sampling import batched_multinomial
  from botorch.utils.transforms import standardize
  from torch import Tensor
  from torch.nn import Module
label_desc: Use image node_id 2 for calling the BoltzmannSampling obj's underlying member method code with example usage: obj.forward(X, num_samples) and returns: torch