/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2011-04-21
**  Copyright: 2011-2019
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "LinearRegression.h"
#include "Statistics.h"

namespace QGpCoreMath {

  /*!
    \class LinearRegression LinearRegression.h
    \brief Computes a linear regression over X, Y pairs

    Computes coefficients a and b that best match the added pairs: Y=a*X+b

    Add new pairs with add(). Remove them with either removeFirst() or remove(int i).
    a() and b() are not automatically updated by add(), removeFirst(), remove(int) or badValue().
    Call calculate() to get updated values.
  */

  void LinearRegression::reset()
  {
    _sumX=0.0;
    _sumY=0.0;
    _sumXY=0.0;
    _sumX2=0.0;
    _a=0.0;
    _b=0.0;
    _values.clear();
  }

  void LinearRegression::add(double x, double y)
  {
    if(_values.size()==0) { // Avoid big numbers on x
      _x0=x;
      x=0.0;
    } else {
      x-=_x0;
    }
    _sumX+=x;
    _sumY+=y;
    _sumXY+=x*y;
    _sumX2+=x*x;
    _values.push_back(QPair<double, double>(x, y));
  }

  /*!
    Computes a and b. Returns false if line is vertical. In this case, equation is x=b.
  */
  bool LinearRegression::calculate()
  {
    double invn=1.0/_values.size();
    double denom=_sumX2-invn*_sumX*_sumX;
    if(denom!=0.0) {
      _a=(_sumXY-invn*_sumY*_sumX)/denom;
      _b=(invn*_sumY*_sumX2-invn*_sumXY*_sumX)/denom;
      return true;
    } else {
      _a=0.0;
      _b=_sumX*invn;
      return false;
    }
  }

  /*!
    Returns the standard deviation around regression line.
    a and b must be computed.
  */
  float LinearRegression::stddev() const
  {
    Statistics s;
    QVector<QPair<double, double> >::const_iterator it;
    for(it=_values.begin(); it!=_values.end(); it++) {
      double y=_a*it->first+_b;  // Already corrected by x0
      s.add(y-it->second);
    }
    return s.stddev();
  }

  /*!
    Never returns an index greater that half of the size. It avoids
    rejecting values that have been just added.
  */
  int LinearRegression::badValue(double deviation) const
  {
    int n=_values.size() >> 1;
    for(int i=0; i<n; i++) {
      const QPair<double, double>& v=_values[i];
      double y=_a*v.first+_b;  // Already corrected by x0
      if(fabs(y-v.second)>deviation) {
        return i;
      }
    }
    return -1;
  }

  /*!
    Remove value at index \a i. The order of value is altered. Do not use
    at the same time as removeFirst().
  */
  void LinearRegression::remove(int i)
  {
    unsigned int n=_values.size();
    QPair<double, double>& v=_values[i];
    double x=v.first;
    double y=v.second;
    _sumX-=x;
    _sumY-=y;
    _sumXY-=x*y;
    _sumX2-=x*x;
    v=_values[n-1];
    _values.resize(n-1);
  }

  /*!
    For a moving estimation of the linear regression, check the size
    after each add(), and eventually remove the fisrt value.

    Do not use remove() with this function. remove() alters the order
    of values.
  */
  void LinearRegression::removeFirst()
  {
    QPair<double, double>& v=_values.front();
    double x=v.first;
    double y=v.second;
    _sumX-=x;
    _sumY-=y;
    _sumXY-=x*y;
    _sumX2-=x*x;
    _values.erase(_values.begin());
  }

} // namespace QGpCoreMath
