< Summary

Class:KMeansClusterer
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Algorithms/Clustering/KMeansClusterer.cs
Covered lines:54
Uncovered lines:2
Coverable lines:56
Total lines:98
Line coverage:96.4% (54 of 56)
Covered branches:0
Total branches:0
Covered methods:4
Total methods:4
Method coverage:100% (4 of 4)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
KMeansClusterer(...)0%110100%
KMeansClusterer(...)0%110100%
Cluster(...)0%10.0710091.3%
AssignToClusters(...)0%770100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5
 6// The k-means clusterer class performs k-means clustering.
 7public class KMeansClusterer : ClustererBase {
 8  // Default maximum number of iterations.
 9  protected const int _defaultMaxNumIterations = 20;
 10
 11  // Convergence threshold.
 12  protected const float _epsilon = 1e-3f;
 13
 14  // Number of clusters.
 15  private readonly int _k;
 16
 17  // Maximum number of iterations.
 18  private readonly int _maxNumIterations;
 19
 3920  public KMeansClusterer(int k) : this(k, _defaultMaxNumIterations) {}
 2621  public KMeansClusterer(int k, int maxNumIterations) {
 1322    _k = k;
 1323    _maxNumIterations = maxNumIterations;
 1324  }
 25
 26  // Generate the clusters from the list of hierarchical objects.
 1327  public override List<Cluster> Cluster(IEnumerable<IHierarchical> hierarchicals) {
 1528    if (hierarchicals == null || !hierarchicals.Any()) {
 229      return new List<Cluster>();
 30    }
 31
 32    // Initialize the clusters with centroids located at the positions of k random hierarchical
 33    // objects. Perform Fisher-Yates shuffling to find k random hierarchical objects.
 1134    List<IHierarchical> hierarchicalsList = hierarchicals.ToList();
 35
 36    // Validate that k is not greater than the number of hierarchical objects.
 1237    if (_k > hierarchicalsList.Count) {
 138      throw new InvalidOperationException(
 39          $"Cannot create {_k} clusters from {hierarchicalsList.Count} hierarchical objects.");
 40    }
 41
 1042    var clusters = new List<Cluster>();
 8343    for (int i = 0; i < _k; ++i) {
 2144      int j = UnityEngine.Random.Range(i, hierarchicalsList.Count);
 2145      (hierarchicalsList[i], hierarchicalsList[j]) = (hierarchicalsList[j], hierarchicalsList[i]);
 46
 2147      clusters.Add(new Cluster { Centroid = hierarchicalsList[i].Position });
 2148    }
 49
 1050    bool converged = false;
 1051    int numIteration = 0;
 4852    while (!converged && numIteration < _maxNumIterations) {
 1953      AssignToClusters(clusters, hierarchicals);
 54
 55      // Calculate the new clusters as the mean of all assigned hierarchical objects.
 1956      converged = true;
 14957      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 3758        Vector3 oldClusterPosition = clusters[clusterIndex].Centroid;
 3759        if (clusters[clusterIndex].IsEmpty) {
 060          int hierarchicalIndex = UnityEngine.Random.Range(0, hierarchicalsList.Count);
 061          clusters[clusterIndex].Centroid = hierarchicalsList[hierarchicalIndex].Position;
 3762        } else {
 3763          clusters[clusterIndex].Recenter();
 3764        }
 65
 66        // Check whether the algorithm has converged by checking whether the cluster has moved.
 4867        if (Vector3.Distance(oldClusterPosition, clusters[clusterIndex].Position) > _epsilon) {
 1168          converged = false;
 1169        }
 3770      }
 1971      ++numIteration;
 1972    }
 1073    AssignToClusters(clusters, hierarchicals);
 1074    return clusters;
 1275  }
 76
 77  private static void AssignToClusters(IReadOnlyList<Cluster> clusters,
 2978                                       IEnumerable<IHierarchical> hierarchicals) {
 79    // Clear all clusters.
 26180    foreach (var cluster in clusters) {
 5881      cluster.ClearSubHierarchicals();
 5882    }
 83
 84    // Determine the closest centroid to each hierarchical object.
 47185    foreach (var hierarchical in hierarchicals) {
 12886      float minDistance = Mathf.Infinity;
 12887      int minIndex = -1;
 102488      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 25689        float distance = Vector3.Distance(clusters[clusterIndex].Centroid, hierarchical.Position);
 42790        if (distance < minDistance) {
 17191          minDistance = distance;
 17192          minIndex = clusterIndex;
 17193        }
 25694      }
 12895      clusters[minIndex].AddSubHierarchical(hierarchical);
 12896    }
 2997  }
 98}