// Programa de ajuste por combinacion lineal de funciones
// Compilar con g++ -I. -I./ templates - o funfit funfit.cpp
// chi2.cpp gnuplot_i_calnum.cpp

#include <iostream>
#include <cmath>
#include <iomanip>
#include <fstream>
#include "algebralineal.h"
#include "templates.h"
#include "chi2.h"
#include "gnuplot_i_calnum.h"
using namespace std;
typedef double Real;
using namespace CALNUM;

//Declaracion de de funciones
template < class T > T func(T & x, Vector < T > &a)
{
//Combinacion lineal de funciones definidas en un punto.
//Coeficientes en vector a.
    int n = a.size();
    T   tmp = T(0);

    for (int i = 0; i < n; i++)
        tmp += a[i] * func(x, i);
    return tmp;
}


template < class T > Vector < T > func(Vector < T > &v, Vector < T > &a)
{
//Combinacion lineal de funciones definidas en un vector de puntos.
//Coeficientes definidos en vector a
    int n = v.size();

    Vector < T > tmp(n);
    for (int i = 0; i < n; i++)
        tmp[i] = func(v[i], a);
    return tmp;
}


template < class T > Vector < T > func(Vector < T > &v, int &p)
{
//Funcion definida en  un vector de puntos.
    int n = v.size();

    Vector < T > tmp(n);
    for (int i = 0; i < n; i++)
        tmp[i] = func(v[i], p);
    return tmp;
}


template < class T > T func(T & x, int &i)
{
//Definicion de las funciones que aparecen en la combinacion lineal
//Deben ser las mismas que en gendat.
//Definir aqui con un switch cada
// una de las funciones para cada valor de i
    switch (i) {
        case 0:
            return sin(x);
            break;
        case 1:
            return exp(-x);
            break;
        case 2:
            return sqrt(x);
            break;
        case 3:
            return log(x);
            break;
        case 4:
            return tan(x);
            break;
        case 5:
            return cosh(x);
            break;
        case 6:
            return cos(x);
            break;
        case 7:
            return pow(x, 3);
            break;
        default:
            return 0.;
    }
}


int main()
{

//Fichero de datos alternativo a la entrada estadar
// ifstream fin("F.d");
    int n;
    int m;

//
    cout << "Ajuste mediante una combinacion lineal de funciones" << endl;
    cout << "0) sin(x)  1) exp(-x)   2) sqrt(x)  3) log(x)"<<endl;
    cout << "4) tan(x)  5) cosh(x)   6) cos(x)   7) x**3 "<<endl;

//Leer numero de funciones m(numero de parametros)
    cout<< "Entrad numero de funciones m:"<<endl;
    cout<< "Se ajusta con una comb. lin. de las m primeras funcciones"<<endl;
    cin >> m;

//Declaracion de matriz de coeficientes, de covarianzas y vector
// de terminos independientes
    Matrix < Real > mat(m, m), covp(m, m);
    Vector < Real > vec(m);

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

//Leer puntos
   
    cout<<"Entrad x(i),  y(i),  sigma(i) para todos los puntos"<<endl; 
    cin >> x >> y >> sigma;
    n = x.size();

//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;

//Creamos un vector de vectores cuyos elementos
// son los vectores formados por los valores de cada una de las funciones en el
// vector conjunto de puntos
    Vector < Vector < Real > > vfunc(m, Vector < Real > (n));
    for (int i = 0; i < m; i++)
        vfunc[i] = func < Real > (x, i) / sigma;
    Vector < Real > vy = y / sigma;
//Calcular matriz coeficientes y vector de terminos independientes
    for (int i = 0; i < m; i++) {
//vec[i] = (func < Real > (x, i) / sigma) * (y / sigma);
        vec[i] = vfunc[i] * vy;
//Calculamos solo la parte triangular superior de mat. Simetrica.
        for (int j = 0; j <= i; j++) {
//mat[i][j] = (func < Real > (x, i) / sigma) * (func < Real > (x, j) / sigma);
            mat[i][j] = vfunc[i] * vfunc[j];
            if (i > j)
                mat[j][i] = mat[i][j];
        }
    }

//Imprimir matriz de coeficientes
    cout << "Matriz de coeficientes" << endl << mat << endl;
    cout << "vector de terminos independientes" << endl << vec << endl;

//Calculo de los parametros ajustados a-- > Solucion mat * a = vec;
    Vector < Real > a(m);
    LU < Real > lu(mat);
    a = lu.solve(vec);
    cout << "Vector de parametros a " << endl << a << endl;

//Calculo de los errores de los parametros
    covp = lu.inv();
    Vector < Real > ea = diag(covp);
    cout << "Vector de errores de los parametros ea " << endl
         << sqrt(ea) << endl;

//Calculo de chi2
    Real chi2 = normsq((func(x, a) - 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;

//evaluacion del polinomio y funcion en los puntos de dibujo(200)
// se dibuja un 2 * deltax100 % fuera del intervalo
    int ndib = 200;
    double delta = 0.;

 // cout << " Entrar delta" <<endl;
 //cout << "(Dibujo en [x0-(x1-x0)*delta, x1+(x1-x0)*delta] " << endl << endl;
 //    cin >> delta;
    double x1 = x[n - 1] + (x[n - 1] - x[0]) * delta;
    double x0 = x[0] - (x1 - x0) * delta;
    double hh = (x1 - x0) / ndib;

    ndib++;
    Vector < double >xx(ndib), yy(ndib);

    for (int i = 0; i < ndib; i++) {
        xx[i] = x0 + i * hh;
        yy[i] = func(xx[i], a);
    }

//Dibujo del polinomio y la funcion
//Modificad "clfun.gpl" al nombre del fichero de vuestros datos
    Gnuplot g1 = Gnuplot();
    g1.set_style("lines");
    g1.plot_xy(xx, yy, "Polinomio ajustado ");
    g1.set_style("points");
    g1.cmd("replot \"clfun.gpl\" with errorbars");

//Descomentar y seleccionar terminal y extension para 
//imprimir en fichero grafico
// g1.cmd("set terminal postscript");
//g1.cmd("set output 'clfunfit.ps' ");
//g1.plot_xy(xx, yy, "Polinomio ajustado ");
//g1.cmd("replot \"clfun.gpl\" with errorbars");

//Eleccion del tiempo de permanencia de la grafica en pantalla
    int SLEEP_LGTH = 25;

    sleep(SLEEP_LGTH);
    return 0;
}
