docstr: Returns a new PredictionStrategy that incorporates the specified inputs and targets as new training data.
  
  This method is primary responsible for updating the mean and covariance caches. To add fantasy data to a
  GP model, use the :meth:`~gpytorch.models.ExactGP.get_fantasy_model` method.
  
  Args:
      inputs (Tensor `b1 x ... x bk x m x d` or `f x b1 x ... x bk x m x d`): Locations of fantasy
          observations.
      targets (Tensor `b1 x ... x bk x m` or `f x b1 x ... x bk x m`): Labels of fantasy observations.
      full_inputs (Tensor `b1 x ... x bk x n+m x d` or `f x b1 x ... x bk x n+m x d`): Training data
          concatenated with fantasy inputs
      full_targets (Tensor `b1 x ... x bk x n+m` or `f x b1 x ... x bk x n+m`): Training labels
          concatenated with fantasy labels.
      full_output (:class:`gpytorch.distributions.MultivariateNormal`): Prior called on full_inputs
  
  Returns:
      A `DefaultPredictionStrategy` model with `n + m` training examples, where the `m` fantasy examples have
      been added and all test-time caches have been updated.
docstr_tok: ["Returns","a","new","PredictionStrategy","that","incorporates","the","specified","inputs","and","targets","as","new","training","data",".","This","method","is","primary","responsible","for","updating","the","mean","and","covariance","caches",".","To","add","fantasy","data","to","a","GP","model",",","use","the",":","meth",":","`","~gpytorch.models.ExactGP.get_fantasy_model","`","method",".","Args",":","inputs","(","Tensor","`","b1","x","...","x","bk","x","m","x","d","`","or","`","f","x","b1","x","...","x","bk","x","m","x","d","`",")",":","Locations","of","fantasy","observations",".","targets","(","Tensor","`","b1","x","...","x","bk","x","m","`","or","`","f","x","b1","x","...","x","bk","x","m","`",")",":","Labels","of","fantasy","observations",".","full_inputs","(","Tensor","`","b1","x","...","x","bk","x","n+m","x","d","`","or","`","f","x","b1","x","...","x","bk","x","n+m","x","d","`",")",":","Training","data","concatenated","with","fantasy","inputs","full_targets","(","Tensor","`","b1","x","...","x","bk","x","n+m","`","or","`","f","x","b1","x","...","x","bk","x","n+m","`",")",":","Training","labels","concatenated","with","fantasy","labels",".","full_output","(",":","class",":","`","gpytorch.distributions.MultivariateNormal","`",")",":","Prior","called","on","full_inputs","Returns",":","A","`","DefaultPredictionStrategy","`","model","with","`","n","+","m","`","training","examples",",","where","the","`","m","`","fantasy","examples","have","been","added","and","all","test-time","caches","have","been","updated","."]
code: def get_fantasy_strategy(
      self,
      inputs,
      targets,
      full_inputs,
      full_targets,
      full_output,
      **kwargs,
  ):
      """
      Returns a new PredictionStrategy that incorporates the specified inputs and targets as new training data.
  
      This method is primary responsible for updating the mean and covariance caches. To add fantasy data to a
      GP model, use the :meth:`~gpytorch.models.ExactGP.get_fantasy_model` method.
  
      Args:
          inputs (Tensor `b1 x ... x bk x m x d` or `f x b1 x ... x bk x m x d`): Locations of fantasy
              observations.
          targets (Tensor `b1 x ... x bk x m` or `f x b1 x ... x bk x m`): Labels of fantasy observations.
          full_inputs (Tensor `b1 x ... x bk x n+m x d` or `f x b1 x ... x bk x n+m x d`): Training data
              concatenated with fantasy inputs
          full_targets (Tensor `b1 x ... x bk x n+m` or `f x b1 x ... x bk x n+m`): Training labels
              concatenated with fantasy labels.
          full_output (:class:`gpytorch.distributions.MultivariateNormal`): Prior called on full_inputs
  
      Returns:
          A `DefaultPredictionStrategy` model with `n + m` training examples, where the `m` fantasy examples have
          been added and all test-time caches have been updated.
      """
      if not isinstance(full_output, MultitaskMultivariateNormal):
          target_batch_shape = targets.shape[:-1]
      else:
          target_batch_shape = targets.shape[:-2]
  
      full_mean, full_covar = (
          full_output.mean,
          full_output.lazy_covariance_matrix,
      )
  
      batch_shape = full_inputs[0].shape[:-2]
  
      num_train = self.num_train
  
      if isinstance(full_output, MultitaskMultivariateNormal):
          num_tasks = full_output.event_shape[-1]
          full_mean = full_mean.view(*batch_shape, -1, num_tasks)
          fant_mean = full_mean[..., (num_train // num_tasks) :, :]
          full_targets = full_targets.view(*target_batch_shape, -1)
      else:
          full_mean = full_mean.view(*batch_shape, -1)
          fant_mean = full_mean[..., num_train:]
  
      # Evaluate fant x train and fant x fant covariance matrices, leave train x train unevaluated.
      fant_fant_covar = full_covar[..., num_train:, num_train:]
      mvn = self.train_prior_dist.__class__(fant_mean, fant_fant_covar)
      fant_likelihood = self.likelihood.get_fantasy_likelihood(**kwargs)
      mvn_obs = fant_likelihood(mvn, inputs, **kwargs)
  
      fant_fant_covar = mvn_obs.covariance_matrix
      fant_train_covar = to_dense(
          full_covar[..., num_train:, :num_train]
      )
  
      self.fantasy_inputs = inputs
      self.fantasy_targets = targets
  
      r"""
          Compute a new mean cache given the old mean cache.
  
          We have \alpha = K^{-1}y, and we want to solve [K U; U' S][a; b] = [y; y_f], where U' is fant_train_covar,
          S is fant_fant_covar, and y_f is (targets - fant_mean)
  
          To do this, we solve the bordered linear system of equations for [a; b]:
              AQ = U  # Q = fant_solve
              [S - U'Q]b = y_f - U'\alpha   ==> b = [S - U'Q]^{-1}(y_f - U'\alpha)
              a = \alpha - Qb
          """
      # Get cached K inverse decomp. (or compute if we somehow don't already have the covariance cache)
      K_inverse = self.lik_train_train_covar.root_inv_decomposition()
      fant_solve = K_inverse.matmul(fant_train_covar.transpose(-2, -1))
  
      # Solve for "b", the lower portion of the *new* \\alpha corresponding to the fantasy points.
      schur_complement = fant_fant_covar - fant_train_covar.matmul(
          fant_solve
      )
  
      # we'd like to use a less hacky approach for the following, but einsum can be much faster than
      # than unsqueezing/squeezing here (esp. in backward passes), unfortunately it currenlty has some
      # issues with broadcasting: https://github.com/pytorch/pytorch/issues/15671
      prefix = string.ascii_lowercase[
          : max(fant_train_covar.dim() - self.mean_cache.dim() - 1, 0)
      ]
      ftcm = torch.einsum(
          prefix + "...yz,...z->" + prefix + "...y",
          [fant_train_covar, self.mean_cache],
      )
  
      small_system_rhs = targets - fant_mean - ftcm
      small_system_rhs = small_system_rhs.unsqueeze(-1)
      # Schur complement of a spd matrix is guaranteed to be positive definite
      schur_cholesky = psd_safe_cholesky(schur_complement)
      fant_cache_lower = torch.cholesky_solve(
          small_system_rhs, schur_cholesky
      )
  
      # Get "a", the new upper portion of the cache corresponding to the old training points.
      fant_cache_upper = self.mean_cache.unsqueeze(
          -1
      ) - fant_solve.matmul(fant_cache_lower)
  
      fant_cache_upper = fant_cache_upper.squeeze(-1)
      fant_cache_lower = fant_cache_lower.squeeze(-1)
  
      # New mean cache.
      fant_mean_cache = torch.cat(
          (fant_cache_upper, fant_cache_lower), dim=-1
      )
  
      # now update the root and root inverse
      new_lt = self.lik_train_train_covar.cat_rows(
          fant_train_covar, fant_fant_covar
      )
      new_root = new_lt.root_decomposition().root.to_dense()
      new_covar_cache = new_lt.root_inv_decomposition().root.to_dense()
  
      # Expand inputs accordingly if necessary (for fantasies at the same points)
      if full_inputs[0].dim() <= full_targets.dim():
          fant_batch_shape = full_targets.shape[:1]
          n_batch = len(full_mean.shape[:-1])
          repeat_shape = fant_batch_shape + torch.Size([1] * n_batch)
          full_inputs = [
              fi.expand(fant_batch_shape + fi.shape)
              for fi in full_inputs
          ]
          full_mean = full_mean.expand(
              fant_batch_shape + full_mean.shape
          )
          full_covar = BatchRepeatLinearOperator(
              full_covar, repeat_shape
          )
          new_root = BatchRepeatLinearOperator(
              DenseLinearOperator(new_root), repeat_shape
          )
          # no need to repeat the covar cache, broadcasting will do the right thing
  
      if isinstance(full_output, MultitaskMultivariateNormal):
          full_mean = full_mean.view(
              *target_batch_shape, -1, num_tasks
          ).contiguous()
  
      # Create new DefaultPredictionStrategy object
      fant_strat = self.__class__(
          train_inputs=full_inputs,
          train_prior_dist=self.train_prior_dist.__class__(
              full_mean, full_covar
          ),
          train_labels=full_targets,
          likelihood=fant_likelihood,
          root=new_root,
          inv_root=new_covar_cache,
      )
      add_to_cache(fant_strat, "mean_cache", fant_mean_cache)
      add_to_cache(fant_strat, "covar_cache", new_covar_cache)
      return fant_strat
  
imports: import functools
  import string
  import warnings
  import torch
  from linear_operator import to_dense, to_linear_operator
  from linear_operator.operators import AddedDiagLinearOperator, BatchRepeatLinearOperator, ConstantMulLinearOperator, DenseLinearOperator, InterpolatedLinearOperator, LinearOperator, LowRankRootAddedDiagLinearOperator, MaskedLinearOperator, MatmulLinearOperator, RootLinearOperator, ZeroLinearOperator
  from linear_operator.utils.cholesky import psd_safe_cholesky
  from linear_operator.utils.interpolation import left_interp, left_t_interp
  from torch import Tensor
  from ..None import settings
  from ..distributions import MultitaskMultivariateNormal
  from ..lazy import LazyEvaluatedKernelTensor
  from ..utils.memoize import add_to_cache, cached, clear_cache_hook, pop_from_cache
label_desc: Use image node_id 5 for calling the DefaultPredictionStrategy obj's underlying member method code with example usage: obj.get_fantasy_strategy(inputs, targets, full_inputs, full_targets, full_output) and returns: fant_strat