// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef _POLYNOMIALS1D_H
#define _POLYNOMIALS1D_H
#include "StOpt/core/utils/powInt.h"
/**
 * \file Polynomials1D.h
 * \brief Implementation of one variate polynomial
 * \author Jérôme Lelong
 */

namespace StOpt
{
  /**
   * \defgroup Polynomial Single variate polynomials
   * \brief It Implements single variate polynomials. each class must provide
   * three methods f, Df, D2f to compute the function value, its first
   * derivatives and its second derivative.
   */
  /**@{*/

  /**
   * \class Canonical
   * \brief Implementation of the canonical polynomials with one variate
   * P(X, n) = X^n
   */
  
  class Canonical
  {
  public:
    /**
     * \brief Evaluate the canonical Polynomial
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P(p_x, p_n)
     */
    template< typename T>
    T F(T p_x, int p_n) const
    {
      return StOpt::powInt(p_x, p_n) ;
    }
    /**
     * \brief Evaluate the first derivative of the canonical Polynomial
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P'(p_x, p_n)
     */
    template< typename T>
    T DF(T p_x, int p_n) const
    {
      if (p_n == 0) return 0.;
      return p_n * StOpt::powInt(p_x, p_n - 1);
    }

    /**
     * \brief Evaluate the second derivative of the canonical Polynomial
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P''(p_x, p_n)
     */
    template< typename T>
    T D2F(T p_x, int p_n) const
    {
      if (p_n <= 1) return 0.;
      return p_n * (p_n - 1) * StOpt::powInt(p_x, p_n - 2);
    }
  };

  /**
   * \class Tchebychev
   * \brief Implementation of the Tchebychev polynomials with one variate
   * P(X, n + 2) = 2 X P(X, n + 1) - P(X, n)
   */
  class Tchebychev
  {
  public:
    /**
     * \brief Evaluate the Tchebychev Polynomial
     *
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P(p_x, p_n)
     */
    template< typename T>
    T F(T p_x, int p_n) const
    {
      T val = p_x;
      T val2, val3, val4;
      switch (p_n)
        {
        case 0 :
          return 1.;
        case 1 :
          return val;
        case 2 :
          return 2. * val * val - 1.;
        case 3 :
          return (4. * val * val - 3.) * val;
        case 4 :
          val2 = val * val;
          return 8. * val2 * val2 - 8. * val2 + 1.;
          break;
        case 5 :
          val2 = val * val;
          val3 = val2 * val;
          return 16. * val3 * val2 - 20. * val3 + 5.* val;
        case 6 :
          val2 = val * val;
          val4 = val2 * val2;
          return 32. * val4 * val2 - 48. * val4 + 18. * val2 - 1;
        case 7 :
          val2 = val * val;
          val3 = val2 * val;
          val4 = val2 * val2;
          return (64. * val4 - 112. * val2 + 56) * val3 - 7. * val;
        default :
          T Fn0 = F(p_x, 7);
          T Fn0_1 = F(p_x, 6);
          return FRec(p_x, p_n, 7, Fn0, Fn0_1);
        }
    }
    /**
     * \brief Evaluate the first derivative of the Tchebychev Polynomial
     *
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P'(p_x, p_n)
     */
    template< typename T>
    T DF(T p_x, int p_n) const
    {
      T val = p_x;
      T val2, val4;
      switch (p_n)
        {
        case 0 :
          return 0.;
        case 1 :
          return 1.;
        case 2 :
          return 4. * val;
        case 3 :
          return (12. * val * val - 3.);
        case 4 :
          return (32. * val * val - 16.) * val;
        case 5 :
          val2 = val * val;
          return 80. * val2 * val2 - 60. * val2 + 5.;
        case 6 :
          val2 = val * val;
          val4 = val2 * val2;
          return (192. * val4 - 192. * val2 + 36.) * val;
        case 7 :
          val2 = val * val;
          val4 = val2 * val2;
          return (448. * val4 - 560. * val2 + 168) * val2 - 7.;
        default :
          T Dfn0 = DF(p_x, 7);
          T Dfn0_1 = DF(p_x, 6);
          return DFRec(p_x, p_n, 7, Dfn0, Dfn0_1);
        }
    }
    /**
     * \brief Evaluate the second derivative of the Tchebychev Polynomial
     *
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P''(p_x, p_n)
     */
    template< typename T>
    T D2F(T p_x, int p_n) const
    {
      T val = p_x;
      T val2, val4;
      switch (p_n)
        {
        case 0 :
          return 0.;
        case 1 :
          return 0.;
        case 2 :
          return 4.;
        case 3 :
          return 24. * val;
        case 4 :
          return (96. * val * val - 16.);
        case 5 :
          val2 = val * val;
          return 320. * val2 * val - 120. * val;
        case 6 :
          val2 = val * val;
          val4 = val2 * val2;
          return (960. * val4 - 576. * val2 + 36.);
        case 7 :
          val2 = val * val;
          val4 = val2 * val2;
          return (2688. * val4 - 2240. * val2 + 336) * val;
        default :
          T D2fn0_1 = D2F(p_x, 6);
          T D2fn0 = D2F(p_x, 7);
          return D2FRec(p_x, p_n, 7, D2fn0, D2fn0_1);
        }
    }
  private:
    /**
     * \brief The terminal recursive function to compute Tchebychev polynomials of any
     * order. This function is only used for order > 7
     *  \param p_x the evaluation point
     *  \param p_n the order of the polynomial to be evaluated
     *  \param p_n0 initialization of the recurrence
     *  \param p_fn0 used to store the polynomial of order p_n0.
     *  \param p_fn0_1 used to store the polynomial of order p_n0 - 1.
     */
    template< typename T>
    T FRec(T p_x, int p_n, int p_n0, T &p_fn0, T &p_fn0_1) const
    {
      if (p_n == p_n0)
        {
          return p_fn0;
        }
      else
        {
          T save = p_fn0;
          p_fn0 = 2 * p_x * p_fn0 - p_fn0_1;
          p_fn0_1 = save;
          return FRec(p_x, p_n, p_n0 + 1, p_fn0, p_fn0_1);
        }
    }
    
    /**
     * \brief The terminal recursive function to compute the first derivative of the
     * Tchebychev polynomials of any order. This function is only used for order > 7
     *
     *  \param p_x the evaluation point
     *  \param p_n the order of the polynomial to be evaluated
     *  \param p_n0 initialization of the recurrence
     *  \param Dfn0 used to store the derivative of the polynomial of order p_n0.
     *  \param Dfn0_1 used to store the derivative of the polynomial of order p_n0 - 1.
     */
    template< typename T>
    T DFRec(T p_x, int p_n, int p_n0, T &p_dfn0, T &p_dfn0_1) const
    {
      if (p_n == p_n0)
        {
          return p_dfn0;
        }
      else
        {
          T save = p_dfn0;
          p_dfn0 = 2 * p_x * T(p_n0 + 1.0) / T(p_n0) * p_dfn0 -   T(p_n0 + 1.) / T(p_n0 - 1.) * p_dfn0_1;
          p_dfn0_1 = save;
          return DFRec(p_x, p_n, p_n0 + 1, p_dfn0, p_dfn0_1);
        }
    }
    /**
     * \brief The terminal recursive function to compute the second derivative of the
     * Tchebychev polynomials of any order. This function is only used for order >  7
     *
     *  \param p_x point to evaluate at
     *  \param p_n the order of the polynomial
     *  \param p_n0 initialization of the recurrence
     *  \param D2fn0 used to store the second derivative of the polynomial of order p_n0.
     *  \param D2fn0_1 used to store the second derivative of the polynomial of order p_n0 - 1.
     */
    template< typename T>
    T D2FRec(T p_x, int p_n, int p_n0, T &p_d2fn0, T &p_d2fn0_1) const
    {
      if (p_n == p_n0)
        {
          return p_d2fn0;
        }
      else
        {
          T save = p_d2fn0;
          p_d2fn0 = 2 * p_x * p_d2fn0 - p_d2fn0_1 + 4 * DF(p_x, p_n0);
          p_d2fn0_1 = save;
          return D2FRec(p_x, p_n, p_n0 + 1, p_d2fn0, p_d2fn0_1);
        }
    }
  };

  /**
   * \class Hermite
   * \brief Implementation of the Hermite polynomials with one variate.
   *  P(X, n + 1) = X P(X, n) - 2 n P(X, n - 1)
   * These polynomials are orthogonal for the normal distribution and morover if
   * G is a standard normal random variable E[P(G, n)] = n!
   */
  class Hermite
  {
  public:

    /**
     * \brief Evaluate the Hermite Polynomial
     *
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P(p_x, p_n)
     */
    template< typename T>
    T F(T p_x, int p_n) const
    {
      T val = p_x;
      T val2;
      switch (p_n)
        {
        case 0 :
          return 1;
        case 1 :
          return val;
        case 2 :
          return val * val - 1.;
        case 3 :
          return (val * val - 3.) * val;
        case 4 :
          val2 = val * val;
          return (val2 - 6.) * val2 + 3;
        case 5 :
          val2 = val * val;
          return ((val2 - 10) * val2 + 15.) * val;
        case 6 :
          val2 = val * val;
          return ((val2 - 15.) * val2 + 45.) * val2 - 15.;
        case 7:
          val2 = val * val;
          return (((val2 - 21.) * val2 + 105.) * val2 - 105) * val;
        default:
          T Fn0 = F(p_x, 7);
          T Fn0_1 = F(p_x, 6);
          return FRec(p_x, p_n, 7, Fn0, Fn0_1);
        }
    }
  
    /**
     * \brief Evaluate the first derivative of the Hermite Polynomial
     *
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P'(p_x, p_n)
     */
    template< typename T>
    T DF(T p_x, int p_n) const
    {
      if (p_n == 0) return 0.;
      else return p_n * F(p_x, p_n - 1);
    }
   
    /**
     * \brief Evaluate the second derivative of the Hermite Polynomial
     *
     * \param p_x point at which to evaluate the polynomial
     * \param p_n degree of the polynomial
     * \return P''(p_x, p_n)
     */
    template< typename T>
    T D2F(T p_x, int p_n) const
    {
      if (p_n == 0 || p_n == 1) return 0.;
      return p_n * (p_n - 1) * F(p_x, p_n - 2);
    }

  private:
    /**
     * \brief The terminal recursive function to compute Hermite polynomials of any
     * order. This function is only used for order > 7
     *
     *  \param p_x point at which to evaluate the polynomial
     *  \param p_n the order of the polynomial to be evaluated
     *  \param p_n0 rank of initialization
     *  \param p_fn0 used to store the polynomial of order p_n0.
     *  \param p_fn0_1 used to store the polynomial of order p_n0 - 1.
     */
    template< typename T>
    T FRec(T p_x, int p_n, int p_n0, T &p_fn0, T &p_fn0_1) const
    {
      if (p_n == p_n0)
        {
          return p_fn0;
        }
      else
        {
          T save = p_fn0;
          p_fn0 = p_x * p_fn0 - p_n0 * p_fn0_1;
          p_fn0_1 = save;
          return FRec(p_x, p_n, p_n0 + 1, p_fn0, p_fn0_1);
        }    
    }
  };
  /**@}*/
}

#endif /* _POLYNOMIALS1D_H */
