code: def update(self, X, y=None, **fit_params):
      if "relations" not in fit_params:
          raise ValueError(
              "Aligned UMAP requires relations between data to be "
              "specified"
          )
  
      new_dict_relations = fit_params["relations"]
      assert isinstance(new_dict_relations, dict)
  
      X = check_array(X)
  
      self.__dict__ = set_aligned_params(
          fit_params, self.__dict__, self.n_models_
      )
  
      # We need n_components to be constant or this won't work
      if type(self.n_components) in (list, tuple, np.ndarray):
          raise ValueError(
              "n_components must be a single integer, and cannot vary"
          )
  
      if self.n_epochs is None:
          self.n_epochs = 200
  
      n_epochs = self.n_epochs
  
      new_mapper = UMAP(
          n_neighbors=get_nth_item_or_val(
              self.n_neighbors, self.n_models_
          ),
          min_dist=get_nth_item_or_val(self.min_dist, self.n_models_),
          n_epochs=get_nth_item_or_val(self.n_epochs, self.n_models_),
          repulsion_strength=get_nth_item_or_val(
              self.repulsion_strength, self.n_models_
          ),
          learning_rate=get_nth_item_or_val(
              self.learning_rate, self.n_models_
          ),
          init=self.init,
          spread=get_nth_item_or_val(self.spread, self.n_models_),
          negative_sample_rate=get_nth_item_or_val(
              self.negative_sample_rate, self.n_models_
          ),
          local_connectivity=get_nth_item_or_val(
              self.local_connectivity, self.n_models_
          ),
          set_op_mix_ratio=get_nth_item_or_val(
              self.set_op_mix_ratio, self.n_models_
          ),
          unique=get_nth_item_or_val(self.unique, self.n_models_),
          n_components=self.n_components,
          metric=self.metric,
          metric_kwds=self.metric_kwds,
          low_memory=self.low_memory,
          random_state=self.random_state,
          angular_rp_forest=self.angular_rp_forest,
          transform_queue_size=self.transform_queue_size,
          target_n_neighbors=self.target_n_neighbors,
          target_metric=self.target_metric,
          target_metric_kwds=self.target_metric_kwds,
          target_weight=self.target_weight,
          transform_seed=self.transform_seed,
          force_approximation_algorithm=self.force_approximation_algorithm,
          verbose=self.verbose,
          a=self.a,
          b=self.b,
      ).fit(X, y)
  
      self.n_models_ += 1
      self.mappers_ += [new_mapper]
  
      self.dict_relations_ += [new_dict_relations]
  
      window_size = fit_params.get(
          "window_size", self.alignment_window_size
      )
      new_relations = expand_relations(
          self.dict_relations_, window_size
      )
  
      indptr_list = numba.typed.List.empty_list(numba.types.int32[::1])
      indices_list = numba.typed.List.empty_list(numba.types.int32[::1])
      heads = numba.typed.List.empty_list(numba.types.int32[::1])
      tails = numba.typed.List.empty_list(numba.types.int32[::1])
      epochs_per_samples = numba.typed.List.empty_list(
          numba.types.float64[::1]
      )
  
      for i, mapper in enumerate(self.mappers_):
          indptr_list.append(mapper.graph_.indptr)
          indices_list.append(mapper.graph_.indices)
          heads.append(mapper.graph_.tocoo().row)
          tails.append(mapper.graph_.tocoo().col)
          if i == len(self.mappers_) - 1:
              epochs_per_samples.append(
                  make_epochs_per_sample(
                      mapper.graph_.tocoo().data, n_epochs
                  )
              )
          else:
              epochs_per_samples.append(
                  np.full(
                      mapper.embedding_.shape[0],
                      n_epochs + 1,
                      dtype=np.float64,
                  )
              )
  
      new_regularisation_weights = build_neighborhood_similarities(
          indptr_list,
          indices_list,
          new_relations,
      )
  
      # TODO: We can likely make this more efficient and not recompute each time
      inv_dict_relations = invert_dict(new_dict_relations)
  
      new_embedding = init_from_existing(
          self.embeddings_[-1], new_mapper.graph_, inv_dict_relations
      )
  
      self.embeddings_.append(new_embedding)
  
      rng_state_transform = np.random.RandomState(self.transform_seed)
      seed_triplet = rng_state_transform.randint(
          INT32_MIN, INT32_MAX, 3
      ).astype(np.int64)
      self.embeddings_ = optimize_layout_aligned_euclidean(
          self.embeddings_,
          self.embeddings_,
          heads,
          tails,
          n_epochs,
          epochs_per_samples,
          new_regularisation_weights,
          new_relations,
          seed_triplet,
          lambda_=self.alignment_regularisation,
      )
  
imports: import numpy
  import numba
  from sklearn.base import BaseEstimator
  from sklearn.utils import check_array
  from umap.sparse import arr_intersect
  from umap.sparse import arr_union
  from umap.umap_ import UMAP, make_epochs_per_sample
  from umap.spectral import spectral_layout
  from umap.layouts import optimize_layout_aligned_euclidean
label_desc: Use image node_id 4 for calling the AlignedUMAP obj's underlying member method code with example usage: obj.update(X, y) without return types