/// <summary>
 /// Extension of the add default method. If the dictionary does not contains the clound, a single point cloud is added 
 /// and the associated value is the point. If the dictionary contains the key, the values are added and the 
 /// key is unchanged.
 /// </summary>
 /// <param name="clouds">The clouds of points</param>
 /// <param name="key">The key of a cloud</param>
 /// <param name="weightedPoint">The point to add to the cloud</param>
 public static void TryAdd(this Dictionary<string, StreamingCloud> clouds, string key, WeightedPoint weightedPoint)
 {
     if (clouds.ContainsKey(key)) { clouds[key].Add(weightedPoint); }
     else
     {
         StreamingCloud sc = new StreamingCloud();
         sc.Add(weightedPoint);
         clouds.Add(key, sc);
     }
 }
        /// <summary>
        /// Returns the Haversine distance between two weighted points.
        /// </summary>
        /// <param name="p1">First point</param>
        /// <param name="p2">Second point</param>
        /// <returns>Haversine's (geodesic) distance</returns>
        public static double Haversine(WeightedPoint p1, WeightedPoint p2)
        {
            double lat = Math.Abs(p1.X - p2.X) * _pi180,
                lon = Math.Abs(p1.Y - p2.Y) * _pi180;

            double lat1 = p1.X * _pi180,
                lat2 = p2.X * _pi180;

            double a = Math.Sin(lat / 2) * Math.Sin(lat / 2) +
                Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(lon / 2) * Math.Sin(lon / 2);

            double Hdist = REarth * 2f * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));

            return Hdist;
        }
 /// <summary>
 /// Adds a point to a streaming cloud : the mean and variance of the clouds are updated.
 /// </summary>
 /// <param name="point">The point to add to the cloud.</param>
 public void Add(WeightedPoint point)
 {
     _nbPoints++;
     if (_barycenter == null)
     {
         _barycenter = new WeightedPoint(point.X, point.Y, 1);
         _dispersion = 0;
     }
     else
     {
         _barycenter = _barycenter.Multiply(_nbPoints - 1);
         _barycenter = _barycenter.Add(point);
         _barycenter = _barycenter.Divide(_nbPoints);
         _dispersion = (_dispersion * (_nbPoints - 1) + Distances.Euclide(_barycenter, point)) / _nbPoints;
     }
 }
        /// <summary>
        /// Yields the lines from a file as predictors and final point (if train set)
        /// </summary>
        /// <param name="filePath">The training file path (after feature extraction).</param>
        /// <param name="train">Set to true for train files, to return the final points as labels.</param>
        /// <param name="learningParameters">The learning parameters.</param>
        /// <returns></returns>
        private IEnumerable<Tuple<WeightedPoint, List<string>>> YieldLines(string filePath, bool train, LearningParameters learningParameters)
        {
            using (StreamReader reader = new StreamReader(filePath))
            {
                string line = ""; //No header in feature files
                while ((line = reader.ReadLine()) != null)
                {
                    List<string> arrayLine = line.Split(',').ToList();
                    WeightedPoint bv = new WeightedPoint(0, 0, 0);

                    if (train)
                    {
                        string[] bidimVector = arrayLine[0].Split('_');
                        bv = new WeightedPoint(Convert.ToDouble(bidimVector[0], CultureInfo.GetCultureInfo("en-US")),
                            Convert.ToDouble(bidimVector[1], CultureInfo.GetCultureInfo("en-US")), 1);
                        arrayLine.RemoveAt(0);
                    }

                    arrayLine = arrayLine.CartesianProduct(learningParameters.Keyword);
                    yield return new Tuple<WeightedPoint, List<string>>(bv, arrayLine);
                }
            }
        }
        private WeightedPoint PredictFromLine(Dictionary<string, StreamingCloud> clouds,
            List<string> predictors, LearningParameters predictionParameters)
        {
            List<WeightedPoint> bvs = new List<WeightedPoint>();
            foreach (string predictor in predictors)
                if (clouds.ContainsKey(predictor) && clouds[predictor].Size > predictionParameters.MinOccurences && clouds[predictor].Size < predictionParameters.MaxOccurences)
                {
                    StreamingCloud cloud = clouds[predictor];
                    WeightedPoint wp = new WeightedPoint(cloud.Barycenter);
                    wp.Weight =
                        Math.Pow(cloud.Size, predictionParameters.SizeExponent) / Math.Pow(cloud.Dispersion, predictionParameters.DispersionExponent);
                    bvs.Add(wp);
                }
            if (bvs.Count == 0) // could not find any predictor respecting the learning conditions
                return new WeightedPoint(-8.611317, 41.146504, 1);

            return WeightedPoint.Barycenter(bvs);
        }
 /// <summary>
 /// (Deep) copy constructor of a weighted point.
 /// </summary>
 /// <param name="wp">Weighted point to copy.</param>
 public WeightedPoint(WeightedPoint wp)
 {
     _x = wp.X;
     _y = wp.Y;
     _weight = wp.Weight;
 }
 /// <summary>
 /// Performs \f$(x_1+x_2,y_1+y_2,m_1+m_2)\f$.
 /// </summary>
 /// <param name="p2">The point to add.</param>
 /// <returns>(x_1+x_2,y_1+y_2,m_1+m_2).</returns>
 public WeightedPoint Add(WeightedPoint p2)
 {
     return new WeightedPoint(_x + p2.X , _y + p2.Y , _weight + p2.Weight);
 }
 /// <summary>
 /// Given a list of WeightedPoints returns.
 /// </summary>
 /// <param name="weightedPoints">Weighted points to average.</param>
 /// <returns>The barycenter of the input points.</returns>
 public static WeightedPoint Barycenter(IList<WeightedPoint> weightedPoints)
 {
     WeightedPoint res = new WeightedPoint();
     double counter = 0;
     foreach (WeightedPoint pt in weightedPoints)
     {
         res = res.Add(pt.Multiply(pt.Weight));
         counter += pt.Weight;
     }
     res = res.Divide(counter);
     return res;
 }
 /// <summary>
 /// Returns the Euclide distance between two weighted points.
 /// </summary>
 /// <param name="p1">First point</param>
 /// <param name="p2">Second point</param>
 /// <returns>Euclide's distance</returns>
 public static double Euclide(WeightedPoint p1, WeightedPoint p2)
 {
     return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y));
 }