docs: def transform(self, X):
          """Transform X into the existing embedded space and return that
          transformed output.
  
          Parameters
          ----------
          X : array, shape (n_samples, n_features)
              New data to be transformed.
  
          Returns
          -------
          X_new : array, shape (n_samples, n_components)
              Embedding of the new data in low-dimensional space.
          """
          # If we fit just a single instance then error
          if self.embedding_.shape[0] == 1:
              raise ValueError('Transform unavailable when model was fit with'
                               'only a single data sample.')
          # If we just have the original input then short circuit things
          X = check_array(X, dtype=np.float32, accept_sparse="csr")
          x_hash = joblib.hash(X)
          if x_hash == self._input_hash:
              return self.embedding_
  
          if self._sparse_data:
              raise ValueError("Transform not available for sparse input.")
          elif self.metric == 'precomputed':
              raise ValueError("Transform  of new data not available for "
                               "precomputed metric.")
  
          X = check_array(X, dtype=np.float32, order="C")
          random_state = check_random_state(self.transform_seed)
          rng_state = random_state.randint(INT32_MIN, INT32_MAX, 3).astype(np.int64)
  
          if self._small_data:
              dmat = pairwise_distances(
                  X, self._raw_data, metric=self.metric, **self._metric_kwds
              )
              indices = np.argpartition(dmat,
                                        self._n_neighbors)[:, :self._n_neighbors]
              dmat_shortened = submatrix(dmat, indices, self._n_neighbors)
              indices_sorted = np.argsort(dmat_shortened)
              indices = submatrix(indices, indices_sorted, self._n_neighbors)
              dists = submatrix(dmat_shortened, indices_sorted,
                                self._n_neighbors)
          else:
              init = initialise_search(
                  self._rp_forest,
                  self._raw_data,
                  X,
                  int(self._n_neighbors * self.transform_queue_size),
                  self._random_init,
                  self._tree_init,
                  rng_state,
              )
              result = self._search(
                  self._raw_data,
                  self._search_graph.indptr,
                  self._search_graph.indices,
                  init,
                  X,
              )
  
              indices, dists = deheap_sort(result)
              indices = indices[:, : self._n_neighbors]
              dists = dists[:, : self._n_neighbors]
  
          adjusted_local_connectivity = max(0, self.local_connectivity - 1.0)
          sigmas, rhos = smooth_knn_dist(
              dists, self._n_neighbors, local_connectivity=adjusted_local_connectivity
          )
  
          rows, cols, vals = compute_membership_strengths(indices, dists, sigmas, rhos)
  
          graph = scipy.sparse.coo_matrix(
              (vals, (rows, cols)), shape=(X.shape[0], self._raw_data.shape[0])
          )
  
          # This was a very specially constructed graph with constant degree.
          # That lets us do fancy unpacking by reshaping the csr matrix indices
          # and data. Doing so relies on the constant degree assumption!
          csr_graph = normalize(graph.tocsr(), norm="l1")
          inds = csr_graph.indices.reshape(X.shape[0], self._n_neighbors)
          weights = csr_graph.data.reshape(X.shape[0], self._n_neighbors)
          embedding = init_transform(inds, weights, self.embedding_)
  
          if self.n_epochs is None:
              # For smaller datasets we can use more epochs
              if graph.shape[0] <= 10000:
                  n_epochs = 100
              else:
                  n_epochs = 30
          else:
              n_epochs = self.n_epochs // 3.0
  
          graph.data[graph.data < (graph.data.max() / float(n_epochs))] = 0.0
          graph.eliminate_zeros()
  
          epochs_per_sample = make_epochs_per_sample(graph.data, n_epochs)
  
          head = graph.row
          tail = graph.col
  
          embedding = optimize_layout(
              embedding,
              self.embedding_,
              head,
              tail,
              n_epochs,
              graph.shape[1],
              epochs_per_sample,
              self._a,
              self._b,
              rng_state,
              self.repulsion_strength,
              self._initial_alpha,
              self.negative_sample_rate,
              verbose=self.verbose,
          )
  
          return embedding