!-------------------------------------- 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 --------------------------------------
!

subroutine oda_HT(lcolumn,lcolumng,lobsSpaceData) 1,14
  use mpivar_mod
  use EarthConstants_mod
  use MathPhysConstants_mod
  use obsSpaceData_mod
  use columnData_mod 
  use bufr
  use modgpsztd_mod
  implicit none
  !
  !Purpose:
  !Call the several adjoint of observation operators
  !
  !Author  : S. Pellerin *ARMA/MRB January 2009
  !
  !Revision:
  ! S. Macpherson ARMA  21 Dec 2012
  !            - update from Rev189 to Rev213
  !            - use new ZTD-specific GPS modules modgps04profilezd, modgps08ztdop
  ! S. Macpherson ARMA  14 Jan 2013
  !            - use numGPSZTD (from modgpsztd_mod) to determine if call oda_HTgp needed
  !            - modified oda_HTgp
  !            - merged with latest version (oda_HTro) from Josep (Rev.213M)
  !            - like oda_HTro, use OpenMP and Jacobian storage for GPS ZTD
  !
  type(struct_columnData) :: lcolumn,lcolumng
  type(struct_obs) :: lobsSpaceData
  type(struct_vco), pointer :: vco_anl

  IF(mpi_myid == 0) THEN
    write(*,*)'ODA_HT- Adjoint of linearized observation operators'
  endif

  vco_anl => col_getVco(lcolumng)

  call tmg_start(47,'OBS_GPSGB_TLAD') !
  if (numGPSZTD > 0) call oda_HTgp
  call tmg_stop (47)        !

  call tmg_start(46,'OBS_ZZZ_TLAD') !
  call oda_HTzp
  call tmg_stop (46)

  call tmg_start(45,'OBS_GPSRO_TLAD') !
  call oda_HTro
  call tmg_stop (45)      !     !

  call tmg_start(44,'OBS_TOV_TLAD') !
  call oda_HTto
  call tmg_stop (44)      !

  call tmg_start(43,'OBS_SFC_TLAD')
  call oda_HTsf
  call tmg_stop (43)      !

  call tmg_start(42,'OBS_PPP_TLAD') !
  call oda_HTpp
  call tmg_stop (42)

  CONTAINS


    SUBROUTINE oda_HTpp 1,46
      !
      !**s/r   - Adjoint of the "vertical" interpolation
      !          for "UPPER AIR" data files.
      !
      !
      !
      !Author  : P. Koclas *CMC/AES  April 1996
      !
      !     Purpose: based on vint3d to build the adjoint of the
      !              vertical interpolation for UPPER-AIR data files.
      !
      implicit none
      INTEGER IPB,IPT,ITYP
      REAL*8 ZRES
      REAL*8 ZWB,ZWT,zcon,zexp,zgamma,ZATV,ZTVG
      REAL*8 ZLEV,ZPT,ZPB,ZDADPS,ZPRESBPB,ZPRESBPT
      INTEGER INDEX_HEADER,IK,nlev_T
      INTEGER INDEX_BODY,INDEX_FAMILY
      REAL*8 columngVarT,columngVarB,lqtoes
      real*8, pointer :: all_column(:),tt_column(:),hu_column(:),ps_column(:)
      REAL*8 :: dPdPsT,dPdPsB
      logical :: llassim
      INTEGER, PARAMETER :: numFamily=3
      CHARACTER(len=2) :: list_family(numFamily),varType
      !
      ! Temperature lapse rate for extrapolation of gz below model surface
      !
      zgamma = 0.0065d0 / GRAV
      zexp = MPC_RGAS_DRY_AIR_R8*zgamma

      list_family(1) = 'UA'
      list_family(2) = 'AI'
      list_family(3) = 'SW'

      FAMILY: do index_family=1,numFamily

        call obs_set_current_body_list(lobsSpaceData,list_family(index_family))
        BODY: DO 
          index_body = obs_getBodyIndex(lobsSpaceData)
          if (index_body < 0) exit BODY

          llassim= (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. 1) &
             .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body) .EQ. 0) &
             .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 2)

          ! Process all data within the domain of the model
          IF (llassim) THEN
            index_header = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ZRES = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
            ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
            varType = vnl_varTypeFromVarnum(ityp)
            IK   = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
            IPT  = IK  + col_getOffsetFromVarno(lcolumng,ityp)
            IPB  = IPT+1
            ZPT  = col_getPressure(LCOLUMNG,IK,INDEX_HEADER,varType)
            ZPB  = col_getPressure(LCOLUMNG,IK+1,INDEX_HEADER,varType)
            dPdPsT = col_getPressureDeriv(LCOLUMNG,IK  ,INDEX_HEADER,varType)
            dPdPsB = col_getPressureDeriv(LCOLUMNG,IK+1,INDEX_HEADER,varType)
            ZWB  = LOG(ZLEV/ZPT)/LOG(ZPB/ZPT)
            ZWT  = 1.0D0 - ZWB

            ZDADPS   = ( LOG(ZLEV/ZPB)*dPdPsT/ZPT -   &
                         LOG(ZLEV/ZPT)*dPdPsB/ZPB )/  &
                       LOG(ZPB/ZPT)**2

            all_column => col_getColumn(lcolumn,INDEX_HEADER)
            tt_column  => col_getColumn(lcolumn,INDEX_HEADER,'TT')
            hu_column  => col_getColumn(lcolumn,INDEX_HEADER,'HU')
            ps_column  => col_getColumn(lcolumn,INDEX_HEADER,'P0')
            if(ITYP.eq.BUFR_NEES) then
              call lqtoes_ad(hu_column(IK+1),  &
                             tt_column(IK+1),  &
                             ps_column(1),     &
                             ZWB*ZRES,         &
                             col_getElem(lcolumng,IK+1,INDEX_HEADER,'HU'),      &
                             col_getPressure(lcolumng,IK+1,INDEX_HEADER,'TH'),  &
                             dPdPsB)
              call lqtoes_ad(hu_column(IK  ),  &
                             tt_column(IK  ),  &
                             ps_column(1),     &
                             ZWT*ZRES,         &
                             col_getElem(lcolumng,IK  ,INDEX_HEADER,'HU'),      &
                             col_getPressure(lcolumng,IK  ,INDEX_HEADER,'TH'),  &
                             dPdPsT)
              columngVarB=lqtoes(col_getElem(lcolumng,IK+1,INDEX_HEADER,'HU'),  &
                                 col_getElem(lcolumng,IK+1,INDEX_HEADER,'TT'),  &
                                 col_getPressure(lcolumng,IK+1,INDEX_HEADER,'TH'))
              columngVarT=lqtoes(col_getElem(lcolumng,IK  ,INDEX_HEADER,'HU'),  &
                                 col_getElem(lcolumng,IK  ,INDEX_HEADER,'TT'),  &
                                 col_getPressure(lcolumng,IK  ,INDEX_HEADER,'TH'))
            else
              all_column(IPB) = all_column(IPB) + ZWB*ZRES
              all_column(IPT) = all_column(IPT) + ZWT*ZRES
              columngVarB=col_getElem(lcolumng,IPB,INDEX_HEADER)
              columngVarT=col_getElem(lcolumng,IPT,INDEX_HEADER)
            endif
            ps_column(1)    = ps_column(1)    +         &
                 (columngVarB - columngVarT)  &
                 *ZDADPS*ZRES
          elseif( (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. 1) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body) .EQ. 2) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 2) ) THEN
            INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ZRES = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
            nlev_T = col_getNumLev(lcolumn,'TH')
            IPT  = nlev_T - 1 + col_getOffsetFromVarno(lcolumng,ityp)
            IPB  = IPT+1
            !
            ! adjoint of TL of geopotential extrapolation below orography
            !
            zcon = (zlev/col_getElem(lcolumng,1,index_header,'P0'))**zexp
            ZATV = ((1.0d0 - ZCON)/ZGAMMA)*ZRES
            ZTVG = lcolumng%OLTV(1,nlev_T,INDEX_HEADER)*col_getElem(lcolumng,nlev_T,INDEX_HEADER,'TT')
            ps_column(1)    = ps_column(1)  &
                 + MPC_RGAS_DRY_AIR_R8*ZTVG*zcon*zres &
                 / col_getElem(lcolumng,1,index_header,'P0')
            tt_column(nlev_T) = tt_column(nlev_T)  &
                 + lcolumng%OLTV(1,nlev_T,INDEX_HEADER)*ZATV
            hu_column(nlev_T) = hu_column(nlev_T)  &
                 + lcolumng%OLTV(2,nlev_T,INDEX_HEADER)*ZATV
          endif

        enddo BODY

      enddo FAMILY

    end subroutine oda_HTpp



      SUBROUTINE oda_HTsf 1,39
!*
!***s/r AOBSSFC  - Adjoint of the "vertical" interpolation
!*                  for "SURFACE" data files.
!*
!*Author  : P. Koclas *CMC/AES  April 1996
!*    -------------------
!*
!*     Purpose: based on surfc1dz to build the adjoint of the
!*              vertical interpolation for SURFACE data files.
!*
      implicit none
      INTEGER IPB,IPT
      REAL*8 ZRES
      REAL*8 ZWB,ZWT,zcon,zexp,zexpgz,zgamma,ZATV,ZTVG
      REAL*8 ZLEV,ZPT,ZPB,ZDADPS,ZDELPS,ZDELTV,ZGAMAZ,ZHHH
      INTEGER INDEX_HEADER,IK,nlev
      INTEGER INDEX_BODY,ITYP,INDEX_FAMILY
      real*8, pointer :: all_column(:),tt_column(:),hu_column(:),ps_column(:)
      REAL*8 :: dPdPsfc
      INTEGER, PARAMETER :: numFamily=4
      CHARACTER(len=2) :: list_family(numFamily),varType
!C
!C     Temperature lapse rate for extrapolation of gz below model surface
!C
      zgamma = 0.0065d0 / GRAV
      zexp   = 1.0d0/(MPC_RGAS_DRY_AIR_R8*zgamma)
      zexpgz = MPC_RGAS_DRY_AIR_R8*zgamma
!C
!C*    1. Fill in COMMVO by using the adjoint of the "vertical" interpolation
!C     .  ---------------------------------------------------------------
      list_family(1) = 'UA'
      list_family(2) = 'SF'
      list_family(3) = 'SC'
      list_family(4) = 'GP'

      FAMILY: do index_family=1,numFamily

        call obs_set_current_body_list(lobsSpaceData, list_family(index_family))
        BODY: do
          index_body = obs_getBodyIndex(lobsSpaceData)
          if (index_body < 0) exit BODY

! Process all data within the domain of the model
          ityp = obs_bodyElem_i(lobsSpaceData,OBS_VNM,index_body)
          if ( ityp.eq.bufr_nezd ) cycle BODY
          if(    (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body).eq.1) &
           .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body).eq.1) &
           .and. (ityp.eq.bufr_nets .or. ityp.eq.bufr_neps  &
             .or. ityp.eq.bufr_nepn .or. ityp.eq.bufr_ness  &
             .or. ityp.eq.bufr_neus .or. ityp.eq.bufr_nevs  &
             .or. obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body).eq.0) ) then

            if( ityp.eq.bufr_neus .or. ityp.eq.bufr_nevs ) then
              varType = 'MM'
            else
              varType = 'TH'
            endif
            nlev = col_getNumLev(lcolumn,varType)

            INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            all_column => col_getColumn(lcolumn,INDEX_HEADER)
            tt_column  => col_getColumn(lcolumn,INDEX_HEADER,'TT')
            hu_column  => col_getColumn(lcolumn,INDEX_HEADER,'HU')
            ps_column  => col_getColumn(lcolumn,INDEX_HEADER,'P0')
            ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
            IK   = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
            ZHHH = ZLEV * GRAV
            IPT  = nlev - 1 + col_getOffsetFromVarno(lcolumng,ityp)
            IPB  = IPT+1
            ZRES = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
            IF (ITYP.EQ.BUFR_NETS .OR. ITYP.EQ.BUFR_NESS .OR.  &
                ITYP.EQ.BUFR_NEUS .OR. ITYP.EQ.BUFR_NEVS ) THEN
              if(ityp.eq.bufr_ness) then
                dPdPsfc = col_getPressureDeriv(lcolumng,nlev,index_header,'TH')
                call lqtoes_ad(hu_column(nlev),  &
                               tt_column(nlev),  &
                               ps_column(1),     &
                               ZRES,             &
                               col_getElem(lcolumng,nlev,INDEX_HEADER,'HU'),      &
                               col_getPressure(lcolumng,nlev,INDEX_HEADER,'TH'),  &
                               dPdPsfc)
              else
                all_column(IPB) = all_column(IPB) + ZRES
              endif
            ELSEIF (ITYP.EQ.BUFR_NEPS .OR. ITYP.EQ.BUFR_NEPN) THEN
              ZTVG  = lcolumng%OLTV(1,nlev,INDEX_HEADER)*col_getElem(lcolumng,nlev,INDEX_HEADER,'TT')
              ZGAMAZ= ZGAMMA*(ZHHH-col_getHeight(lcolumng,nlev,INDEX_HEADER,varType))
              ZCON  = ((ZTVG-ZGAMAZ)/ZTVG)
              ZDELTV= (col_getElem(lcolumng,1,INDEX_HEADER,'P0')*ZEXP*ZCON**(ZEXP-1))  &
                   *(ZGAMAZ/(ZTVG*ZTVG))
              ZDELPS= ZCON**ZEXP
              ZATV  = ZDELTV*ZRES
              ps_column(1)    = ps_column(1) + ZDELPS*ZRES
              tt_column(nlev) = tt_column(nlev)  &
                   + lcolumng%OLTV(1,nlev,INDEX_HEADER)*ZATV
              hu_column(nlev)= hu_column(nlev)   &
                   + lcolumng%OLTV(2,nlev,INDEX_HEADER)*ZATV
            ELSE
              IPT  = IK + col_getOffsetFromVarno(lcolumng,ityp)
              IPB  = IPT+1
              ZPT  = col_getHeight(lcolumng,IK  ,INDEX_HEADER,varType)
              ZPB  = col_getHeight(lcolumng,IK+1,INDEX_HEADER,varType)
              ZWB  = (ZPT-ZHHH)/(ZPT-ZPB)
              ZWT  = 1.d0 - ZWB
!ccc ATTN ATTN ZDADPS EST A DEFINIR POUR UNE COORDONNEE Z
              ZDADPS= 0.d0
              all_column(IPB) = all_column(IPB) + ZWB*ZRES
              all_column(IPT) = all_column(IPT) + ZWT*ZRES
              ps_column(1)    = ps_column(1)    +         &
                   (col_getElem(lcolumng,IPB,INDEX_HEADER) - col_getElem(lcolumng,IPT,INDEX_HEADER))  &
                   *ZDADPS*ZRES
            ENDIF
            
          elseif( (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. 1) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body) .EQ. 2) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,index_body) .EQ. BUFR_NEGZ ) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 1) ) then

            nlev = col_getNumLev(lcolumn,'TH')           
            INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ZRES = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
!C
!c  adjoint of TL of geopotential extrapolation below orography
!c
            zcon = (zlev/col_getElem(lcolumng,1,index_header,'P0'))**zexpgz
            ZATV = ((1.0d0 - ZCON)/ZGAMMA)*ZRES
            ZTVG = lcolumng%OLTV(1,nlev,INDEX_HEADER)*col_getElem(lcolumng,nlev,INDEX_HEADER,'TT')
            ps_column(1)    = ps_column(1)     &
                 + MPC_RGAS_DRY_AIR_R8*ZTVG*zcon*zres &
                 / col_getElem(lcolumng,1,index_header,'P0')
            tt_column(nlev) = tt_column(nlev)  &
                 + lcolumng%OLTV(1,nlev,INDEX_HEADER)*ZATV
            hu_column(nlev) = hu_column(nlev)  &
                 + lcolumng%OLTV(2,nlev,INDEX_HEADER)*ZATV                
          endif

        ENDDO BODY

      ENDDO FAMILY

      RETURN
      END subroutine oda_HTsf



subroutine oda_HTto 1,3
!
!**s/r tovs_obs_ad  - Adjoint of computation of residuals to the tovs observations
!
!
!author        : j. halle *cmda/aes  april 19, 2005
!
!revision 001  :
!                S. Pellerin - ARMA, jan. 2009
!                - call  to oda_get_radiance_ad
!
!    -------------------
!     purpose:
!
      implicit none

!     1.   Getting the adjoint of the residuals
!     .    ----------------------------------
!
  call oda_get_radiance_ad(lobsSpaceData)

!     2.   Adjoint of computing radiance
!     .    -----------------------------
!
  call tovs_rttov_ad(lobsSpaceData)


!     3.   Adjoint of preparation of atmospheric profiles
!     .    ----------------------------------------------
!
  call tovs_fill_profiles_ad(lcolumn,lcolumng)

end subroutine oda_HTto



      SUBROUTINE oda_HTro 1,24
!*
!* Purpose: Compute the adjoint operator for GPSRO observations.
!*
!*Author  : J. M. Aparicio Jan 2004
!*Modified: J. M. Aparicio Dec 2012 adapt to accept bending angle data
!*    -------------------
      use modgps00base      , only : ngpscvmx
      use modgps01ctphys    , only : p_TC
      use modgps03diff      , only : gpsdiff
      use modgpsro_mod
      use IndexListDepot_mod, only : struct_index_list
      implicit none

      REAL*8 DPJO0(ngpscvmx)
      REAL*8 DPJO1(ngpscvmx)

      REAL*8 zLat, Lat
      REAL*8 zAzm, Azm
      INTEGER IAZM, ISAT
      REAL*8 Rad, Geo, HNH1
      REAL*8 zP0, zMT

      REAL*8 ZINC, ZOER

      real*8, pointer :: tt_column(:),hu_column(:),ps_column(:)
      INTEGER IDATYP
      INTEGER JL, NGPSLEV
      integer :: index_header, index_body, iProfile
      type(struct_index_list), pointer :: local_current_list

      LOGICAL  ASSIM, LUSE

      INTEGER NH, NH1
!C      WRITE(*,*)'ENTER oda_HTro'
!C
!C     * 1.  Initializations
!C     *     ---------------
!C
      NGPSLEV=col_getNumLev(lcolumn,'TH')
!C
!C    Loop over all header indices of the 'RO' family (Radio Occultation)
!C
      ! Set the header list (start at the beginning of the list)
      call obs_set_current_header_list(lobsSpaceData,'RO')
!##$omp parallel default(shared) &
!##$omp private(index_header,dpjo0,idatyp,assim,nh,local_current_list,index_body,luse) &
!##$omp private(iProfile,zlat,irad,igeo,iazm,isat,rad,geo,zazm,zmt,lat) &
!##$omp private(nh1,zinc,zoer,dpjo1) &
!##$omp private(tt_column,hu_column,ps_column)
      nullify(local_current_list)
      HEADER: do
         INDEX_HEADER = obs_getHeaderIndex(lobsSpaceData)
         if (INDEX_HEADER < 0) exit HEADER

         DPJO0 = 0.d0
!C
!C     * Process only refractivity data (codtyp 169)
!C
         IDATYP = obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER)
         DATYP: IF ( IDATYP .EQ. 169 ) THEN
!C
!C     *    Scan for requested data values of the profile, and count them
!C
            ASSIM = .FALSE.
            NH = 0
!C
!C     *    Loop over all body indices for this index_header:
!C     *    (start at the beginning of the list)
!C
            call obs_set_current_body_list(lobsSpaceData, INDEX_HEADER, &
                 current_list=local_current_list)
            BODY: do 
               index_body = obs_getBodyIndex(local_current_list)
               if (index_body < 0) exit BODY

               LUSE=( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 )
               IF ( LUSE ) THEN
                  ASSIM = .TRUE.
                  NH = NH + 1
               ENDIF
            ENDDO BODY
!C
!C     *    If assimilations are requested, prepare and apply the observation operator
!C
            ASSIMILATE: IF (ASSIM) THEN
               iProfile=iProfile_from_index(INDEX_HEADER)
!C
!C     *    Basic geometric variables of the profile:
!C
               zLat = obs_headElem_r(lobsSpaceData,OBS_LAT,INDEX_HEADER)
               IAZM = obs_headElem_i(lobsSpaceData,OBS_AZA,INDEX_HEADER)
               ISAT = obs_headElem_i(lobsSpaceData,OBS_SAT,INDEX_HEADER)
               Rad  = obs_headElem_r(lobsSpaceData,OBS_TRAD,INDEX_HEADER)
               Geo  = obs_headElem_r(lobsSpaceData,OBS_GEOI,INDEX_HEADER)
               zAzm = 0.01d0*IAZM / MPC_DEGREES_PER_RADIAN_R8
               zMT  = col_getHeight(lcolumng,NGPSLEV,INDEX_HEADER,'TH')/RG
               Lat  = zLat * MPC_DEGREES_PER_RADIAN_R8
!C
!C     *       Perform the (H(xb)DX-Y')/S operation
!C
               NH1 = 0
!C
!C     *       Loop over all body indices for this index_header:
!C     *       (start at the beginning of the list)
!C
               call obs_set_current_body_list(lobsSpaceData, index_header, &
                    current_list=local_current_list)
               BODY_3: do 
                  index_body = obs_getBodyIndex(local_current_list)
                  if (index_body < 0) exit BODY_3

                  LUSE=( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 )
                  IF ( LUSE ) THEN
                     NH1 = NH1 + 1
!C
!C     *             Normalized increment
!C
                     ZINC = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
!                     ZOER = obs_bodyElem_r(lobsSpaceData,OBS_OER,INDEX_BODY)
!C
!C     *             O-F Tested criteria:
!C
                     DPJO1(1:2*NGPSLEV+1) = ZINC * vGPSRO_Jacobian(iProfile,NH1,:)
!C
!C     *             Accumulate the gradient of the observation cost function:
!C
                     DPJO0(1:2*NGPSLEV+1) = DPJO0(1:2*NGPSLEV+1) + DPJO1(1:2*NGPSLEV+1)
                  ENDIF
               ENDDO BODY_3
            ENDIF ASSIMILATE
         ENDIF DATYP
!C
!C     * Store H* (HX - Z)/SIGMA in COMMVO
!C
         tt_column => col_getColumn(lcolumn,index_header,'TT')
         hu_column => col_getColumn(lcolumn,index_header,'HU')
         ps_column => col_getColumn(lcolumn,index_header,'P0')
         DO JL = 1, NGPSLEV
           tt_column(JL) = DPJO0(JL)
           hu_column(JL) = DPJO0(JL+NGPSLEV)
         ENDDO
         ps_column(1) = DPJO0(1+2*NGPSLEV)
      ENDDO HEADER
!##$omp end parallel

!C      WRITE(*,*)'EXIT oda_HTro'
      RETURN
      END subroutine oda_HTro



      SUBROUTINE oda_HTzp 1,19
!*
!***s/r AOBSZZZ  - Adjoint of the "vertical" interpolation in z
!*                 for profiler data.
!*
!*Author  : J. St-James *CMDA/SMC  July 2003
!*Revision :
!*    -------------------
!*
!*     Purpose: based on vint3d to build the adjoint of the
!*              vertical interpolation for profiler data.
!*
      implicit none
      INTEGER IPB,IPT
      REAL*8 ZRES,ZDA1,ZDA2,ZDENO
      REAL*8 ZWB,ZWT
      REAL*8 ZLEV,ZPT,ZPB
      INTEGER INDEX_HEADER,IK,ITYP
      INTEGER INDEX_BODY
      real*8, pointer :: gz_column(:),all_column(:)
      character(len=2) :: varType
!C
!C     Process all data within the domain of the model
!C
      call obs_set_current_body_list(lobsSpaceData, 'PR')
      BODY: do
        index_body = obs_getBodyIndex(lobsSpaceData)
        if (index_body < 0) exit BODY

        IF (   (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY) .EQ. 1) &
         .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,INDEX_BODY) .EQ. 0) &
         .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,INDEX_BODY) .EQ. 1)  ) THEN
          INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
          ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
          varType = vnl_varTypeFromVarnum(ityp)
          gz_column  => col_getColumn(lcolumn,INDEX_HEADER,'GZ',varType)
          all_column => col_getColumn(lcolumn,INDEX_HEADER)
          ZRES = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
          ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
          IK   = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
          IPT  = IK  + col_getOffsetFromVarno(lcolumng,ityp)
          IPB  = IPT+1
          ZPT  = col_getHeight(lcolumng,IK,INDEX_HEADER,varType)/RG
          ZPB  = col_getHeight(lcolumng,IK+1,INDEX_HEADER,varType)/RG
          ZDENO= ZPT-ZPB
          ZWB  = (ZPT-ZLEV)/ZDENO
          ZWT  = 1.0D0 - ZWB

          ZDA1= (ZLEV-ZPB)/(ZDENO**2)
          ZDA2= (ZPT-ZLEV)/(ZDENO**2)
!C
          gz_column(IK+1) = gz_column(IK+1) +    &
               (col_getElem(lcolumng,IPB,INDEX_HEADER)-col_getElem(lcolumng,IPT,INDEX_HEADER))*ZDA2*ZRES/RG
          gz_column(IK) = gz_column(IK) +        &
               (col_getElem(lcolumng,IPB,INDEX_HEADER)-col_getElem(lcolumng,IPT,INDEX_HEADER))*ZDA1*ZRES/RG
          all_column(IPB) = all_column(IPB) + ZWB*ZRES
          all_column(IPT) = all_column(IPT) + ZWT*ZRES

        ENDIF
      ENDDO BODY
      RETURN
      END subroutine oda_HTzp



      SUBROUTINE oda_HTgp 1,21
!*
!***s/r  -oda_HTgp Adjoint of TL routine oda_Hgp
!*
!*
!*Author  : S. Macpherson *ARMA October 2012
!
!*Revisions:
!
! S. Macpherson ARMA  14 Jan 2013
!            - like oda_HTro, use OpenMP and Jacobian storage to speed up

!*    -------------------
!**    Purpose: Compute Ht*grad(Jo) for all GPS ZTD observations
!
!  NOTE:  ZTD Jacobians are computed and stored in oda_Hgp (first iter.)
!
!*
      use modgps00base      , only : ngpscvmx
      use modgpsztd_mod
      implicit none

      REAL*8 DPJO0(ngpscvmx)
      REAL*8 JAC(ngpscvmx)
! 
      REAL*8 ZINC
      INTEGER JL, NFLEV, iztd
      integer :: index_header, index_body, icount
      LOGICAL ASSIM

      real*8, pointer :: tt_column(:),hu_column(:),ps_column(:)

!      WRITE(*,*)'ENTER oda_HTgp'

      NFLEV  = col_getNumLev(lcolumng,'TH')

      IF ( .not.vGPSZTD_lJac(1) ) THEN
         call abort3d('oda_HTgp:ERROR: ZTD Jacobians not stored!')
      ENDIF

      ! loop over all header indices of the 'GP' family (GPS observations)
      ! Set the header list & start at the beginning of the list
      call obs_set_current_header_list(lobsSpaceData,'GP')

      icount = 0
      
      HEADER: do
         index_header = obs_getHeaderIndex(lobsSpaceData)
         if (index_header < 0) exit HEADER
        
         DPJO0(:) = 0.0D0
         JAC(:)   = 0.0D0
!C
!C       Scan for requested ZTD assimilation
!C
         ASSIM = .FALSE.
         ! loop over all body indices (still in the 'GP' family)
         ! Set the body list & start at the beginning of the list)
         call obs_set_current_body_list(lobsSpaceData, index_header)
         BODY: DO 
            index_body = obs_getBodyIndex(lobsSpaceData)
            if (index_body < 0) exit BODY

            IF (   (obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER).eq.189) &
             .and. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY).EQ.BUFR_NEZD) &
             .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1) ) then
               ASSIM = .TRUE.
            ENDIF
         ENDDO BODY
!C
         IF (ASSIM) THEN
         
            icount = icount + 1
            iztd = i_from_index(INDEX_HEADER)
            if ( iztd < 1 .or. iztd > numGPSZTD ) then
              call abort3d('oda_HTgp: ERROR: index from i_from_index() is out of range!')
            endif            
            
            DO JL = 1, 2*NFLEV+1
              JAC(JL) = vGPSZTD_Jacobian(iztd,JL)
            ENDDO
!C
!C          Get Ht*grad(Index_header) = Ht*(H'dx - d)/sigma_o^2
!C
            ! loop over all body indices (still in the 'GP' family)
            ! Start at the beginning of the list)
            call obs_set_current_body_list(lobsSpaceData, index_header)
            BODY_2: do 
               index_body = obs_getBodyIndex(lobsSpaceData)
               if (index_body < 0) exit BODY_2
               if (   (obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER).eq.189) &
                .and. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY).EQ.BUFR_NEZD) &
                .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1) ) then
                  ZINC = -obs_bodyElem_r(lobsSpaceData,OBS_WORK,INDEX_BODY)
!C     *       Accumulate the gradient of the observation cost function
                  DPJO0(1:2*NFLEV+1) = ZINC * vGPSZTD_Jacobian(iztd,:)
               endif
            ENDDO BODY_2
!c
!C      *   Store Ht*grad(Index_header) in COMMVO
!c
            tt_column => col_getColumn(lcolumn,index_header,'TT')
            hu_column => col_getColumn(lcolumn,index_header,'HU')
            ps_column => col_getColumn(lcolumn,index_header,'P0')
            DO JL = 1, NFLEV
              tt_column(JL) = DPJO0(JL)
              hu_column(JL) = DPJO0(JL+NFLEV)
            ENDDO
            ps_column(1) = DPJO0(2*NFLEV+1)
            
         ENDIF ! ASSIM
         
       
      ENDDO HEADER
      
!      WRITE(*,*) 'oda_HTgp: Number of ZTD data locations processed = ', icount
      
!      WRITE(*,*)'EXIT oda_HTgp'
      
      RETURN
      END subroutine oda_HTgp


end subroutine oda_HT