module interpolate_data 63,11
! Description:
!   Routines for interpolation of data in latitude, longitude and time.
!
! Author: Gathered from a number of places and put into the current format by Jim Edwards
!   
! Modules Used:
!
  use shr_kind_mod,   only: r8 => shr_kind_r8
  use abortutils,     only: endrun
  use scamMod,        only: single_column
  use cam_logfile,    only: iulog
  implicit none
  private
!
! Public Methods:
!

  public :: interp_type, lininterp, vertinterp, bilin, get_timeinterp_factors
  public :: lininterp_init, lininterp_finish
  type interp_type
     real(r8), pointer :: wgts(:)
     real(r8), pointer :: wgtn(:)
     integer, pointer  :: jjm(:)
     integer, pointer  :: jjp(:)
  end type interp_type

  interface lininterp 6
     module procedure lininterp_original
     module procedure lininterp_full1d
     module procedure lininterp1d
     module procedure lininterp2d2d
     module procedure lininterp2d1d
     module procedure lininterp3d2d
  end interface
  

contains

  subroutine lininterp_full1d (arrin, yin, nin, arrout, yout, nout) 1,3
    integer, intent(in) :: nin, nout
    real(r8), intent(in) :: arrin(nin), yin(nin), yout(nout)
    real(r8), intent(out) :: arrout(nout)
    type (interp_type) :: interp_wgts

    call lininterp_init(yin, nin, yout, nout, 1, interp_wgts)
    call lininterp1d(arrin, nin, arrout, nout, interp_wgts)
    call lininterp_finish(interp_wgts)

  end subroutine lininterp_full1d


  subroutine lininterp_init(yin, nin, yout, nout, extrap_method, interp_wgts, & 10,7
       cyclicmin, cyclicmax)
!
! Description:
!   Initialize a variable of type(interp_type) with weights for linear interpolation.
!       this variable can then be used in calls to lininterp1d and lininterp2d.
!   yin is a 1d array of length nin of locations to interpolate from - this array must 
!       be monotonic but can be increasing or decreasing
!   yout is a 1d array of length nout of locations to interpolate to, this array need
!       not be ordered
!   extrap_method determines how to handle yout points beyond the bounds of yin
!       if 0 set values outside output grid to 0 
!       if 1 set to boundary value
!       if 2 set to cyclic boundaries
!         optional values cyclicmin and cyclicmax can be used to set the bounds of the 
!         cyclic mapping - these default to 0 and 360.
!

    integer, intent(in) :: nin
    integer, intent(in) :: nout
    real(r8), intent(in) :: yin(:)           ! input mesh
    real(r8), intent(in) :: yout(:)         ! output mesh
    integer, intent(in) :: extrap_method       ! if 0 set values outside output grid to 0 
                                               ! if 1 set to boundary value
                                               ! if 2 set to cyclic boundaries
    real(r8), intent(in), optional :: cyclicmin, cyclicmax

    type (interp_type), intent(out) :: interp_wgts
    real(r8) :: cmin, cmax
    real(r8) :: extrap
    real(r8) :: dyinwrap
    real(r8) :: ratio
    real(r8) :: avgdyin
    integer :: i, j, icount
    integer :: jj
    real(r8), pointer :: wgts(:)
    real(r8), pointer :: wgtn(:)
    integer, pointer :: jjm(:)
    integer, pointer :: jjp(:)
    logical :: increasing
    !
    ! Check validity of input coordinate arrays: must be monotonically increasing,
    ! and have a total of at least 2 elements
    !
    if (nin.lt.2) then
       call endrun('LININTERP: Must have at least 2 input points for interpolation')
    end if
    if(present(cyclicmin)) then
       cmin=cyclicmin
    else
       cmin=0_r8
    end if
    if(present(cyclicmax)) then
       cmax=cyclicmax
    else
       cmax=360_r8
    end if
    if(cmax<=cmin) then
       call endrun('LININTERP: cyclic min value must be < max value')
    end if
    increasing=.true.
    icount = 0
    do j=1,nin-1
       if (yin(j).gt.yin(j+1)) icount = icount + 1
    end do
    if(icount.eq.nin-1) then
       increasing = .false.
       icount=0
    endif
    if (icount.gt.0) then
       call endrun('LININTERP: Non-monotonic input coordinate array found')
    end if
    allocate(interp_wgts%jjm(nout), &
         interp_wgts%jjp(nout), &
         interp_wgts%wgts(nout), &
         interp_wgts%wgtn(nout))

    jjm => interp_wgts%jjm
    jjp => interp_wgts%jjp
    wgts =>  interp_wgts%wgts
    wgtn =>  interp_wgts%wgtn

    !
    ! Initialize index arrays for later checking
    !
    jjm = 0
    jjp = 0

    extrap = 0.
    if(extrap_method.eq.0) then
       !
       ! For values which extend beyond boundaries, set weights
       ! such that values will be 0.
       !
       do j=1,nout
          if(increasing) then
             if (yout(j).lt.yin(1)) then
                jjm(j) = 1
                jjp(j) = 1
                wgts(j) = 0.
                wgtn(j) = 0.
                extrap = extrap + 1.
             else if (yout(j).gt.yin(nin)) then
                jjm(j) = nin
                jjp(j) = nin
                wgts(j) = 0.
                wgtn(j) = 0.
                extrap = extrap + 1.
             end if
          else
             if (yout(j).gt.yin(1)) then
                jjm(j) = 1
                jjp(j) = 1
                wgts(j) = 0.
                wgtn(j) = 0.
                extrap = extrap + 1.
             else if (yout(j).lt.yin(nin)) then
                jjm(j) = nin
                jjp(j) = nin
                wgts(j) = 0.
                wgtn(j) = 0.
                extrap = extrap + 1.
             end if
          end if
       end do
    else if(extrap_method.eq.1) then
       !
       ! For values which extend beyond boundaries, set weights
       ! such that values will just be copied.
       !
       do j=1,nout
          if(increasing) then
             if (yout(j).le.yin(1)) then
                jjm(j) = 1
                jjp(j) = 1
                wgts(j) = 1.
                wgtn(j) = 0.
                extrap = extrap + 1.
             else if (yout(j).gt.yin(nin)) then
                jjm(j) = nin
                jjp(j) = nin
                wgts(j) = 1.
                wgtn(j) = 0.
                extrap = extrap + 1.
             end if
          else
             if (yout(j).gt.yin(1)) then
                jjm(j) = 1
                jjp(j) = 1
                wgts(j) = 1.
                wgtn(j) = 0.
                extrap = extrap + 1.
             else if (yout(j).le.yin(nin)) then
                jjm(j) = nin
                jjp(j) = nin
                wgts(j) = 1.
                wgtn(j) = 0.
                extrap = extrap + 1.
             end if
          end if
       end do
    else if(extrap_method.eq.2) then
       !
       ! For values which extend beyond boundaries, set weights
       ! for circular boundaries 
       !
       dyinwrap = yin(1) + (cmax-cmin) - yin(nin)
       avgdyin = abs(yin(nin)-yin(1))/(nin-1.)
       ratio = dyinwrap/avgdyin
       if (ratio < 0.9 .or. ratio > 1.1) then
          write(iulog,*) 'Lininterp: Bad dyinwrap value =',dyinwrap,&
               ' avg=', avgdyin, yin(1),yin(nin)
          call endrun('interpolate_data')
       end if

       do j=1,nout
          if(increasing) then
             if (yout(j) <= yin(1)) then
                jjm(j) = nin
                jjp(j) = 1
                wgts(j) = (yin(1)-yout(j))/dyinwrap
                wgtn(j) = (yout(j)+(cmax-cmin) - yin(nin))/dyinwrap
             else if (yout(j) > yin(nin)) then
                jjm(j) = nin
                jjp(j) = 1
                wgts(j) = (yin(1)+(cmax-cmin)-yout(j))/dyinwrap
                wgtn(j) = (yout(j)-yin(nin))/dyinwrap
             end if
          else
             if (yout(j) > yin(1)) then
                jjm(j) = nin
                jjp(j) = 1
                wgts(j) = (yin(1)-yout(j))/dyinwrap
                wgtn(j) = (yout(j)+(cmax-cmin) - yin(nin))/dyinwrap
             else if (yout(j) <= yin(nin)) then
                jjm(j) = nin
                jjp(j) = 1
                wgts(j) = (yin(1)+(cmax-cmin)-yout(j))/dyinwrap
                wgtn(j) = (yout(j)+(cmax-cmin)-yin(nin))/dyinwrap
             end if

          endif
       end do
    end if

    !
    ! Loop though output indices finding input indices and weights
    !
    if(increasing) then
       do j=1,nout
          do jj=1,nin-1
             if (yout(j).gt.yin(jj) .and. yout(j).le.yin(jj+1)) then
                jjm(j) = jj
                jjp(j) = jj + 1
                wgts(j) = (yin(jj+1)-yout(j))/(yin(jj+1)-yin(jj))
                wgtn(j) = (yout(j)-yin(jj))/(yin(jj+1)-yin(jj))
                exit
             end if
          end do
       end do
    else
       do j=1,nout
          do jj=1,nin-1
             if (yout(j).le.yin(jj) .and. yout(j).gt.yin(jj+1)) then
                jjm(j) = jj
                jjp(j) = jj + 1
                wgts(j) = (yin(jj+1)-yout(j))/(yin(jj+1)-yin(jj))
                wgtn(j) = (yout(j)-yin(jj))/(yin(jj+1)-yin(jj))
                exit
             end if
          end do
       end do
    end if

#ifndef SPMD
    !
    ! Check grid overlap
    !
    extrap = 100.*extrap/real(nout,r8)
    if (extrap.gt.50. .and. .not. single_column) then
       write(iulog,*) 'interpolate_data:','yout=',minval(yout),maxval(yout),increasing,nout
       write(iulog,*) 'interpolate_data:','yin=',yin(1),yin(nin)
       write(iulog,*) 'interpolate_data:',extrap,' % of output grid will have to be extrapolated'
       call endrun('interpolate_data: ')
    end if
#endif

    !
    ! Check that interp/extrap points have been found for all outputs
    !
    icount = 0
    do j=1,nout
       if (jjm(j).eq.0 .or. jjp(j).eq.0) icount = icount + 1
       ratio=wgts(j)+wgtn(j)
       if((ratio<0.9.or.ratio>1.1).and.extrap_method.ne.0) then
          write(iulog,*) j, wgts(j),wgtn(j),jjm(j),jjp(j), increasing,extrap_method
          call endrun('Bad weight computed in LININTERP_init')
       end if
    end do
    if (icount.gt.0) then
       call endrun('LININTERP: Point found without interp indices')
    end if

  end subroutine lininterp_init


  subroutine lininterp1d (arrin, n1, arrout, m1, interp_wgts) 2
    !-----------------------------------------------------------------------
    !
    ! Purpose: Do a linear interpolation from input mesh to output
    !          mesh with weights as set in lininterp_init.
    !
    !
    ! Author: Jim Edwards
    !
    !-----------------------------------------------------------------------
    !-----------------------------------------------------------------------
    implicit none
    !-----------------------------------------------------------------------
    !
    ! Arguments
    !
    integer, intent(in) :: n1                 ! number of input latitudes
    integer, intent(in) :: m1                ! number of output latitudes
                                                                                                  
    real(r8), intent(in) :: arrin(n1)    ! input array of values to interpolate
    type(interp_type), intent(in) :: interp_wgts
    real(r8), intent(out) :: arrout(m1) ! interpolated array
                                                                                                  
    !
    ! Local workspace
    !
    integer j                ! latitude indices
    integer, pointer :: jjm(:)
    integer, pointer :: jjp(:)
                                                                                                  
    real(r8), pointer :: wgts(:)
    real(r8), pointer :: wgtn(:)
                                                                                                  
                                                                                                  
    jjm => interp_wgts%jjm
    jjp => interp_wgts%jjp
    wgts =>  interp_wgts%wgts
    wgtn =>  interp_wgts%wgtn
                                                                                                  
    !
    ! Do the interpolation
    !
    do j=1,m1
      arrout(j) = arrin(jjm(j))*wgts(j) + arrin(jjp(j))*wgtn(j)
    end do
                                                                                                  
    return
  end subroutine lininterp1d
                                                                                                  

  subroutine lininterp2d2d(arrin, n1, n2, arrout, m1, m2, wgt1, wgt2) 1
    implicit none
    !-----------------------------------------------------------------------
    !
    ! Arguments
    !
    integer, intent(in) :: n1, n2, m1, m2
    real(r8), intent(in) :: arrin(n1,n2)    ! input array of values to interpolate
    type(interp_type), intent(in) :: wgt1, wgt2
    real(r8), intent(out) :: arrout(m1,m2) ! interpolated array
    !
    ! locals
    !
    integer i,j                ! indices
    integer, pointer :: iim(:), jjm(:)
    integer, pointer :: iip(:), jjp(:)
                                                                                                  
    real(r8), pointer :: wgts1(:), wgts2(:)
    real(r8), pointer :: wgtn1(:), wgtn2(:)
                                                                                                  
    real(r8) :: arrtmp(n1,m2)
                                                                                                  
                                                                                                  
    jjm => wgt2%jjm
    jjp => wgt2%jjp
    wgts2 => wgt2%wgts
    wgtn2 => wgt2%wgtn
                                                                                                  
    iim => wgt1%jjm
    iip => wgt1%jjp
    wgts1 => wgt1%wgts
    wgtn1 => wgt1%wgtn

    do j=1,m2
      do i=1,n1
        arrtmp(i,j) = arrin(i,jjm(j))*wgts2(j) + arrin(i,jjp(j))*wgtn2(j)
      end do
    end do

    do j=1,m2
      do i=1,m1
        arrout(i,j) = arrtmp(iim(i),j)*wgts1(i) + arrtmp(iip(i),j)*wgtn1(i)
      end do
    end do
                                                                                                  
  end subroutine lininterp2d2d

  subroutine lininterp2d1d(arrin, n1, n2, arrout, m1, wgt1, wgt2, fldname) 1
    implicit none
    !-----------------------------------------------------------------------
    !
    ! Arguments
    !
    integer, intent(in) :: n1, n2, m1
    real(r8), intent(in) :: arrin(n1,n2)    ! input array of values to interpolate
    type(interp_type), intent(in) :: wgt1, wgt2
    real(r8), intent(out) :: arrout(m1) ! interpolated array
    character(len=*), intent(in), optional :: fldname(:)
    !
    ! locals
    !
    integer i                ! indices
    integer, pointer :: iim(:), jjm(:)
    integer, pointer :: iip(:), jjp(:)
                                                                                                  
    real(r8), pointer :: wgts(:), wgte(:)
    real(r8), pointer :: wgtn(:), wgtw(:)

    jjm => wgt2%jjm
    jjp => wgt2%jjp
    wgts => wgt2%wgts
    wgtn => wgt2%wgtn
                                                                                                  
    iim => wgt1%jjm
    iip => wgt1%jjp
    wgtw => wgt1%wgts
    wgte => wgt1%wgtn

    do i=1,m1
       arrout(i) = arrin(iim(i),jjm(i))*wgtw(i)*wgts(i)+arrin(iip(i),jjm(i))*wgte(i)*wgts(i) + &
                   arrin(iim(i),jjp(i))*wgtw(i)*wgtn(i)+arrin(iip(i),jjp(i))*wgte(i)*wgtn(i)
    end do

                                                                                                  
  end subroutine lininterp2d1d

  subroutine lininterp3d2d(arrin, n1, n2, n3, arrout, m1, len1, wgt1, wgt2) 1
    implicit none
    !-----------------------------------------------------------------------
    !
    ! Arguments
    !
    integer, intent(in) :: n1, n2, n3, m1, len1   ! m1 is to len1 as ncols is to pcols 
    real(r8), intent(in) :: arrin(n1,n2,n3)    ! input array of values to interpolate
    type(interp_type), intent(in) :: wgt1, wgt2
    real(r8), intent(out) :: arrout(len1, n3) ! interpolated array

    !
    ! locals
    !
    integer i, k               ! indices
    integer, pointer :: iim(:), jjm(:)
    integer, pointer :: iip(:), jjp(:)
                                                                                                  
    real(r8), pointer :: wgts(:), wgte(:)
    real(r8), pointer :: wgtn(:), wgtw(:)

    jjm => wgt2%jjm
    jjp => wgt2%jjp
    wgts => wgt2%wgts
    wgtn => wgt2%wgtn
                                                                                                  
    iim => wgt1%jjm
    iip => wgt1%jjp
    wgtw => wgt1%wgts
    wgte => wgt1%wgtn

    do k=1,n3
       do i=1,m1
          arrout(i,k) = arrin(iim(i),jjm(i),k)*wgtw(i)*wgts(i)+arrin(iip(i),jjm(i),k)*wgte(i)*wgts(i) + &
               arrin(iim(i),jjp(i),k)*wgtw(i)*wgtn(i)+arrin(iip(i),jjp(i),k)*wgte(i)*wgtn(i)
       end do
    end do
                                                                                                  
  end subroutine lininterp3d2d
                                                                                                  




  subroutine lininterp_finish(interp_wgts) 10
    type(interp_type) :: interp_wgts

    deallocate(interp_wgts%jjm, &
         interp_wgts%jjp, &
         interp_wgts%wgts, &
         interp_wgts%wgtn)

    nullify(interp_wgts%jjm, &
         interp_wgts%jjp, &
         interp_wgts%wgts, &
         interp_wgts%wgtn)
  end subroutine lininterp_finish


  subroutine lininterp_original (arrin, yin, nlev, nlatin, arrout, & 1,3
       yout, nlatout)
    !----------------------------------------------------------------------- 
    ! 
    ! Purpose: Do a linear interpolation from input mesh defined by yin to output
    !          mesh defined by yout.  Where extrapolation is necessary, values will
    !          be copied from the extreme edge of the input grid.  Vectorization is over
    !          the vertical (nlev) dimension.
    ! 
    ! Method: Check validity of input, then determine weights, then do the N-S interpolation.
    ! 
    ! Author: Jim Rosinski
    ! Modified: Jim Edwards so that there is no requirement of monotonacity on the yout array
    !
    !-----------------------------------------------------------------------
    implicit none
    !-----------------------------------------------------------------------
    !
    ! Arguments
    !
    integer, intent(in) :: nlev                   ! number of vertical levels
    integer, intent(in) :: nlatin                 ! number of input latitudes
    integer, intent(in) :: nlatout                ! number of output latitudes

    real(r8), intent(in) :: arrin(nlev,nlatin)    ! input array of values to interpolate
    real(r8), intent(in) :: yin(nlatin)           ! input mesh
    real(r8), intent(in) :: yout(nlatout)         ! output mesh

    real(r8), intent(out) :: arrout(nlev,nlatout) ! interpolated array
    !
    ! Local workspace
    !
    integer j, jj              ! latitude indices
    integer jjprev             ! latitude indices
    integer k                  ! level index
    integer icount             ! number of values

    real(r8) extrap            ! percent grid non-overlap
    !
    ! Dynamic
    !
    integer :: jjm(nlatout)
    integer :: jjp(nlatout)

    real(r8) :: wgts(nlatout)
    real(r8) :: wgtn(nlatout)
    !
    ! Check validity of input coordinate arrays: must be monotonically increasing,
    ! and have a total of at least 2 elements
    !
    if (nlatin.lt.2) then
       call endrun('LININTERP: Must have at least 2 input points for interpolation')
    end if

    icount = 0
    do j=1,nlatin-1
       if (yin(j).gt.yin(j+1)) icount = icount + 1
    end do


    if (icount.gt.0) then
       call endrun('LININTERP: Non-monotonic coordinate array(s) found')
    end if
    !
    ! Initialize index arrays for later checking
    !
    do j=1,nlatout
       jjm(j) = 0
       jjp(j) = 0
    end do
    !
    ! For values which extend beyond N and S boundaries, set weights
    ! such that values will just be copied.
    !
    extrap = 0.

    do j=1,nlatout
       if (yout(j).le.yin(1)) then
          jjm(j) = 1
          jjp(j) = 1
          wgts(j) = 1.
          wgtn(j) = 0.
          extrap=extrap+1.
       else if (yout(j).gt.yin(nlatin)) then
          jjm(j) = nlatin
          jjp(j) = nlatin
          wgts(j) = 1.
          wgtn(j) = 0.
          extrap=extrap+1.
       endif
    end do

    !
    ! Loop though output indices finding input indices and weights
    !
    do j=1,nlatout
       do jj=1,nlatin-1
          if (yout(j).gt.yin(jj) .and. yout(j).le.yin(jj+1)) then
             jjm(j) = jj
             jjp(j) = jj + 1
             wgts(j) = (yin(jj+1)-yout(j))/(yin(jj+1)-yin(jj))
             wgtn(j) = (yout(j)-yin(jj))/(yin(jj+1)-yin(jj))
             exit
          end if
       end do
    end do
    !
    ! Check that interp/extrap points have been found for all outputs
    !
    icount = 0
    do j=1,nlatout
       if (jjm(j).eq.0 .or. jjp(j).eq.0) then
          icount = icount + 1
       end if
    end do
    if (icount.gt.0) then
       call endrun('LININTERP: Point found without interp indices')
    end if
    !
    ! Do the interpolation
    !
    do j=1,nlatout
       do k=1,nlev
          arrout(k,j) = arrin(k,jjm(j))*wgts(j) + arrin(k,jjp(j))*wgtn(j)
       end do
    end do

    return
  end subroutine lininterp_original



  subroutine bilin (arrin, xin, yin, nlondin, nlonin, &,13
       nlevdin, nlev, nlatin, arrout, xout, &
       yout, nlondout, nlonout, nlevdout, nlatout)
    !----------------------------------------------------------------------- 
    ! 
    ! Purpose: 
    !
    ! Do a bilinear interpolation from input mesh defined by xin, yin to output
    ! mesh defined by xout, yout.  Circularity is assumed in the x-direction so
    ! input x-grid must be in degrees east and must start from Greenwich.  When
    ! extrapolation is necessary in the N-S direction, values will be copied 
    ! from the extreme edge of the input grid.  Vectorization is over the
    ! longitude dimension.  Input grid is assumed rectangular. Output grid
    ! is assumed ragged, i.e. xout is a 2-d mesh.
    ! 
    ! Method: Interpolate first in longitude, then in latitude.
    ! 
    ! Author: Jim Rosinski
    ! 
    !-----------------------------------------------------------------------
    use shr_kind_mod, only: r8 => shr_kind_r8
    use abortutils,   only: endrun
    !-----------------------------------------------------------------------
    implicit none
    !-----------------------------------------------------------------------
    !
    ! Input arguments
    !
    integer, intent(in) :: nlondin                        ! longitude dimension of input grid
    integer, intent(in) :: nlonin                         ! number of real longitudes (input)
    integer, intent(in) :: nlevdin                        ! vertical dimension of input grid
    integer, intent(in) :: nlev                           ! number of vertical levels
    integer, intent(in) :: nlatin                         ! number of input latitudes
    integer, intent(in) :: nlatout                        ! number of output latitudes
    integer, intent(in) :: nlondout                       ! longitude dimension of output grid
    integer, intent(in) :: nlonout(nlatout)               ! number of output longitudes per lat
    integer, intent(in) :: nlevdout                       ! vertical dimension of output grid

    real(r8), intent(in) :: arrin(nlondin,nlevdin,nlatin) ! input array of values to interpolate
    real(r8), intent(in) :: xin(nlondin)                  ! input x mesh
    real(r8), intent(in) :: yin(nlatin)                   ! input y mesh
    real(r8), intent(in) :: xout(nlondout,nlatout)        ! output x mesh
    real(r8), intent(in) :: yout(nlatout)                 ! output y mesh
    !
    ! Output arguments
    !
    real(r8), intent(out) :: arrout(nlondout,nlevdout,nlatout) ! interpolated array
    !
    ! Local workspace
    !
    integer :: i, ii, iw, ie, iiprev ! longitude indices
    integer :: j, jj, js, jn, jjprev ! latitude indices
    integer :: k                     ! level index
    integer :: icount                ! number of bad values

    real(r8) :: extrap               ! percent grid non-overlap
    real(r8) :: dxinwrap             ! delta-x on input grid for 2-pi
    real(r8) :: avgdxin              ! avg input delta-x
    real(r8) :: ratio                ! compare dxinwrap to avgdxin
    real(r8) :: sum                  ! sum of weights (used for testing)
    !
    ! Dynamic
    !
    integer :: iim(nlondout)         ! interpolation index to the left
    integer :: iip(nlondout)         ! interpolation index to the right
    integer :: jjm(nlatout)          ! interpolation index to the south
    integer :: jjp(nlatout)          ! interpolation index to the north

    real(r8) :: wgts(nlatout)        ! interpolation weight to the north
    real(r8) :: wgtn(nlatout)        ! interpolation weight to the north
    real(r8) :: wgte(nlondout)       ! interpolation weight to the north
    real(r8) :: wgtw(nlondout)       ! interpolation weight to the north
    real(r8) :: igrid(nlonin)        ! interpolation weight to the north
    !
    ! Check validity of input coordinate arrays: must be monotonically increasing,
    ! and have a total of at least 2 elements
    !
    if (nlonin < 2 .or. nlatin < 2) then
       call endrun ('BILIN: Must have at least 2 input points for interpolation')
    end if

    if (xin(1) < 0._r8 .or. xin(nlonin) > 360._r8) then
       call endrun ('BILIN: Input x-grid must be between 0 and 360')
    end if

    icount = 0
    do i=1,nlonin-1
       if (xin(i) >= xin(i+1)) icount = icount + 1
    end do

    do j=1,nlatin-1
       if (yin(j) >= yin(j+1)) icount = icount + 1
    end do

    do j=1,nlatout-1
       if (yout(j) >= yout(j+1)) icount = icount + 1
    end do

    do j=1,nlatout
       do i=1,nlonout(j)-1
          if (xout(i,j) >= xout(i+1,j)) icount = icount + 1
       end do
    end do

    if (icount > 0) then
       call endrun ('BILIN: Non-monotonic coordinate array(s) found')
    end if

    if (yout(nlatout) <= yin(1) .or. yout(1) >= yin(nlatin)) then
       call endrun ('BILIN: No overlap in y-coordinates')
    end if

    do j=1,nlatout
       if (xout(1,j) < 0._r8 .or. xout(nlonout(j),j) > 360._r8) then
          call endrun ('BILIN: Output x-grid must be between 0 and 360')
       end if

       if (xout(nlonout(j),j) <= xin(1) .or.  &
            xout(1,j)          >= xin(nlonin)) then
          call endrun ('BILIN: No overlap in x-coordinates')
       end if
    end do
    !
    ! Initialize index arrays for later checking
    !
    do j=1,nlatout
       jjm(j) = 0
       jjp(j) = 0
    end do
    !
    ! For values which extend beyond N and S boundaries, set weights
    ! such that values will just be copied.
    !
    do js=1,nlatout
       if (yout(js) > yin(1)) exit
       jjm(js) = 1
       jjp(js) = 1
       wgts(js) = 1._r8
       wgtn(js) = 0._r8
    end do

    do jn=nlatout,1,-1
       if (yout(jn) <= yin(nlatin)) exit
       jjm(jn) = nlatin
       jjp(jn) = nlatin
       wgts(jn) = 1._r8
       wgtn(jn) = 0._r8
    end do
    !
    ! Loop though output indices finding input indices and weights
    !
    jjprev = 1
    do j=js,jn
       do jj=jjprev,nlatin-1
          if (yout(j) > yin(jj) .and. yout(j) <= yin(jj+1)) then
             jjm(j) = jj
             jjp(j) = jj + 1
             wgts(j) = (yin(jj+1) - yout(j)) / (yin(jj+1) - yin(jj))
             wgtn(j) = (yout(j)   - yin(jj)) / (yin(jj+1) - yin(jj))
             goto 30
          end if
       end do
       call endrun ('BILIN: Failed to find interp values')
30     jjprev = jj
    end do

    dxinwrap = xin(1) + 360._r8 - xin(nlonin)
    !
    ! Check for sane dxinwrap values.  Allow to differ no more than 10% from avg
    !
    avgdxin = (xin(nlonin)-xin(1))/(nlonin-1._r8)
    ratio = dxinwrap/avgdxin
    if (ratio < 0.9_r8 .or. ratio > 1.1_r8) then
       write(iulog,*)'BILIN: Insane dxinwrap value =',dxinwrap,' avg=', avgdxin
       call endrun
    end if
    !
    ! Check grid overlap
    ! Do not do on spmd since distributed output grid may be expected to fail this test
#ifndef SPMD
    extrap = 100._r8*((js - 1._r8) + real(nlatout - jn,r8))/nlatout
    if (extrap > 20._r8) then
       write(iulog,*)'BILIN:',extrap,' % of N/S output grid will have to be extrapolated'
    end if
#endif
    !
    ! Check that interp/extrap points have been found for all outputs, and that
    ! they are reasonable (i.e. within 32-bit roundoff)
    !
    icount = 0
    do j=1,nlatout
       if (jjm(j) == 0 .or. jjp(j) == 0) icount = icount + 1
       sum = wgts(j) + wgtn(j)
       if (sum < 0.99999_r8 .or. sum > 1.00001_r8) icount = icount + 1
       if (wgts(j) < 0._r8 .or. wgts(j) > 1._r8) icount = icount + 1
       if (wgtn(j) < 0._r8 .or. wgtn(j) > 1._r8) icount = icount + 1
    end do

    if (icount > 0) then
       call endrun ('BILIN: something bad in latitude indices or weights')
    end if
    !
    ! Do the bilinear interpolation
    !
    do j=1,nlatout
       !
       ! Initialize index arrays for later checking
       !
       do i=1,nlondout
          iim(i) = 0
          iip(i) = 0
       end do
       !
       ! For values which extend beyond E and W boundaries, set weights such that
       ! values will be interpolated between E and W edges of input grid.
       !
       do iw=1,nlonout(j)
          if (xout(iw,j) > xin(1)) exit
          iim(iw) = nlonin
          iip(iw) = 1
          wgtw(iw) = (xin(1)        - xout(iw,j))   /dxinwrap
          wgte(iw) = (xout(iw,j)+360._r8 - xin(nlonin))/dxinwrap
       end do

       do ie=nlonout(j),1,-1
          if (xout(ie,j) <= xin(nlonin)) exit
          iim(ie) = nlonin
          iip(ie) = 1
          wgtw(ie) = (xin(1)+360._r8 - xout(ie,j))   /dxinwrap
          wgte(ie) = (xout(ie,j)    - xin(nlonin))/dxinwrap
       end do
       !
       ! Loop though output indices finding input indices and weights
       !
       iiprev = 1
       do i=iw,ie
          do ii=iiprev,nlonin-1
             if (xout(i,j) > xin(ii) .and. xout(i,j) <= xin(ii+1)) then
                iim(i) = ii
                iip(i) = ii + 1
                wgtw(i) = (xin(ii+1) - xout(i,j)) / (xin(ii+1) - xin(ii))
                wgte(i) = (xout(i,j) - xin(ii))   / (xin(ii+1) - xin(ii))
                goto 60
             end if
          end do
          call endrun ('BILIN: Failed to find interp values')
60        iiprev = ii
       end do

       icount = 0
       do i=1,nlonout(j)
          if (iim(i) == 0 .or. iip(i) == 0) icount = icount + 1
          sum = wgtw(i) + wgte(i)
          if (sum < 0.99999_r8 .or. sum > 1.00001_r8) icount = icount + 1
          if (wgtw(i) < 0._r8 .or. wgtw(i) > 1._r8) icount = icount + 1
          if (wgte(i) < 0._r8 .or. wgte(i) > 1._r8) icount = icount + 1
       end do

       if (icount > 0) then
          write(iulog,*)'BILIN: j=',j,' Something bad in longitude indices or weights'
          call endrun
       end if
       !
       ! Do the interpolation, 1st in longitude then latitude
       !
       do k=1,nlev
          do i=1,nlonin
             igrid(i) = arrin(i,k,jjm(j))*wgts(j) + arrin(i,k,jjp(j))*wgtn(j)
          end do

          do i=1,nlonout(j)
             arrout(i,k,j) = igrid(iim(i))*wgtw(i) + igrid(iip(i))*wgte(i)
          end do
       end do
    end do


    return
  end subroutine bilin


  subroutine vertinterp(ncol, ncold, nlev, pmid, pout, arrin, arrout) 28,1

    !----------------------------------------------------------------------- 
    ! 
    ! Purpose: 
    ! Vertically interpolate input array to output pressure level
    ! Copy values at boundaries.  
    ! 
    ! Method: 
    ! 
    ! Author: 
    ! 
    !-----------------------------------------------------------------------

    implicit none

    !------------------------------Arguments--------------------------------
    integer , intent(in)  :: ncol              ! column dimension
    integer , intent(in)  :: ncold             ! declared column dimension
    integer , intent(in)  :: nlev              ! vertical dimension
    real(r8), intent(in)  :: pmid(ncold,nlev)  ! input level pressure levels 
    real(r8), intent(in)  :: pout              ! output pressure level 
    real(r8), intent(in)  :: arrin(ncold,nlev) ! input  array
    real(r8), intent(out) :: arrout(ncold)     ! output array (interpolated)
    !--------------------------------------------------------------------------

    !---------------------------Local variables-----------------------------
    integer i,k               ! indices
    integer kupper(ncold)     ! Level indices for interpolation
    real(r8) dpu              ! upper level pressure difference
    real(r8) dpl              ! lower level pressure difference
    logical found(ncold)      ! true if input levels found
    logical error             ! error flag 
    !-----------------------------------------------------------------
    !
    ! Initialize index array and logical flags
    !
    do i=1,ncol
       found(i)  = .false.
       kupper(i) = 1
    end do
    error = .false.
    !     
    ! Store level indices for interpolation. 
    ! If all indices for this level have been found, 
    ! do the interpolation 
    !     
    do k=1,nlev-1
       do i=1,ncol
          if ((.not. found(i)) .and. pmid(i,k)<pout .and. pout<=pmid(i,k+1)) then
             found(i) = .true.
             kupper(i) = k
          end if
       end do
    end do
    !
    ! If we've fallen through the k=1,nlev-1 loop, we cannot interpolate and
    ! must extrapolate from the bottom or top data level for at least some
    ! of the longitude points.
    !
    do i=1,ncol
       if (pout <= pmid(i,1)) then
          arrout(i) = arrin(i,1)
       else if (pout >= pmid(i,nlev)) then
          arrout(i) = arrin(i,nlev)
       else if (found(i)) then
          dpu = pout - pmid(i,kupper(i))
          dpl = pmid(i,kupper(i)+1) - pout
          arrout(i) = (arrin(i,kupper(i)  )*dpl + arrin(i,kupper(i)+1)*dpu)/(dpl + dpu)
       else
          error = .true.
       end if
    end do
    !     
    ! Error check
    !
    if (error) then
       call endrun ('VERTINTERP: ERROR FLAG')
    end if

    return
  end subroutine vertinterp


  subroutine get_timeinterp_factors (cycflag, np1, cdayminus, cdayplus, cday, & 4,5
       fact1, fact2, str)
    !---------------------------------------------------------------------------
    !
    ! Purpose: Determine time interpolation factors (normally for a boundary dataset)
    !          for linear interpolation.
    !
    ! Method:  Assume 365 days per year.  Output variable fact1 will be the weight to
    !          apply to data at calendar time "cdayminus", and fact2 the weight to apply
    !          to data at time "cdayplus".  Combining these values will produce a result 
    !          valid at time "cday".  Output arguments fact1 and fact2 will be between 
    !          0 and 1, and fact1 + fact2 = 1 to roundoff.
    !
    ! Author:  Jim Rosinski
    !
    !--------------------------------------------------------------------------- 
    implicit none
    !
    ! Arguments
    !
    logical, intent(in) :: cycflag             ! flag indicates whether dataset is being cycled yearly

    integer, intent(in) :: np1                 ! index points to forward time slice matching cdayplus

    real(r8), intent(in) :: cdayminus          ! calendar day of rearward time slice
    real(r8), intent(in) :: cdayplus           ! calendar day of forward time slice
    real(r8), intent(in) :: cday               ! calenar day to be interpolated to
    real(r8), intent(out) :: fact1             ! time interpolation factor to apply to rearward time slice
    real(r8), intent(out) :: fact2             ! time interpolation factor to apply to forward time slice

    character(len=*), intent(in) :: str        ! string to be added to print in case of error (normally the callers name)
    !
    ! Local workspace
    !
    real(r8) :: deltat                         ! time difference (days) between cdayminus and cdayplus
    real(r8), parameter :: daysperyear = 365.  ! number of days in a year
    !
    ! Initial sanity checks
    !
    if (np1 == 1 .and. .not. cycflag) then
       call endrun ('GETFACTORS:'//str//' cycflag false and forward month index = Jan. not allowed')
    end if

    if (np1 < 1) then
       call endrun ('GETFACTORS:'//str//' input arg np1 must be > 0')
    end if

    if (cycflag) then
       if ((cday < 1.) .or. (cday > (daysperyear+1.))) then
          write(iulog,*) 'GETFACTORS:', str, ' bad cday=',cday
          call endrun ()
       end if
    else
       if (cday < 1.) then
          write(iulog,*) 'GETFACTORS:', str, ' bad cday=',cday
          call endrun ()
       end if
    end if
    !
    ! Determine time interpolation factors.  Account for December-January 
    ! interpolation if dataset is being cycled yearly.
    !
    if (cycflag .and. np1 == 1) then                     ! Dec-Jan interpolation
       deltat = cdayplus + daysperyear - cdayminus
       if (cday > cdayplus) then                         ! We are in December
          fact1 = (cdayplus + daysperyear - cday)/deltat
          fact2 = (cday - cdayminus)/deltat
       else                                              ! We are in January
          fact1 = (cdayplus - cday)/deltat
          fact2 = (cday + daysperyear - cdayminus)/deltat
       end if
    else
       deltat = cdayplus - cdayminus
       fact1 = (cdayplus - cday)/deltat
       fact2 = (cday - cdayminus)/deltat
    end if

    if (.not. valid_timeinterp_factors (fact1, fact2)) then
       write(iulog,*) 'GETFACTORS: ', str, ' bad fact1 and/or fact2=', fact1, fact2
       call endrun ()
    end if

    return
  end subroutine get_timeinterp_factors


  logical function valid_timeinterp_factors (fact1, fact2)
    !---------------------------------------------------------------------------
    !
    ! Purpose: check sanity of time interpolation factors to within 32-bit roundoff
    !
    !---------------------------------------------------------------------------
    implicit none

    real(r8), intent(in) :: fact1, fact2           ! time interpolation factors

    valid_timeinterp_factors = .true.

    ! The fact1 .ne. fact1 and fact2 .ne. fact2 comparisons are to detect NaNs.
    if (abs(fact1+fact2-1.) > 1.e-6 .or. &
         fact1 > 1.000001 .or. fact1 < -1.e-6 .or. &
         fact2 > 1.000001 .or. fact2 < -1.e-6 .or. &
         fact1 .ne. fact1 .or. fact2 .ne. fact2) then

       valid_timeinterp_factors = .false.
    end if

    return
  end function valid_timeinterp_factors

end module interpolate_data