code: def expand_trace_observations(
      X: Tensor,
      fidelity_dims: Optional[List[int]] = None,
      num_trace_obs: int = 0,
  ) -> Tensor:
      r"""Expand `X` with trace observations.
  
      Expand a tensor of inputs with "trace observations" that are obtained during
      the evaluation of the candidate set. This is used in multi-fidelity
      optimization. It can be though of as augmenting the `q`-batch with additional
      points that are the expected trace observations.
  
      Let `f_i` be the `i`-th fidelity parameter. Then this functions assumes that
      for each element of the q-batch, besides the fidelity `f_i`, we will observe
      additonal fidelities `f_i1, ..., f_iK`, where `K = num_trace_obs`, during
      evaluation of the candidate set `X`. Specifically, this function assumes
      that `f_ij = (K-j) / (num_trace_obs + 1) * f_i` for all `i`. That is, the
      expansion is performed in parallel for all fidelities (it does not expand
      out all possible combinations).
  
      Args:
          X: A `batch_shape x q x d`-dim Tensor of with `q` `d`-dim design points
              (incl. the fidelity parameters) for each t-batch.
          fidelity_dims: The indices of the fidelity parameters. If omitted,
              assumes that the last column of X contains the fidelity parameters.
          num_trace_obs: The number of trace observations to use.
  
      Return:
          A `batch_shape x (q + num_trace_obs x q) x d` Tensor `X_expanded` that
              expands `X` with trace observations.
      """
      if (
          num_trace_obs == 0
      ):  # No need to expand if we don't use trace observations
          return X
  
      if fidelity_dims is None:
          fidelity_dims = [-1]
  
      # The general strategy in the following is to expand `X` to the desired
      # shape, and then multiply it (point-wise) with a tensor of scaling factors
      reps = [1] * (X.ndim - 2) + [1 + num_trace_obs, 1]
      X_expanded = X.repeat(
          *reps
      )  # batch_shape x (q + num_trace_obs x q) x d
      scale_fac = torch.ones_like(X_expanded)
      s_pad = 1 / (num_trace_obs + 1)
      # tensor of  num_trace_obs scaling factors equally space between 1-s_pad and s_pad
      sf = torch.linspace(
          1 - s_pad,
          s_pad,
          num_trace_obs,
          device=X.device,
          dtype=X.dtype,
      )
      # repeat each element q times
      q = X.size(-2)
      sf = torch.repeat_interleave(sf, q)  # num_trace_obs * q
      # now expand this to num_trace_obs x q x num_fidelities
      sf = sf.unsqueeze(-1).expand(
          X_expanded.size(-2) - q, len(fidelity_dims)
      )
      # change relevant entries of the scaling tensor
      scale_fac[..., q:, fidelity_dims] = sf
      return scale_fac * X_expanded
  
imports: from __future__ import annotations
  import math
  from typing import Callable, Dict, List, Optional, Tuple
  import torch
  from botorch.acquisition.objective import IdentityMCObjective, MCAcquisitionObjective, PosteriorTransform
  from botorch.exceptions.errors import BotorchTensorDimensionError, DeprecationError, UnsupportedError
  from botorch.models.fully_bayesian import MCMC_DIM
  from botorch.models.model import Model
  from botorch.sampling.base import MCSampler
  from botorch.sampling.get_sampler import get_sampler
  from botorch.sampling.pathwise import draw_matheron_paths
  from botorch.utils.objective import compute_feasibility_indicator
  from botorch.utils.sampling import optimize_posterior_samples
  from botorch.utils.transforms import is_ensemble, normalize_indices
  from torch import Tensor
label_desc: Use image node_id 8 for calling a global function with example usage: expand_trace_observations(X, fidelity_dims, num_trace_obs) and returns: unknown, X