class_docstr: The base class for any Gaussian process latent function to be used in conjunction
  with exact inference.
  
  :param torch.Tensor train_inputs: (size n x d) The training features :math:`\mathbf X`.
  :param torch.Tensor train_targets: (size n) The training targets :math:`\mathbf y`.
  :param ~gpytorch.likelihoods.GaussianLikelihood likelihood: The Gaussian likelihood that defines
      the observational distribution. Since we're using exact inference, the likelihood must be Gaussian.
  
  The :meth:`forward` function should describe how to compute the prior latent distribution
  on a given input. Typically, this will involve a mean and kernel function.
  The result must be a :obj:`~gpytorch.distributions.MultivariateNormal`.
  
  Calling this model will return the posterior of the latent Gaussian process when conditioned
  on the training data. The output will be a :obj:`~gpytorch.distributions.MultivariateNormal`.
  
  Example:
      >>> class MyGP(gpytorch.models.ExactGP):
      >>>     def __init__(self, train_x, train_y, likelihood):
      >>>         super().__init__(train_x, train_y, likelihood)
      >>>         self.mean_module = gpytorch.means.ZeroMean()
      >>>         self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel())
      >>>
      >>>     def forward(self, x):
      >>>         mean = self.mean_module(x)
      >>>         covar = self.covar_module(x)
      >>>         return gpytorch.distributions.MultivariateNormal(mean, covar)
      >>>
      >>> # train_x = ...; train_y = ...
      >>> likelihood = gpytorch.likelihoods.GaussianLikelihood()
      >>> model = MyGP(train_x, train_y, likelihood)
      >>>
      >>> # test_x = ...;
      >>> model(test_x)  # Returns the GP latent function at test_x
      >>> likelihood(model(test_x))  # Returns the (approximate) predictive posterior distribution at test_x
class_docstr_tok: ["The","base","class","for","any","Gaussian","process","latent","function","to","be","used","in","conjunction","with","exact","inference",".",":","param","torch.Tensor","train_inputs",":","(","size","n","x","d",")","The","training","features",":","math",":","`","\\mathbf","X","`",".",":","param","torch.Tensor","train_targets",":","(","size","n",")","The","training","targets",":","math",":","`","\\mathbf","y","`",".",":","param","~gpytorch.likelihoods.GaussianLikelihood","likelihood",":","The","Gaussian","likelihood","that","defines","the","observational","distribution",".","Since","we","'re","using","exact","inference",",","the","likelihood","must","be","Gaussian",".","The",":","meth",":","`","forward","`","function","should","describe","how","to","compute","the","prior","latent","distribution","on","a","given","input",".","Typically",",","this","will","involve","a","mean","and","kernel","function",".","The","result","must","be","a",":","obj",":","`","~gpytorch.distributions.MultivariateNormal","`",".","Calling","this","model","will","return","the","posterior","of","the","latent","Gaussian","process","when","conditioned","on","the","training","data",".","The","output","will","be","a",":","obj",":","`","~gpytorch.distributions.MultivariateNormal","`",".","Example",":",">",">",">","class","MyGP","(","gpytorch.models.ExactGP",")",":",">",">",">","def","__init__","(","self",",","train_x",",","train_y",",","likelihood",")",":",">",">",">","super","(",")",".__init__","(","train_x",",","train_y",",","likelihood",")",">",">",">","self.mean_module","=","gpytorch.means.ZeroMean","(",")",">",">",">","self.covar_module","=","gpytorch.kernels.ScaleKernel","(","gpytorch.kernels.RBFKernel","(",")",")",">",">",">",">",">",">","def","forward","(","self",",","x",")",":",">",">",">","mean","=","self.mean_module","(","x",")",">",">",">","covar","=","self.covar_module","(","x",")",">",">",">","return","gpytorch.distributions.MultivariateNormal","(","mean",",","covar",")",">",">",">",">",">",">","#","train_x","=","...",";","train_y","=","...",">",">",">","likelihood","=","gpytorch.likelihoods.GaussianLikelihood","(",")",">",">",">","model","=","MyGP","(","train_x",",","train_y",",","likelihood",")",">",">",">",">",">",">","#","test_x","=","...",";",">",">",">","model","(","test_x",")","#","Returns","the","GP","latent","function","at","test_x",">",">",">","likelihood","(","model","(","test_x",")",")","#","Returns","the","(","approximate",")","predictive","posterior","distribution","at","test_x"]
code: def __call__(self, *args, **kwargs):
      train_inputs = (
          list(self.train_inputs)
          if self.train_inputs is not None
          else []
      )
      inputs = [
          i.unsqueeze(-1) if i.ndimension() == 1 else i for i in args
      ]
  
      # Training mode: optimizing
      if self.training:
          if self.train_inputs is None:
              raise RuntimeError(
                  "train_inputs, train_targets cannot be None in training mode. "
                  "Call .eval() for prior predictions, or call .set_train_data() to add training data."
              )
          if settings.debug.on():
              if not all(
                  torch.equal(train_input, input)
                  for train_input, input in length_safe_zip(
                      train_inputs, inputs
                  )
              ):
                  raise RuntimeError(
                      "You must train on the training inputs!"
                  )
          res = super().__call__(*inputs, **kwargs)
          return res
  
      # Prior mode
      elif (
          settings.prior_mode.on()
          or self.train_inputs is None
          or self.train_targets is None
      ):
          full_inputs = args
          full_output = super(ExactGP, self).__call__(
              *full_inputs, **kwargs
          )
          if settings.debug().on():
              if not isinstance(full_output, MultivariateNormal):
                  raise RuntimeError(
                      "ExactGP.forward must return a MultivariateNormal"
                  )
          return full_output
  
      # Posterior mode
      else:
          if settings.debug.on():
              if all(
                  torch.equal(train_input, input)
                  for train_input, input in length_safe_zip(
                      train_inputs, inputs
                  )
              ):
                  warnings.warn(
                      "The input matches the stored training data. Did you forget to call model.train()?",
                      GPInputWarning,
                  )
  
          # Get the terms that only depend on training data
          if self.prediction_strategy is None:
              train_output = super().__call__(*train_inputs, **kwargs)
  
              # Create the prediction strategy for
              self.prediction_strategy = prediction_strategy(
                  train_inputs=train_inputs,
                  train_prior_dist=train_output,
                  train_labels=self.train_targets,
                  likelihood=self.likelihood,
              )
  
          # Concatenate the input to the training input
          full_inputs = []
          batch_shape = train_inputs[0].shape[:-2]
          for train_input, input in length_safe_zip(
              train_inputs, inputs
          ):
              # Make sure the batch shapes agree for training/test data
              if batch_shape != train_input.shape[:-2]:
                  batch_shape = torch.broadcast_shapes(
                      batch_shape, train_input.shape[:-2]
                  )
                  train_input = train_input.expand(
                      *batch_shape, *train_input.shape[-2:]
                  )
              if batch_shape != input.shape[:-2]:
                  batch_shape = torch.broadcast_shapes(
                      batch_shape, input.shape[:-2]
                  )
                  train_input = train_input.expand(
                      *batch_shape, *train_input.shape[-2:]
                  )
                  input = input.expand(*batch_shape, *input.shape[-2:])
              full_inputs.append(
                  torch.cat([train_input, input], dim=-2)
              )
  
          # Get the joint distribution for training/test data
          full_output = super(ExactGP, self).__call__(
              *full_inputs, **kwargs
          )
          if settings.debug().on():
              if not isinstance(full_output, MultivariateNormal):
                  raise RuntimeError(
                      "ExactGP.forward must return a MultivariateNormal"
                  )
          full_mean, full_covar = (
              full_output.loc,
              full_output.lazy_covariance_matrix,
          )
  
          # Determine the shape of the joint distribution
          batch_shape = full_output.batch_shape
          joint_shape = full_output.event_shape
          tasks_shape = joint_shape[1:]  # For multitask learning
          test_shape = torch.Size(
              [
                  joint_shape[0]
                  - self.prediction_strategy.train_shape[0],
                  *tasks_shape,
              ]
          )
  
          # Make the prediction
          with settings.cg_tolerance(
              settings.eval_cg_tolerance.value()
          ):
              (
                  predictive_mean,
                  predictive_covar,
              ) = self.prediction_strategy.exact_prediction(
                  full_mean, full_covar
              )
  
          # Reshape predictive mean to match the appropriate event shape
          predictive_mean = predictive_mean.view(
              *batch_shape, *test_shape
          ).contiguous()
          return full_output.__class__(
              predictive_mean, predictive_covar
          )
  
imports: import warnings
  from copy import deepcopy
  import torch
  from ..None import settings
  from ..distributions import MultitaskMultivariateNormal, MultivariateNormal
  from ..likelihoods import _GaussianLikelihoodBase
  from ..utils.generic import length_safe_zip
  from ..utils.warnings import GPInputWarning
  from .exact_prediction_strategies import prediction_strategy
  from .gp import GP
label_desc: Use image node_id 9 for calling the ExactGP obj's underlying member method code with example usage: obj.__call__() and returns: res, full_output, full_output