class_docstr: MVaR Approximation based on Random Scalarizations as introduced
  in [Daulton2022MARS]_.
  
  This approximates MVaR via VaR of Chebyshev scalarizations, where each
  scalarization corresponds to a point in the MVaR set. As implemented,
  this uses one set of scalarization weights to approximate a single MVaR value.
  Note that due to the normalization within the Chebyshev scalarization,
  the output of this risk measure may not be on the same scale as its inputs.
docstr: Get normalization bounds for scalarizations.
  
  Args:
      Y: A `n x m`-dim tensor of outcomes.
      ref_point: The reference point.
  
  Returns:
      A `2 x m`-dim tensor containing the normalization bounds.
docstr_tok: ["Get","normalization","bounds","for","scalarizations",".","Args",":","Y",":","A","`","n","x","m","`","-dim","tensor","of","outcomes",".","ref_point",":","The","reference","point",".","Returns",":","A","`","2","x","m","`","-dim","tensor","containing","the","normalization","bounds","."]
code: def _get_Y_normalization_bounds(
      Y: Tensor,
      ref_point: Optional[Tensor] = None,
  ) -> Tensor:
      r"""Get normalization bounds for scalarizations.
  
      Args:
          Y: A `n x m`-dim tensor of outcomes.
          ref_point: The reference point.
  
      Returns:
          A `2 x m`-dim tensor containing the normalization bounds.
      """
      if ref_point is not None:
          ref_point = ref_point.to(Y)
  
      if Y.ndim != 2:
          raise UnsupportedError("Batched Y is not supported.")
  
      if Y.shape[-2] == 0:
          # If there are no observations, return standard bounds.
          Y_bounds = torch.zeros(
              2, Y.shape[-1], dtype=Y.dtype, device=Y.device
          )
          Y_bounds[1] = 1.0
          return Y_bounds
  
      pareto_Y = Y[is_non_dominated(Y)]
      if pareto_Y.shape[-2] == 1:
          if ref_point is not None and (pareto_Y > ref_point).all():
              Y_bounds = torch.cat(
                  [ref_point.unsqueeze(0), pareto_Y], dim=0
              )
          else:
              # If there is only one observation, set the bounds to be [Y_m, Y_m + 1]
              # for each objective m. This ensures we do not divide by zero.
              Y_bounds = torch.cat([pareto_Y, pareto_Y + 1], dim=0)
      else:
          if ref_point is None:
              better_than_ref = torch.ones(
                  pareto_Y.shape[0],
                  device=pareto_Y.device,
                  dtype=torch.long,
              )
          else:
              better_than_ref = (pareto_Y > ref_point).all(dim=-1)
          if ref_point is not None and better_than_ref.any():
              nadir = ref_point
              pareto_Y = pareto_Y[better_than_ref]
          else:
              nadir = pareto_Y.min(dim=-2).values
          ideal = pareto_Y.max(dim=-2).values
          Y_bounds = torch.stack([nadir, ideal])
  
      # If any of the lower bounds is equal to the upper bound, increase the
      # upper bound to prevent division by zero.
      Y_range = Y_bounds.max(dim=0).values - Y_bounds.min(dim=0).values
      mask = Y_range <= 0
      Y_bounds[1, mask] = Y_bounds[1, mask] + 1.0
      return Y_bounds
  
imports: import warnings
  from abc import ABC, abstractmethod
  from math import ceil
  from typing import Callable, List, Optional, Union
  import torch
  from botorch.acquisition.multi_objective.objective import IdentityMCMultiOutputObjective, MCMultiOutputObjective
  from botorch.acquisition.risk_measures import CVaR, RiskMeasureMCObjective, VaR
  from botorch.exceptions.errors import UnsupportedError
  from botorch.exceptions.warnings import BotorchWarning
  from botorch.models.model import Model
  from botorch.utils.multi_objective.pareto import is_non_dominated
  from botorch.utils.transforms import normalize
  from torch import Tensor
label_desc: Use image node_id 10 for calling the MARS obj's underlying member method code with example usage: obj._get_Y_normalization_bounds(Y, ref_point) and returns: Y_bounds, Y_bounds