code: def test_round_transform(self) -> None:
      int_idcs = [0, 4]
      categorical_feats = {2: 2, 5: 3}
      # set seed to range where this is known to not be flaky
      torch.manual_seed(randint(0, 1000))
      for (
          dtype,
          batch_shape,
          approx,
          categorical_features,
      ) in itertools.product(
          (torch.float, torch.double),
          (torch.Size(), torch.Size([3])),
          (False, True),
          (None, categorical_feats),
      ):
          with self.subTest(
              dtype=dtype,
              batch_shape=batch_shape,
              approx=approx,
              categorical_features=categorical_features,
          ):
              X = torch.rand(
                  *batch_shape, 4, 8, device=self.device, dtype=dtype
              )
              X[..., int_idcs] *= 5
              if categorical_features is not None and approx:
                  with self.assertRaises(NotImplementedError):
                      Round(
                          integer_indices=int_idcs,
                          categorical_features=categorical_features,
                          approximate=approx,
                      )
                  continue
              tau = 1e-1
              round_tf = Round(
                  integer_indices=int_idcs,
                  categorical_features=categorical_features,
                  approximate=approx,
                  tau=tau,
              )
              X_rounded = round_tf(X)
              exact_rounded_X_ints = X[..., int_idcs].round()
              # check non-integers parameters are unchanged
              self.assertTrue(torch.equal(X_rounded[..., 1], X[..., 1]))
              if approx:
                  # check that approximate rounding is closer to rounded values than
                  # the original inputs
                  dist_approx_to_rounded = (
                      X_rounded[..., int_idcs] - exact_rounded_X_ints
                  ).abs()
                  dist_orig_to_rounded = (
                      X[..., int_idcs] - exact_rounded_X_ints
                  ).abs()
                  tol = torch.tanh(torch.tensor(0.5 / tau, dtype=dtype))
                  self.assertGreater(
                      (
                          dist_orig_to_rounded - dist_approx_to_rounded
                      ).min(),
                      -tol,
                  )
  
                  self.assertFalse(
                      torch.equal(
                          X_rounded[..., int_idcs], exact_rounded_X_ints
                      )
                  )
              else:
                  # check that exact rounding behaves as expected for integers
                  self.assertTrue(
                      torch.equal(
                          X_rounded[..., int_idcs], exact_rounded_X_ints
                      )
                  )
                  if categorical_features is not None:
                      # test that discretization works as expected for categoricals
                      for start, card in categorical_features.items():
                          end = start + card
                          expected_categorical = one_hot(
                              X[..., start:end].argmax(dim=-1),
                              num_classes=card,
                          ).to(X)
                          self.assertTrue(
                              torch.equal(
                                  X_rounded[..., start:end],
                                  expected_categorical,
                              )
                          )
                  # test that gradient information is passed via STE
                  X2 = X.clone().requires_grad_(True)
                  round_tf(X2).sum().backward()
                  self.assertTrue(
                      torch.equal(X2.grad, torch.ones_like(X2))
                  )
              with self.assertRaises(NotImplementedError):
                  round_tf.untransform(X_rounded)
  
              # test no transform on eval
              round_tf = Round(
                  integer_indices=int_idcs,
                  categorical_features=categorical_features,
                  approximate=approx,
                  transform_on_eval=False,
              )
              X_rounded = round_tf(X)
              self.assertFalse(torch.equal(X, X_rounded))
              round_tf.eval()
              X_rounded = round_tf(X)
              self.assertTrue(torch.equal(X, X_rounded))
  
              # test no transform on train
              round_tf = Round(
                  integer_indices=int_idcs,
                  categorical_features=categorical_features,
                  approximate=approx,
                  transform_on_train=False,
              )
              X_rounded = round_tf(X)
              self.assertTrue(torch.equal(X, X_rounded))
              round_tf.eval()
              X_rounded = round_tf(X)
              self.assertFalse(torch.equal(X, X_rounded))
  
              # test equals
              round_tf2 = Round(
                  integer_indices=int_idcs,
                  categorical_features=categorical_features,
                  approximate=approx,
                  transform_on_train=False,
              )
              self.assertTrue(round_tf.equals(round_tf2))
              # test different transform_on_train
              round_tf2 = Round(
                  integer_indices=int_idcs,
                  categorical_features=categorical_features,
                  approximate=approx,
              )
              self.assertFalse(round_tf.equals(round_tf2))
              # test different approx
              round_tf = Round(
                  integer_indices=int_idcs,
              )
              round_tf2 = Round(
                  integer_indices=int_idcs,
                  approximate=not approx,
                  transform_on_train=False,
              )
              self.assertFalse(round_tf.equals(round_tf2))
              # test different indices
              round_tf = Round(
                  integer_indices=int_idcs,
                  categorical_features=categorical_features,
                  transform_on_train=False,
              )
              round_tf2 = Round(
                  integer_indices=[0, 1],
                  categorical_features=categorical_features,
                  approximate=approx,
                  transform_on_train=False,
              )
              self.assertFalse(round_tf.equals(round_tf2))
  
              # test preprocess_transform
              round_tf.transform_on_train = False
              self.assertTrue(
                  torch.equal(round_tf.preprocess_transform(X), X)
              )
              round_tf.transform_on_train = True
              X_rounded = round_tf(X)
              self.assertTrue(
                  torch.equal(
                      round_tf.preprocess_transform(X), X_rounded
                  )
              )
  
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 6 for calling the TestInputTransforms obj's underlying member method code with example usage: obj.test_round_transform() without return types