docs: def _spherical_kmeans_single_lloyd(
      X,
      n_clusters,
      sample_weight=None,
      max_iter=300,
      init="k-means++",
      verbose=False,
      x_squared_norms=None,
      random_state=None,
      tol=1e-4,
      precompute_distances=True,
  ):
      """
      Modified from sklearn.cluster.k_means_.k_means_single_lloyd.
      """
      random_state = check_random_state(random_state)
  
      sample_weight = _check_sample_weight(X, sample_weight)
  
      best_labels, best_inertia, best_centers = None, None, None
  
      # init
      centers = _init_centroids(
          X, n_clusters, init, random_state=random_state, x_squared_norms=x_squared_norms
      )
      if verbose:
          print("Initialization complete")
  
      # Allocate memory to store the distances for each sample to its
      # closer center for reallocation in case of ties
      distances = np.zeros(shape=(X.shape[0],), dtype=X.dtype)
  
      # iterations
      for i in range(max_iter):
          centers_old = centers.copy()
  
          # labels assignment
          # TODO: _labels_inertia should be done with cosine distance
          #       since ||a - b|| = 2(1 - cos(a,b)) when a,b are unit normalized
          #       this doesn't really matter.
          labels, inertia = _labels_inertia(
              X,
              sample_weight,
              x_squared_norms,
              centers,
              precompute_distances=precompute_distances,
              distances=distances,
          )
  
          # computation of the means
          if sp.issparse(X):
              centers = _k_means._centers_sparse(
                  X, sample_weight, labels, n_clusters, distances
              )
          else:
              centers = _k_means._centers_dense(
                  X, sample_weight, labels, n_clusters, distances
              )
  
          # l2-normalize centers (this is the main contibution here)
          centers = normalize(centers)
  
          if verbose:
              print("Iteration %2d, inertia %.3f" % (i, inertia))
  
          if best_inertia is None or inertia < best_inertia:
              best_labels = labels.copy()
              best_centers = centers.copy()
              best_inertia = inertia
  
          center_shift_total = squared_norm(centers_old - centers)
          if center_shift_total <= tol:
              if verbose:
                  print(
                      "Converged at iteration %d: "
                      "center shift %e within tolerance %e" % (i, center_shift_total, tol)
                  )
              break
  
      if center_shift_total > 0:
          # rerun E-step in case of non-convergence so that predicted labels
          # match cluster centers
          best_labels, best_inertia = _labels_inertia(
              X,
              sample_weight,
              x_squared_norms,
              best_centers,
              precompute_distances=precompute_distances,
              distances=distances,
          )
  
      return best_labels, best_inertia, best_centers, i + 1