/* Fesslix - Stochastic Analysis
 * Copyright (C) 2010-2016 Wolfgang Betz
 *
 * Fesslix is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fesslix 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fesslix.  If not, see <http://www.gnu.org/licenses/>. 
 */

#include "flxfem_legendrePolyH.h"

#include "flxmath_rnd.h"


void flxLegendrePoly::resize(size_t new_size)
{
  if (new_size <= size) return;
  
  LPvec.reserve(new_size+1);
  pdouble* tmp;
  tdouble tmpV;
  size_t tmpN, tmpN2;
  int tmp1;
  for (size_t i = size+1; i <= new_size; ++i) {
    tmpN = i/2+1;
    tmp = new pdouble[tmpN];
    LPvec.push_back( tmp );
    
    // initialize with '0'
      tmpN2 = (i-1)/2+1;
      for (size_t j = tmpN2; j < tmpN; ++j) {
	tmp[j] = 0.0;
      }
    // add L_{i-1}
      tmp1 = 2*i-1;
      for (size_t j = 0; j < tmpN2; ++j) {
	tmp[j] = LPvec[i-1][j] * tmp1;
      }
    // add L_{i-2}
      tmp1 = (i-1);
      tmpN2 = i/2;
      for (size_t j = tmpN2; j > 0; --j) {
	tmp[tmpN-j] -= LPvec[i-2][tmpN2-j] * tmp1;
      }
    // multiply with 1/n
      tmpV = 1.0/i;
      for (size_t j = 0; j < tmpN; ++j) {
	tmp[j] *= tmpV;
      }
  }
  size = new_size;
}

const tdouble flxLegendrePolyH::comp_Pn(const tuint nth, const tdouble& x)
{
  tdouble*tv=LPHvec[nth]; 
  if ( nth>1) {
    register tdouble t=tv[0]; const tuint N=nth/2+1; const tdouble x2 = pow2(x);
    for (register tuint i=1;i<N;++i) { t = t*x2 + tv[i]; }
    if (nth%2==1) { t*=x; }
    return t;
  } else { return (nth==0)?(0.5*(1.0-x)):(0.5*(1.0+x)); }
}

const tdouble flxLegendrePolyH::comp_dPn(const tuint nth, const tdouble& x)
{ 
  tdouble*tv=LPHvec[nth]; 
  if ( nth>1) {
    tdouble t=nth*tv[0]; tuint n = nth; const tuint N=(nth+1)/2; const tdouble x2 = pow2(x);
    for (tuint i=1;i<N;++i) { n-=2; t = t*x2 + n*tv[i]; }
    if (nth%2==0) { t*=x; }
    return t;
  } else { return (nth==0)?-0.5:0.5; }
}

void flxLegendrePolyH::print_Pn(const tuint nth, std::ostream& os)
{
  tdouble*tv=LPHvec[nth]; 
  if ( nth>1) {
    const tuint N=nth/2+1;
    os << GlobalVar.Double2String(tv[N-1]);
    tuint j=0;
    if (nth%2==1) {
      j=1;
      os << "*x";
    }
    for (tuint i=2;i<=N;++i) {
      j+=2;
      if (tv[N-i]>=0.0) {
	os << "+" ;
      }
      os << GlobalVar.Double2String(tv[N-i]) << "*x^" << j;
    }
  } else { 
    if (nth==0) {
      os << "0.5*(1-x)";
    } else {
      os << "0.5*(1+x)";
    }
  }
}

void flxLegendrePolyH::code_Pn_single(const tuint i, std::ostream& os)
{
  if (i%2==1) os << "(";
  const tuint N=i/2+1;
  for (tuint j=2;j<N;++j) os << "(";
  tdouble*tv=LPHvec[i]; 
  os << GlobalVar.Double2String(tv[0]) << "*x2";
  if (N>1) {
    if (!(tv[1]<0)) os << "+";
    os << GlobalVar.Double2String(tv[1]);
    for (tuint j=2;j<N;++j) {
      os << ")*x2";
      if (!(tv[j]<0)) os << "+";
      os << GlobalVar.Double2String(tv[j]);
    }
  }
  if (i%2==1) os << ")*x";
}


void flxLegendrePolyH::code_Pn(const tuint uptoN, std::ostream& os)
{
  resize(uptoN);
  for (tuint i=2;i<=uptoN;++i) {
    os << "inline const tdouble hLP" << i << "(const tdouble& x)" << std::endl << "{" << std::endl;
    os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
    os << "\treturn ";
    code_Pn_single(i,os);
    os << ";" << std::endl;
    os << "}" << std::endl << std::endl;
  }
  os << std::endl << std::endl;
  os << "void hLP_const(tVec& r, const tdouble& x, const tdouble& f) {" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\tconst size_t rs = r.size();" << std::endl;
  os << "\ttdouble* rp = &r[0];" << std::endl;
  os << "\t  rp[0] += ((1.0-x)/2.0)*f;" << std::endl;
  os << "\t  rp[1] += ((1.0+x)/2.0)*f;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\tif (rs==" << i << ") return;" << std::endl;
    os << "\t  rp[" << i << "] += ("; code_Pn_single(i,os); os << ")*f;" << std::endl;
  }
  os << "}" << std::endl;
  os << std::endl << std::endl;
  os << "void hLP_vec(tVec& r, const tdouble& x) {" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\tconst size_t rs = r.size();" << std::endl;
  os << "\ttdouble* rp = &r[0];" << std::endl;
  os << "\t  rp[0] = (1.0-x)/2.0;" << std::endl;
  os << "\t  rp[1] = (1.0+x)/2.0;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\tif (rs==" << i << ") return;" << std::endl;
    os << "\t  rp[" << i << "] = "; code_Pn_single(i,os); os << ";" << std::endl;
  }
  os << "}" << std::endl;
  os << std::endl << std::endl;
  os << "const tdouble hLP_rlz(const tuint N, const tdouble& x, const tdouble& c0, const tdouble& c1, const tdouble* coordH)" << std::endl << "{" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\ttdouble r = 0.0;" << std::endl;
  os << "\t  r += ((1.0-x)/2.0)*c0;" << std::endl;
  os << "\t  r += ((1.0+x)/2.0)*c1;" << std::endl;
  os << "\tif (N==1) return r;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\t  r += ("; code_Pn_single(i,os); os << ")*coordH[" << (i-2) << "];" << std::endl;
    os << "\tif (N==" << i << ") return r;" << std::endl;
  }
  os << "\treturn r;" << std::endl;
  os << "}" << std::endl;
  os << std::endl << std::endl;
}

void flxLegendrePolyH::code_dPn_single(const tuint i, std::ostream& os)
{
  if (i%2==0) os << "(";
    const tuint N=(i+1)/2;
    tuint n = i;
    for (tuint j=2;j<N;++j) os << "(";
    tdouble*tv=LPHvec[i]; 
    os << GlobalVar.Double2String(n*tv[0]);
    if (N>1) {
      os << "*x2";
      if (!(tv[1]<0)) os << "+";
      n-=2;
      os << GlobalVar.Double2String(n*tv[1]);
      for (tuint j=2;j<N;++j) {
	os << ")*x2";
	if (!(tv[j]<0)) os << "+";
	n-=2;
	os << GlobalVar.Double2String(n*tv[j]);
      }
    }
    if (i%2==0) os << ")*x";
}

void flxLegendrePolyH::code_dPn(const tuint uptoN, std::ostream& os)
{
  resize(uptoN);
  for (tuint i=2;i<=uptoN;++i) {
    os << "const tdouble dhLP" << i << "(const tdouble& x)" << std::endl << "{" << std::endl;
    os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
    os << "\treturn ";
    code_dPn_single(i,os);
    os << ";" << std::endl;
    os << "}" << std::endl << std::endl;
  }
  os << std::endl << std::endl;
  os << "void dhLP_vec(tVec& r, const tdouble& x) {" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\tconst size_t rs = r.size();" << std::endl;
  os << "\ttdouble* rp = &r[0];" << std::endl;
  os << "\t  rp[0] = -0.5;" << std::endl;
  os << "\t  rp[1] = 0.5;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\tif (rs==" << i << ") return;" << std::endl;
    os << "\t  rp[" << i << "] = "; code_dPn_single(i,os); os << ";" << std::endl;
  }
  os << "}" << std::endl;
  os << std::endl << std::endl;
}

tdouble* flxLegendrePoly::get_hierarchic(size_t nth) const
{
  #if FLX_DEBUG
    if (size < nth) {
      throw FlxException_Crude("flxLegendrePoly::get_hierarchic"); 
    }
  #endif
  const size_t tmpN = nth/2+1;
  #ifndef FLX_CV_1
    pdouble tmp[tmpN];
  #else
    pdouble *tmp = new pdouble[tmpN];
  #endif
  for (size_t i = 0; i < tmpN; ++i) {
    tmp[i] = LPvec[nth][i];
  }
  for (size_t i = 1; i < tmpN; ++i) {
    tmp[i] -= LPvec[nth-2][i-1];
  }
  tdouble tmpV = 1.0/sqrt(4.0*nth - 2.0);
  tdouble* tmpd = new tdouble[tmpN];
  for (size_t i = 0; i < tmpN; ++i) {
    tmpd[i] = (tmp[i] * tmpV).cast2double();
  }
  #ifdef FLC_CV_1
    delete [] tmp;
  #endif
  return tmpd;
}


flxLegendrePoly::flxLegendrePoly()
: size(1), LPvec(2)
{
  LPvec[0] = new pdouble[1];
  LPvec[0][0] = 1;
  LPvec[1] = new pdouble[1];
  LPvec[1][0] = 1;
  
  resize(GlobalVar.LegendrePolyH_init_numb);
}

flxLegendrePoly::~flxLegendrePoly()
{
  for (size_t i = 0; i <= size; ++i) {
    delete [] LPvec[i];
  }
}


void flxLegendrePolyH::resize(size_t new_size)
{
  if (new_size <= size) return;
  
  LPbase.resize(new_size);
  LPHvec.reserve(new_size+1);
  for (size_t i = size+1; i <= new_size; ++i) {
    LPHvec.push_back( LPbase.get_hierarchic(i) );
  }
  GlobalVar.slog(4) << "Legendre-polynomials: up to " << new_size << " Legendre polynomials initialized." << std::endl;
  size = new_size;
}

flxLegendrePolyH::flxLegendrePolyH()
: size(1), LPHvec(2)
{
  LPHvec[0] = new tdouble[2];
  LPHvec[0][0] = -0.5;
  LPHvec[0][1] = 0.5;
  LPHvec[1] = new tdouble[2];
  LPHvec[1][0] = 0.5;
  LPHvec[1][1] = 0.5;
  
  resize(GlobalVar.LegendrePolyH_init_numb);
}

flxLegendrePolyH::~flxLegendrePolyH()
{
  for (size_t i = 0; i < LPHvec.size(); ++i) {
    delete [] LPHvec[i];
  }
}



flxGegenbauerPolyH::flxGegenbauerPolyH(size_t new_size)
: size(1), GBPvec(2)
{
  GBPvec[0] = new pdouble[1];
  GBPvec[0][0] = 1;
  GBPvec[1] = new pdouble[1];
  GBPvec[1][0] = 5;
  
  // resize 
    if (new_size <= size) return;

    GBPvec.reserve(new_size+1);
    for (size_t i = size+1; i <= new_size; ++i) {
      GBPvec.push_back( get_hierarchic(i) );
    }
    GlobalVar.slog(4) << "Gegenbauer-polynomials: up to " << new_size << " Gegenbauer polynomials initialized." << std::endl;
    size = new_size;
  
  // normalize
    for (tuint i=0;i<=size;++i) {
      const tdouble c = (Factorial(i)*(i+2.5)*pow2(flxgamma(2.5)))/(PI*pow(2,-4.0)*flxgamma(i+5.0));
      const tdouble c2 = sqrt(c);
      const size_t tmpN = i/2+1;
      pdouble* pp = GBPvec[i];
      for (tuint j=0;j<tmpN;++j) {
	pp[j] *= c2;
      }
    }
    
}

flxGegenbauerPolyH::~flxGegenbauerPolyH()
{
  for (size_t i = 0; i < GBPvec.size(); ++i) {
    delete [] GBPvec[i];
  }
}

pdouble* flxGegenbauerPolyH::get_hierarchic(size_t nth) const
{
  #if FLX_DEBUG
    if (nth != GBPvec.size()) {
      throw FlxException_Crude("flxGegenbauerPolyH::get_hierarchic"); 
    }
  #endif
  const size_t tmpN = nth/2+1;
  const size_t tmpNp = (nth-1)/2+1;
  #ifndef FLX_CV_1
    pdouble tmp[tmpN];
  #else
    pdouble *tmp = new pdouble[tmpN];
  #endif
  const tdouble h1 = 2.0 * (nth + 1.5);
  if (tmpN-tmpNp>0) tmp[0] = 0.0;
  for (size_t i = tmpNp; i > 0; --i) {
    tmp[tmpN-i] = GBPvec[nth-1][tmpNp-i]*h1;
  }
  const tdouble h2 = 3.0 + nth;
  for (size_t i = 0; i < tmpN-1; ++i) {
    tmp[i] -= GBPvec[nth-2][i]*h2;
  }
  const tdouble h3 = 1.0/nth;
  pdouble* tmpd = new pdouble[tmpN];
  for (size_t i = 0; i < tmpN; ++i) {
    tmpd[i] = (tmp[i] * h3);
  }
  #ifdef FLC_CV_1
    delete [] tmp;
  #endif
  return tmpd;
}


void flxGegenbauerPolyH::code_hGBP(const tuint uptoN, std::ostream& os)
{
  if (uptoN < 2) return;
  if (uptoN > size+2) throw FlxException_Crude("flxGegenbauerPolyH::code_hGBP");
  for (tuint i=2;i<=uptoN;++i) {
    os << "inline const tdouble hGBP" << i << "(const tdouble& x)" << std::endl << "{" << std::endl;
    os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
    os << "\treturn (1-x2)*";
    code_GBP_single(i-2,os);
    os << ";" << std::endl;
    os << "}" << std::endl << std::endl;
  }
  os << std::endl << std::endl;
  os << "void hGBP_const(tVec& r, const tdouble& x, const tdouble& f) {" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\tregister const tdouble pf = (1-x2)*f;" << std::endl;
  os << "\tconst size_t rs = r.size();" << std::endl;
  os << "\ttdouble* rp = &r[0];" << std::endl;
  os << "\t  rp[0] += ((1.0-x)/2.0)*f;" << std::endl;
  os << "\t  rp[1] += ((1.0+x)/2.0)*f;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\tif (rs==" << i << ") return;" << std::endl;
    os << "\t  rp[" << i << "] += "; code_GBP_single(i-2,os); os << "*pf;" << std::endl;
  }
  os << "}" << std::endl;
  os << std::endl << std::endl;
  os << "void hGBP_vec(tVec& r, const tdouble& x) {" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\tregister const tdouble pf = (1-x2);" << std::endl;
  os << "\tconst size_t rs = r.size();" << std::endl;
  os << "\ttdouble* rp = &r[0];" << std::endl;
  os << "\t  rp[0] = (1.0-x)/2.0;" << std::endl;
  os << "\t  rp[1] = (1.0+x)/2.0;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\tif (rs==" << i << ") return;" << std::endl;
    os << "\t  rp[" << i << "] = pf*"; code_GBP_single(i-2,os); os << ";" << std::endl;
  }
  os << "}" << std::endl;
  os << std::endl << std::endl;
  os << "const tdouble hGBP_rlz(const tuint N, const tdouble& x, const tdouble& c0, const tdouble& c1, const tdouble* coordH)" << std::endl << "{" << std::endl;
  os << "\tregister const tdouble x2 = pow2(x);" << std::endl;
  os << "\tregister const tdouble pf = (1-x2);" << std::endl;
  os << "\ttdouble r = 0.0;" << std::endl;
  os << "\t  r += ((1.0-x)/2.0)*c0;" << std::endl;
  os << "\t  r += ((1.0+x)/2.0)*c1;" << std::endl;
  os << "\tif (N==1) return r;" << std::endl;
  for (tuint i=2;i<=uptoN;++i) {
    os << "\t  r += pf*"; code_GBP_single(i-2,os); os << "*coordH[" << (i-2) << "];" << std::endl;
    os << "\tif (N==" << i << ") return r;" << std::endl;
  }
  os << "\treturn r;" << std::endl;
  os << "}" << std::endl;
  os << std::endl << std::endl;
}

void flxGegenbauerPolyH::code_GBP_single(const tuint i, std::ostream& os)
{
  const tuint N=i/2+1;
  if (N>1) os << "(";
  for (tuint j=2;j<N;++j) os << "(";
  pdouble*tv=GBPvec[i]; 
  if (N>1) {
    os << GlobalVar.Double2String(tv[N-1].cast2double()) << "*x2";
    for (tuint j=2;j<N;++j) {
      if (!(tv[N-j].cast2double()<0)) os << "+";
      os << GlobalVar.Double2String(tv[N-j].cast2double());
      os << ")*x2";
    }
    if (!(tv[0].cast2double()<0)) os << "+";
  }  
  os << GlobalVar.Double2String(tv[0].cast2double());
  if (N>1) os << ")";
  if (i%2==1) os << "*x";
}








