code: def test_input_perturbation(self):
      with self.assertRaisesRegex(ValueError, "-dim tensor!"):
          InputPerturbation(torch.ones(1))
      with self.assertRaisesRegex(ValueError, "-dim tensor!"):
          InputPerturbation(torch.ones(3, 2, 1))
      with self.assertRaisesRegex(
          ValueError, "the same number of columns"
      ):
          InputPerturbation(torch.ones(2, 1), bounds=torch.ones(2, 4))
  
      for dtype in (torch.float, torch.double):
          perturbation_set = torch.tensor(
              [[0.5, -0.3], [0.2, 0.4], [-0.7, 0.1]],
              device=self.device,
              dtype=dtype,
          )
          transform = InputPerturbation(
              perturbation_set=perturbation_set
          )
          self.assertTrue(transform.is_one_to_many)
          X = torch.tensor(
              [[[0.5, 0.5], [0.9, 0.7]], [[0.3, 0.2], [0.1, 0.4]]],
              device=self.device,
              dtype=dtype,
          )
          expected = torch.tensor(
              [
                  [
                      [1.0, 0.2],
                      [0.7, 0.9],
                      [-0.2, 0.6],
                      [1.4, 0.4],
                      [1.1, 1.1],
                      [0.2, 0.8],
                  ],
                  [
                      [0.8, -0.1],
                      [0.5, 0.6],
                      [-0.4, 0.3],
                      [0.6, 0.1],
                      [0.3, 0.8],
                      [-0.6, 0.5],
                  ],
              ],
              device=self.device,
              dtype=dtype,
          )
          # in train - no transform
          transformed = transform(X)
          self.assertTrue(torch.equal(transformed, X))
          # in eval - transform
          transform.eval()
          transformed = transform(X)
          self.assertAllClose(transformed, expected)
          # in fantasize - no transform
          with fantasize():
              transformed = transform(X)
          self.assertTrue(torch.equal(transformed, X))
          # with bounds
          bounds = torch.tensor(
              [[0.0, 0.2], [1.2, 0.9]], device=self.device, dtype=dtype
          )
          transform = InputPerturbation(
              perturbation_set=perturbation_set, bounds=bounds
          )
          transform.eval()
          expected = torch.tensor(
              [
                  [
                      [1.0, 0.2],
                      [0.7, 0.9],
                      [0.0, 0.6],
                      [1.2, 0.4],
                      [1.1, 0.9],
                      [0.2, 0.8],
                  ],
                  [
                      [0.8, 0.2],
                      [0.5, 0.6],
                      [0.0, 0.3],
                      [0.6, 0.2],
                      [0.3, 0.8],
                      [0.0, 0.5],
                  ],
              ],
              device=self.device,
              dtype=dtype,
          )
          transformed = transform(X)
          self.assertAllClose(transformed, expected)
  
          # Make sure .to calls work.
          transform.to(device=torch.device("cpu"), dtype=torch.half)
          self.assertEqual(
              transform.perturbation_set.device.type, "cpu"
          )
          self.assertEqual(transform.perturbation_set.dtype, torch.half)
          self.assertEqual(transform.bounds.device.type, "cpu")
          self.assertEqual(transform.bounds.dtype, torch.half)
  
          # multiplicative
          perturbation_set = torch.tensor(
              [[0.5, 1.5], [1.0, 2.0]],
              device=self.device,
              dtype=dtype,
          )
          transform = InputPerturbation(
              perturbation_set=perturbation_set, multiplicative=True
          )
          transform.eval()
          transformed = transform(X)
          expected = torch.tensor(
              [
                  [[0.25, 0.75], [0.5, 1.0], [0.45, 1.05], [0.9, 1.4]],
                  [[0.15, 0.3], [0.3, 0.4], [0.05, 0.6], [0.1, 0.8]],
              ],
              device=self.device,
              dtype=dtype,
          )
          self.assertAllClose(transformed, expected)
  
          # heteroscedastic
          def perturbation_generator(X: Tensor) -> Tensor:
              return torch.stack([X * 0.1, X * 0.2], dim=-2)
  
          transform = InputPerturbation(
              perturbation_set=perturbation_generator
          ).eval()
          transformed = transform(X)
          expected = torch.stack(
              [
                  X[..., 0, :] * 1.1,
                  X[..., 0, :] * 1.2,
                  X[..., 1, :] * 1.1,
                  X[..., 1, :] * 1.2,
              ],
              dim=-2,
          )
          self.assertAllClose(transformed, expected)
  
          # testing same heteroscedastic transform with subset of indices
          indices = [0, 1]
          subset_transform = InputPerturbation(
              perturbation_set=perturbation_generator, indices=indices
          ).eval()
          X_repeat = X.repeat(1, 1, 2)
          subset_transformed = subset_transform(X_repeat)
          # first set of two indices are the same as with previous transform
          self.assertAllClose(subset_transformed[..., :2], expected)
  
          # second set of two indices are untransformed but have expanded batch shape
          num_pert = subset_transform.batch_shape[-1]
          sec_expected = X.unsqueeze(-2).expand(
              *X.shape[:-1], num_pert, -1
          )
          sec_expected = sec_expected.flatten(-3, -2)
          self.assertAllClose(subset_transformed[..., 2:], sec_expected)
  
imports: import itertools
  import warnings
  from copy import deepcopy
  from random import randint
  import torch
  from botorch import settings
  from botorch.exceptions.errors import BotorchTensorDimensionError
  from botorch.exceptions.warnings import UserInputWarning
  from botorch.models.transforms.input import AffineInputTransform, AppendFeatures, ChainedInputTransform, FilterFeatures, InputPerturbation, InputStandardize, InputTransform, Log10, Normalize, OneHotToNumeric, Round, Warp
  from botorch.models.transforms.utils import expand_and_copy_tensor
  from botorch.models.utils import fantasize
  from botorch.utils.testing import BotorchTestCase
  from gpytorch.priors import LogNormalPrior
  from torch import Tensor
  from torch.distributions import Kumaraswamy
  from torch.nn import Module
  from torch.nn.functional import one_hot
label_desc: Use image node_id 1 for calling the TestInputPerturbation obj's underlying member method code with example usage: obj.test_input_perturbation() and returns: torch