docs: def movMF(
      X,
      n_clusters,
      posterior_type="soft",
      force_weights=None,
      n_init=10,
      n_jobs=1,
      max_iter=300,
      verbose=False,
      init="random-class",
      random_state=None,
      tol=1e-6,
      copy_x=True,
  ):
      """Wrapper for parallelization of _movMF and running n_init times.
      """
      if n_init <= 0:
          raise ValueError(
              "Invalid number of initializations."
              " n_init=%d must be bigger than zero." % n_init
          )
      random_state = check_random_state(random_state)
  
      if max_iter <= 0:
          raise ValueError(
              "Number of iterations should be a positive number,"
              " got %d instead" % max_iter
          )
  
      best_inertia = np.infty
      X = as_float_array(X, copy=copy_x)
      tol = _tolerance(X, tol)
  
      if hasattr(init, "__array__"):
          init = check_array(init, dtype=X.dtype.type, copy=True)
          _validate_center_shape(X, n_clusters, init)
  
          if n_init != 1:
              warnings.warn(
                  "Explicit initial center position passed: "
                  "performing only one init in k-means instead of n_init=%d" % n_init,
                  RuntimeWarning,
                  stacklevel=2,
              )
              n_init = 1
  
      # defaults
      best_centers = None
      best_labels = None
      best_weights = None
      best_concentrations = None
      best_posterior = None
      best_inertia = None
  
      if n_jobs == 1:
          # For a single thread, less memory is needed if we just store one set
          # of the best results (as opposed to one set per run per thread).
          for it in range(n_init):
              # cluster on the sphere
              (centers, weights, concentrations, posterior, labels, inertia) = _movMF(
                  X,
                  n_clusters,
                  posterior_type=posterior_type,
                  force_weights=force_weights,
                  max_iter=max_iter,
                  verbose=verbose,
                  init=init,
                  random_state=random_state,
                  tol=tol,
              )
  
              # determine if these results are the best so far
              if best_inertia is None or inertia < best_inertia:
                  best_centers = centers.copy()
                  best_labels = labels.copy()
                  best_weights = weights.copy()
                  best_concentrations = concentrations.copy()
                  best_posterior = posterior.copy()
                  best_inertia = inertia
      else:
          # parallelisation of movMF runs
          seeds = random_state.randint(np.iinfo(np.int32).max, size=n_init)
          results = Parallel(n_jobs=n_jobs, verbose=0)(
              delayed(_movMF)(
                  X,
                  n_clusters,
                  posterior_type=posterior_type,
                  force_weights=force_weights,
                  max_iter=max_iter,
                  verbose=verbose,
                  init=init,
                  random_state=random_state,
                  tol=tol,
              )
              for seed in seeds
          )
  
          # Get results with the lowest inertia
          centers, weights, concentrations, posteriors, labels, inertia = zip(*results)
          best = np.argmin(inertia)
          best_labels = labels[best]
          best_inertia = inertia[best]
          best_centers = centers[best]
          best_concentrations = concentrations[best]
          best_posterior = posteriors[best]
          best_weights = weights[best]
  
      return (
          best_centers,
          best_labels,
          best_inertia,
          best_weights,
          best_concentrations,
          best_posterior,
      )