!--------------------------------------- LICENCE BEGIN -----------------------------------
!Environment Canada - Atmospheric Science and Technology License/Disclaimer,
!                     version 3; Last Modified: May 7, 2008.
!This is free but copyrighted software; you can use/redistribute/modify it under the terms
!of the Environment Canada - Atmospheric Science and Technology License/Disclaimer
!version 3 or (at your option) any later version that should be found at:
!http://collaboration.cmc.ec.gc.ca/science/rpn.comm/license.html
!
!This software 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 above mentioned License/Disclaimer for more details.
!You should have received a copy of the License/Disclaimer along with this software;
!if not, you can write to: EC-RPN COMM Group, 2121 TransCanada, suite 500, Dorval (Quebec),
!CANADA, H9P 1J3; or send e-mail to service.rpn@ec.gc.ca
!-------------------------------------- LICENCE END --------------------------------------

!--------------------------------------------------------------------------
! MODULE gridStateVector (The grid-point state vector and related information.  prefix="gsv")
!
! Purpose: 
!
! Type definitions:
!    struct_gsv
!
! Subroutines:
!    (see "public" statements for publicly accessible subroutines and functions)
!
! Dependencies:
!    varNameList_mod
!    verticalCoord_mod
!    MathPhysConstants_mod
!    getStampList
!
!--------------------------------------------------------------------------

MODULE gridStateVector_mod 18,5
  use mpivar_mod
  use varNameList_mod
  use verticalCoord_mod
  use horizontalCoord_mod
  use MathPhysConstants_mod
  implicit none
  save
  private

  ! public structure definition
  public :: struct_gsv

  ! public subroutines and functions
  public :: gsv_setup, gsv_allocate, gsv_deallocate, gsv_zero, gsv_3dto4d, gsv_3dto4dAdj, gsv_varExist, gsv_getOffsetFromVarName
  public :: gsv_getField, gsv_getField3D, gsv_getDateStamp, gsv_getNumLev, gsv_add
  public :: gsv_getVco, gsv_setVco, gsv_getHco, gsv_setHco
  public :: gsv_commMPIGlobal, gsv_commMPIGlobal3D
  public :: gsv_commLatLon, gsv_commLatLonAd

  ! public entities accessed through inheritance
  public :: struct_vco, vco_SetupFromFile
  public :: vnl_varnameFromVarnum, vnl_vartypeFromVarnum, vnl_vartypeFromVarname
  public :: vnl_numvarmax2d, vnl_numvarmax3d,vnl_numvarmax
  public :: vnl_varNameList2d, vnl_varNameList3d, vnl_varNameList
  public :: vgd_get,vgd_levels,vgd_ok,vgd_dpidpis,vgd_write


  type struct_gsv
    real*8,pointer      :: gd(:,:,:,:)
    real*8,pointer      :: gd3d(:,:,:)
    integer             :: ni,nj,nk,numStep,anltime
    integer             :: latPerPE,myLatBeg,myLatEnd
    integer             :: lonPerPE,myLonBeg,myLonEnd
    integer, pointer    :: allLatBeg(:), allLatEnd(:)
    integer, pointer    :: allLonBeg(:), allLonEnd(:)
    integer, pointer    :: dateStampList(:)
    integer, pointer    :: dateStamp3d
    logical             :: allocated=.false.
    type(struct_vco),pointer   :: vco => null()
    type(struct_hco),pointer   :: hco => null()
    integer,pointer     :: varOffset(:),varNumLev(:)
    logical             :: mpi_local=.false.
  end type struct_gsv  

  logical :: NGEXIST(VNL_NUMVARMAX)
  character(len=8) :: ANLTIME_BIN

  CONTAINS


    function gsv_getOffsetFromVarName(statevector,varName) result(offset) 6,1
      implicit none
      type(struct_gsv)             :: statevector
      character(len=*), intent(in) :: varName
      integer                      :: offset

      offset=statevector%varOffset(vnl_varListIndex(varName))

    end function gsv_getOffsetFromVarName


    function gsv_varExist(varName) result(varExist) 35,1
      implicit none
      character(len=*), intent(in) :: varName
      logical                      :: varExist 

      if(ngexist(vnl_varListIndex(varName))) then
        varExist = .true.
      else
        varExist = .false.
      endif

    end function gsv_varExist



  function gsv_getNumLev(statevector,varType) result(nlev) 17,1
    implicit none
    type(struct_gsv), intent(in)  :: statevector
    character(len=*), intent(in)  :: varType
    integer                       :: nlev

    nlev = vco_getNumLev(statevector%vco,varType)

  end function gsv_getNumLev



  SUBROUTINE gsv_setup 2,6
      implicit none
      INTEGER JVAR, IPOS
      integer :: fnom,fclos,nulnam,ierr
      CHARACTER(len=4) :: CGNEED(VNL_NUMVARMAX)
      NAMELIST /NAMSTATE/CGNEED,ANLTIME_BIN

      if(mpi_myid.eq.0) write(*,*) 'gsv_setup: List of known (valid) variable names'
      if(mpi_myid.eq.0) write(*,*) 'gsv_setup: varNameList3D=',vnl_varNameList3D
      if(mpi_myid.eq.0) write(*,*) 'gsv_setup: varNameList2D=',vnl_varNameList2D
      if(mpi_myid.eq.0) write(*,*) 'gsv_setup: varNameList  =',vnl_varNameList

!     Read NAMELIST NAMSTATE to find which fields are needed

      cgneed(1:vnl_numvarmax) = '    '
      ANLTIME_BIN = 'MIDDLE'

      nulnam=0
      ierr=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
      read(nulnam,nml=namstate,iostat=ierr)
      if(ierr.ne.0) call abort3d('gsv_setup: Error reading namelist')
      if(mpi_myid.eq.0) write(*,nml=namstate)
      ierr=fclos(nulnam)

      if(varneed('GZ')) call abort3d('gsv_setup: GZ can no longer be included as a variable in gridStateVector!')

      do jvar = 1, vnl_numvarmax3D
        if (varneed(vnl_varNameList3D(jvar))) then
          ngexist(jvar) = .true.
        else
          ngexist(jvar) = .false.
        endif
      enddo

      do jvar = 1, vnl_numvarmax2D
        if (varneed(vnl_varNameList2D(jvar))) then
          ngexist(jvar+vnl_numvarmax3D) = .true.
        else
          ngexist(jvar+vnl_numvarmax3D) = .false.
        endif
      enddo

      if(mpi_myid.eq.0) write(*,*) 'gsv_setup: ngexist =',ngexist

!
!     Check value for ANLTIME_BIN
!
      if (ANLTIME_BIN .ne. 'MIDDLE' .and. ANLTIME_BIN .ne. 'FIRST' .and.  ANLTIME_BIN .ne. 'LAST') then
         call abort3d('gsv_setup: Problem setting ANLTIME_BIN. Verify NAMSTATE namelist. Aborting!')
      endif

      RETURN

      CONTAINS


        LOGICAL FUNCTION VARNEED(varName) 6
          character(len=*) :: varName
          integer :: jvar
 
          varneed=.false.
          do jvar=1,VNL_NUMVARMAX
            if (trim(varName).eq.trim(cgneed(jvar))) then
              varneed=.true.
            endif
          enddo

        END FUNCTION VARNEED

  END SUBROUTINE gsv_setup



  SUBROUTINE gsv_allocate(statevector,numStep,dateStamp,mpi_local) 11,8
    implicit none

    type(struct_gsv)    :: statevector
    integer, intent(in) :: numStep
    integer, optional   :: dateStamp
    integer             :: ierr,iloc,jvar,jvar2,jstep,lon1,lat1
    logical, optional   :: mpi_local

    if(.not.statevector%vco%initialized) then
       call abort3d('statevector_allocate: VerticalCoord has not been initialized!')
    endif

    if (statevector%allocated)then
      if(mpi_myid.eq.0) write(*,*) 'gridStateVector already allocated! Deallocating first.'
      call gsv_deallocate(statevector)
      statevector%allocated=.false.
    end if

    statevector%ni=statevector%hco%ni
    statevector%nj=statevector%hco%nj
    statevector%numStep=numStep
    if(present(mpi_local)) then
      statevector%mpi_local=mpi_local
    else
      statevector%mpi_local=.false.
    endif

    if(statevector%mpi_local) then
      call mpivar_setup_latbands(statevector%hco%nj,statevector%latPerPE,statevector%myLatBeg, &
                                 statevector%myLatEnd)
      call mpivar_setup_lonbands(statevector%hco%ni,statevector%lonPerPE,statevector%myLonBeg, &
                                 statevector%myLonEnd)
    else
      statevector%mpi_local=.false.
      statevector%latPerPE=statevector%hco%nj
      statevector%myLatBeg=1
      statevector%myLatEnd=statevector%hco%nj
      statevector%lonPerPE=statevector%hco%ni
      statevector%myLonBeg=1
      statevector%myLonEnd=statevector%hco%ni
    endif
    
    allocate(statevector%allLonBeg(mpi_npex))
    CALL rpn_comm_allgather(statevector%myLonBeg,1,"mpi_integer",       &
                            statevector%allLonBeg,1,"mpi_integer","EW",ierr)
    allocate(statevector%allLonEnd(mpi_npex))
    CALL rpn_comm_allgather(statevector%myLonEnd,1,"mpi_integer",       &
                            statevector%allLonEnd,1,"mpi_integer","EW",ierr)

    allocate(statevector%allLatBeg(mpi_npey))
    CALL rpn_comm_allgather(statevector%myLatBeg,1,"mpi_integer",       &
                            statevector%allLatBeg,1,"mpi_integer","NS",ierr)
    allocate(statevector%allLatEnd(mpi_npey))
    CALL rpn_comm_allgather(statevector%myLatEnd,1,"mpi_integer",       &
                            statevector%allLatEnd,1,"mpi_integer","NS",ierr)

    select case (ANLTIME_BIN)
    case ("FIRST")
       statevector%anltime=1
    case ("MIDDLE")
       statevector%anltime=nint((real(numStep,8)+1.0d0)/2.0d0)
    case ("LAST")
       statevector%anltime=numStep
    end select          

    if(present(dateStamp)) then
      allocate(statevector%dateStampList(numStep))
      call getstamplist(statevector%dateStampList,numStep,dateStamp)
      !do jstep = 1,numStep
      !  write(*,*) 'gsv_allocate: jstep,dateStampList=',jstep,statevector%dateStampList(jstep)
      !enddo
      statevector%dateStamp3d => statevector%dateStampList(statevector%anltime)
      !write(*,*) 'gsv_allocate: dateStamp3d=',statevector%dateStamp3d
    else
      nullify(statevector%dateStamplist)
      !write(*,*) 'gsv_allocate: no date stamp supplied, dateStampList not available!'
    endif

    allocate(statevector%varOffset(vnl_numvarmax))
    statevector%varOffset(:)=0
    allocate(statevector%varNumLev(vnl_numvarmax))
    statevector%varNumLev(:)=0

    iloc=0
    do jvar = 1, vnl_numvarmax3d
        if(ngexist(jvar)) then
          statevector%varOffset(jvar)=iloc
          statevector%varNumLev(jvar)=gsv_getNumLev(statevector,vnl_vartypeFromVarname(vnl_varNameList(jvar)))
          iloc = iloc + statevector%varNumLev(jvar)
        endif
    enddo
    do jvar2 = 1, vnl_numvarmax2d
        jvar=jvar2+vnl_numvarmax3d
        if(ngexist(jvar)) then
          statevector%varOffset(jvar)=iloc
          statevector%varNumLev(jvar)=1
          iloc = iloc + 1
        endif
    enddo
    statevector%nk=iloc

    if(statevector%mpi_local) then
      allocate(statevector%gd(statevector%myLonBeg:(statevector%myLonEnd+1),statevector%nk,  &
                              statevector%myLatBeg:(statevector%myLatEnd+1),numStep),stat=ierr)
    else
      allocate(statevector%gd(statevector%hco%ni,statevector%nk,statevector%hco%nj,numStep),stat=ierr)
    endif
    if(ierr.ne.0) then
      write(*,*) 'gridStateVector: Problem allocating memory! id=1',ierr
      call abort3d('aborting in gsv_allocate')
    endif

    lon1=statevector%myLonBeg
    lat1=statevector%myLatBeg
    statevector%gd3d(lon1:,1:,lat1:) => statevector%gd(:,:,:,statevector%anltime)

    statevector%allocated=.true.

  END SUBROUTINE GSV_allocate



  SUBROUTINE GSV_zero(statevector) 5,1
    implicit none
    type(struct_gsv) :: statevector
    integer          :: jstep,jlon,jlev,jlat,lat1,lat2,lon1,lon2

    if(.not.statevector%allocated) then
      call abort3d('gridStateVector not yet allocated! Aborting.')
    endif

    lon1=statevector%myLonBeg
    lon2=min(statevector%myLonEnd+1,statevector%hco%ni)
    lat1=statevector%myLatBeg
    lat2=min(statevector%myLatEnd+1,statevector%hco%nj)

!$OMP PARALLEL
!$OMP DO PRIVATE (jstep,jlat,jlev,jlon)    
    do jlat = lat1, lat2
      do jstep = 1, statevector%numStep
        do jlev = 1, statevector%nk
          do jlon = lon1, lon2
            statevector%gd(jlon,jlev,jlat,jstep)= 0.0d0
          enddo
        enddo
      enddo
    enddo
!$OMP END DO
!$OMP END PARALLEL

  END SUBROUTINE GSV_zero

  

  SUBROUTINE GSV_commLatLon(statevector_in) 1
    implicit none
    type(struct_gsv) :: statevector_in
    integer :: nsize, ierr, status, latPerPEhalo, myLatEndP1

    ! ******First send latitude halos

    nsize=statevector_in%lonPerPE*statevector_in%nk*statevector_in%numStep

    ! southern most latitude band
    if(mpi_myidy.eq.(mpi_npey-1)) then
      call rpn_comm_send(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                                           statevector_in%myLatBeg:statevector_in%myLatBeg,:),nsize, &
                         "mpi_double_precision",mpi_myidy-1,mpi_myidy*500+(mpi_myidy-1),  &
                         "NS",ierr)
    endif

    ! all latitude bands not at the north or south poles
    if(mpi_myidy.ne.0.and.mpi_myidy.ne.(mpi_npey-1)) then
      call rpn_comm_sendrecv(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                                               statevector_in%myLatBeg:statevector_in%myLatBeg,:), &
                             nsize,"mpi_double_precision",mpi_myidy-1,mpi_myidy*500+(mpi_myidy-1), &
                             statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:, &
                                               (statevector_in%myLatEnd+1):(statevector_in%myLatEnd+1),:), &
                             nsize,"mpi_double_precision",mpi_myidy+1,(mpi_myidy+1)*500+mpi_myidy, &
                             "NS",status,ierr)
    endif

    ! northern most latitude band
    if(mpi_myidy.eq.0) then
      call rpn_comm_recv(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:, &
                                           (statevector_in%myLatEnd+1):(statevector_in%myLatEnd+1),:),nsize, &
                        "mpi_double_precision",mpi_myidy+1,(mpi_myidy+1)*500+mpi_myidy,  &
                        "NS",status,ierr)
    endif

    ! ******Now send longitude halos

    if(mpi_myidy.eq.(mpi_npey-1)) then
      ! southern most latitude band does not have a latitude halo to the south
      latPerPEhalo = statevector_in%latPerPE
      myLatEndP1 = statevector_in%myLatEnd
    else
      ! all others do
      latPerPEhalo = statevector_in%latPerPE + 1
      myLatEndP1 = statevector_in%myLatEnd + 1
    endif

    if(mpi_npex.gt.1) then  ! only do exchange when more than one mpi task in X direction

      nsize=latPerPEhalo*statevector_in%nk*statevector_in%numStep

      ! eastern most longitude band
      if(mpi_myidx.eq.(mpi_npex-1)) then
        call rpn_comm_send(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonBeg,:,  &
                                             statevector_in%myLatBeg:myLatEndP1,:),nsize, &
          "mpi_double_precision",mpi_myidx-1,mpi_myidx*500+(mpi_myidx-1),"EW",ierr)
      endif

      ! all other longitude bands (not first nor last)
      if(mpi_myidx.ne.0.and.mpi_myidx.ne.(mpi_npex-1)) then
        call rpn_comm_sendrecv(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonBeg,:,  &
                                                 statevector_in%myLatBeg:myLatEndP1,:), &
                               nsize,"mpi_double_precision",mpi_myidx-1,mpi_myidx*500+(mpi_myidx-1), &
                               statevector_in%gd((statevector_in%myLonEnd+1):(statevector_in%myLonEnd+1),:, &
                                                 statevector_in%myLatBeg:myLatEndP1,:), &
                               nsize,"mpi_double_precision",mpi_myidx+1,(mpi_myidx+1)*500+mpi_myidx, &
                               "EW",status,ierr)
      endif

      ! western most longitude band
      if(mpi_myidx.eq.0) then
        call rpn_comm_recv(statevector_in%gd((statevector_in%myLonEnd+1):(statevector_in%myLonEnd+1),:, &
                                             statevector_in%myLatBeg:myLatEndP1,:),nsize, &
          "mpi_double_precision",mpi_myidx+1,(mpi_myidx+1)*500+mpi_myidx,"EW",status,ierr)
      endif

      ! periodic, so also send the first meridian on myidx=0 to the last meridian on myidx=(npex-1)
      if(mpi_myidx.eq.0) then
        call rpn_comm_send(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonBeg,:,  &
                                             statevector_in%myLatBeg:myLatEndP1,:),nsize, &
          "mpi_double_precision",mpi_npex-1,mpi_myidx*500+(mpi_npex-1),"EW",ierr)
      endif
      if(mpi_myidx.eq.(mpi_npex-1)) then
        call rpn_comm_recv(statevector_in%gd((statevector_in%myLonEnd+1):(statevector_in%myLonEnd+1),:, &
                                             statevector_in%myLatBeg:myLatEndP1,:),nsize, &
          "mpi_double_precision",0,0*500+mpi_myidx,"EW",status,ierr)
      endif

    else ! only one mpi task in X direction, so just copy first meridian to last (plus 1)
      
      statevector_in%gd(statevector_in%myLonEnd+1,:,statevector_in%myLatBeg:myLatEndP1,:) = &
        statevector_in%gd(1                      ,:,statevector_in%myLatBeg:myLatEndP1,:)

    endif

  END SUBROUTINE GSV_commLatLon



  SUBROUTINE GSV_commLatLonAd(statevector_in) 1
    implicit none
    type(struct_gsv) :: statevector_in
    integer :: nsize, ierr, status, latPerPEhalo, myLatEndP1
    real*8, allocatable :: latHalo(:,:,:,:)
    real*8, allocatable :: lonHalo(:,:,:,:)

    ! ******Adjoint of sending longitude halos

    if(mpi_myidy.eq.(mpi_npey-1)) then
      ! southern most latitude band does not have a latitude halo to the south
      latPerPEhalo = statevector_in%latPerPE
      myLatEndP1 = statevector_in%myLatEnd
    else
      ! all others do
      latPerPEhalo = statevector_in%latPerPE + 1
      myLatEndP1 = statevector_in%myLatEnd + 1
    endif

    if(mpi_npex.gt.1) then  ! only do adjoint of exchange when more than one mpi task in X direction

      allocate(lonHalo(1, statevector_in%nk, latPerPEhalo, statevector_in%numStep))

      nsize=latPerPEhalo*statevector_in%nk*statevector_in%numStep

      ! periodic, so also do adjoint of sending the first meridian on myidx=0 to the last meridian on myidx=(npex-1)
      if(mpi_myidx.eq.(mpi_npex-1)) then
        call rpn_comm_send(statevector_in%gd((statevector_in%myLonEnd+1):(statevector_in%myLonEnd+1),:, &
                                             statevector_in%myLatBeg:myLatEndP1,:),nsize, &
                           "mpi_double_precision",0,0*500+mpi_myidx,  &
                           "EW",ierr)
      endif
      if(mpi_myidx.eq.0) then
        call rpn_comm_recv(lonHalo,nsize, &
                           "mpi_double_precision",mpi_npex-1,mpi_myidx*500+(mpi_npex-1),"EW",status,ierr)
      endif

      ! western most longitude band
      if(mpi_myidx.eq.0) then
        call rpn_comm_send(statevector_in%gd((statevector_in%myLonEnd+1):(statevector_in%myLonEnd+1),:, &
                                             statevector_in%myLatBeg:myLatEndP1,:),nsize, &
                           "mpi_double_precision",mpi_myidx+1,(mpi_myidx+1)*500+mpi_myidx,  &
                           "EW",ierr)
      endif

      ! all other longitude bands (not first nor last)
      if(mpi_myidx.ne.0.and.mpi_myidx.ne.(mpi_npex-1)) then
        call rpn_comm_sendrecv(statevector_in%gd((statevector_in%myLonEnd+1):(statevector_in%myLonEnd+1),:, &
                                                 statevector_in%myLatBeg:myLatEndP1,:), &
                               nsize,"mpi_double_precision",mpi_myidx+1,(mpi_myidx+1)*500+mpi_myidx, &
                               lonHalo, &
                               nsize,"mpi_double_precision",mpi_myidx-1,mpi_myidx*500+(mpi_myidx-1), &
                               "EW",status,ierr)
      endif

      ! eastern most longitude band
      if(mpi_myidx.eq.(mpi_npex-1)) then
        call rpn_comm_recv(lonHalo,nsize, &
                           "mpi_double_precision",mpi_myidx-1,mpi_myidx*500+(mpi_myidx-1),"EW",status,ierr)
      endif

      ! add the sensitivity from the halo to the in situ sensitivity
      statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonBeg,:,  &
                        statevector_in%myLatBeg:myLatEndP1,:) = &
        statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonBeg,:,  &
                          statevector_in%myLatBeg:myLatEndP1,:) + lonHalo(:,:,:,:)

      ! to make sure sensitivity from the halo is not double counted, set to zero
      statevector_in%gd(statevector_in%myLonEnd+1,:,statevector_in%myLatBeg:myLatEndP1,:) = 0.0d0

      deallocate(lonHalo)

    else ! only one mpi task in X direction, so just adjoint of copying first meridian to last (plus 1)
      
      statevector_in%gd(1                          ,:,statevector_in%myLatBeg:myLatEndP1,:) =  &
        statevector_in%gd(1                        ,:,statevector_in%myLatBeg:myLatEndP1,:) +  &
        statevector_in%gd(statevector_in%myLonEnd+1,:,statevector_in%myLatBeg:myLatEndP1,:)
        
      ! to make sure sensitivity from the halo is not double counted, set to zero
      statevector_in%gd(statevector_in%myLonEnd+1,:,statevector_in%myLatBeg:myLatEndP1,:) = 0.0d0

    endif

    ! ******Adjoint of sending latitude halos

    allocate(latHalo(statevector_in%lonPerPE, statevector_in%nk, 1, statevector_in%numStep))

    nsize=statevector_in%lonPerPE*statevector_in%nk*statevector_in%numStep

    ! northern most latitude band
    if(mpi_myidy.eq.0) then
      call rpn_comm_send(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                                           (statevector_in%myLatEnd+1):(statevector_in%myLatEnd+1),:), &
                         nsize,"mpi_double_precision",mpi_myidy+1,(mpi_myidy+1)*500+mpi_myidy,"NS",ierr)
    endif

    ! all latitude bands not at the north or south poles
    if(mpi_myidy.ne.0.and.mpi_myidy.ne.(mpi_npey-1)) then
      call rpn_comm_sendrecv(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                                               (statevector_in%myLatEnd+1):(statevector_in%myLatEnd+1),:), &
                             nsize,"mpi_double_precision",mpi_myidy+1,(mpi_myidy+1)*500+mpi_myidy, &
                             latHalo, &
                             nsize,"mpi_double_precision",mpi_myidy-1,mpi_myidy*500+(mpi_myidy-1), &
                             "NS",status,ierr)
    endif

    ! southern most latitude band
    if(mpi_myidy.eq.(mpi_npey-1)) then
      call rpn_comm_recv(latHalo,nsize,  &
                         "mpi_double_precision",mpi_myidy-1,mpi_myidy*500+(mpi_myidy-1), &
                         "NS",status,ierr)
    endif

    ! add the sensitivity from the halo to the in situ sensitivity
    if(mpi_myidy.ne.0) then
      statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                        statevector_in%myLatBeg:statevector_in%myLatBeg,:) = &
        statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                          statevector_in%myLatBeg:statevector_in%myLatBeg,:) + latHalo(:,:,:,:)
    endif

    ! to make sure sensitivity from the halo is not double counted, set to zero
    if(mpi_myidy.ne.(mpi_npey-1)) then
      statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,statevector_in%myLatEnd+1,:)=0.0d0
    endif

    deallocate(latHalo)

  END SUBROUTINE GSV_commLatLonAd



  SUBROUTINE GSV_commMPIGlobal(statevector_in) 3,1
    implicit none
    type(struct_gsv) :: statevector_in
    integer :: nsize,ierr,jlat,jstep,jlev,jlon
    integer :: IP, IP_x, IP_y
    real*8, pointer :: gd_mpiglobal(:,:,:,:) => null()
    real*8, allocatable :: gd_temp(:,:,:,:)

    if(.not.statevector_in%mpi_local) then
      call abort3d('gsv_commMPIGlobal: statevector already global!')
    endif

    ! only allocate a single 3D mpiglobal statevector as an array of latlon tiles
    allocate(gd_temp(statevector_in%lonPerPE,statevector_in%nk,statevector_in%latPerPE,mpi_nprocs))

    nsize=statevector_in%lonPerPE*statevector_in%nk*statevector_in%latPerPE

    ! NOTE: result of mpiglobal 3D statevector for timestep jstep is sent to proc jstep-1
    do jstep=1,statevector_in%numStep

      call rpn_comm_gather(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                                             statevector_in%myLatBeg:statevector_in%myLatEnd,jstep), &
                           nsize,"mpi_double_precision", &
                           gd_temp, &
                           nsize,"mpi_double_precision", &
                           jstep-1,"GRID",ierr)

    enddo
    deallocate(statevector_in%gd)

    if(mpi_myid.lt.statevector_in%numStep) then
      ! reorganize data into mpiglobal array
      allocate(gd_mpiglobal(statevector_in%hco%ni,statevector_in%nk,statevector_in%hco%nj,1))

      do IP_y = 0, (mpi_npey-1)
        do IP_x = 0, (mpi_npex-1)
          IP = IP_x + IP_y*mpi_npex
          gd_mpiglobal(statevector_in%allLonBeg(IP_x+1):statevector_in%allLonEnd(IP_x+1),:,  &
                       statevector_in%allLatBeg(IP_y+1):statevector_in%allLatEnd(IP_y+1),1) = &
              gd_temp(:,:,:,IP+1)        
        enddo
      enddo
    else
      allocate(gd_mpiglobal(1,1,1,1)) ! dummy allocation to avoid error when deallocating
    endif

    deallocate(gd_temp)

    ! reset values in statevector object for mpiglobal
    statevector_in%gd => gd_mpiglobal
    statevector_in%gd3d => statevector_in%gd(:,:,:,1)
    statevector_in%myLonBeg=1
    statevector_in%myLonEnd=statevector_in%hco%ni
    statevector_in%lonPerPE=statevector_in%hco%ni
    statevector_in%myLatBeg=1
    statevector_in%myLatEnd=statevector_in%hco%nj
    statevector_in%latPerPE=statevector_in%hco%nj
    statevector_in%numStep=1
    statevector_in%mpi_local=.false.

  END SUBROUTINE GSV_commMPIGlobal



  SUBROUTINE GSV_commMPIGlobal3D(statevector_in) 3,1
    implicit none
    type(struct_gsv) :: statevector_in
    integer :: nsize,ierr,jlat,jstep,jlev,jlon
    integer :: IP, IP_x, IP_y
    real*8, pointer :: gd_mpiglobal(:,:,:,:) => null()
    real*8, allocatable :: gd_temp(:,:,:,:)

    if(.not.statevector_in%mpi_local) then
      call abort3d('gsv_commMPIGlobal3D: statevector already global!')
    endif

    ! only allocate a single 3D mpiglobal statevector as an array of latlon tiles
    allocate(gd_temp(statevector_in%lonPerPE,statevector_in%nk,statevector_in%latPerPE,mpi_nprocs))

    nsize=statevector_in%lonPerPE*statevector_in%nk*statevector_in%latPerPE

    ! NOTE: result of mpiglobal 3D statevector for timestep anltime is sent to proc 0
    jstep=statevector_in%anltime

    call rpn_comm_gather(statevector_in%gd(statevector_in%myLonBeg:statevector_in%myLonEnd,:,  &
                                           statevector_in%myLatBeg:statevector_in%myLatEnd,jstep), &
                         nsize,"mpi_double_precision", &
                         gd_temp, &
                         nsize,"mpi_double_precision", &
                         0,"GRID",ierr)

    deallocate(statevector_in%gd)

    if(mpi_myid.eq.0) then
      ! reorganize data into mpiglobal array
      allocate(gd_mpiglobal(statevector_in%hco%ni,statevector_in%nk,statevector_in%hco%nj,1))

      do IP_y = 0, (mpi_npey-1)
        do IP_x = 0, (mpi_npex-1)
          IP = IP_x + IP_y*mpi_npex
          gd_mpiglobal(statevector_in%allLonBeg(IP_x+1):statevector_in%allLonEnd(IP_x+1),:,  &
                       statevector_in%allLatBeg(IP_y+1):statevector_in%allLatEnd(IP_y+1),1) = &
              gd_temp(:,:,:,IP+1)        
        enddo
      enddo
    else
      allocate(gd_mpiglobal(1,1,1,1)) ! dummy allocation to avoid error when deallocating
    endif

    deallocate(gd_temp)

    ! reset values in statevector object for mpiglobal
    statevector_in%gd => gd_mpiglobal
    statevector_in%gd3d => statevector_in%gd(:,:,:,1)
    statevector_in%myLonBeg=1
    statevector_in%myLonEnd=statevector_in%hco%ni
    statevector_in%lonPerPE=statevector_in%hco%ni
    statevector_in%myLatBeg=1
    statevector_in%myLatEnd=statevector_in%hco%nj
    statevector_in%latPerPE=statevector_in%hco%nj
    statevector_in%numStep=1
    statevector_in%mpi_local=.false.

  END SUBROUTINE GSV_commMPIGlobal3D



  SUBROUTINE GSV_add(statevector_in,statevector_inout,scaleFactor) 3,2
    implicit none
    type(struct_gsv) :: statevector_in,statevector_inout
    integer          :: jstep,jlon,jlev,jlat,lon1,lon2,lat1,lat2
    real(8), optional :: scaleFactor

    if(.not.statevector_in%allocated) then
      call abort3d('gridStateVector_in not yet allocated! Aborting.')
    endif
    if(.not.statevector_inout%allocated) then
      call abort3d('gridStateVector_inout not yet allocated! Aborting.')
    endif

    lon1=statevector_in%myLonBeg
    lon2=min(statevector_in%myLonEnd+1,statevector_inout%hco%ni)
    lat1=statevector_in%myLatBeg
    lat2=min(statevector_in%myLatEnd+1,statevector_inout%hco%nj)

    if(present(scaleFactor)) then
!$OMP PARALLEL
!$OMP DO PRIVATE (jstep,jlat,jlev,jlon)    
      do jlat = lat1, lat2
        do jstep = 1, statevector_inout%numStep
          do jlev = 1, statevector_inout%nk
            do jlon = lon1, lon2
              statevector_inout%gd(jlon,jlev,jlat,jstep)= statevector_inout%gd(jlon,jlev,jlat,jstep) +  &
                                            scaleFactor * statevector_in%gd(jlon,jlev,jlat,jstep)
            enddo
          enddo
        enddo
      enddo
!$OMP END DO
!$OMP END PARALLEL
    else
!$OMP PARALLEL
!$OMP DO PRIVATE (jstep,jlat,jlev,jlon)    
      do jlat = lat1, lat2
        do jstep = 1, statevector_inout%numStep
          do jlev = 1, statevector_inout%nk
            do jlon = lon1, lon2
              statevector_inout%gd(jlon,jlev,jlat,jstep)= statevector_inout%gd(jlon,jlev,jlat,jstep) +  &
                                                          statevector_in%gd(jlon,jlev,jlat,jstep)
            enddo
          enddo
        enddo
      enddo
!$OMP END DO
!$OMP END PARALLEL
    endif

  END SUBROUTINE GSV_add



  SUBROUTINE GSV_3dto4d(statevector) 1,1
    implicit none
    type(struct_gsv) :: statevector
    integer          :: jstep,jlon,jlev,jlat,lon1,lon2,lat1,lat2

    if(.not.statevector%allocated) then
      call abort3d('gridStateVector not yet allocated! Aborting.')
    endif

    lon1=statevector%myLonBeg
    lon2=min(statevector%myLonEnd+1,statevector%hco%ni)
    lat1=statevector%myLatBeg
    lat2=min(statevector%myLatEnd+1,statevector%hco%nj)

    if(statevector%numStep.eq.1) return

!$OMP PARALLEL
!$OMP DO PRIVATE (jstep,jlat,jlev,jlon)    
    do jlat = lat1, lat2
      do jstep = 1, statevector%numStep
        if(jstep.ne.statevector%anltime) then
          do jlev = 1, statevector%nk
            do jlon = lon1, lon2
              statevector%gd(jlon,jlev,jlat,jstep)= statevector%gd3d(jlon,jlev,jlat)
            enddo
          enddo
        endif
      enddo
    enddo
!$OMP END DO
!$OMP END PARALLEL

  END SUBROUTINE GSV_3dto4d



  SUBROUTINE GSV_3dto4dAdj(statevector) 1,1
    implicit none
    type(struct_gsv) :: statevector
    integer          :: jstep,jlon,jlev,jlat,lon1,lon2,lat1,lat2
    real(8)          :: gd3d_tmp(statevector%myLonBeg:(statevector%myLonEnd+1),statevector%nk,  &
                                 statevector%myLatBeg:(statevector%myLatEnd+1))

    if(.not.statevector%allocated) then
      call abort3d('gridStateVector not yet allocated! Aborting.')
    endif

    lon1=statevector%myLonBeg
    lon2=min(statevector%myLonEnd+1,statevector%hco%ni)
    lat1=statevector%myLatBeg
    lat2=min(statevector%myLatEnd+1,statevector%hco%nj)

    if(statevector%numStep.eq.1) return

!$OMP PARALLEL
!$OMP DO PRIVATE (jlat,jlev,jlon)    
    do jlat = lat1, lat2
      do jlev = 1, statevector%nk
        do jlon = lon1, lon2
          gd3d_tmp(jlon,jlev,jlat) = 0.0d0
        enddo
      enddo
    enddo
!$OMP END DO
!$OMP DO PRIVATE (jstep,jlat,jlev,jlon)    
    do jlat = lat1, lat2
      do jstep = 1, statevector%numStep
        do jlev = 1, statevector%nk
          do jlon = lon1, lon2
            gd3d_tmp(jlon,jlev,jlat) = gd3d_tmp(jlon,jlev,jlat) +   &
                                       statevector%gd(jlon,jlev,jlat,jstep)
          enddo
        enddo
      enddo
    enddo
!$OMP END DO
!$OMP DO PRIVATE (jlat,jlev,jlon)    
    do jlat = lat1, lat2
      do jlev = 1, statevector%nk
        do jlon = lon1, lon2
          statevector%gd3d(jlon,jlev,jlat) = gd3d_tmp(jlon,jlev,jlat)
        enddo
      enddo
    enddo
!$OMP END DO
!$OMP END PARALLEL

  END SUBROUTINE GSV_3dto4dAdj



  SUBROUTINE GSV_deallocate(statevector) 10,1
    implicit none

    type(struct_gsv) :: statevector
    integer        :: ierr

    if(.not.statevector%allocated) then
      call abort3d('gridStateVector not yet allocated! Aborting.')
    endif

    statevector%allocated=.false.

    deallocate(statevector%allLonBeg)
    deallocate(statevector%allLonEnd)
    deallocate(statevector%allLatBeg)
    deallocate(statevector%allLatEnd)

    deallocate(statevector%gd,stat=ierr)
    nullify(statevector%gd)
    if(ierr.ne.0) then
      write(*,*) 'Problem detected in gridStateVector. IERR =',ierr
    endif

    if(associated(statevector%dateStampList)) deallocate(statevector%dateStampList)
    deallocate(statevector%varOffset)
    deallocate(statevector%varNumLev)
    
  END SUBROUTINE GSV_deallocate



  function gsv_getField(statevector,varName) result(field) 33,4
    implicit none
    type(struct_gsv), intent(in)           :: statevector
    character(len=*), intent(in), optional :: varName
    real*8,pointer                         :: field(:,:,:,:)
    integer                                :: ilev1,ilev2,lon1,lat1

    lon1=statevector%myLonBeg
    lat1=statevector%myLatBeg

    if(present(varName)) then
      if(gsv_varExist(varName)) then
        ilev1 = 1 + statevector%varOffset(vnl_varListIndex(varName))
        ilev2 = ilev1 - 1 + statevector%varNumLev(vnl_varListIndex(varName))
        field(lon1:,1:,lat1:,1:) => statevector%gd(:,ilev1:ilev2,:,:)
      else
        call abort3d('gsv_getField: Unknown variable name! ' // varName)
      endif
    else
      field(lon1:,1:,lat1:,1:) => statevector%gd(:,:,:,:)
    endif

  end function gsv_getField



  function gsv_getField3D(statevector,varName) result(field3D) 31,4
    implicit none
    type(struct_gsv), intent(in)           :: statevector
    character(len=*), intent(in), optional :: varName
    real*8,pointer                         :: field3D(:,:,:)
    integer                                :: ilev1,ilev2,lon1,lat1

    lon1=statevector%myLonBeg
    lat1=statevector%myLatBeg

    if(present(varName)) then
      if(gsv_varExist(varName)) then
        ilev1 = 1 + statevector%varOffset(vnl_varListIndex(varName))
        ilev2 = ilev1 - 1 + statevector%varNumLev(vnl_varListIndex(varName))
        field3D(lon1:,1:,lat1:) => statevector%gd3d(:,ilev1:ilev2,:)
      else
        call abort3d('gsv_getField: Unknown variable name! ' // varName)
      endif
    else
      field3D(lon1:,1:,lat1:) => statevector%gd3d(:,:,:)
    endif

  end function gsv_getField3D



  function gsv_getDateStamp(statevector,step) result(dateStamp) 1,2
    implicit none
    type(struct_gsv), intent(in)   :: statevector
    integer, intent(in), optional  :: step 
    integer                        :: dateStamp

    if(associated(statevector%dateStampList)) then
      if(present(step)) then
        if(step.gt.0.and.step.le.statevector%numStep) then
          dateStamp=statevector%dateStampList(step)
        else
          write(*,*) 'gsv_getDateStamp: requested step is out of range! Step,numStep=',step,statevector%numStep
          call abort3d('aborting in gsv_getDateStamp')
        endif    
      else
        dateStamp=statevector%dateStamp3D
      endif
    else
      call abort3d('gsv_getDateStamp: dateStampList was not created during allocation!')
    endif

  end function gsv_getDateStamp



  function gsv_getVco(statevector) result(vco_ptr) 2
    implicit none
    type(struct_gsv)          :: statevector
    type(struct_vco), pointer :: vco_ptr

    vco_ptr => statevector%vco

  end function gsv_getVco



  subroutine gsv_setVco(statevector,vco_ptr) 9
    implicit none
    type(struct_gsv)          :: statevector
    type(struct_vco), pointer :: vco_ptr

    statevector%vco => vco_ptr

  end subroutine gsv_setVco


  function gsv_getHco(statevector) result(hco_ptr)
    implicit none
    type(struct_gsv)          :: statevector
    type(struct_hco), pointer :: hco_ptr

    hco_ptr => statevector%hco

  end function gsv_getHco


  subroutine gsv_setHco(statevector,hco_ptr) 9
    implicit none
    type(struct_gsv)          :: statevector
    type(struct_hco), pointer :: hco_ptr

    statevector%hco => hco_ptr

  end subroutine gsv_setHco

END MODULE gridStateVector_mod