docs: public Clustering<M> run(Database database, Relation<V> relation) {
      if(relation.size() == 0) {
        throw new IllegalArgumentException("database empty: must contain elements");
      }
      // initial models
      List<? extends EMClusterModel<M>> models = mfactory.buildInitialModels(database, relation, k, SquaredEuclideanDistanceFunction.STATIC);
      WritableDataStore<double[]> probClusterIGivenX = DataStoreUtil.makeStorage(relation.getDBIDs(), DataStoreFactory.HINT_HOT | DataStoreFactory.HINT_SORTED, double[].class);
      double loglikelihood = assignProbabilitiesToInstances(relation, models, probClusterIGivenX);
      DoubleStatistic likestat = LOG.isStatistics() ? new DoubleStatistic(this.getClass().getName() + ".loglikelihood") : null;
      if(LOG.isStatistics()) {
        LOG.statistics(likestat.setDouble(loglikelihood));
      }
  
      // iteration unless no change
      int it = 0, lastimprovement = 0;
      double bestloglikelihood = loglikelihood; // For detecting instabilities.
      for(++it; it < maxiter || maxiter < 0; it++) {
        final double oldloglikelihood = loglikelihood;
        recomputeCovarianceMatrices(relation, probClusterIGivenX, models, prior);
        // reassign probabilities
        loglikelihood = assignProbabilitiesToInstances(relation, models, probClusterIGivenX);
  
        if(LOG.isStatistics()) {
          LOG.statistics(likestat.setDouble(loglikelihood));
        }
        if(loglikelihood - bestloglikelihood > delta) {
          lastimprovement = it;
          bestloglikelihood = loglikelihood;
        }
        if(Math.abs(loglikelihood - oldloglikelihood) <= delta || lastimprovement < it >> 1) {
          break;
        }
      }
      if(LOG.isStatistics()) {
        LOG.statistics(new LongStatistic(KEY + ".iterations", it));
      }
  
      // fill result with clusters and models
      List<ModifiableDBIDs> hardClusters = new ArrayList<>(k);
      for(int i = 0; i < k; i++) {
        hardClusters.add(DBIDUtil.newArray());
      }
  
      // provide a hard clustering
      for(DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) {
        hardClusters.get(argmax(probClusterIGivenX.get(iditer))).add(iditer);
      }
      Clustering<M> result = new Clustering<>("EM Clustering", "em-clustering");
      // provide models within the result
      for(int i = 0; i < k; i++) {
        result.addToplevelCluster(new Cluster<>(hardClusters.get(i), models.get(i).finalizeCluster()));
      }
      if(isSoft()) {
        result.addChildResult(new MaterializedRelation<>("cluster assignments", "em-soft-score", SOFT_TYPE, probClusterIGivenX, relation.getDBIDs()));
      }
      else {
        probClusterIGivenX.destroy();
      }
      return result;
    }