code: def get_gp_samples(
      model: Model,
      num_outputs: int,
      n_samples: int,
      num_rff_features: int = 512,
  ) -> GenericDeterministicModel:
      r"""Sample functions from GP posterior using RFFs. The returned
      `GenericDeterministicModel` effectively wraps `num_outputs` models,
      each of which has a batch shape of `n_samples`. Refer
      `get_deterministic_model_multi_samples` for more details.
  
      NOTE: If using input / outcome transforms, the gp samples must be accessed via
      the `gp_sample.posterior(X)` call. Otherwise, `gp_sample(X)` will produce bogus
      values that do not agree with the underlying `model`. It is also highly recommended
      to use outcome transforms to standardize the input data, since the gp samples do
      not work well when training outcomes are not zero-mean.
  
      Args:
          model: The model.
          num_outputs: The number of outputs.
          n_samples: The number of functions to be sampled IID.
          num_rff_features: The number of random Fourier features.
  
      Returns:
          A `GenericDeterministicModel` that evaluates `n_samples` sampled functions.
          If `n_samples > 1`, this will be a batched model.
      """
      # Get transforms from the model.
      intf = getattr(model, "input_transform", None)
      octf = getattr(model, "outcome_transform", None)
      # Remove the outcome transform - leads to buggy draws.
      if octf is not None:
          del model.outcome_transform
      if intf is not None:
          del model.input_transform
  
      if num_outputs > 1:
          if not isinstance(model, ModelListGP):
              models = batched_to_model_list(model).models
          else:
              models = model.models
      else:
          models = [model]
      if isinstance(models[0], MultiTaskGP):
          raise NotImplementedError
  
      weights = []
      bases = []
      octfs = []
      intfs = []
      for m in range(num_outputs):
          train_X = models[m].train_inputs[0]
          train_targets = models[m].train_targets
          _model = models[m]
          _intf = getattr(_model, "input_transform", None)
          _octf = getattr(_model, "outcome_transform", None)
          # Remove the outcome transform - leads to buggy draws.
          if _octf is not None:
              del _model.outcome_transform
  
          octfs.append(_octf)
          intfs.append(_intf)
          # Get random Fourier features.
          # sample_shape controls the number of iid functions.
          basis = RandomFourierFeatures(
              kernel=_model.covar_module,
              input_dim=train_X.shape[-1],
              num_rff_features=num_rff_features,
              sample_shape=torch.Size(
                  [n_samples] if n_samples > 1 else []
              ),
          )
          bases.append(basis)
          phi_X = basis(train_X)
          # Sample weights from bayesian linear model.
          # weights.sample().shape == (n_samples, batch_shape_input, num_rff_features)
          sigma_sq = _model.likelihood.noise.mean(dim=-1, keepdim=True)
          if len(basis.kernel_batch_shape) > 0:
              sigma_sq = sigma_sq.unsqueeze(-2)
          mvn = get_weights_posterior(
              X=phi_X,
              y=train_targets,
              sigma_sq=sigma_sq,
          )
          weights.append(mvn.sample())
  
      # TODO: Ideally support RFFs for multi-outputs instead of having to
      # generate a basis for each output serially.
      if any(_octf is not None for _octf in octfs) or any(
          _intf is not None for _intf in intfs
      ):
          base_gp_samples = get_deterministic_model_list(
              weights=weights,
              bases=bases,
          )
          for m in range(len(weights)):
              _octf = octfs[m]
              _intf = intfs[m]
              if _octf is not None:
                  base_gp_samples.models[m].outcome_transform = _octf
                  models[m].outcome_transform = _octf
              if _intf is not None:
                  base_gp_samples.models[m].input_transform = _intf
          base_gp_samples._is_ensemble = is_ensemble(model=model)
          return base_gp_samples
      elif n_samples > 1:
          base_gp_samples = get_deterministic_model_multi_samples(
              weights=weights,
              bases=bases,
          )
      else:
          base_gp_samples = get_deterministic_model(
              weights=weights,
              bases=bases,
          )
      # Load the transforms on the models.
      if intf is not None:
          base_gp_samples.input_transform = intf
          model.input_transform = intf
      if octf is not None:
          base_gp_samples.outcome_transform = octf
          model.outcome_transform = octf
      base_gp_samples._is_ensemble = is_ensemble(model=model)
      return base_gp_samples
  
imports: from __future__ import annotations
  from copy import deepcopy
  from math import pi
  from typing import List, Optional
  import torch
  from botorch.models.converter import batched_to_model_list
  from botorch.models.deterministic import GenericDeterministicModel
  from botorch.models.model import Model, ModelList
  from botorch.models.model_list_gp_regression import ModelListGP
  from botorch.models.multitask import MultiTaskGP
  from botorch.utils.sampling import manual_seed
  from botorch.utils.transforms import is_ensemble
  from gpytorch.kernels import Kernel, MaternKernel, RBFKernel, ScaleKernel
  from linear_operator.utils.cholesky import psd_safe_cholesky
  from torch import Tensor
  from torch.distributions import MultivariateNormal
  from torch.nn import Module
label_desc: Use image node_id 6 for calling a global function with example usage: get_gp_samples(model, num_outputs, n_samples, num_rff_features) and returns: base_gp_samples, base_gp_samples