< Summary

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

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
KMeansClusterer(...)0%2100%
KMeansClusterer(...)0%2100%
Cluster(...)0%1321100%
AssignToClusters(...)0%56700%

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
 020  public KMeansClusterer(int k) : this(k, _defaultMaxNumIterations) {}
 021  public KMeansClusterer(int k, int maxNumIterations) {
 022    _k = k;
 023    _maxNumIterations = maxNumIterations;
 024  }
 25
 26  // Generate the clusters from the list of hierarchical objects.
 027  public override List<Cluster> Cluster(IEnumerable<IHierarchical> hierarchicals) {
 028    if (hierarchicals == null || !hierarchicals.Any()) {
 029      return new List<Cluster>();
 30    }
 031    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.
 038    List<IHierarchical> hierarchicalsList = hierarchicals.ToList();
 39
 40    // Validate that k is not greater than the number of hierarchical objects.
 041    if (_k > hierarchicalsList.Count) {
 042      throw new InvalidOperationException(
 43          $"Cannot create {_k} clusters from {hierarchicalsList.Count} hierarchical objects.");
 44    }
 45
 046    var clusters = new List<Cluster>();
 047    for (int i = 0; i < _k; ++i) {
 048      int j = UnityEngine.Random.Range(i, hierarchicalsList.Count);
 049      (hierarchicalsList[i], hierarchicalsList[j]) = (hierarchicalsList[j], hierarchicalsList[i]);
 50
 051      clusters.Add(new Cluster { Centroid = hierarchicalsList[i].Position });
 052    }
 53
 054    bool converged = false;
 055    int numIteration = 0;
 056    while (!converged && numIteration < _maxNumIterations) {
 057      AssignToClusters(clusters, hierarchicals);
 58
 59      // Calculate the new clusters as the mean of all assigned hierarchical objects.
 060      converged = true;
 061      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 062        Vector3 oldClusterPosition = clusters[clusterIndex].Centroid;
 063        if (clusters[clusterIndex].IsEmpty) {
 064          int hierarchicalIndex = UnityEngine.Random.Range(0, hierarchicalsList.Count);
 065          clusters[clusterIndex].Centroid = hierarchicalsList[hierarchicalIndex].Position;
 066        } else {
 067          clusters[clusterIndex].Recenter();
 068        }
 69
 70        // Check whether the algorithm has converged by checking whether the cluster has moved.
 071        if (Vector3.Distance(oldClusterPosition, clusters[clusterIndex].Position) > _epsilon) {
 072          converged = false;
 073        }
 074      }
 075      ++numIteration;
 076    }
 077    AssignToClusters(clusters, hierarchicals);
 078    return clusters;
 079  }
 80
 81  private static void AssignToClusters(IReadOnlyList<Cluster> clusters,
 082                                       IEnumerable<IHierarchical> hierarchicals) {
 83    // Clear all clusters.
 084    foreach (var cluster in clusters) {
 085      cluster.ClearSubHierarchicals();
 086    }
 87
 88    // Determine the closest centroid to each hierarchical object.
 089    foreach (var hierarchical in hierarchicals) {
 090      float minDistance = Mathf.Infinity;
 091      int minIndex = -1;
 092      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 093        float distance = Vector3.Distance(clusters[clusterIndex].Centroid, hierarchical.Position);
 094        if (distance < minDistance) {
 095          minDistance = distance;
 096          minIndex = clusterIndex;
 097        }
 098      }
 099      clusters[minIndex].AddSubHierarchical(hierarchical);
 0100    }
 0101  }
 102}