docs: def _movMF(
      X,
      n_clusters,
      posterior_type="soft",
      force_weights=None,
      max_iter=300,
      verbose=False,
      init="random-class",
      random_state=None,
      tol=1e-6,
  ):
      """Mixture of von Mises Fisher clustering.
  
      Implements the algorithms (i) and (ii) from
  
        "Clustering on the Unit Hypersphere using von Mises-Fisher Distributions"
        by Banerjee, Dhillon, Ghosh, and Sra.
  
      TODO: Currently only supports Banerjee et al 2005 approximation of kappa,
            however, there are numerous other approximations see _update_params.
  
      Attribution
      ----------
      Approximation of log-vmf distribution function from movMF R-package.
  
      movMF: An R Package for Fitting Mixtures of von Mises-Fisher Distributions
      by Kurt Hornik, Bettina Grun, 2014
  
      Find more at:
        https://cran.r-project.org/web/packages/movMF/vignettes/movMF.pdf
        https://cran.r-project.org/web/packages/movMF/index.html
  
      Parameters
      ----------
      n_clusters : int, optional, default: 8
          The number of clusters to form as well as the number of
          centroids to generate.
  
      posterior_type: 'soft' or 'hard'
          Type of posterior computed in exepectation step.
          See note about attribute: self.posterior_
  
      force_weights : None or array [n_clusters, ]
          If None, the algorithm will estimate the weights.
          If an array of weights, algorithm will estimate concentrations and
          centers with given weights.
  
      max_iter : int, default: 300
          Maximum number of iterations of the k-means algorithm for a
          single run.
  
      n_init : int, default: 10
          Number of time the k-means algorithm will be run with different
          centroid seeds. The final results will be the best output of
          n_init consecutive runs in terms of inertia.
  
      init:  (string) one of
          random-class [default]: random class assignment & centroid computation
          k-means++ : uses sklearn k-means++ initialization algorithm
          spherical-k-means : use centroids from one pass of spherical k-means
          random : random unit norm vectors
          random-orthonormal : random orthonormal vectors
          If an ndarray is passed, it should be of shape (n_clusters, n_features)
          and gives the initial centers.
  
      tol : float, default: 1e-6
          Relative tolerance with regards to inertia to declare convergence
  
      n_jobs : int
          The number of jobs to use for the computation. This works by computing
          each of the n_init runs in parallel.
          If -1 all CPUs are used. If 1 is given, no parallel computing code is
          used at all, which is useful for debugging. For n_jobs below -1,
          (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one
          are used.
  
      random_state : integer or numpy.RandomState, optional
          The generator used to initialize the centers. If an integer is
          given, it fixes the seed. Defaults to the global numpy random
          number generator.
  
      verbose : int, default 0
          Verbosity mode.
  
      copy_x : boolean, default True
          When pre-computing distances it is more numerically accurate to center
          the data first.  If copy_x is True, then the original data is not
          modified.  If False, the original data is modified, and put back before
          the function returns, but small numerical differences may be introduced
          by subtracting and then adding the data mean.
      """
      random_state = check_random_state(random_state)
      n_examples, n_features = np.shape(X)
  
      # init centers (mus)
      centers = _init_unit_centers(X, n_clusters, random_state, init)
  
      # init weights (alphas)
      if force_weights is None:
          weights = np.ones((n_clusters,))
          weights = weights / np.sum(weights)
      else:
          weights = force_weights
  
      # init concentrations (kappas)
      concentrations = np.ones((n_clusters,))
  
      if verbose:
          print("Initialization complete")
  
      for iter in range(max_iter):
          centers_prev = centers.copy()
  
          # expectation step
          posterior = _expectation(
              X, centers, weights, concentrations, posterior_type=posterior_type
          )
  
          # maximization step
          centers, weights, concentrations = _maximization(
              X, posterior, force_weights=force_weights
          )
  
          # check convergence
          tolcheck = squared_norm(centers_prev - centers)
          if tolcheck <= tol:
              if verbose:
                  print(
                      "Converged at iteration %d: "
                      "center shift %e within tolerance %e" % (iter, tolcheck, tol)
                  )
              break
  
      # labels come for free via posterior
      labels = np.zeros((n_examples,))
      for ee in range(n_examples):
          labels[ee] = np.argmax(posterior[:, ee])
  
      inertia = _inertia_from_labels(X, centers, labels)
  
      return centers, weights, concentrations, posterior, labels, inertia