< Summary

Class:AgglomerativeClusterer
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Algorithms/Clustering/AgglomerativeClusterer.cs
Covered lines:0
Uncovered lines:59
Coverable lines:59
Total lines:97
Line coverage:0% (0 of 59)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:2
Method coverage:0% (0 of 2)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AgglomerativeClusterer(...)0%2100%
Cluster(...)0%4622100%

File(s)

/github/workspace/Assets/Scripts/Algorithms/Clustering/AgglomerativeClusterer.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Linq;
 3using UnityEngine;
 4
 5// The agglomerative clusterer performs agglomerative clustering with the stopping condition given
 6// by the size and radius constraints.
 7public class AgglomerativeClusterer : SizeAndRadiusConstrainedClustererBase {
 08  public AgglomerativeClusterer(int maxSize, float maxRadius) : base(maxSize, maxRadius) {}
 9
 10  // Generate the clusters from the list of hierarchical objects.
 011  public override List<Cluster> Cluster(IEnumerable<IHierarchical> hierarchicals) {
 012    var clusters = new List<Cluster>();
 013    if (hierarchicals == null || !hierarchicals.Any()) {
 014      return clusters;
 15    }
 16
 017    foreach (var hierarchical in hierarchicals) {
 018      var cluster = new Cluster { Centroid = hierarchical.Position };
 019      cluster.AddSubHierarchical(hierarchical);
 020      clusters.Add(cluster);
 021    }
 22
 23    // Create a set containing all valid cluster indices.
 024    var validClusterIndices = new HashSet<int>();
 025    for (int i = 0; i < clusters.Count; ++i) {
 026      validClusterIndices.Add(i);
 027    }
 28
 29    // Find the pairwise distances between all clusters.
 30    // Only the lower triangular part of the distances matrix is used, i.e., row index > column
 31    // index.
 032    var distances = new float[clusters.Count, clusters.Count];
 033    for (int i = 0; i < clusters.Count; ++i) {
 034      for (int j = 0; j < i; ++j) {
 035        distances[i, j] = Vector3.Distance(clusters[i].Centroid, clusters[j].Centroid);
 036      }
 037    }
 38
 039    while (true) {
 40      // Find the minimum distance between two clusters.
 041      float minDistance = Mathf.Infinity;
 042      int clusterIndex1 = -1;
 043      int clusterIndex2 = -1;
 44      // Invariant: clusterIndex1 > clusterIndex2.
 045      foreach (int i in validClusterIndices) {
 046        for (int j = 0; j < i; ++j) {
 047          if (validClusterIndices.Contains(j) && distances[i, j] < minDistance) {
 048            minDistance = distances[i, j];
 049            clusterIndex1 = i;
 050            clusterIndex2 = j;
 051          }
 052        }
 053      }
 54
 55      // Check whether the minimum distance exceeds the maximum cluster radius, in which case the
 56      // algorithm has converged. This produces a conservative solution because the radius of a
 57      // merged cluster is less than or equal to the sum of the original cluster radii.
 058      if (minDistance == Mathf.Infinity || minDistance > _maxRadius) {
 059        break;
 60      }
 61
 62      // Check whether merging the two clusters would violate the size constraint.
 063      if (clusters[clusterIndex1].Size + clusters[clusterIndex2].Size > _maxSize) {
 064        distances[clusterIndex1, clusterIndex2] = Mathf.Infinity;
 065        continue;
 66      }
 67
 68      // Merge the two clusters together and keep the cluster with the smaller index, i.e.,
 69      // clusterIndex2.
 070      clusters[clusterIndex2].Merge(clusters[clusterIndex1]);
 071      clusters[clusterIndex2].Recenter();
 072      validClusterIndices.Remove(clusterIndex1);
 73
 74      // Update the distances matrix using the distance between the cluster centroids.
 75      // TODO(titan): Change the distance metric to use average or maximum linkage.
 076      foreach (int i in validClusterIndices) {
 077        if (i == clusterIndex2) {
 078          continue;
 79        }
 080        float distance = Vector3.Distance(clusters[clusterIndex2].Centroid, clusters[i].Centroid);
 081        if (i < clusterIndex2) {
 082          distances[clusterIndex2, i] = distance;
 083        } else if (i > clusterIndex2) {
 084          distances[i, clusterIndex2] = distance;
 085        }
 086      }
 087    }
 88
 89    // Select only the valid clusters.
 090    for (int i = clusters.Count - 1; i >= 0; --i) {
 091      if (!validClusterIndices.Contains(i)) {
 092        clusters.RemoveAt(i);
 093      }
 094    }
 095    return clusters;
 096  }
 97}