class_docstr: Posterior class for a Higher order Gaussian process model [Zhe2019hogp]_. Extends
  the standard GPyTorch posterior class by overwriting the rsample method.
  The posterior variance is handled internally by the HigherOrderGP model.
  HOGP is a tensorized GP model so the posterior covariance grows to be extremely
  large, but is highly structured, which means that we can exploit Kronecker
  identities to sample from the posterior using Matheron's rule as described in
  [Doucet2010sampl]_.
  
  In general, this posterior should ONLY be used for HOGP models
  that have highly structured covariances. It should also only be used internally when
  called from the HigherOrderGP.posterior(...) method. At this time, the posterior
  does not support gradients with respect to the training data.
docstr: Sample from the posterior (with gradients) using base samples.
  
  As the posterior covariance is difficult to draw from in this model,
  we implement Matheron's rule as described in [Doucet2010sampl]-. This may not
  work entirely correctly for deterministic base samples unless base samples
  are provided that are of shape `n + 2 * n_train` because the sampling method
  draws `2 * n_train` samples as well as the standard `n`.
  samples.
  
  Args:
      sample_shape: A `torch.Size` object specifying the sample shape. To
          draw `n` samples, set to `torch.Size([n])`. To draw `b` batches
          of `n` samples each, set to `torch.Size([b, n])`.
      base_samples: An (optional) Tensor of `N(0, I)` base samples of
          appropriate dimension, typically obtained from a `Sampler`.
          This is used for deterministic optimization.
  
  Returns:
      Samples from the posterior, a tensor of shape
      `self._extended_shape(sample_shape=sample_shape)`.
docstr_tok: ["Sample","from","the","posterior","(","with","gradients",")","using","base","samples",".","As","the","posterior","covariance","is","difficult","to","draw","from","in","this","model",",","we","implement","Matheron","'s","rule","as","described","in","[","Doucet2010sampl","]","-",".","This","may","not","work","entirely","correctly","for","deterministic","base","samples","unless","base","samples","are","provided","that","are","of","shape","`","n","+","2","*","n_train","`","because","the","sampling","method","draws","`","2","*","n_train","`","samples","as","well","as","the","standard","`","n","`",".","samples",".","Args",":","sample_shape",":","A","`","torch.Size","`","object","specifying","the","sample","shape",".","To","draw","`","n","`","samples",",","set","to","`","torch.Size","(","[","n","]",")","`",".","To","draw","`","b","`","batches","of","`","n","`","samples","each",",","set","to","`","torch.Size","(","[","b",",","n","]",")","`",".","base_samples",":","An","(","optional",")","Tensor","of","`","N","(","0",",","I",")","`","base","samples","of","appropriate","dimension",",","typically","obtained","from","a","`","Sampler","`",".","This","is","used","for","deterministic","optimization",".","Returns",":","Samples","from","the","posterior",",","a","tensor","of","shape","`","self._extended_shape","(","sample_shape=sample_shape",")","`","."]
code: def rsample_from_base_samples(
      self,
      sample_shape: torch.Size,
      base_samples: Optional[Tensor],
  ) -> Tensor:
      r"""Sample from the posterior (with gradients) using base samples.
  
      As the posterior covariance is difficult to draw from in this model,
      we implement Matheron's rule as described in [Doucet2010sampl]-. This may not
      work entirely correctly for deterministic base samples unless base samples
      are provided that are of shape `n + 2 * n_train` because the sampling method
      draws `2 * n_train` samples as well as the standard `n`.
      samples.
  
      Args:
          sample_shape: A `torch.Size` object specifying the sample shape. To
              draw `n` samples, set to `torch.Size([n])`. To draw `b` batches
              of `n` samples each, set to `torch.Size([b, n])`.
          base_samples: An (optional) Tensor of `N(0, I)` base samples of
              appropriate dimension, typically obtained from a `Sampler`.
              This is used for deterministic optimization.
  
      Returns:
          Samples from the posterior, a tensor of shape
          `self._extended_shape(sample_shape=sample_shape)`.
      """
      base_samples, noise_base_samples = self._prepare_base_samples(
          sample_shape, base_samples
      )
  
      # base samples now have trailing sample dimension
      covariance_matrix = self.joint_covariance_matrix
      covar_root = covariance_matrix.root_decomposition().root
  
      samples = covar_root.matmul(
          base_samples[..., : covar_root.shape[-1], :]
      )
  
      # now pluck out Y_x and X_x
      noiseless_train_marginal_samples = samples[
          ..., : self.train_train_covar.shape[-1], :
      ]
      test_marginal_samples = samples[
          ..., self.train_train_covar.shape[-1] :, :
      ]
      # we need to add noise to the train_joint_samples
      # THIS ASSUMES CONSTANT NOISE
      # The following assumes test_train_covar is a SumLinearOperator. TODO: Improve
      noise_std = (
          self.train_train_covar.linear_ops[1]._diag[..., 0] ** 0.5
      )
      # TODO: cleanup the reshaping here
      # expands the noise to allow broadcasting against the noise base samples
      # reshape_as or view_as don't work here because we need to expand to
      # broadcast against `samples x batch_shape x output_shape` while noise_std
      # is `batch_shape x 1`.
      if self.num_outputs > 1 or noise_std.ndim > 1:
          ntms_dims = [
              i == noise_std.shape[0]
              for i in noiseless_train_marginal_samples.shape
          ]
          for matched in ntms_dims:
              if not matched:
                  noise_std = noise_std.unsqueeze(-1)
  
      # we need to add noise into the noiseless samples
      noise_marginal_samples = noise_std * noise_base_samples
  
      train_marginal_samples = (
          noiseless_train_marginal_samples + noise_marginal_samples
      )
  
      # compute y - Y_x
      train_rhs = self.train_targets - train_marginal_samples
  
      # K_{train, train}^{-1} (y - Y_x)
      # internally, this solve is done using Kronecker algebra and is fast.
      kinv_rhs = self.train_train_covar.solve(train_rhs)
      # multiply by cross-covariance
      test_updated_samples = self.test_train_covar.matmul(kinv_rhs)
  
      # add samples
      test_cond_samples = test_marginal_samples + test_updated_samples
      test_cond_samples = test_cond_samples.permute(
          test_cond_samples.ndim - 1,
          *range(0, test_cond_samples.ndim - 1),
      )
  
      # reshape samples to be the actual size of the train targets
      return test_cond_samples.reshape(
          *sample_shape, *self.output_shape
      )
  
file_path: botorch/botorch/posteriors/higher_order.py
imports: from typing import Optional, Tuple
  import torch
  from botorch.exceptions.errors import BotorchTensorDimensionError
  from botorch.posteriors.gpytorch import GPyTorchPosterior
  from gpytorch.distributions import MultivariateNormal
  from linear_operator.operators import LinearOperator
  from torch import Tensor
label_desc: Use image node_id 6 for calling the HigherOrderGPPosterior obj's underlying member method code with example usage: obj.rsample_from_base_samples(sample_shape, base_samples) and returns: test_cond_samples