private void MergeClusters(int mergedId, int droppedId)
        {
            WeightedCentroid mergedCentroid  = weightedCentroids[mergedId];
            WeightedCentroid droppedCentroid = weightedCentroids[droppedId];

            weightedCentroids[mergedId] = WeightedCentroid.Merge(mergedCentroid, droppedCentroid);
            isDropped[droppedId]        = true;
        }
        public virtual double[] Clusterize(int maxIterations, List <int> layersToExport)
        {
            int iterationCount = (Descriptors.Length - 1 < maxIterations) ? Descriptors.Length - 1 : maxIterations;

            List <double>     iterationDistances = new List <double>();
            List <Centroid[]> results            = new List <Centroid[]>();

            weightedCentroids = WeightedCentroid.FromDescriptors(Descriptors);
            Centroids         = new Centroid[iterationCount][];

#if VERBOSE
            Console.WriteLine("Precomputing {0}x{0} = {1} distances.",
                              centroidDistances.Length, centroidDistances.Length * centroidDistances.Length);
#endif
            PrecomputeAllDistances();

#if VERBOSE
            Console.WriteLine("Running {0} iterations:", iterationCount);
#endif
            // iterations
            for (int i = 0; i < iterationCount; i++)
            {
#if VERBOSE
                Console.WriteLine("Iteration {0} / {1}.", i, iterationCount);
#endif
                Tuple <int, int> idPair = FindMinimalDistanceIds();

                int mergedId;
                int droppedId;
                // merge lighter into heavier
                if (weightedCentroids[idPair.Item1].Weight >= weightedCentroids[idPair.Item2].Weight)
                {
                    mergedId  = idPair.Item1;
                    droppedId = idPair.Item2;
                }
                else
                {
                    mergedId  = idPair.Item2;
                    droppedId = idPair.Item1;
                }

                MergeClusters(mergedId, droppedId);

                ComputeNewDistances(mergedId, droppedId);

                // export levels
                if (layersToExport.Contains((iterationCount - 1) - i))
                {
                    Centroid[] layerCentroids = ExportLayer();
                    AssignClosestDescriptors(layerCentroids);
                    //Centroids[(iterationCount - 1) - i] = layerCentroids;
                    results.Add(layerCentroids);
                }
            }
            Centroids = results.ToArray();
            return(iterationDistances.ToArray());
        }
            public static double GetDistance(WeightedCentroid a, WeightedCentroid b)
            {
                double l2sqr            = Descriptor.GetDistanceSQR(a.Centroid.Mean.Values, b.Centroid.Mean.Values);
                double weightMultiplied = a.Weight * b.Weight;
                double weightAdded      = a.Weight + b.Weight;
                double result           = l2sqr * weightMultiplied / weightAdded;

                return(result);
            }
 public static WeightedCentroid[] FromDescriptors(Descriptor[] descriptors)
 {
     WeightedCentroid[] result = new WeightedCentroid[descriptors.Length];
     for (int i = 0; i < descriptors.Length; i++)
     {
         result[i] = new WeightedCentroid(i, descriptors[i]);
     }
     return(result);
 }
            public static WeightedCentroid Merge(WeightedCentroid merged, WeightedCentroid dropped)
            {
                Centroid mergedCentroid = new Centroid(merged.Centroid.Id);

                mergedCentroid.Descriptors.AddRange(merged.Centroid.Descriptors);
                mergedCentroid.Descriptors.AddRange(dropped.Centroid.Descriptors);
                mergedCentroid.ComputeMean();
                WeightedCentroid mergedWeightedCentroid = new WeightedCentroid(mergedCentroid);

                return(mergedWeightedCentroid);
            }
 private void PrecomputeAllDistances()
 {
     Parallel.For(0, centroidDistances.Length, i =>
     {
         for (int j = 0; j < i; j++)
         {
             // precompute distance
             centroidDistances[i][j] = WeightedCentroid.GetDistance(weightedCentroids[i], weightedCentroids[j]);
             // precompute minimal distance in a row
             if (centroidDistances[i][j] < rowMinimalDistances[i])
             {
                 rowMinimalDistances[i]   = centroidDistances[i][j];
                 rowMinimalDistanceIds[i] = j;
             }
         }
     });
 }
        private void ComputeNewDistances(int mergedCentroidId, int droppedCentroidId)
        {
            // invalidate dropped data
            rowMinimalDistances[droppedCentroidId]   = double.NaN;
            rowMinimalDistanceIds[droppedCentroidId] = -1;
            Parallel.For(0, droppedCentroidId, j =>
            {
                centroidDistances[droppedCentroidId][j] = double.NaN;
            });
            Parallel.For(droppedCentroidId + 1, centroidDistances.Length, i =>
            {
                centroidDistances[i][droppedCentroidId] = double.NaN;
            });


            // compute new distances and minimal value in mergedId row
            rowMinimalDistances[mergedCentroidId] = double.MaxValue;
            Parallel.For(0, mergedCentroidId, j =>
            {
                if (!isDropped[j])
                {
                    centroidDistances[mergedCentroidId][j]
                        = WeightedCentroid.GetDistance(weightedCentroids[j], weightedCentroids[mergedCentroidId]);

                    // update minimal distances
                    if (centroidDistances[mergedCentroidId][j] < rowMinimalDistances[mergedCentroidId])
                    {
                        rowMinimalDistances[mergedCentroidId]   = centroidDistances[mergedCentroidId][j];
                        rowMinimalDistanceIds[mergedCentroidId] = j;
                    }
                }
            });
            if (rowMinimalDistances[mergedCentroidId] == double.MaxValue)
            {
                rowMinimalDistances[mergedCentroidId] = double.NaN;
            }

            // compute new distances in mergedId column
            Parallel.For(mergedCentroidId + 1, centroidDistances.Length, i =>
            {
                if (!isDropped[i])
                {
                    centroidDistances[i][mergedCentroidId]
                        = WeightedCentroid.GetDistance(weightedCentroids[i], weightedCentroids[mergedCentroidId]);

                    // update minimal distances
                    if (centroidDistances[i][mergedCentroidId] < rowMinimalDistances[i])
                    {
                        rowMinimalDistances[i]   = centroidDistances[i][mergedCentroidId];
                        rowMinimalDistanceIds[i] = mergedCentroidId;
                    }
                }
            });


            // find new minimal value in the row if the value from dropped column was minimal
            for (int i = droppedCentroidId + 1; i < centroidDistances.Length; i++)
            {
                if (!isDropped[i] && rowMinimalDistanceIds[i] == droppedCentroidId)
                {
                    rowMinimalDistances[i]   = double.MaxValue;
                    rowMinimalDistanceIds[i] = -1;
                    for (int j = 0; j < i; j++)
                    {
                        if (!isDropped[j] && centroidDistances[i][j] < rowMinimalDistances[i])
                        {
                            rowMinimalDistances[i]   = centroidDistances[i][j];
                            rowMinimalDistanceIds[i] = j;
                        }
                    }
                    if (rowMinimalDistances[i] == double.MaxValue)
                    {
                        rowMinimalDistances[i] = double.NaN;
                    }
                }
            }
        }