code: def _expectation(X, centers, weights, concentrations, posterior_type="soft"):
      
      n_examples, n_features = np.shape(X)
      n_clusters, _ = centers.shape
  
      if n_features <= 50:  # works up to about 50 before numrically unstable
          vmf_f = _vmf_log
      else:
          vmf_f = _vmf_log_asymptotic
  
      f_log = np.zeros((n_clusters, n_examples))
      for cc in range(n_clusters):
          f_log[cc, :] = vmf_f(X, concentrations[cc], centers[cc, :])
  
      posterior = np.zeros((n_clusters, n_examples))
      if posterior_type == "soft":
          weights_log = np.log(weights)
          posterior = np.tile(weights_log.T, (n_examples, 1)).T + f_log
          for ee in range(n_examples):
              posterior[:, ee] = np.exp(posterior[:, ee] - logsumexp(posterior[:, ee]))
  
      elif posterior_type == "hard":
          weights_log = np.log(weights)
          weighted_f_log = np.tile(weights_log.T, (n_examples, 1)).T + f_log
          for ee in range(n_examples):
              posterior[np.argmax(weighted_f_log[:, ee]), ee] = 1.0
  
      return posterior
docs: Compute the log-likelihood of each datapoint being in each cluster.
  
      Parameters
      ----------
      centers (mu) : array, [n_centers x n_features]
      weights (alpha) : array, [n_centers, ] (alpha)
      concentrations (kappa) : array, [n_centers, ]
  
      Returns
      ----------
      posterior : array, [n_centers, n_examples]