// Programa de ajuste por polinomios ortogonales
// Compilar con
// g++ -I. -I./templates -o ortpolfit ortpolfit.cpp chi2.cpp
// gnuplot_i_calnum.cpp
// Utilizacion:./ ortpol < pol.d
#include <unistd.h>
#include "gnuplot_i_calnum.h"
#include <iostream>
#include <cmath>
#include <iomanip>
#include <fstream>
#include "algebralineal.h"
#include "templates.h"
#include "chi2.h"
using namespace std;
using namespace CALNUM;
typedef double Real;

//Calculo de polinomios ortogonales

template < class T >
Vector < Vector < T > >ortpol(const Vector < T > &x, const Vector < T > &y,
const Vector < T > &sigma, Vector < T > &beta, Vector < T > &gamma, const int &m)
{
//Devuelve m vectores de dimension n que contienen los valores
// de los m polinomios ortogonales sobre n puntos
// en los n puntos
// Los vectores beta y gamma dan los coeficientes de recurrencia
// de los polinomios ortogonales
    int n = x.size();

    Vector < Vector < T > >ortpol(m, Vector < T > (n));
    Vector < T > opold, opnew;
    Vector < T > xsigma = x / sigma;
    T   tmpold, tmpnew;

    ortpol[0] = Vector < T > (n, T(1.));
    opnew = ortpol[0] / sigma;
    tmpnew = opnew * opnew;
    gamma[0] = T(0.);
    beta[0] = x * pow(opnew, 2) / tmpnew;
//Simbolo %:producto de arrays elemento a elemento(libreria TNT)
    ortpol[1] = (x - beta[0]) % ortpol[0];
    for (int i = 2; i < m; i++) {
        tmpold = tmpnew;
        opnew = ortpol[i - 1] / sigma;
        tmpnew = opnew * opnew;
        beta[i - 1] = x * pow(opnew, 2) / tmpnew;
        gamma[i - 1] = tmpnew / tmpold;
        ortpol[i] = (x - beta[i - 1]) % ortpol[i - 1] - gamma[i - 1] * ortpol[i - 2];

    }
    return ortpol;
}


template < class T >
Vector < T > eval_ortpol(const Vector < T > &beta, const Vector < T > &gamma,
const Vector < T > &a, Vector < T > &xx)
{
//Evalua una combinacion de polinomios ortogonales con vector de coeficientes
// a en las abcisas dadas por el vector xx
// beta y gamma son los coeficientes de la relacion de recurrencia
// de los polinomios ortogonales

// numero de coeficientes
    int m = a.size();

//numero de puntos
    int n = xx.size();

//vector de valores de la combinacion lineal
    Vector < T > fun(n, T(0));
//ortpol: vector de vectores(elemento i:polinomio i en los n puntos)
    Vector < Vector < T > >ortpol(m, Vector < T > (n));
    ortpol[0] = Vector < T > (n, T(1.));
    ortpol[1] = (xx - beta[0]) % ortpol[0];
    for (int i = 2; i < m; i++) {
        ortpol[i] = (xx - beta[i - 1]) % ortpol[i - 1] - gamma[i - 1] * ortpol[i - 2];
    }
    for (int i = 0; i < m; i++)
        fun += a[i] * ortpol[i];
    return fun;
}


int
main()
{

//Fichero de datos "pol.d" leido de la entrada standard ortopolfit < pol.d
    int n;
    int m;

//Leer numero de funciones m(numero de parametros)
    cin >> m;

//Declaracion de vector de coeficientes(vcoef), de covarianzas(covp) y vector
//  de terminos independientes(a).beta y gamma definen los polinomios
    Vector < Real > vcoef(m), covp(m), a(m), beta(m), gamma(m);

    Vector < Real > vec(m);

//Definir vectores de puntos y de errores
    Vector < Real > x;
    Vector < Real > y;
    Vector < Real > sigma;

//Leer puntos
    cin >> x >> y >> sigma;
    n = x.size();

//Si los puntos no tienen error el mismo error para todos
// Minimos cuadrados en vez de Chi2
    for (int i = 0; i < n; i++)
        if (sigma[i] == 0)
            sigma[i] = 1.;

//Verificar que la lectura es correcta
    cout << "Puntos experimentales" << endl << "     x    " << "       y     " <<
        "      sigma  " << endl;
    for (int j = 0; j < n; j++)
        cout << setw(10) << x[j] << "   " << setw(10) << y[j] << "   " << 
              setw(10) << sigma[j] << "   " << endl;
    cout << endl;

//si sigma = 0. ponemos sigma = 1. Puntos con el mismo error.Minimos cuadrados.
    for (int i = 0; i < n; i++)
        if (sigma[i] == 0)
            sigma[i] = 1.;

//Creamos un vector de vectores para almacenar cada una de las funciones en el
// conjunto de puntos
    Vector < Vector < Real > >vortpol(m, Vector < Real > (n));
    vortpol = ortpol < Real > (x, y, sigma, beta, gamma, m);

//Comprobacion de ortogonalidad
// for (int i = 0; i < m; i++) {
//for (int j = 0; j <= i; j++) cout << "  " << i << "  " << j << "  "
// <<vortpol[i] * vortpol[j];
//cout << endl;
//}


Vector < Real > vy = y / sigma;
Vector < Vector < Real > >vortpolsigma(m);
for (int i = 0; i < m; i++)
    vortpolsigma[i] = vortpol[i] / sigma;

//Calcular vector de coeficientes vecoef
// y vector de terminos independientes vec
for (int i = 0; i < m; i++)
{
    vec[i] = (vortpolsigma[i]) * vy;
    vcoef[i] = vortpolsigma[i] * vortpolsigma[i];
}


//Calculo del vector de parametros
a = vec / vcoef;

//Impresion de los coeficientes de recurrencia
cout << "beta=" << beta << endl;
cout << "gamma=" << gamma << endl;

//Imprimir matriz de coeficientes, de terminos independientes y parametros
cout << "Vector de coeficientes" << endl << vcoef << endl;
cout << "vector de terminos independientes" << endl << vec << endl;
cout << "Vector de parametros a " << endl << a << endl;

//Calculo de los errores de los parametros
Vector < Real > ea = Real(1.) / vcoef;
cout << "Vector de errores de los parametros ea " << endl << sqrt(ea) << endl;

//Calculo de la funcion ajustada en el vector fun
// vortpol[i] es un vector de vectores cada uno de los cuales contiene
//  los valores de l polinomio ortogonal[i] en los n puntos de ajuste
//  a[i] * vortpol[i] producto de escalar por vector
Vector < Real > fun(n, Real(0));

for (int i = 0; i < m; i++)
    fun += a[i] * vortpol[i];
cout << "Puntos ajustados=" << fun << endl;

//Calculo de chi2
Real chi2 = normsq((fun - y) / sigma);
Real nu = n - m;
Real chi2nu = chi2 / nu;
Real chi2p = Chi2P(chi2, nu);

cout << "chi2= " << chi2 << "  chi2/nu=  " << chi2nu <<
" Prob(chi2/nu)= " << chi2p << endl;

//Dibulo puntos con su error y funcion ajustada
// Dibujamos la funcion ajustada en 10 veces elnumero
// de puntos experimentales
int ndib = 10 * n;

Vector < Real > xx(ndib), ff(ndib);
double h = abs(x[n - 1] - x[0]) / (ndib - 1);

for (int i = 0; i < ndib; i++)
    xx[i] = x[0] + h * i;
ff = eval_ortpol < Real > (beta, gamma, a, xx);
//Impresion de la funcion ajustada en ortpol.gpl
ofstream fout("ortpol.gpl");
for (int i = 0; i < ndib; i++)
    fout << xx[i] << "  " << ff[i] << endl;
fout.close();

//Creacion de elemento de la clase Gnuplot
Gnuplot g1 = Gnuplot();
g1.set_style("lines");
g1.plot_xy(xx, ff, " Ajuste polinomios ortogonales ");
g1.cmd("replot \"pol.gpl\" with errorbars");

//Tiempo de permanencia de la grafica en pantalla
int SLEEP_LGTH = 30;

sleep(SLEEP_LGTH);
return 0;
}
