docstr: Check that fantasized posteriors from a `ModelListGP` with transforms
  relate in a predictable way to posteriors from a `ModelListGP` when the
  outputs have been manually transformed.
  
  We are essentially fitting "Y = 10 * X" with Y standardized.
  - In the original space, we should predict a mean of ~5 at 0.5
  - In the standardized space, we should predict ~0.
  - If we untransform the result in the standardized space, we should recover
  the prediction of ~5 we would have gotten in the original space.
docstr_tok: ["Check","that","fantasized","posteriors","from","a","`","ModelListGP","`","with","transforms","relate","in","a","predictable","way","to","posteriors","from","a","`","ModelListGP","`","when","the","outputs","have","been","manually","transformed",".","We","are","essentially","fitting","``","Y","=","10","*","X","''","with","Y","standardized",".","-","In","the","original","space",",","we","should","predict","a","mean","of","~5","at","0.5","-","In","the","standardized","space",",","we","should","predict","~0",".","-","If","we","untransform","the","result","in","the","standardized","space",",","we","should","recover","the","prediction","of","~5","we","would","have","gotten","in","the","original","space","."]
code: def test_fantasize_with_outcome_transform(self) -> None:
      """
      Check that fantasized posteriors from a `ModelListGP` with transforms
      relate in a predictable way to posteriors from a `ModelListGP` when the
      outputs have been manually transformed.
  
      We are essentially fitting "Y = 10 * X" with Y standardized.
      - In the original space, we should predict a mean of ~5 at 0.5
      - In the standardized space, we should predict ~0.
      - If we untransform the result in the standardized space, we should recover
      the prediction of ~5 we would have gotten in the original space.
      """
  
      for dtype in [torch.float, torch.double]:
          with self.subTest(dtype=dtype):
              tkwargs = {"device": self.device, "dtype": dtype}
              X = torch.linspace(0, 1, 20, **tkwargs)[:, None]
              Y1 = 10 * torch.linspace(0, 1, 20, **tkwargs)[:, None]
              Y2 = 2 * Y1
              Y = torch.cat([Y1, Y2], dim=-1)
              target_x = torch.tensor([[0.5]], **tkwargs)
  
              model_with_transform = ModelListGP(
                  SingleTaskGP(
                      X, Y1, outcome_transform=Standardize(m=1)
                  ),
                  SingleTaskGP(
                      X, Y2, outcome_transform=Standardize(m=1)
                  ),
              )
              outcome_transform = Standardize(m=2)
              y_standardized, _ = outcome_transform(Y)
              outcome_transform.eval()
              model_manually_transformed = ModelListGP(
                  SingleTaskGP(X, y_standardized[:, :1]),
                  SingleTaskGP(X, y_standardized[:, 1:]),
              )
  
              def _get_fant_mean(
                  model: ModelListGP,
                  sampler: MCSampler,
                  eval_mask: Optional[Tensor] = None,
              ) -> float:
                  fant = model.fantasize(
                      target_x,  # noqa
                      sampler=sampler,
                      evaluation_mask=eval_mask,
                  )
                  return fant.posterior(target_x).mean.mean(
                      dim=(-2, -3)
                  )  # noqa
  
              # ~0
              sampler = IIDNormalSampler(
                  sample_shape=torch.Size([10]), seed=0
              )
              fant_mean_with_manual_transform = _get_fant_mean(
                  model_manually_transformed, sampler=sampler
              )
              # Inexact since this is an MC test and we don't want it flaky
              self.assertLessEqual(
                  (fant_mean_with_manual_transform - 0.0)
                  .abs()
                  .max()
                  .item(),
                  0.1,
              )
              manually_rescaled_mean = outcome_transform.untransform(
                  fant_mean_with_manual_transform
              )[0].view(-1)
              fant_mean_with_native_transform = _get_fant_mean(
                  model_with_transform, sampler=sampler
              )
              # Inexact since this is an MC test and we don't want it flaky
              self.assertLessEqual(
                  (
                      fant_mean_with_native_transform
                      - torch.tensor([5.0, 10.0], **tkwargs)
                  )
                  .abs()
                  .max()
                  .item(),
                  0.5,
              )
  
              # tighter tolerance here since the models should use the same samples
              self.assertAllClose(
                  manually_rescaled_mean,
                  fant_mean_with_native_transform,
              )
              # test decoupled
              sampler = ListSampler(
                  IIDNormalSampler(
                      sample_shape=torch.Size([10]), seed=0
                  ),
                  IIDNormalSampler(
                      sample_shape=torch.Size([10]), seed=0
                  ),
              )
              fant_mean_with_manual_transform = _get_fant_mean(
                  model_manually_transformed,
                  sampler=sampler,
                  eval_mask=torch.tensor(
                      [[0, 1]],
                      dtype=torch.bool,
                      device=tkwargs["device"],
                  ),
              )
              # Inexact since this is an MC test and we don't want it flaky
              self.assertLessEqual(
                  (fant_mean_with_manual_transform - 0.0)
                  .abs()
                  .max()
                  .item(),
                  0.1,
              )
              manually_rescaled_mean = outcome_transform.untransform(
                  fant_mean_with_manual_transform
              )[0].view(-1)
              fant_mean_with_native_transform = _get_fant_mean(
                  model_with_transform,
                  sampler=sampler,
                  eval_mask=torch.tensor(
                      [[0, 1]],
                      dtype=torch.bool,
                      device=tkwargs["device"],
                  ),
              )
              # Inexact since this is an MC test and we don't want it flaky
              self.assertLessEqual(
                  (
                      fant_mean_with_native_transform
                      - torch.tensor([5.0, 10.0], **tkwargs)
                  )
                  .abs()
                  .max()
                  .item(),
                  0.5,
              )
              # tighter tolerance here since the models should use the same samples
              self.assertAllClose(
                  manually_rescaled_mean,
                  fant_mean_with_native_transform,
              )
  
imports: import itertools
  import warnings
  from copy import deepcopy
  from typing import Optional
  import torch
  from botorch.acquisition.objective import ScalarizedPosteriorTransform
  from botorch.exceptions.errors import BotorchTensorDimensionError
  from botorch.exceptions.warnings import OptimizationWarning
  from botorch.fit import fit_gpytorch_mll
  from botorch.models.gp_regression import SingleTaskGP
  from botorch.models.model_list_gp_regression import ModelListGP
  from botorch.models.multitask import MultiTaskGP
  from botorch.models.transforms.input import Normalize
  from botorch.models.transforms.outcome import ChainedOutcomeTransform, Log, Standardize
  from botorch.posteriors import GPyTorchPosterior, PosteriorList, TransformedPosterior
  from botorch.sampling.base import MCSampler
  from botorch.sampling.list_sampler import ListSampler
  from botorch.sampling.normal import IIDNormalSampler
  from botorch.utils.testing import _get_random_data, BotorchTestCase
  from gpytorch.distributions import MultitaskMultivariateNormal, MultivariateNormal
  from gpytorch.kernels import MaternKernel, ScaleKernel
  from gpytorch.likelihoods import LikelihoodList
  from gpytorch.likelihoods.gaussian_likelihood import FixedNoiseGaussianLikelihood, GaussianLikelihood
  from gpytorch.means import ConstantMean
  from gpytorch.mlls import SumMarginalLogLikelihood
  from gpytorch.mlls.exact_marginal_log_likelihood import ExactMarginalLogLikelihood
  from gpytorch.priors import GammaPrior
  from torch import Tensor
label_desc: Use image node_id 8 for calling the TestModelListGP obj's underlying member method code with example usage: obj.test_fantasize_with_outcome_transform() and returns: fant