! Laphi.f90 Lagrange Phi functions, orthogonal polynomials
!         function prototype naming convention:
!           phi            basic Lagrange polynomial
!           phip           first derivative "p do prime"
!           phipp          second derivative,  etc
!
! example  phip(x, i, n, xg()) is
!                     ^__ nth-1 degree
!                  ^__ ith polynomial=set
!             ^___first derivative "prime"
!  xg() is x  grid values, not necessarily uniformly spaced.
!  xg(1) is minimum, left boundary.
!  xg(n) is maximum, right boundary.
!  All grid coordinates must be=increasing order.

module laphi

public :: phi
public :: phip
public :: phipp
public :: phippp
public :: phipppp

contains
  ! phi
  function phi(x, j, n, xg) result(y)
    implicit none
    integer, intent(in) :: j, n
    double precision,  intent(in) :: x
    double precision,  intent(in), dimension(*) :: xg
    double precision :: y, denom
    integer :: i

    denom  = 1.0
    do i=1,n
      if(i .ne. j) then
        denom = denom * (xg(j)-xg(i))
      end if
    end do
    ! evaluate function, n,j,x,xg,denom
    y = 1.0 ! accumulate product
    do i=1,n
      if(i .ne. j) then
        y = y * (x-xg(i))
      end if
    end do
    y = y/denom
  end function phi

  ! first derivative
  function phip(x, j, n, xg) result(yp)
    implicit none
    integer, intent(in) :: j, n
    double precision,  intent(in) :: x
    double precision,  intent(in), dimension(*) :: xg
    double precision :: yp, prod, denom
    integer :: i, ii

    denom = 1.0
    do i=1,n
      if(i .ne. j) then
        denom = denom * (xg(j)-xg(i))
      end if
    end do
    ! evaluate first derivative of function, n,j,x,xg,denom
    yp = 0.0 ! accumulate sum of products
    do i=1,n
      if(i .ne. j) then
        prod = 1.0
        do ii=1,n
          if(ii.ne.i .and. ii.ne.j) then
            prod = prod * (x-xg(ii))
          end if
        end do
        yp = yp + prod
      end if
    end do
    yp = yp/denom ! have first derivative
  end function phip

  ! second derivative 
  function phipp(x, j, n, xg) result(ypp)
    implicit none
    integer, intent(in) :: j, n
    double precision,  intent(in) :: x
    double precision,  intent(in), dimension(*) :: xg
    double precision :: ypp, prod, denom
    integer :: i, ii, iii

    denom = 1.0
    do i=1,n
      if(i .ne. j) then
        denom = denom * (xg(j)-xg(i))
      end if
    end do
    ! evaluate second derivative of function, n,j,x,xg,denom
    ypp = 0.0 ! accumulate sum of products
    do i=1,n
      if(i .ne. j) then
        do ii=1,n
          if(ii.ne.i .and. ii.ne.j) then
            prod = 1.0
            do iii=1,n
              if(iii.ne.i .and. iii.ne.ii .and. iii.ne.j) then
                prod = prod * (x-xg(iii))
              end if
            end do
            ypp = ypp + prod
          end if
        end do
      end if
    end do
    ypp = ypp/denom ! have second derivative
  end function phipp

  ! third derivative
  function phippp(x, j, n, xg) result(yppp)
    implicit none
    integer, intent(in) :: j, n
    double precision,  intent(in) :: x
    double precision,  intent(in), dimension(*) :: xg
    double precision :: yppp, prod, denom
    integer :: i, ii, iii, iiii

    denom = 1.0
    do i=1,n
      if(i .ne. j) then
        denom = denom * (xg(j)-xg(i))
      end if
    end do
    ! evaluate third derivative of function, n,j,x,xg,denom
    yppp = 0.0 ! accumulate sum of products
    do i=1,n
      if(i .ne. j) then
        do ii=1,n
          if(ii.ne.i .and. ii.ne.j) then
            do iii=1,n
              if(iii.ne.i .and. iii.ne.ii .and. iii.ne.j) then
                prod = 1.0
                do iiii=1,n
                  if(iiii.ne.i .and. iiii.ne.ii .and. iiii.ne.iii .and. &
                     iiii.ne.j) then
                    prod = prod * (x-xg(iiii))
                  end if
                end do
                yppp = yppp + prod
              end if
            end do
          end if
        end do
      end if
    end do
    yppp = yppp/denom  ! have third derivative
  end function phippp

  ! fourth derivative
  function phipppp(x, j, n, xg) result(ypppp)
    implicit none
    integer, intent(in) :: j, n
    double precision,  intent(in) :: x
    double precision,  intent(in), dimension(*) :: xg
    double precision :: ypppp, prod, denom
    integer :: i, ii, iii, iiii, iiiii

    denom = 1.0
    do i=1,n
      if(i .ne. j) then
        denom = denom * (xg(j)-xg(i))
      end if
    end do
    ! evaluate fourth derivative of function, n,j,x,xg,denom
    ypppp = 0.0 ! accumulate sum of products
    do i=1,n
      if(i .ne. j) then
        do ii=1,n
          if(ii.ne.i .and. ii.ne.j) then
            do iii=1,n
              if(iii.ne.i .and. iii.ne.ii .and. iii.ne.j) then
                do iiii=1,n
                  if(iiii.ne.i .and. iiii.ne.ii .and. iiii.ne.iii .and. &
                     iiii.ne.j) then
                    prod = 1.0
                    do iiiii=1,n
                      if(iiiii.ne.i .and. iiiii.ne.ii .and. iiiii.ne.iii &
                         .and. iiiii.ne.iiii .and. iiiii.ne.j) then
                        prod = prod * (x-xg(iiiii))
                      end if
                    end do
                    ypppp = ypppp + prod
                  end if
                end do
              end if
            end do
          end if
        end do
      end if
    end do
    ypppp = ypppp/denom ! have fourth derivative
  end function phipppp

end module laphi
