docs: public double evaluateClustering(Database db, Relation<O> rel, DistanceQuery<O> dq, Clustering<?> c) {
      List<? extends Cluster<?>> clusters = c.getAllClusters();
      MeanVariance msil = new MeanVariance();
      int ignorednoise = 0;
      for(Cluster<?> cluster : clusters) {
        // Note: we treat 1-element clusters the same as noise.
        if(cluster.size() <= 1 || cluster.isNoise()) {
          switch(noiseOption){
          case IGNORE_NOISE:
            ignorednoise += cluster.size();
            continue; // Ignore noise elements
          case TREAT_NOISE_AS_SINGLETONS:
            // As suggested in Rousseeuw, we use 0 for singletons.
            msil.put(0., cluster.size());
            continue;
          case MERGE_NOISE:
            break; // Treat as cluster below
          }
        }
        ArrayDBIDs ids = DBIDUtil.ensureArray(cluster.getIDs());
        double[] as = new double[ids.size()]; // temporary storage.
        DBIDArrayIter it1 = ids.iter(), it2 = ids.iter();
        for(it1.seek(0); it1.valid(); it1.advance()) {
          // a: In-cluster distances
          double a = as[it1.getOffset()]; // Already computed distances
          for(it2.seek(it1.getOffset() + 1); it2.valid(); it2.advance()) {
            final double dist = dq.distance(it1, it2);
            a += dist;
            as[it2.getOffset()] += dist;
          }
          a /= (ids.size() - 1);
          // b: minimum average distance to other clusters:
          double b = Double.POSITIVE_INFINITY;
          for(Cluster<?> ocluster : clusters) {
            if(ocluster == /* yes, reference identity */cluster) {
              continue; // Same cluster
            }
            if(ocluster.size() <= 1 || ocluster.isNoise()) {
              switch(noiseOption){
              case IGNORE_NOISE:
                continue; // Ignore noise elements
              case TREAT_NOISE_AS_SINGLETONS:
                // Treat noise cluster as singletons:
                for(DBIDIter it3 = ocluster.getIDs().iter(); it3.valid(); it3.advance()) {
                  final double dist = dq.distance(it1, it3);
                  b = dist < b ? dist : b; // Minimum average
                }
                continue;
              case MERGE_NOISE:
                break; // Treat as cluster below
              }
            }
            final DBIDs oids = ocluster.getIDs();
            double btmp = 0.;
            for(DBIDIter it3 = oids.iter(); it3.valid(); it3.advance()) {
              btmp += dq.distance(it1, it3);
            }
            btmp /= oids.size(); // Average
            b = btmp < b ? btmp : b; // Minimum average
          }
          // One cluster only?
          b = b < Double.POSITIVE_INFINITY ? b : a;
          msil.put((b - a) / (b > a ? b : a));
        }
      }
      double penalty = 1.;
      // Only if {@link NoiseHandling#IGNORE_NOISE}:
      if(penalize && ignorednoise > 0) {
        penalty = (rel.size() - ignorednoise) / (double) rel.size();
      }
      final double meansil = penalty * msil.getMean();
      final double stdsil = penalty * msil.getSampleStddev();
      if(LOG.isStatistics()) {
        LOG.statistics(new StringStatistic(key + ".silhouette.noise-handling", noiseOption.toString()));
        if(ignorednoise > 0) {
          LOG.statistics(new LongStatistic(key + ".silhouette.noise", ignorednoise));
        }
        LOG.statistics(new DoubleStatistic(key + ".silhouette.mean", meansil));
        LOG.statistics(new DoubleStatistic(key + ".silhouette.stddev", stdsil));
      }
  
      EvaluationResult ev = EvaluationResult.findOrCreate(db.getHierarchy(), c, "Internal Clustering Evaluation", "internal evaluation");
      MeasurementGroup g = ev.findOrCreateGroup("Distance-based Evaluation");
      g.addMeasure("Silhouette +-" + FormatUtil.NF2.format(stdsil), meansil, -1., 1., 0., false);
      db.getHierarchy().resultChanged(ev);
      return meansil;
    }