class_docstr: Implements REMBO (Bayesian optimization in a linear subspace).
  
  The (D x d) projection matrix A must be provided, and must be that used for
  the initialization. In the original REMBO paper A ~ N(0, 1). Box bounds
  in the low-d space must also be provided, which in the REMBO paper should
  be [(-sqrt(d), sqrt(d)]^d.
  
  Function evaluations happen in the high-D space, and so the arms on the
  experiment will also be tracked in the high-D space. This class maintains
  a list of points in the low-d spac that have been launched, so we can match
  arms in high-D space back to their low-d point on update.
  
  Args:
      A: (D x d) projection matrix.
      initial_X_d: Points in low-d space for initial data.
      bounds_d: Box bounds in the low-d space.
      kwargs: kwargs for BotorchModel init
code: def fit(
      self,
      datasets: List[SupervisedDataset],
      metric_names: List[str],
      search_space_digest: SearchSpaceDigest,
      candidate_metadata: Optional[
          List[List[TCandidateMetadata]]
      ] = None,
  ) -> None:
      assert len(search_space_digest.task_features) == 0
      assert len(search_space_digest.fidelity_features) == 0
      for b in search_space_digest.bounds:
          # REMBO assumes the input space is [-1, 1]^D
          assert b == (-1, 1)
      self.num_outputs = len(datasets)
      # For convenience for now, assume X for all outcomes the same
      low_d_datasets = self._convert_and_normalize_datasets(
          datasets=datasets
      )
      super().fit(
          datasets=low_d_datasets,
          metric_names=metric_names,
          search_space_digest=SearchSpaceDigest(
              feature_names=[f"x{i}" for i in range(self.A.shape[1])],
              bounds=[(0.0, 1.0)] * len(self.bounds_d),
              task_features=search_space_digest.task_features,
              fidelity_features=search_space_digest.fidelity_features,
          ),
          candidate_metadata=candidate_metadata,
      )
  
imports: import dataclasses
  from typing import Any, List, Optional, Tuple
  from warnings import warn
  import torch
  from ax.core.search_space import SearchSpaceDigest
  from ax.core.types import TCandidateMetadata
  from ax.models.torch.botorch import BotorchModel
  from ax.models.torch_base import TorchGenResults, TorchModel, TorchOptConfig
  from ax.utils.common.docutils import copy_doc
  from botorch.utils.datasets import SupervisedDataset
  from torch import Tensor
label_desc: Use image node_id 2 for calling the REMBO obj's underlying member method code with example usage: obj.fit(datasets, metric_names, search_space_digest, candidate_metadata) without return types