< Summary

Class:KMeansClusterer
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Algorithms/Clustering/KMeansClusterer.cs
Covered lines:55
Uncovered lines:3
Coverable lines:58
Total lines:102
Line coverage:94.8% (55 of 58)
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%11.2211087.76%
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    }
 1131    if (_k <= 0) {
 032      throw new ArgumentOutOfRangeException(
 33          paramName: "k", actualValue: _k, message: "Number of clusters must be positive.");
 34    }
 35
 36    // Initialize the clusters with centroids located at the positions of k random hierarchical
 37    // objects. Perform Fisher-Yates shuffling to find k random hierarchical objects.
 1138    List<IHierarchical> hierarchicalsList = hierarchicals.ToList();
 39
 40    // Validate that k is not greater than the number of hierarchical objects.
 1241    if (_k > hierarchicalsList.Count) {
 142      throw new InvalidOperationException(
 43          $"Cannot create {_k} clusters from {hierarchicalsList.Count} hierarchical objects.");
 44    }
 45
 1046    var clusters = new List<Cluster>();
 8347    for (int i = 0; i < _k; ++i) {
 2148      int j = UnityEngine.Random.Range(i, hierarchicalsList.Count);
 2149      (hierarchicalsList[i], hierarchicalsList[j]) = (hierarchicalsList[j], hierarchicalsList[i]);
 50
 2151      clusters.Add(new Cluster { Centroid = hierarchicalsList[i].Position });
 2152    }
 53
 1054    bool converged = false;
 1055    int numIteration = 0;
 4856    while (!converged && numIteration < _maxNumIterations) {
 1957      AssignToClusters(clusters, hierarchicals);
 58
 59      // Calculate the new clusters as the mean of all assigned hierarchical objects.
 1960      converged = true;
 14961      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 3762        Vector3 oldClusterPosition = clusters[clusterIndex].Centroid;
 3763        if (clusters[clusterIndex].IsEmpty) {
 064          int hierarchicalIndex = UnityEngine.Random.Range(0, hierarchicalsList.Count);
 065          clusters[clusterIndex].Centroid = hierarchicalsList[hierarchicalIndex].Position;
 3766        } else {
 3767          clusters[clusterIndex].Recenter();
 3768        }
 69
 70        // Check whether the algorithm has converged by checking whether the cluster has moved.
 4871        if (Vector3.Distance(oldClusterPosition, clusters[clusterIndex].Position) > _epsilon) {
 1172          converged = false;
 1173        }
 3774      }
 1975      ++numIteration;
 1976    }
 1077    AssignToClusters(clusters, hierarchicals);
 1078    return clusters;
 1279  }
 80
 81  private static void AssignToClusters(IReadOnlyList<Cluster> clusters,
 2982                                       IEnumerable<IHierarchical> hierarchicals) {
 83    // Clear all clusters.
 26184    foreach (var cluster in clusters) {
 5885      cluster.ClearSubHierarchicals();
 5886    }
 87
 88    // Determine the closest centroid to each hierarchical object.
 47189    foreach (var hierarchical in hierarchicals) {
 12890      float minDistance = Mathf.Infinity;
 12891      int minIndex = -1;
 102492      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 25693        float distance = Vector3.Distance(clusters[clusterIndex].Centroid, hierarchical.Position);
 42794        if (distance < minDistance) {
 17195          minDistance = distance;
 17196          minIndex = clusterIndex;
 17197        }
 25698      }
 12899      clusters[minIndex].AddSubHierarchical(hierarchical);
 128100    }
 29101  }
 102}