< Summary

Class:KMeansClusterer
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Algorithms/Clustering/KMeansClusterer.cs
Covered lines:0
Uncovered lines:56
Coverable lines:56
Total lines:98
Line coverage:0% (0 of 56)
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%1101000%
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    }
 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.
 034    List<IHierarchical> hierarchicalsList = hierarchicals.ToList();
 35
 36    // Validate that k is not greater than the number of hierarchical objects.
 037    if (_k > hierarchicalsList.Count) {
 038      throw new InvalidOperationException(
 39          $"Cannot create {_k} clusters from {hierarchicalsList.Count} hierarchical objects.");
 40    }
 41
 042    var clusters = new List<Cluster>();
 043    for (int i = 0; i < _k; ++i) {
 044      int j = UnityEngine.Random.Range(i, hierarchicalsList.Count);
 045      (hierarchicalsList[i], hierarchicalsList[j]) = (hierarchicalsList[j], hierarchicalsList[i]);
 46
 047      clusters.Add(new Cluster { Centroid = hierarchicalsList[i].Position });
 048    }
 49
 050    bool converged = false;
 051    int numIteration = 0;
 052    while (!converged && numIteration < _maxNumIterations) {
 053      AssignToClusters(clusters, hierarchicals);
 54
 55      // Calculate the new clusters as the mean of all assigned hierarchical objects.
 056      converged = true;
 057      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 058        Vector3 oldClusterPosition = clusters[clusterIndex].Centroid;
 059        if (clusters[clusterIndex].IsEmpty) {
 060          int hierarchicalIndex = UnityEngine.Random.Range(0, hierarchicalsList.Count);
 061          clusters[clusterIndex].Centroid = hierarchicalsList[hierarchicalIndex].Position;
 062        } else {
 063          clusters[clusterIndex].Recenter();
 064        }
 65
 66        // Check whether the algorithm has converged by checking whether the cluster has moved.
 067        if (Vector3.Distance(oldClusterPosition, clusters[clusterIndex].Position) > _epsilon) {
 068          converged = false;
 069        }
 070      }
 071      ++numIteration;
 072    }
 073    AssignToClusters(clusters, hierarchicals);
 074    return clusters;
 075  }
 76
 77  private static void AssignToClusters(IReadOnlyList<Cluster> clusters,
 078                                       IEnumerable<IHierarchical> hierarchicals) {
 79    // Clear all clusters.
 080    foreach (var cluster in clusters) {
 081      cluster.ClearSubHierarchicals();
 082    }
 83
 84    // Determine the closest centroid to each hierarchical object.
 085    foreach (var hierarchical in hierarchicals) {
 086      float minDistance = Mathf.Infinity;
 087      int minIndex = -1;
 088      for (int clusterIndex = 0; clusterIndex < clusters.Count; ++clusterIndex) {
 089        float distance = Vector3.Distance(clusters[clusterIndex].Centroid, hierarchical.Position);
 090        if (distance < minDistance) {
 091          minDistance = distance;
 092          minIndex = clusterIndex;
 093        }
 094      }
 095      clusters[minIndex].AddSubHierarchical(hierarchical);
 096    }
 097  }
 98}