docstr: Expected Hypervolume Improvement supporting m>=2 outcomes.
  
  This implements the computes EHVI using the algorithm from [Yang2019]_, but
  additionally computes gradients via auto-differentiation as proposed by
  [Daulton2020qehvi]_.
  
  Note: this is currently inefficient in two ways due to the binary partitioning
  algorithm that we use for the box decomposition:
  
      - We have more boxes in our decomposition
      - If we used a box decomposition that used `inf` as the upper bound for
          the last dimension *in all hypercells*, then we could reduce the number
          of terms we need to compute from 2^m to 2^(m-1). [Yang2019]_ do this
          by using DKLV17 and LKF17 for the box decomposition.
  
  TODO: Use DKLV17 and LKF17 for the box decomposition as in [Yang2019]_ for
  greater efficiency.
  
  TODO: Add support for outcome constraints.
  
  Example:
      >>> model = SingleTaskGP(train_X, train_Y)
      >>> ref_point = [0.0, 0.0]
      >>> EHVI = ExpectedHypervolumeImprovement(model, ref_point, partitioning)
      >>> ehvi = EHVI(test_X)
  
  Args:
      model: A fitted model.
      ref_point: A list with `m` elements representing the reference point (in the
          outcome space) w.r.t. to which compute the hypervolume. This is a
          reference point for the objective values (i.e. after applying
          `objective` to the samples).
      partitioning: A `NondominatedPartitioning` module that provides the non-
          dominated front and a partitioning of the non-dominated space in hyper-
          rectangles.
      posterior_transform: A `PosteriorTransform`.
docstr_tok: ["Expected","Hypervolume","Improvement","supporting","m",">","=2","outcomes",".","This","implements","the","computes","EHVI","using","the","algorithm","from","[","Yang2019","]","_",",","but","additionally","computes","gradients","via","auto-differentiation","as","proposed","by","[","Daulton2020qehvi","]","_",".","Note",":","this","is","currently","inefficient","in","two","ways","due","to","the","binary","partitioning","algorithm","that","we","use","for","the","box","decomposition",":","-","We","have","more","boxes","in","our","decomposition","-","If","we","used","a","box","decomposition","that","used","`","inf","`","as","the","upper","bound","for","the","last","dimension","*","in","all","hypercells","*",",","then","we","could","reduce","the","number","of","terms","we","need","to","compute","from","2^m","to","2^","(","m-1",")",".","[","Yang2019","]","_","do","this","by","using","DKLV17","and","LKF17","for","the","box","decomposition",".","TODO",":","Use","DKLV17","and","LKF17","for","the","box","decomposition","as","in","[","Yang2019","]","_","for","greater","efficiency",".","TODO",":","Add","support","for","outcome","constraints",".","Example",":",">",">",">","model","=","SingleTaskGP","(","train_X",",","train_Y",")",">",">",">","ref_point","=","[","0.0",",","0.0","]",">",">",">","EHVI","=","ExpectedHypervolumeImprovement","(","model",",","ref_point",",","partitioning",")",">",">",">","ehvi","=","EHVI","(","test_X",")","Args",":","model",":","A","fitted","model",".","ref_point",":","A","list","with","`","m","`","elements","representing","the","reference","point","(","in","the","outcome","space",")","w.r.t",".","to","which","compute","the","hypervolume",".","This","is","a","reference","point","for","the","objective","values","(","i.e",".","after","applying","`","objective","`","to","the","samples",")",".","partitioning",":","A","`","NondominatedPartitioning","`","module","that","provides","the","non-","dominated","front","and","a","partitioning","of","the","non-dominated","space","in","hyper-","rectangles",".","posterior_transform",":","A","`","PosteriorTransform","`","."]
code: def __init__(
      self,
      model: Model,
      ref_point: List[float],
      partitioning: NondominatedPartitioning,
      posterior_transform: Optional[PosteriorTransform] = None,
      **kwargs,
  ) -> None:
      r"""Expected Hypervolume Improvement supporting m>=2 outcomes.
  
      This implements the computes EHVI using the algorithm from [Yang2019]_, but
      additionally computes gradients via auto-differentiation as proposed by
      [Daulton2020qehvi]_.
  
      Note: this is currently inefficient in two ways due to the binary partitioning
      algorithm that we use for the box decomposition:
  
          - We have more boxes in our decomposition
          - If we used a box decomposition that used `inf` as the upper bound for
              the last dimension *in all hypercells*, then we could reduce the number
              of terms we need to compute from 2^m to 2^(m-1). [Yang2019]_ do this
              by using DKLV17 and LKF17 for the box decomposition.
  
      TODO: Use DKLV17 and LKF17 for the box decomposition as in [Yang2019]_ for
      greater efficiency.
  
      TODO: Add support for outcome constraints.
  
      Example:
          >>> model = SingleTaskGP(train_X, train_Y)
          >>> ref_point = [0.0, 0.0]
          >>> EHVI = ExpectedHypervolumeImprovement(model, ref_point, partitioning)
          >>> ehvi = EHVI(test_X)
  
      Args:
          model: A fitted model.
          ref_point: A list with `m` elements representing the reference point (in the
              outcome space) w.r.t. to which compute the hypervolume. This is a
              reference point for the objective values (i.e. after applying
              `objective` to the samples).
          partitioning: A `NondominatedPartitioning` module that provides the non-
              dominated front and a partitioning of the non-dominated space in hyper-
              rectangles.
          posterior_transform: A `PosteriorTransform`.
      """
      # TODO: we could refactor this __init__ logic into a
      # HypervolumeAcquisitionFunction Mixin
      if len(ref_point) != partitioning.num_outcomes:
          raise ValueError(
              "The length of the reference point must match the number of outcomes. "
              f"Got ref_point with {len(ref_point)} elements, but expected "
              f"{partitioning.num_outcomes}."
          )
      ref_point = torch.tensor(
          ref_point,
          dtype=partitioning.pareto_Y.dtype,
          device=partitioning.pareto_Y.device,
      )
      better_than_ref = (partitioning.pareto_Y > ref_point).all(dim=1)
      if (
          not better_than_ref.any()
          and partitioning.pareto_Y.shape[0] > 0
      ):
          raise ValueError(
              "At least one pareto point must be better than the reference point."
          )
      super().__init__(
          model=model, posterior_transform=posterior_transform, **kwargs
      )
      self.register_buffer("ref_point", ref_point)
      self.partitioning = partitioning
      cell_bounds = self.partitioning.get_hypercell_bounds()
      self.register_buffer("cell_lower_bounds", cell_bounds[0])
      self.register_buffer("cell_upper_bounds", cell_bounds[1])
      # create indexing tensor of shape `2^m x m`
      self._cross_product_indices = torch.tensor(
          list(product(*[[0, 1] for _ in range(ref_point.shape[0])])),
          dtype=torch.long,
          device=ref_point.device,
      )
      self.normal = Normal(0, 1)
  
imports: from __future__ import annotations
  from abc import abstractmethod
  from itertools import product
  from typing import List, Optional
  import torch
  from botorch.acquisition.acquisition import AcquisitionFunction
  from botorch.acquisition.objective import PosteriorTransform
  from botorch.exceptions.errors import UnsupportedError
  from botorch.models.model import Model
  from botorch.utils.multi_objective.box_decompositions.non_dominated import NondominatedPartitioning
  from botorch.utils.transforms import t_batch_mode_transform
  from torch import Tensor
  from torch.distributions import Normal
label_desc: Use image node_id 1 to create a new ExpectedHypervolumeImprovement object from inherited base classes: MultiObjectiveAnalyticAcquisitionFunction with example: obj = ExpectedHypervolumeImprovement(model, ref_point, partitioning, posterior_transform)