<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def assign(self, spike_ids, spike_clusters_rel=0):
 """Make new spike cluster assignments. Parameters spike_ids : array-like List of spike ids. spike_clusters_rel : array-like Relative cluster ids of the spikes in `spike_ids`. This must have the same size as `spike_ids`. Returns ------- up : UpdateInfo instance Note ---- `spike_clusters_rel` contain *relative* cluster indices. Their values don't matter: what matters is whether two give spikes should end up in the same cluster or not. Adding a constant number to all elements in `spike_clusters_rel` results in exactly the same operation. The final cluster ids are automatically generated by the `Clustering` class. This is because we must ensure that all modified clusters get brand new ids. The whole library is based on the assumption that cluster ids are unique and "disposable". Changing a cluster always results in a new cluster id being assigned. If a spike is assigned to a new cluster, then all other spikes belonging to the same cluster are assigned to a brand new cluster, even if they were not changed explicitely by the `assign()` method. In other words, the list of spikes affected by an `assign()` is almost always a strict superset of the `spike_ids` parameter. The only case where this is not true is when whole clusters change: this is called a merge. It is implemented in a separate `merge()` method because it is logically much simpler, and faster to execute. """


        assert not isinstance(spike_ids, slice)

        # Ensure `spike_clusters_rel` is an array-like.
        if not hasattr(spike_clusters_rel, '__len__'):
            spike_clusters_rel = spike_clusters_rel * np.ones(len(spike_ids),
                                                              dtype=np.int64)

        spike_ids = _as_array(spike_ids)
        if len(spike_ids) == 0:
            return UpdateInfo()
        assert len(spike_ids) == len(spike_clusters_rel)
        assert spike_ids.min() >= 0
        assert spike_ids.max() < self._n_spikes, "Some spikes don't exist."

        # Normalize the spike-cluster assignment such that
        # there are only new or dead clusters, not modified clusters.
        # This implies that spikes not explicitly selected, but that
        # belong to clusters affected by the operation, will be assigned
        # to brand new clusters.
        spike_ids, cluster_ids = _extend_assignment(spike_ids,
                                                    self._spike_clusters,
                                                    spike_clusters_rel,
                                                    self.new_cluster_id(),
                                                    )

        up = self._do_assign(spike_ids, cluster_ids)
        undo_state = self.emit('request_undo_state', up)

        # Add the assignment to the undo stack.
        self._undo_stack.add((spike_ids, cluster_ids, undo_state))

        self.emit('cluster', up)
        return up