docstr: Converts observations to a dictionary of `Dataset` containers and (optional)
  candidate metadata.
docstr_tok: ["Converts","observations","to","a","dictionary","of","`","Dataset","`","containers","and","(","optional",")","candidate","metadata","."]
code: def _convert_observations(
      self,
      observation_data: List[ObservationData],
      observation_features: List[ObservationFeatures],
      outcomes: List[str],
      parameters: List[str],
      search_space_digest: Optional[SearchSpaceDigest],
  ) -> Tuple[
      List[SupervisedDataset],
      List[str],
      Optional[List[List[TCandidateMetadata]]],
  ]:
      """Converts observations to a dictionary of `Dataset` containers and (optional)
      candidate metadata.
      """
      if len(observation_features) != len(observation_data):
          raise ValueError(
              "Observation features and data must have the same length!"
          )
      ordered_idx = np.argsort(
          [od.trial_index for od in observation_features]
      )
      observation_features = [
          observation_features[i] for i in ordered_idx
      ]
      observation_data = [observation_data[i] for i in ordered_idx]
  
      (
          Xs,
          Ys,
          Yvars,
          candidate_metadata_dict,
          any_candidate_metadata_is_not_none,
      ) = self._extract_observation_data(
          observation_data, observation_features, parameters
      )
  
      datasets: List[SupervisedDataset] = []
      candidate_metadata = []
      for outcome in outcomes:
          X = torch.stack(Xs[outcome], dim=0)
          Y = torch.tensor(Ys[outcome], dtype=torch.long).unsqueeze(-1)
  
          # Update Xs and Ys shapes for PairwiseGP
          Y = _binary_pref_to_comp_pair(Y=Y)
          X, Y = _consolidate_comparisons(X=X, Y=Y)
  
          datapoints, comparisons = X, Y.long()
          event_shape = torch.Size([2 * datapoints.shape[-1]])
          # pyre-fixme[6]: For 2nd param expected `LongTensor` but got `Tensor`.
          dataset_X = SliceContainer(
              datapoints, comparisons, event_shape=event_shape
          )
          dataset_Y = torch.tensor([[0, 1]]).expand(comparisons.shape)
          dataset = RankingDataset(
              X=dataset_X,
              Y=dataset_Y,
              feature_names=parameters,
              outcome_names=[outcome],
          )
  
          datasets.append(dataset)
          candidate_metadata.append(candidate_metadata_dict[outcome])
  
      if not any_candidate_metadata_is_not_none:
          return datasets, outcomes, None
  
      return datasets, outcomes, candidate_metadata
  
file_path: Ax/ax/modelbridge/pairwise.py
imports: from __future__ import annotations
  from typing import List, Optional, Tuple
  import numpy
  import torch
  from ax.core.observation import ObservationData, ObservationFeatures
  from ax.core.search_space import SearchSpaceDigest
  from ax.core.types import TCandidateMetadata
  from ax.modelbridge.torch import TorchModelBridge
  from botorch.models.utils.assorted import consolidate_duplicates
  from botorch.utils.containers import SliceContainer
  from botorch.utils.datasets import RankingDataset, SupervisedDataset
  from torch import Tensor
label_desc: Use image node_id 1 for calling the PairwiseModelBridge obj's underlying member method code with example usage: obj._convert_observations(observation_data, observation_features, outcomes, parameters, search_space_digest) and returns: datasets,  outcomes,  candidate_metadata, datasets,  outcomes,  None