// Programa para generar datos igualmente espaciados de diferentes funciones
// de acuerdo a una  distribucion prefijada, con ruido gausiano.
// Genera: Ficheros con extension .d = ajustes, interpolacion, integracion.
//Ficheros .gpl  = dibujo con gnuplot.
// sigma =0: funcion exacta. Interpolacion, integracion.
// sigma !=0: funcion con ruido. ajustes.
// Compilar con: g++ -o gendat gendat.cpp

#include <iostream>
#include <string>
#include <cmath>
#include <vector>
#include <fstream>
#include <ctime>
#include <stdio.h>
#include <cassert>
using namespace std;
double fun(double x, int 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.;
 }
} 

struct Gendat{
    double sigma;    // error de los datos
    double dsigma;   // dispersion en el error de los datos
    int n;           // numero de puntos
    double xmin;     // intervalo [xmin,xmax]
    double xmax;
    long semilla ;
};

struct datos{
      vector<double> xd;
      vector<double> yd;
      vector<double> sigmad;
      };

struct Genfun{
    vector<double> par;
    int ifun;
    string sfun;
    

    double recta(double x){
	return par[0]*x + par[1];
    }

    double parabola(double x){
	return par[0]*x*x + par[1]*x + par[2];
    }
    
    double F(double x){
	//	return par(1)*exp(-x)+par(2)*cubo(x)+par(3)*sin(x)+
	//  par(4)*cos(x);
	return par[0]*sin(x) +par[1]*pow((1+x),4);
    }
    
    double tau(double x){
	return par[0]*exp(-x/par[1]);
    }
    
	double polinom(double x){
	return pow(par[0]+0.1*x,int(par[1]));
    }
    
    double clfun(double x){
    int n = int (par[0]);
    double clfun=0.;
    for (int i=0; i<n; i++) clfun+= par[i+1]*fun(x,i);
	return clfun;
    }
    
    double evalua(double x){
	if (ifun == 1) return recta(x);
	else if (ifun == 2) return parabola(x);
	else if (ifun == 3) return F(x);
	else if (ifun == 4) return tau(x);
	else if (ifun == 5) return polinom(x);
	else if (ifun == 6) return clfun(x);
    }
};

inline void expande(vector<double>& v, double val)
{
    // expande un vector STL
    v.push_back(val);
}

inline void expande(datos& dat, double x, double y, double ey)
{
    // expande los tres vectores dinamicos
    expande(dat.xd,x);
    expande(dat.yd, y);
    expande(dat.sigmad,ey);

}


double ran1(long& idum)
{
    const long IA = 16807 ;
    const long IM = 2147483647 ;
    const long IQ = 127773 ;
    const long IR = 2836;
    const int NTAB =32;
    const double EPS = 1.2e-7;
    const double RNMX =1.0-EPS;

    int j;
    long k;
    static long iy=0;
    static long iv[NTAB];
    double temp;

    int NDIV =1+(IM-1)/NTAB;
    double AM =1.0/IM;
    
    if (idum <= 0 || !iy) {
	if (-(idum) < 1) idum=1;
	else idum = -(idum);
	for (j=NTAB+7;j>=0;j--) {
	    k=(idum)/IQ;
	    idum=IA*(idum-k*IQ)-IR*k;
	    if (idum < 0) idum += IM;
	    if (j < NTAB) iv[j] = idum;
	}
	iy=iv[0];
    }
    k=(idum)/IQ;
    idum=IA*(idum-k*IQ)-IR*k;
    if (idum < 0) idum += IM;
    j=iy/NDIV;
    iy=iv[j];
    iv[j] = idum;
    if ((temp=AM*iy) > RNMX) return RNMX;
    else return temp;
}

double agauss(long& idum)
{
    static int iset=0;
    static double gset;
    double fac,rsq,v1,v2;
    
    if (idum < 0) iset=0;
    if  (iset == 0) {
	do {
	    v1=2.0*ran1(idum)-1.0;
	    v2=2.0*ran1(idum)-1.0;
	    rsq=v1*v1+v2*v2;
	} while (rsq >= 1.0 || rsq == 0.0);
	fac=sqrt(-2.0*log(rsq)/rsq);
	gset=v1*fac;
	iset=1;
	return v2*fac;
    } else {
	iset=0;
	return gset;
    }
}
    


void inidat(Gendat& gd)
{
    cout << "  Cuantos puntos quiere generar? ";

    cin >> gd.n;
     assert(gd.n >= 3); 


    cout << " Introduzca rango [xmin,xmax]" << endl;
    cout << "xmin? ";  cin >> gd.xmin;
    cout << "xmax? ";  cin >> gd.xmax;

    if (gd.xmin > gd.xmax)
	cout<< "error!  xmin > xmax!!!!"<<endl;

    cout << " Introduzca error medio de los puntos" << endl;
    cout << "sigma? ";  cin >> gd.sigma;
    cout << " Introduzca dispersion de los errores" << endl;
    cout << "dispersion? ";  cin >> gd.dsigma;
//    cout << "introduzca un entero para inicializar el generador " << endl;
//    cout << "de numeros aleatorios";  cin >> gd.semilla;
    gd.semilla = time(0);  
}

void inifun(Genfun& gf)
{

    cout << "Que funcion desea?" << endl;
    cout << "Opciones: (1 = a*x+b, 2 = a*x**2+b*x+c, 3 = F, 4 = tau, 5= Polinomio (a+0.1*x)**n,  6= clfun)" ;
    int ifun; cin >> gf.ifun;
    
    if (gf.ifun == 1){
	cout << " Introduzca a,b " << endl;
	cout << "a? "; double a; cin >> a; expande(gf.par,a);
	cout << "b? "; double b; cin >> b; expande(gf.par, b);
	gf.sfun = "recta.d";
    }
    else if (gf.ifun == 2){
	cout << " Introduzca a,b,c " << endl;
	cout << "a? "; double a; cin >> a; expande(gf.par,a);
	cout << "b? "; double b; cin >> b; expande(gf.par,b);
	cout << "c? "; double c; cin >> c; expande(gf.par,c);
	gf.sfun = "parabola.d";
    }
    else if (gf.ifun == 3){
	cout << "Funcion de usuario" << endl;
	// Parámetros
	cout << " Introduzca a,b " << endl;
	cout << "a? "; double a; cin >> a; expande(gf.par,a);
	cout << "b? "; double b; cin >> b; expande(gf.par, b);
	gf.sfun = "F.d";
    } 
    else if (gf.ifun == 4){
	cout << "decay radioactivo" << endl;
	// Parámetros
	cout << " Introduzca N0, tau" << endl;
	cout << "No? "; double a; cin >> a; expande(gf.par,a);
	cout << "tau? "; double b; cin >> b; expande(gf.par,b);
	gf.sfun = "tau.d";
    } 
    else if (gf.ifun == 5){
	cout << "polinomio de orden n = (a+0.1*x)**n" << endl;
	// Parametros
	cout << " Introduzca a y n" << endl;
	cout << "a? "; double a; cin >> a; expande(gf.par,a);
	cout << "n? "; double m; cin >> m; expande(gf.par,m);
	gf.sfun = "pol.d";
    }
    else if (gf.ifun == 6){
	cout << "combinacion lineal de funciones" << endl;
        cout << "1) sin(x)  2) exp(-x)   3) sqrt(x)  4) log(x)"<<endl;
        cout << "5) tan(x)  6) cosh(x)   7) cos(x)   8) x**3 "<<endl;
	// Parametros
	cout << " Introduzca numero de funciones n y parametros a(i)" << endl;
	cout << "n? "; double n; cin >> n; expande(gf.par,n);
	for(int i=0; i<n; i++){
	cout << "a("<<i<<")= ? "; double a; cin >> a; expande(gf.par,a);}
	gf.sfun = "clfun.d";
    }
    
    else{
	cout<<"Error, ifun debe ser 1, 2, 3, 4, 5 o 6"<<endl;;
    }
}

datos idatos(Genfun& gf, const Gendat& gd)
{
    long semilla = -gd.semilla;

    // Crea un conjunto de datos vacio
    datos d;
    // igualmente espaciados
    double h = (gd.xmax - gd.xmin)/(gd.n-1);
    if (gf.ifun != 4){
    for (int i = 0; i< gd.n; i++){
	    double x = gd.xmin + i*h;
	    double y = gf.evalua(x);
	    double disp,error;
	    if(gd.sigma==0) disp =0;
	    else  disp = gd.sigma + agauss(semilla)*gd.dsigma;
	    if (disp <0)  disp = 0; 
	    if(gd.sigma==0)  error=0;
	    else  error = agauss(semilla)*disp;
//        double error = agauss(semilla)*gd.sigma;
	    double ey = y + error;
	    // expande el conjunto de datos
	    expande(d,x,ey,disp);
	    x+=h;
	}
    }
    else if (gf.ifun == 4){
	for (int i = 0; i< gd.n; i++){
	    double x = gd.xmin + i*h;
	    double y = gf.evalua(x);
	    double error = agauss(semilla)*sqrt(y);
	    double ey = y + error;
	    // expande el conjunto de datos
	    expande(d, x,ey,sqrt(y));
	    x+=h;
	}

    }
    return d;
}

void escribe(const vector<double>& v,const string s){
cout<<v.size()<<endl;
for (int i=0;i<v.size();i++) cout<<v[i]<<"   ";
cout<<endl;
ofstream fout(s.data(),ios::app);
fout<<v.size()<<endl;
for (int i=0;i<v.size();i++) fout<<v[i]<<"   ";
fout<<endl;
}

int main ()
{
    // Crea una estructura con los parametros
    // necesarios para generar los datos
    Gendat gd;
    // e incializa
     inidat(gd);
    // Crea una estructura para la funcion
    Genfun gf;
    // e incializa
    inifun(gf);
    // Crea estructura de datos
    datos d;
    // Y llenala
    d = idatos(gf,gd);
   cout << "dimension de d = " << d.xd.size() <<endl;
    // escribe en disco
    cout<<gf.sfun<<endl; //error
  ofstream fout(gf.sfun.data());

// y en pantalla
cout << "conjunto de datos = " << endl;
vector<double> vv(gf.par);
int dim = vv.size();
if(gf.ifun==6) dim--;
else if (gf.ifun==5) dim=int(gf.par[1])+1;
fout<<dim<<endl;
cout<<dim<<endl;
escribe(d.xd,gf.sfun);
escribe (d.yd, gf.sfun);
escribe (d.sigmad, gf.sfun);
fout.close();
//salida de datos para gnuplot
string gps=gf.sfun.erase(gf.sfun.find(".d")+1,1)+"gpl";
ofstream fout1(gps.data());
for(int i=0; i<gd.n;i++) 
  fout1<<d.xd[i]<<"\t"<<d.yd[i]<<"\t"<<d.sigmad[i]<<endl;
fout1.close();
  
//dibujamos los datos creados
ofstream fout2("gnuplot.macro");
fout2<<"plot "<<'"'<<gps.data()<<'"'<<endl;
fout2<<"pause 10 "<<endl;
fout2.close();
popen("gnuplot gnuplot.macro","r");
return 0;
}
