docs: def cluster(self, X):
          """Run GMM and return the cluster indices"""
  
          # Initialize the gaussians randomly
          self._init_random_gaussians(X)
  
          # Run EM until convergence or for max iterations
          for _ in range(self.max_iterations):
              self._expectation(X)    # E-step
              self._maximization(X)   # M-step
  
              # Check convergence
              if self._converged(X):
                  break
  
          # Make new assignments and return them
          self._expectation(X)
          return self.sample_assignments