/// <summary>
        /// adjustInClusterMaxDistance is a function that recalculates/approximate the maximum distance in the cluster for partial clustering
        /// </summary>
        /// <param name="cluster">index of the cluster</param>
        private void adjustInClusterMaxDistance(int cluster, KMeansScore res, double[][] oldCentroids)
        {
            // calculate new in cluster max distance
            double curDistance = KMeansAlgorithm.calculateDistance(res.Model.Clusters[cluster].Centroid, this.Score.Centroids[cluster]);


            // compare to max possible old in cluster max distance
            double oldDistance = this.Score.InClusterMaxDistance[cluster] + KMeansAlgorithm.calculateDistance(this.Score.Centroids[cluster], oldCentroids[cluster]);

            if (oldDistance > curDistance)
            {
                curDistance = oldDistance;
            }

            this.Score.InClusterMaxDistance[cluster] = curDistance;
        }
        /// <summary>
        /// Data of a single function for which KMeans will be calculated.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        public IScore Run(double[][] data, IContext ctx)
        {
            /*
             * if (this.Score.NomOfTrainedFunctions == 1)
             * {
             *  this.Settings.InitialCentroids = new double[this.Settings.NumberOfClusters][];
             *  for (int i = 0; i < this.Settings.NumberOfClusters; i++)
             *  {
             *      this.Settings.InitialCentroids[i] = this.Score.Centroids[i];
             *  }
             * }*/

            this.Settings.InitialCentroids = null;
            KMeansAlgorithm kmeans = new KMeansAlgorithm(this.Settings.Clone());

            KMeansScore res = kmeans.Train(data, ctx) as KMeansScore;

            this.Score.NomOfTrainedFunctions += 1;



            if (this.Settings.FuncRecogMethod == 1)
            {
                double[][] oldCentroids = this.Score.Centroids;
                for (int clusterIndx = 0; clusterIndx < res.Model.Clusters.Length; clusterIndx++)
                {
                    if (this.Score.NomOfTrainedFunctions == 1)
                    {
                        this.Score.Centroids[clusterIndx] = res.Model.Clusters[clusterIndx].Centroid;
                        //this.Score.InClusterMaxDistance[clusterIndx] = res.Model.Clusters[clusterIndx].InClusterMaxDistance;
                    }
                    else
                    {
                        for (int d = 0; d < this.Settings.NumOfDimensions; d++)
                        {
                            this.Score.Centroids[clusterIndx][d] = (res.Model.Clusters[clusterIndx].Centroid[d] + oldCentroids[clusterIndx][d] * (this.Score.NomOfTrainedFunctions - 1)) / this.Score.NomOfTrainedFunctions;
                        }

                        adjustInClusterMaxDistance(clusterIndx, res, oldCentroids);
                    }
                }
            }
            else
            {
                //Debug.WriteLine($"C0: {res.Model.Clusters[0].Centroid[0]},{res.Model.Clusters[0].Centroid[1]}");
                //Debug.WriteLine($"C1: {res.Model.Clusters[1].Centroid[0]},{res.Model.Clusters[0].Centroid[1]}");
                //Debug.WriteLine($"C2: {res.Model.Clusters[2].Centroid[0]},{res.Model.Clusters[0].Centroid[1]}");
                //Debug.WriteLine($"C3: {res.Model.Clusters[3].Centroid[0]},{res.Model.Clusters[0].Centroid[1]}");

                for (int clusterIndx = 0; clusterIndx < res.Model.Clusters.Length; clusterIndx++)
                {
                    for (int dim = 0; dim < this.Settings.NumOfDimensions; dim++)
                    {
                        if (res.Model.Clusters[clusterIndx].Centroid[dim] > this.Score.MaxCentroid[clusterIndx][dim])
                        {
                            this.Score.MaxCentroid[clusterIndx][dim] = res.Model.Clusters[clusterIndx].Centroid[dim];
                        }

                        if (res.Model.Clusters[clusterIndx].Centroid[dim] < this.Score.MinCentroid[clusterIndx][dim])
                        {
                            this.Score.MinCentroid[clusterIndx][dim] = res.Model.Clusters[clusterIndx].Centroid[dim];
                        }
                    }
                }



                for (int clusterIndex = 0; clusterIndex < res.Model.Clusters.Length; clusterIndex++)
                {
                    this.Score.Centroids[clusterIndex] = new double[Settings.NumOfDimensions];

                    for (int dim = 0; dim < Settings.NumOfDimensions; dim++)
                    {
                        if (this.Score.MinCentroid[clusterIndex][dim] >= 0)
                        {
                            this.Score.Centroids[clusterIndex][dim] = (this.Score.MaxCentroid[clusterIndex][dim] + this.Score.MinCentroid[clusterIndex][dim]) / 2;
                        }
                        else
                        {
                            this.Score.Centroids[clusterIndex][dim] = ((this.Score.MaxCentroid[clusterIndex][dim] - this.Score.MinCentroid[clusterIndex][dim]) / 2) + this.Score.MinCentroid[clusterIndex][dim];
                        }
                    }
                }
            }

            return(Score);
        }
        /// <summary>
        /// Predicts if the specified function fits in the trainined MIN-MAX cluster interval.
        /// All calculated clusters must fit in trained cluster MIN-MAX intervals.
        /// </summary>
        /// <param name="funcData"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        public IResult Predict(double[][] funcData, IContext ctx)
        {
            /*
             * for (int i = 0; i < this.Settings.NumberOfClusters; i++)
             * {
             *  this.Settings.InitialCentroids[i] = this.Score.Centroids[i];
             * }*/
            this.Settings.InitialCentroids = null;
            KMeansAlgorithm kmeans = new KMeansAlgorithm(this.Settings.Clone());

            kmeans.Instance = null;
            KMeansScore res = kmeans.Train(funcData, ctx) as KMeansScore;

            int scores = 0;

            KMeansFunctionRecognitionResult predRes = new KMeansFunctionRecognitionResult();

            predRes.ResultsPerCluster = new bool[Settings.NumberOfClusters];

            double[][] results = new double[Settings.NumberOfClusters][];

            if (this.Settings.FuncRecogMethod == 1)
            {
                //double[] currDistance = new double[results.Length];
                double currDistance;
                for (int i = 0; i < results.Length; i++)
                {
                    currDistance = KMeansAlgorithm.calculateDistance(Score.Centroids[i], res.Model.Clusters[i].Centroid);

                    if (currDistance <= Score.InClusterMaxDistance[i] * (1.0 + this.Settings.Tolerance / 100.0))
                    {
                        predRes.ResultsPerCluster[i] = true;
                        scores++;
                    }
                    else
                    {
                        predRes.ResultsPerCluster[i] = false;
                    }
                }
                predRes.Result = (scores == Settings.NumberOfClusters);
                predRes.Loss   = ((float)scores) / (Settings.NumberOfClusters);
            }
            else
            {
                for (int i = 0; i < results.Length; i++)
                {
                    results[i] = new double[Settings.NumOfDimensions];
                    for (int dim = 0; dim < Settings.NumOfDimensions; dim++)
                    {
                        if (res.Model.Clusters[i].Centroid[dim] >= Score.MinCentroid[i][dim] &&
                            res.Model.Clusters[i].Centroid[dim] <= Score.MaxCentroid[i][dim])
                        {
                            results[i][dim] = 1;
                            scores++;
                        }
                        else
                        {
                            results[i][dim] = 0;
                        }

                        //
                        // We calculate here the result of cluster over all dimensions. If all dimensions fits then cluster result is true.
                        if (results[i].Count(r => r == 1) == Settings.NumOfDimensions)
                        {
                            predRes.ResultsPerCluster[i] = true;
                        }
                        else
                        {
                            predRes.ResultsPerCluster[i] = false;
                        }
                    }
                }


                predRes.Result = (scores == Settings.NumberOfClusters * Settings.NumOfDimensions);
                predRes.Loss   = ((float)scores) / (Settings.NumberOfClusters * Settings.NumOfDimensions);
            }


            return(predRes);
        }