app/oryx-app-mllib/src/main/java/com/cloudera/oryx/app/batch/mllib/kmeans/SilhouetteCoefficient.java
double silhouetteCoefficient(Map<Integer,Iterable<double[]>> clusterPointsByID) {
    double totalSilhouetteCoefficient = 0.0;
    long sampleCount = 0L;

    for (Map.Entry<Integer, Iterable<double[]>> entry : clusterPointsByID.entrySet()) {
      List<double[]> clusteredPoints = iterableToList(entry.getValue());
      long clusterSize = clusteredPoints.size();
      // Increment the total sample count for computing silhouette coefficient
      sampleCount += clusterSize;
      // if there's only one element in a cluster, then assume the silhouetteCoefficient for
      // the cluster = 0, this is an arbitrary choice per Section 2: Construction of Silhouettes
      // in the referenced paper
      if (clusterSize > 1) {
        for (double[] point : clusteredPoints) {
          double pointIntraClusterDissimilarity = clusterDissimilarityForPoint(point, clusteredPoints, true);
          double pointInterClusterDissimilarity =
              minInterClusterDissimilarityForPoint(entry.getKey(), point, clusterPointsByID);
          totalSilhouetteCoefficient += silhouetteCoefficient(
              pointIntraClusterDissimilarity, pointInterClusterDissimilarity);
        }
      }

    }

    return sampleCount == 0 ? 0.0 : totalSilhouetteCoefficient / sampleCount;
  }
double silhouetteCoefficient(Map<Integer,Iterable<double[]>> clusterPointsByID) {
    double totalSilhouetteCoefficient = 0.0;
    long sampleCount = 0L;

    for (Map.Entry<Integer, Iterable<double[]>> entry : clusterPointsByID.entrySet()) {
      List<double[]> clusteredPoints = iterableToList(entry.getValue());
      long clusterSize = clusteredPoints.size();
      // Increment the total sample count for computing silhouette coefficient
      sampleCount += clusterSize;
      // if there's only one element in a cluster, then assume the silhouetteCoefficient for
      // the cluster = 0, this is an arbitrary choice per Section 2: Construction of Silhouettes
      // in the referenced paper
      if (clusterSize > 1) {
        for (double[] point : clusteredPoints) {
          double pointIntraClusterDissimilarity = clusterDissimilarityForPoint(point, clusteredPoints, true);
          double pointInterClusterDissimilarity =
              minInterClusterDissimilarityForPoint(entry.getKey(), point, clusterPointsByID);
          totalSilhouetteCoefficient += silhouetteCoefficient(
              pointIntraClusterDissimilarity, pointInterClusterDissimilarity);
        }
      }

    }

    return sampleCount == 0 ? 0.0 : totalSilhouetteCoefficient / sampleCount;
  }
Computes the Silhouette Coefficient for this clustering.
See https://en.wikipedia.org/wiki/Silhouette_(clustering)

@param clusterPointsByID map of cluster ID to points that map to that cluster,
for whom Silhouette coefficient needs to be computed
@return Silhouette Coefficient in range [-1,1]; higher is better