!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
module cubemain_chebyshev_tool
  use cubemain_messaging
  use cubemain_svd_tool
  !
  public :: chebyshev_t,svd_t
  private
  !
  integer(kind=4), parameter :: coef_k = inte_k
  integer(kind=4), parameter :: degr_k = inte_k
  !
  type chebyshev_t
     integer(kind=degr_k),  private :: n = 0              ! Polynomial degree
     real(kind=4), pointer, private :: coeff(:) => null() ! Coefficients
     real(kind=4), pointer, private :: cheby(:) => null() ! Chebyshev polynomial values
     real(kind=coor_k),     private :: xcur = 0.0 ! X current value where Chebyshev polynomial values are computed
     real(kind=coor_k),     private :: xmin = 0.0 ! Minimum value of the x-axis computation interval
     real(kind=coor_k),     private :: xmax = 0.0 ! Maximum value of the x-axis computation interval
     real(kind=coor_k),     private :: xcen = 0.0 ! Its center 
     real(kind=coor_k),     private :: xsiz = 0.0 ! Its size
   contains
     procedure, private :: reallocate => cubemain_chebyshev_reallocate
     procedure, private :: free       => cubemain_chebyshev_free
     procedure, public  :: fit        => cubemain_chebyshev_fit
     procedure, public  :: subtract   => cubemain_chebyshev_subtract
     procedure, public  :: list       => cubemain_chebyshev_list
     final :: cubemain_chebyshev_final
  end type chebyshev_t
  !
contains
  !
  subroutine cubemain_chebyshev_reallocate(poly,degree,error)
    use gkernel_interfaces
    !----------------------------------------------------------------------
    !
    !----------------------------------------------------------------------
    class(chebyshev_t),   intent(inout) :: poly
    integer(kind=degr_k), intent(in)    :: degree
    logical,              intent(inout) :: error
    !
    logical :: alloc
    integer(kind=4) :: ier
    integer(kind=coef_k)  :: ncoeff
    character(len=mess_l) :: mess
    character(len=*), parameter :: rname='CHEBYSHEV>REALLOCATE'
    !
    call cubemain_message(mainseve%trace,rname,'Welcome')
    !
    ! Sanity check
    ncoeff = degree+1
    if (ncoeff.le.0) then
       call cubemain_message(seve%e,rname,'Negative or zero number of channels')
       error = .true.
       return
    endif
    alloc = .true.
    if (associated(poly%coeff)) then
       if (poly%n.eq.ncoeff) then
          write(mess,'(a,i0)')  &
               'Chebishev coefficients already associated at the right size: ',ncoeff
          call cubemain_message(mainseve%alloc,rname,mess)
          alloc = .false.
       else
          write(mess,'(a)') &
               'Chebishev coefficients already associated but with a different size => Freeing it first'
          call cubemain_message(mainseve%alloc,rname,mess)
          call poly%free(error)
          if (error) return
       endif
    endif
    if (alloc) then
       allocate(poly%coeff(ncoeff),poly%cheby(ncoeff),stat=ier)
       if (failed_allocate(rname,'Chebyshev coefficients',ier,error)) return
    endif
    ! Allocation success => poly%n may be updated
    poly%n = ncoeff
  end subroutine cubemain_chebyshev_reallocate
  !
  subroutine cubemain_chebyshev_free(poly,error)
    !----------------------------------------------------------------------
    ! 
    !----------------------------------------------------------------------
    class(chebyshev_t), intent(inout) :: poly
    logical,            intent(inout) :: error
    !
    character(len=*), parameter :: rname='CHEBYSHEV>FREE'
    !
    call cubemain_message(mainseve%trace,rname,'Welcome')
    !
    poly%n = 0
    if (associated(poly%coeff)) deallocate(poly%coeff)
    if (associated(poly%cheby)) deallocate(poly%cheby)
  end subroutine cubemain_chebyshev_free
  !
  subroutine cubemain_chebyshev_final(poly)
    !----------------------------------------------------------------------
    ! 
    !----------------------------------------------------------------------
    type(chebyshev_t), intent(inout) :: poly
    !
    logical :: error
    character(len=*), parameter :: rname='CHEBYSHEV>FINAL'
    !
    call cubemain_message(mainseve%trace,rname,'Welcome')
    !
    call poly%free(error)
  end subroutine cubemain_chebyshev_final
  !
  !---------------------------------------------------------------------
  !
  subroutine cubemain_chebyshev_polynomials(x,t,np)
    !---------------------------------------------------------------------
    ! Compute the first NP-1 Chebyshev polynomial of first kind at x
    !    T_n(x), n=0...np-1
    ! Recurrence relation : T_(n+1)(x) - 2xT_(n)(x) + T_(n-1)(x) = 0
    ! Initialization      : T_0 = 1
    !                     : T_1 = x
    !---------------------------------------------------------------------
    real(kind=coor_k),    intent(in)  :: x      ! Variable value
    integer(kind=degr_k), intent(in)  :: np     ! Polynomial degree
    real(kind=sign_k),    intent(out) :: t(np)  ! NP values of Chebyshev polynomial
    !
    integer(kind=degr_k) :: ip
    !
    t(1) = 1.
    if (np.gt.1) then
       t(2) = x
       do ip=3,np
          t(ip)=2*t(ip-1)*x-t(ip-2)
       enddo
    endif
  end subroutine cubemain_chebyshev_polynomials
  !
  function cubemain_chebyshev_approximation(poly,x) result(val)
    !---------------------------------------------------------------------
    ! 
    !---------------------------------------------------------------------
    type(chebyshev_t), intent(in) :: poly
    real(kind=coor_k), intent(in) :: x
    real(kind=sign_k)             :: val
    !
    integer(kind=coef_k) :: icoeff
    !
    call cubemain_chebyshev_polynomials(x,poly%cheby,poly%n)
    val = 0
    do icoeff=1,poly%n
       val = val+poly%coeff(icoeff)*poly%cheby(icoeff)
    enddo ! icoeff
  end function cubemain_chebyshev_approximation
  !
  subroutine cubemain_chebyshev_check_degree(rname,svd)
    use cubemain_svd_tool
    !---------------------------------------------------------------------
    ! *** JP: Not used anywhere anymore but could be used in the future
    !---------------------------------------------------------------------
    character,   intent(in) :: rname
    type(svd_t), intent(in) :: svd
    !
    integer(kind=degr_k)  :: imin
    character(len=mess_l) :: mess
    !
    if (svd%minloc_w_lt_np()) then
       write(mess,'(A,I2,A)') 'Degree ',imin-1,' would be even better'
       call cubemain_message(seve%i,rname,mess)
    endif
  end subroutine cubemain_chebyshev_check_degree
  !
  subroutine cubemain_chebyshev_fit(poly,degree,spec,svd,error)
    use cubeadm_spectrum_types
    use cubemain_svd_tool
    !---------------------------------------------------------------------
    ! Fit a Chebyshev polynomial baseline using singular value decomposition
    ! This one assumes that spec does not contain blanked values
    !---------------------------------------------------------------------
    class(chebyshev_t),   intent(inout) :: poly
    integer(kind=degr_k), intent(in)    :: degree
    type(spectrum_t),     intent(in)    :: spec
    type(svd_t),          intent(inout) :: svd
    logical,              intent(inout) :: error
    !
    integer(kind=chan_k) :: ic
    real(kind=sign_k) :: chisq
    character(len=mess_l) :: mess
    character(len=*), parameter :: rname='CHEBYSHEV>FIT'
    !
    ! Check that we have enough data points
    ! Note: polynomial degree >= 0 implies nchan > 0
    if (spec%n.le.degree) then
       error = .true.
       write(mess,*) 'Not enough channels to fit baseline: ',spec%n,degree
       call cubemain_message(seve%e,rname,mess)
       return
    endif
    !
    call poly%reallocate(degree,error)
    if (error) return
    ! Normalize range to [-1,1]
    ! *** JP: What happens when npoint = 1 is unclear... *** JP
    poly%xmin = spec%x%val(1)
    poly%xmax = spec%x%val(spec%n)
    poly%xcen = 0.5*(poly%xmax+poly%xmin)
    poly%xsiz = 0.5*(poly%xmax-poly%xmin)
    do ic=1,spec%n
       spec%x%val(ic) = (spec%x%val(ic)-poly%xcen)/poly%xsiz
    enddo ! ic
    !
    call svd%reallocate(spec%n,poly%n,error)
    if (error) return
    call svd%fit(spec,poly%coeff,poly%n,&
         chisq,cubemain_chebyshev_polynomials,&
         error)
    if (error) return
  end subroutine cubemain_chebyshev_fit
  !
  subroutine cubemain_chebyshev_subtract(poly,spec,ifirst,ilast,base,resi,error)
    use cubeadm_spectrum_types
    !---------------------------------------------------------------------
    ! Subtract a Chebyshev approximation of the baseline to the spectrum
    !---------------------------------------------------------------------
    class(chebyshev_t),   intent(in)    :: poly
    type(spectrum_t),     intent(in)    :: spec
    integer(kind=chan_k), intent(in)    :: ifirst
    integer(kind=chan_k), intent(in)    :: ilast
    type(spectrum_t),     intent(inout) :: base
    type(spectrum_t),     intent(inout) :: resi
    logical,              intent(inout) :: error
    !
    integer(kind=chan_k) :: ic
    real(kind=coor_k)    :: xic
    real(kind=sign_k) :: yic,ymin,ymax
    character(len=*), parameter :: rname='CHEBYSHEV>SUBTRACT'
    !
    ! Compute baseline value
    ! First at interval edges
    ymin = cubemain_chebyshev_approximation(poly,-1d0)
    ymax = cubemain_chebyshev_approximation(poly,+1d0)
    ! Then everywhere
    if (poly%n.gt.2) then
       ! Degree > 1 => poly%n.gt.2
       ! Compute polynomial inside fitting range and constant or zero outside
       do ic=ifirst,ilast
          xic = (spec%x%val(ic)-poly%xcen) /poly%xsiz
          if (xic.le.-1.0) then
             yic = ymin
          else if (xic.ge.1.0) then
             yic = ymax
          else
             yic = cubemain_chebyshev_approximation(poly,xic)
          endif
          base%y%val(ic) = yic
          resi%y%val(ic) = spec%y%val(ic)-yic
       enddo ! ic
    else
       ! Degree 0 or 1: compute polynomial everywhere (including
       ! extrapolation beyond edges)
       do ic=ifirst,ilast
          xic = (spec%x%val(ic)-poly%xcen) /poly%xsiz
          yic = ymin + (xic+1.0)*(ymax-ymin)/2.0
          base%y%val(ic) = yic
          resi%y%val(ic) = spec%y%val(ic)-yic
       enddo ! ic
    endif
  end subroutine cubemain_chebyshev_subtract
  !
  !------------------------------------------------------------------------
  !
  subroutine cubemain_chebyshev_list(poly,error)
    !----------------------------------------------------------------------
    !
    !----------------------------------------------------------------------
    class(chebyshev_t), intent(in)    :: poly
    logical,            intent(inout) :: error
    !
    integer(kind=degr_k) :: ip
    character(len=*), parameter :: rname='CHEBYSHEV>LIST'
    !
    call cubemain_message(mainseve%trace,rname,'Welcome')
    !
    print *,'Chebyshev Polynomial'
    print *,'   x current value  ',poly%xcur
    print *,'   x range mininum  ',poly%xmin
    print *,'   x range maxinum  ',poly%xmax
    print *,'   x range center   ',poly%xcen
    print *,'   x range size     ',poly%xsiz
    print *,'   Degree           ',poly%n
    print *,'   Coefficients and values'
    do ip=1,poly%n
       print *,'       ',ip,poly%coeff(ip),poly%cheby(ip)
    enddo ! id
  end subroutine cubemain_chebyshev_list
end module cubemain_chebyshev_tool
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
