code: def sample_perturbed_subset_dims(
      X: Tensor,
      bounds: Tensor,
      n_discrete_points: int,
      sigma: float = 1e-1,
      qmc: bool = True,
      prob_perturb: Optional[float] = None,
  ) -> Tensor:
      r"""Sample around `X` by perturbing a subset of the dimensions.
  
      By default, dimensions are perturbed with probability equal to
      `min(20 / d, 1)`. As shown in [Regis]_, perturbing a small number
      of dimensions can be beneificial. The perturbations are sampled
      from N(0, sigma^2 I) and truncated to be within [0,1]^d.
  
      Args:
          X: A `n x d`-dim tensor starting points. `X`
              must be normalized to be within `[0, 1]^d`.
          bounds: The bounds to sample perturbed values from
          n_discrete_points: The number of points to sample.
          sigma: The standard deviation of the additive gaussian noise for
              perturbing the points.
          qmc: A boolean indicating whether to use qmc.
          prob_perturb: The probability of perturbing each dimension. If omitted,
              defaults to `min(20 / d, 1)`.
  
      Returns:
          A `n_discrete_points x d`-dim tensor containing the sampled points.
  
      """
      if bounds.ndim != 2:
          raise BotorchTensorDimensionError(
              "bounds must be a `2 x d`-dim tensor."
          )
      elif X.ndim != 2:
          raise BotorchTensorDimensionError(
              "X must be a `n x d`-dim tensor."
          )
      d = bounds.shape[-1]
      if prob_perturb is None:
          # Only perturb a subset of the features
          prob_perturb = min(20.0 / d, 1.0)
  
      if X.shape[0] == 1:
          X_cand = X.repeat(n_discrete_points, 1)
      else:
          rand_indices = torch.randint(
              X.shape[0], (n_discrete_points,), device=X.device
          )
          X_cand = X[rand_indices]
      pert = sample_truncated_normal_perturbations(
          X=X_cand,
          n_discrete_points=n_discrete_points,
          sigma=sigma,
          bounds=bounds,
          qmc=qmc,
      )
  
      # find cases where we are not perturbing any dimensions
      mask = (
          torch.rand(
              n_discrete_points,
              d,
              dtype=bounds.dtype,
              device=bounds.device,
          )
          <= prob_perturb
      )
      ind = (~mask).all(dim=-1).nonzero()
      # perturb `n_perturb` of the dimensions
      n_perturb = ceil(d * prob_perturb)
      perturb_mask = torch.zeros(
          d, dtype=mask.dtype, device=mask.device
      )
      perturb_mask[:n_perturb].fill_(1)
      # TODO: use batched `torch.randperm` when available:
      # https://github.com/pytorch/pytorch/issues/42502
      for idx in ind:
          mask[idx] = perturb_mask[
              torch.randperm(d, device=bounds.device)
          ]
      # Create candidate points
      X_cand[mask] = pert[mask]
      return X_cand
  
imports: from __future__ import annotations
  import warnings
  from math import ceil
  from typing import Callable, Dict, List, Optional, Tuple, Union
  import torch
  from botorch import settings
  from botorch.acquisition import analytic, monte_carlo, multi_objective
  from botorch.acquisition.acquisition import AcquisitionFunction
  from botorch.acquisition.fixed_feature import FixedFeatureAcquisitionFunction
  from botorch.acquisition.knowledge_gradient import _get_value_function, qKnowledgeGradient
  from botorch.acquisition.multi_objective.hypervolume_knowledge_gradient import _get_hv_value_function, qHypervolumeKnowledgeGradient, qMultiFidelityHypervolumeKnowledgeGradient
  from botorch.exceptions.errors import BotorchTensorDimensionError, UnsupportedError
  from botorch.exceptions.warnings import BadInitialCandidatesWarning, BotorchWarning, SamplingWarning
  from botorch.models.model import Model
  from botorch.optim.utils import fix_features, get_X_baseline
  from botorch.utils.multi_objective.pareto import is_non_dominated
  from botorch.utils.sampling import batched_multinomial, draw_sobol_samples, get_polytope_samples, manual_seed
  from botorch.utils.transforms import normalize, standardize, unnormalize
  from torch import Tensor
  from torch.distributions import Normal
  from torch.quasirandom import SobolEngine
label_desc: Use image node_id 13 for calling a global function with example usage: sample_perturbed_subset_dims(X, bounds, n_discrete_points, sigma, qmc, prob_perturb) and returns: X_cand