docs: public double evaluateClustering(Database db, Relation<O> rel, Clustering<?> cl) {
      final DistanceQuery<O> dq = rel.getDistanceQuery(distanceFunction);
  
      List<? extends Cluster<?>> clusters = cl.getAllClusters();
      final int numc = clusters.size();
  
      // DBCV needs a "dimensionality".
      @SuppressWarnings("unchecked")
      final Relation<? extends SpatialComparable> vrel = (Relation<? extends SpatialComparable>) rel;
      final int dim = RelationUtil.dimensionality(vrel);
  
      // precompute all core distances
      ArrayDBIDs[] cids = new ArrayDBIDs[numc];
      double[][] coreDists = new double[numc][];
      for(int c = 0; c < numc; c++) {
        Cluster<?> cluster = clusters.get(c);
        // Singletons are considered as Noise, because they have no sparseness
        if(cluster.isNoise() || cluster.size() < 2) {
          coreDists[c] = null;
          continue;
        }
        // Store for use below:
        ArrayDBIDs ids = cids[c] = DBIDUtil.ensureArray(cluster.getIDs());
        double[] clusterCoreDists = coreDists[c] = new double[ids.size()];
        for(DBIDArrayIter it = ids.iter(), it2 = ids.iter(); it.valid(); it.advance()) {
          double currentCoreDist = 0;
          int neighbors = 0;
          for(it2.seek(0); it2.valid(); it2.advance()) {
            if(DBIDUtil.equal(it, it2)) {
              continue;
            }
            double dist = dq.distance(it, it2);
            // Unfortunately, the DBCV definition has a division by zero.
            // We ignore such objects.
            if(dist > 0) {
              currentCoreDist += MathUtil.powi(1. / dist, dim);
              ++neighbors;
            }
          }
          // Average, and undo power.
          clusterCoreDists[it.getOffset()] = FastMath.pow(currentCoreDist / neighbors, -1. / dim);
        }
      }
  
      // compute density sparseness of all clusters
      int[][] clusterDegrees = new int[numc][];
      double[] clusterDscMax = new double[numc];
      // describes if a cluster contains any internal edges
      boolean[] internalEdges = new boolean[numc];
      for(int c = 0; c < numc; c++) {
        Cluster<?> cluster = clusters.get(c);
        if(cluster.isNoise() || cluster.size() < 2) {
          clusterDegrees[c] = null;
          clusterDscMax[c] = Double.NaN;
          continue;
        }
        double[] clusterCoreDists = coreDists[c];
        ArrayDBIDs ids = cids[c];
        double dscMax = 0; // Density Sparseness of the Cluster
        double[][] distances = new double[cluster.size()][cluster.size()];
  
        // create mutability distance matrix for Minimum Spanning Tree
        for(DBIDArrayIter it = ids.iter(), it2 = ids.iter(); it.valid(); it.advance()) {
          double currentCoreDist = clusterCoreDists[it.getOffset()];
          for(it2.seek(it.getOffset() + 1); it2.valid(); it2.advance()) {
            double mutualReachDist = MathUtil.max(currentCoreDist, clusterCoreDists[it2.getOffset()], dq.distance(it, it2));
            distances[it.getOffset()][it2.getOffset()] = mutualReachDist;
            distances[it2.getOffset()][it.getOffset()] = mutualReachDist;
          }
        }
  
        // generate Minimum Spanning Tree
        int[] nodes = PrimsMinimumSpanningTree.processDense(distances);
  
        // get degree of all nodes in the spanning tree
        int[] degree = new int[cluster.size()];
        for(int i = 0; i < nodes.length; i++) {
          degree[nodes[i]]++;
        }
        // check if cluster contains any internal edges
        for(int i = 0, e = nodes.length - 1; i < e; i += 2) {
          if(degree[nodes[i]] > 1 && degree[nodes[i + 1]] > 1) {
            internalEdges[c] = true;
          }
        }
  
        clusterDegrees[c] = degree;
  
        // find maximum sparseness in the Minimum Spanning Tree
        for(int i = 0, e = nodes.length - 1; i < e; i += 2) {
          final int n1 = nodes[i], n2 = nodes[i + 1];
          // We only consider edges where both vertices are internal nodes.
          // If a cluster has no internal nodes we consider all edges.
          if(distances[n1][n2] > dscMax && (!internalEdges[c] || (degree[n1] > 1 && degree[n2] > 1))) {
            dscMax = distances[n1][n2];
          }
        }
        clusterDscMax[c] = dscMax;
      }
  
      // compute density separation of all clusters
      double dbcv = 0;
      for(int c = 0; c < numc; c++) {
        Cluster<?> cluster = clusters.get(c);
        if(cluster.isNoise() || cluster.size() < 2) {
          continue;
        }
        double currentDscMax = clusterDscMax[c];
        double[] clusterCoreDists = coreDists[c];
        int[] currentDegree = clusterDegrees[c];
  
        // minimal Density Separation of the Cluster
        double dspcMin = Double.POSITIVE_INFINITY;
        for(DBIDArrayIter it = cids[c].iter(); it.valid(); it.advance()) {
          // We again ignore external nodes, if the cluster has any internal
          // nodes.
          if(currentDegree[it.getOffset()] < 2 && internalEdges[c]) {
            continue;
          }
          double currentCoreDist = clusterCoreDists[it.getOffset()];
          for(int oc = 0; oc < numc; oc++) {
            Cluster<?> ocluster = clusters.get(oc);
            if(ocluster.isNoise() || ocluster.size() < 2 || cluster == ocluster) {
              continue;
            }
            int[] oDegree = clusterDegrees[oc];
            double[] oclusterCoreDists = coreDists[oc];
            for(DBIDArrayIter it2 = cids[oc].iter(); it2.valid(); it2.advance()) {
              if(oDegree[it2.getOffset()] < 2 && internalEdges[oc]) {
                continue;
              }
              double mutualReachDist = MathUtil.max(currentCoreDist, oclusterCoreDists[it2.getOffset()], dq.distance(it, it2));
              dspcMin = mutualReachDist < dspcMin ? mutualReachDist : dspcMin;
            }
          }
        }
  
        // compute DBCV
        double vc = (dspcMin - currentDscMax) / MathUtil.max(dspcMin, currentDscMax);
        double weight = cluster.size() / (double) rel.size();
        dbcv += weight * vc;
      }
  
      EvaluationResult ev = EvaluationResult.findOrCreate(db.getHierarchy(), cl, "Internal Clustering Evaluation", "internal evaluation");
      MeasurementGroup g = ev.findOrCreateGroup("Distance-based Evaluation");
      g.addMeasure("Density Based Clustering Validation", dbcv, 0., Double.POSITIVE_INFINITY, 0., true);
      db.getHierarchy().resultChanged(ev);
      return dbcv;
    }