obsOperators_mod sourceΒΆ

   1module obsOperators_mod
   2  ! MODULE obsOperators_mod (prefix='oop' category='5. Observation operators')
   3  !
   4  !:Purpose:  All observation operators, including nonlinear, tangent-linear
   5  !           and adjoint versions.
   6  !
   7  use codePrecision_mod
   8  use earthConstants_mod
   9  use mathPhysConstants_mod
  10  use obsSpaceData_mod
  11  use columnData_mod 
  12  use bufr_mod
  13  use physicsFunctions_mod
  14  use gps_mod
  15  use midasMpi_mod
  16  use timeCoord_mod
  17  use tovsNL_mod
  18  use utilities_mod
  19  use tovsLin_mod
  20  use verticalCoord_mod
  21  use varNameList_mod
  22  use obsOperatorsChem_mod
  23  use obserrors_mod
  24  implicit none
  25  save
  26  private
  27
  28  ! public procedures
  29  public :: oop_ppp_nl, oop_sfc_nl, oop_zzz_nl, oop_gpsro_nl, oop_hydro_nl
  30  public :: oop_gpsgb_nl, oop_tovs_nl, oop_chm_nl, oop_sst_nl, oop_ice_nl, oop_raDvel_nl
  31  public :: oop_Htl, oop_Had, oop_vobslyrs, oop_iceScaling
  32
  33  integer, external :: get_max_rss
  34
  35  real(8), parameter :: temperatureLapseRate = 0.0065D0 ! K/m (i.e. 6.5 K/km)
  36
  37  ! Jacobian caches
  38  real*8 , allocatable :: oop_vRO_Jacobian(:,:,:)
  39  logical, allocatable :: oop_vRO_lJac(:)
  40  real*8 , allocatable :: oop_vZTD_Jacobian(:,:)
  41
  42contains
  43
  44  !--------------------------------------------------------------------------
  45  ! oop_vobslyrs
  46  !--------------------------------------------------------------------------
  47  subroutine oop_vobslyrs( columnTrl, obsSpaceData, beSilent )
  48    !:Purpose:
  49    !      Find which model levels to use for the vertical interpolation
  50    !      of model fields to obs data.
  51    implicit none
  52
  53    ! Arguments:
  54    type(struct_columnData), intent(in)    :: columnTrl
  55    type(struct_obs),        intent(inout) :: obsSpaceData
  56    logical,                 intent(in)    :: beSilent
  57
  58    ! Locals:
  59    integer :: levIndex,JDATA,NLEV
  60    real(8) :: ZLEV,ZPT,ZPB
  61    integer :: IOBS,layerIndex,bufrCode
  62    character(len=4) :: varLevel
  63    integer :: bodyIndex
  64
  65    if (.not.beSilent) write(*,*) "Entering subroutine OOP_VOBSLYRS"
  66
  67    ! 2D mode patch
  68    if ( col_getNumLev(columnTrl,'MM') <= 1 ) then 
  69      do bodyIndex = 1, obs_numbody( obsSpaceData )
  70        if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated .and. &
  71             obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) == 1 ) then
  72          call obs_bodySet_i(obsSpaceData,OBS_LYR,bodyIndex, 0) ! set OBS_LYR = 0
  73        end if
  74      end do
  75      return
  76    end if
  77
  78    !
  79    !-----------------------------------------------------------------------
  80    !         --------
  81    !           ETA
  82    !         --------
  83    !
  84    !     1. Find where extrapolation is needed
  85    !        ----------------------------------
  86    !
  87    !     1.1 PPP Vertical coordinate
  88    ! 
  89
  90    !$OMP PARALLEL DO PRIVATE(jdata,zlev,iobs,bufrCode,varLevel,zpt,zpb)
  91    do JDATA= 1,obs_numbody(obsSpaceData)
  92       if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,JDATA) == obs_assimilated .and. &
  93            obs_bodyElem_i(obsSpaceData,OBS_VCO,JDATA) == 2 ) THEN
  94          if (obs_bodyElem_i(obsSpaceData,OBS_VNM,JDATA) /= bufr_nedz ) THEN
  95             ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,JDATA)
  96          else
  97             call utl_abort('oop_vobslyr: ZLEV cannot be set, bufr_nedz not supported!')
  98          end if
  99          IOBS = obs_bodyElem_i(obsSpaceData,OBS_HIND,JDATA)
 100          bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,JDATA)
 101          if (bufr_IsAtmosConstituent(bufrCode)) then
 102             varLevel = vnl_varLevelFromVarnum(bufrCode, &
 103                        obs_headElem_i(obsSpaceData,OBS_CHM,IOBS))
 104          else
 105             varLevel = vnl_varLevelFromVarnum(bufrCode)
 106          end if
 107          ZPT= col_getPressure(columnTrl,1,IOBS,varLevel)
 108          ZPB= col_getPressure(columnTrl,COL_GETNUMLEV(columnTrl,varLevel),IOBS,varLevel)
 109          if ( ZLEV < ZPT ) THEN
 110             call obs_bodySet_i(obsSpaceData,OBS_XTR,JDATA,1)
 111             !
 112             !- !!! WARNING !!! This obs is above the model lid. 
 113             !  We must turn off its assimilation flag  because the
 114             !  current obs operators cannot deal with this situation (JFC)                  
 115             if (varLevel /= 'SF') then
 116                write(*,*) 'oop_vobslyrs: Rejecting OBS above model lid, pressure = ', ZLEV,' < ',ZPT
 117                call obs_bodySet_i(obsSpaceData,OBS_ASS,JDATA, obs_notAssimilated)
 118             end if
 119          else if ( ZLEV > ZPB ) THEN
 120             call obs_bodySet_i(obsSpaceData,OBS_XTR,JDATA,2)
 121          else
 122             call obs_bodySet_i(obsSpaceData,OBS_XTR,JDATA,0)
 123          end if
 124       end if
 125    end do
 126    !$OMP END PARALLEL DO
 127    !
 128    !     1.2 ZZZ Vertical coordinate
 129    !
 130    !$OMP PARALLEL do PRIVATE(jdata,zlev,iobs,bufrCode,varLevel,zpt,zpb,nlev)
 131    do JDATA= 1,obs_numbody(obsSpaceData)
 132      if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,JDATA) == obs_assimilated .and. &
 133           obs_bodyElem_i(obsSpaceData,OBS_VCO,JDATA) == 1 ) then
 134        IOBS = obs_bodyElem_i(obsSpaceData,OBS_HIND,JDATA)
 135        bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,JDATA)
 136        if ( bufrCode /= bufr_nedz ) then
 137          ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,JDATA)
 138          if ( bufrCode == bufr_nebd ) THEN
 139             ZLEV = ZLEV - obs_headElem_r(obsSpaceData,OBS_TRAD,IOBS)
 140          endif
 141        else
 142          call utl_abort('oop_vobslyr: ZLEV cannot be set, bufr_nedz not supported!')
 143        end if
 144        if (bufr_IsAtmosConstituent(bufrCode)) then
 145          varLevel = vnl_varLevelFromVarnum(bufrCode, &
 146                     obs_headElem_i(obsSpaceData,OBS_CHM,IOBS))
 147        else
 148          varLevel = vnl_varLevelFromVarnum(bufrCode)
 149        end if
 150        if (varLevel == 'SF') then
 151          ZPT= col_getHeight(columnTrl,1,IOBS,'TH')
 152          ZPB= col_getHeight(columnTrl,0,IOBS,'SF')
 153        else
 154          nlev=col_getNumLev(columnTrl,varLevel)
 155          ZPT= col_getHeight(columnTrl,1,IOBS,varLevel)
 156          ZPB= col_getHeight(columnTrl,NLEV,IOBS,varLevel)
 157        end if
 158        if ( ZLEV > ZPT ) then
 159          call obs_bodySet_i(obsSpaceData,OBS_XTR,JDATA,1)
 160          write(*,*) 'oop_vobslyrs: Rejecting OBS above model lid, height =', ZLEV,' > ',ZPT
 161          call obs_bodySet_i(obsSpaceData,OBS_ASS,JDATA, obs_notAssimilated)
 162        else if ( ZLEV < ZPB ) then
 163          call obs_bodySet_i(obsSpaceData,OBS_XTR,JDATA,2)
 164        else
 165          call obs_bodySet_i(obsSpaceData,OBS_XTR,JDATA,0)
 166        end if
 167      end if
 168    end do
 169    !$OMP END PARALLEL DO
 170    !
 171    !
 172    !     2. FInd interpolation layer
 173    !        ------------------------
 174    !        (Model levels are assumed to be in increasing order in Mbs)
 175    !
 176    !     2.1  PPP Vertical coordinate
 177    !
 178    !$OMP PARALLEL DO PRIVATE(jdata,iobs,zlev,bufrCode,varLevel,layerIndex,nlev,levIndex,zpt,zpb)
 179    do JDATA = 1, obs_numbody(obsSpaceData)
 180      call obs_bodySet_i(obsSpaceData,OBS_LYR,JDATA,0)
 181      if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,JDATA) == obs_assimilated .and. &
 182           obs_bodyElem_i(obsSpaceData,OBS_VCO,JDATA) == 2 ) then
 183        IOBS = obs_bodyElem_i(obsSpaceData,OBS_HIND,JDATA)
 184        ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,JDATA)
 185        bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,JDATA)
 186        if (bufr_IsAtmosConstituent(bufrCode)) then
 187           varLevel = vnl_varLevelFromVarnum(bufrCode, &
 188                      obs_headElem_i(obsSpaceData,OBS_CHM,IOBS))
 189        else
 190           varLevel = vnl_varLevelFromVarnum(bufrCode)
 191        end if
 192        layerIndex = 1
 193        nlev=COL_GETNUMLEV(columnTrl,varLevel)
 194        do levIndex = 2,NLEV - 1
 195          ZPT = col_getPressure(columnTrl,levIndex,IOBS,varLevel)
 196          if ( ZLEV > ZPT ) layerIndex = levIndex
 197        end do
 198        ZPT = col_getPressure(columnTrl,layerIndex,IOBS,varLevel)
 199        ZPB = col_getPressure(columnTrl,layerIndex+1,IOBS,varLevel) 
 200        call obs_bodySet_i(obsSpaceData,OBS_LYR,JDATA, layerIndex)
 201      end if
 202    end do
 203    !$OMP END PARALLEL DO
 204    !
 205    !     2.2  ZZZ Vertical coordinate and surface observations
 206    !
 207    !$OMP PARALLEL DO PRIVATE(jdata,iobs,zlev,bufrCode,varLevel,layerIndex,nlev,levIndex,zpt)
 208    do JDATA = 1, obs_numbody(obsSpaceData)
 209      if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,JDATA) == obs_assimilated .and. &
 210           obs_bodyElem_i(obsSpaceData,OBS_VCO,JDATA) == 1 ) then
 211        IOBS = obs_bodyElem_i(obsSpaceData,OBS_HIND,JDATA)
 212        ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,JDATA)
 213        bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,JDATA)
 214        if (bufr_IsAtmosConstituent(bufrCode)) then
 215          varLevel = vnl_varLevelFromVarnum(bufrCode, &
 216                     obs_headElem_i(obsSpaceData,OBS_CHM,IOBS))
 217        else
 218          varLevel = vnl_varLevelFromVarnum(bufrCode)
 219        end if
 220        layerIndex = 1
 221        nlev=COL_GETNUMLEV(columnTrl,varLevel)
 222        do levIndex = 2, NLEV - 1
 223          ZPT = col_getHeight(columnTrl,levIndex,IOBS,varLevel)
 224          if ( ZLEV < ZPT ) layerIndex = levIndex
 225        end do
 226        if ( bufrCode == bufr_neps .or. bufrCode == bufr_nepn .or. &
 227             bufrCode == bufr_nezd .or. bufrCode == bufr_gust .or. &
 228             bufrCode == bufr_radarPrecip .or. bufrCode == bufr_logRadarPrecip ) THEN
 229          ! for surface observations associated with surface analysis variables
 230          layerIndex = 0
 231        else if ( bufrCode == bufr_nets .or. bufrCode == bufr_ness .or. &
 232                  bufrCode == bufr_neus .or. bufrCode == bufr_nevs .or. &
 233                  bufrCode == bufr_nehs .or. bufrCode == bufr_vis  .or.  &
 234                  bufrCode == bufr_logVis ) then
 235          ! for surface observations associated with NON-surface analysis variables
 236          layerIndex = nlev - 1
 237        end if
 238        call obs_bodySet_i(obsSpaceData,OBS_LYR,JDATA, layerIndex)
 239      end if
 240    end do
 241    !$OMP END PARALLEL DO
 242    !
 243  end subroutine oop_vobslyrs
 244
 245  !--------------------------------------------------------------------------
 246  ! oop_ppp_nl
 247  !--------------------------------------------------------------------------
 248  subroutine oop_ppp_nl( columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam, destObsColumn )
 249    !:Purpose: Computation of y - H(x)
 250    !           for pressure-level observations.
 251    !           Interpolate vertically columnTrlOnTrlLev to
 252    !           the pressure levels of the observations.
 253    !           A linear interpolation in ln(p) is performed.
 254    !
 255    implicit none
 256
 257    ! Arguments:
 258    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 259    type(struct_obs)       , intent(inout) :: obsSpaceData
 260    logical                , intent(in)    :: beSilent
 261    character(len=*)       , intent(in)    :: cdfam ! family of obsservation
 262    integer                , intent(in)    :: destObsColumn
 263
 264    ! Locals:
 265    integer :: headerIndex,bodyIndex,ilyr
 266    integer :: iass,ixtr,ivco,bufrCode,nlev_T
 267    real(8) :: zvar,zwb,zwt,zexp
 268    real(8) :: zlev,zpt,zpb,zomp,ztvg
 269    real(8) :: trlValueBot,trlValueTop,lat
 270    character(len=4) :: varName
 271    character(len=4) :: varLevel
 272    real(8),pointer :: col_ptr(:),col_ptr_tt(:),col_ptr_hu(:)
 273    real(8), allocatable :: geopotential(:)
 274    real(8) :: heightSfc(1), geopotentialSfc(1)
 275
 276    if (.not.beSilent) write(*,*) 'Entering subroutine oop_ppp_nl'
 277
 278    zexp = MPC_RGAS_DRY_AIR_R8 * temperatureLapseRate / ec_rg
 279
 280    nlev_T = col_getNumLev(columnTrlOnTrlLev,'TH')
 281    allocate(geopotential(nlev_T))
 282
 283    call obs_set_current_body_list(obsSpaceData, cdfam)
 284    BODY: do
 285       bodyIndex = obs_getBodyIndex(obsSpaceData)
 286       if (bodyIndex < 0) exit BODY
 287
 288       ! Only process pressure level observations flagged to be assimilated
 289       iass=obs_bodyElem_i (obsSpaceData,OBS_ASS,bodyIndex)
 290       ivco=obs_bodyElem_i (obsSpaceData,OBS_VCO,bodyIndex)
 291       if (iass /= 1 .or. ivco /= 2) cycle BODY
 292
 293       ixtr=obs_bodyElem_i (obsSpaceData,OBS_XTR,bodyIndex)
 294       bufrCode=obs_bodyElem_i (obsSpaceData,OBS_VNM,bodyIndex)
 295       zvar=obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex)
 296       zlev=obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
 297       headerIndex=obs_bodyElem_i (obsSpaceData,OBS_HIND,bodyIndex)
 298
 299       if ( ixtr == 0 ) then
 300
 301         ! Process all data within the domain of the model
 302         ilyr  =obs_bodyElem_i (obsSpaceData,OBS_LYR,bodyIndex)
 303         varName = vnl_varNameFromVarnum(bufrCode)
 304         varLevel = vnl_varLevelFromVarnum(bufrCode)
 305         zpt= col_getPressure(columnTrlOnTrlLev,ilyr  ,headerIndex,varLevel)
 306         zpb= col_getPressure(columnTrlOnTrlLev,ilyr+1,headerIndex,varLevel)
 307         zwb  = log(zlev/zpt)/log(zpb/zpt)
 308         zwt  = 1.d0 - zwb
 309         lat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
 310         if (bufrCode == bufr_nees) then
 311           col_ptr_hu=>col_getColumn(columnTrlOnTrlLev,headerIndex,'HU')
 312           col_ptr_tt=>col_getColumn(columnTrlOnTrlLev,headerIndex,'TT')
 313           trlValueBot=hutoes(col_ptr_hu(ilyr+1),col_ptr_tt(ilyr+1),zpb)
 314           trlValueTop=hutoes(col_ptr_hu(ilyr  ),col_ptr_tt(ilyr  ),zpt)
 315         else
 316           if (trim(varName) == 'Z_T') then
 317             col_ptr=>col_getColumn(columnTrlOnTrlLev,headerIndex,'Z_T')
 318             call phf_height2geopotential(col_ptr,lat,geopotential)
 319             trlValueBot=geopotential(ilyr+1)
 320             trlValueTop=geopotential(ilyr  )
 321           else
 322             col_ptr=>col_getColumn(columnTrlOnTrlLev,headerIndex,varName)
 323             trlValueBot=col_ptr(ilyr+1)
 324             trlValueTop=col_ptr(ilyr  )
 325           end if
 326         end if
 327         zomp = zvar-(zwb*trlValueBot+zwt*trlValueTop)
 328         call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex,zomp)
 329
 330       else if (ixtr == 2) then
 331
 332         ! Process only GZ that is data below model's orography
 333         if (bufrCode == bufr_negz ) then
 334           ! Forward nonlinear model for geopotential data below model's orography
 335           ztvg = (1.0d0 + MPC_DELTA_R8 * col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'HU'))*  &
 336                col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'TT')
 337
 338           ! convert height of surface to geopotential
 339           heightSfc(1) = col_getHeight(columnTrlOnTrlLev,0,headerIndex,'SF')
 340           lat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
 341           call phf_height2geopotential(heightSfc,lat,geopotentialSfc)
 342
 343           zomp = (  zvar - geopotentialSfc(1) -  &
 344                ztvg/(temperatureLapseRate/ec_rg)*(1.D0-(zlev/col_getElem(columnTrlOnTrlLev,1,headerIndex,'P0'))**zexp))
 345           call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex,zomp)
 346         end if
 347
 348       end if
 349
 350    end do body
 351
 352    deallocate(geopotential)
 353
 354  end subroutine oop_ppp_nl
 355
 356  !--------------------------------------------------------------------------
 357  ! oop_zzz_nl
 358  !--------------------------------------------------------------------------
 359  subroutine oop_zzz_nl( columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam,  &
 360                         destObsColumn )
 361    !:Purpose: Computation of y - H(x) for geometric-height observations
 362    !           Interpolate vertically columnTrlOnTrlLev to the geometric heights (in
 363    !           meters) of the observations.
 364    !           A linear interpolation in z is performed.
 365    !
 366    !:Notes:
 367    !     As a first approximation, use the geopotential height.  Once this is
 368    !     working, this should be changed for a calculation of the geometric
 369    !     height.
 370    !
 371    !     Note that, in the case of an aladin HLOS wind, the correction to zvar
 372    !     (OBS_VAR) is not written back to obsSpaceData.  It is simply used to
 373    !     calculate OMP (which is written to obsSpaceData) and then is discarded.
 374    !     Thereafter, if one calculates OMP - O (this will be the uncorrected O),
 375    !     the result will be a corrected P.
 376    implicit none
 377
 378    ! Arguments:
 379    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 380    type(struct_obs),        intent(inout) :: obsSpaceData
 381    logical,                 intent(in)    :: beSilent
 382    integer,                 intent(in)    :: destObsColumn
 383    character(len=*),        intent(in)    :: cdfam ! family of observation
 384
 385    ! Locals:
 386    integer :: headerIndex,bodyIndex,ilyr,bufrCode,levIndexTop,levIndexBot
 387    integer :: bodyIndexStart,bodyIndexEnd,bodyIndex2
 388    integer :: found  ! a group of bit flags
 389    integer :: ierr, nulnam, fnom,fclos
 390    real(8) :: zvar,zwb,zwt
 391    real(8) :: zlev,zpt,zpb,zomp
 392    real(8) :: trlValueBot,trlValueTop
 393    character(len=4) :: varLevel
 394    real(8) :: value   ! temporary holder
 395    real(8) :: azimuth ! HLOS wind direction CW from true north
 396    real(8) :: tempRef ! reference temperature used to calculate HLOS wind
 397    real(8) :: presRef ! reference pressure used to calculate HLOS wind
 398    real(8) :: dwdp    ! derivative of HLOS wind wrt P
 399    real(8) :: dwdt    ! derivative of HLOS wind wrt T
 400    real(8) :: uuLyr, vvLyr   ! wind on layer, OBS_LYR
 401    real(8) :: uuLyr1,vvLyr1  ! wind on layer plus 1
 402    real(8) :: ttLyr, ppLyr   ! T, P on layer, OBS_LYR
 403    real(8) :: ttLyr1,ppLyr1  ! T, P on layer, OBS_LYR plus 1
 404    real(8) :: ttbg,  ppbg    ! background T, P at the observation location
 405    logical :: list_is_empty
 406
 407    ! Namelist variables:
 408    logical :: do_adjust_aladin ! choose to adjust obs value as if it was retrieved using our temp and pressure 
 409
 410    namelist /NAMALADIN_OBS/do_adjust_aladin
 411
 412    if (.not.beSilent) write(*,*) "Entering subroutine oop_zzz_nl"
 413
 414    call obs_set_current_body_list(obsSpaceData, cdfam, list_is_empty)
 415
 416    if (list_is_empty)then
 417      return
 418    end if
 419
 420    ! Read in the namelist NAMALADIN_OBS
 421    do_adjust_aladin = .false.
 422    nulnam=0
 423    ierr=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
 424    read(nulnam,nml=namaladin_obs,iostat=ierr)
 425    if (ierr.ne.0) call utl_abort('oop_zzz_nl: Error reading namelist')
 426    if (.not.beSilent) write(*,nml=namaladin_obs)
 427    ierr=fclos(nulnam)
 428
 429    BODY: do
 430      bodyIndex = obs_getBodyIndex(obsSpaceData)
 431      if (bodyIndex < 0) exit BODY
 432
 433      ! Process all geometric-height data within the domain of the model
 434      if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) /= obs_assimilated .or.  &
 435          obs_bodyElem_i(obsSpaceData,OBS_XTR,bodyIndex) /= 0 .or.  &
 436          obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) /= 1 ) &
 437        cycle BODY
 438      ! So, OBS_VCO==1 => OBS_PPP is a height in m
 439
 440      bufrCode=obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
 441      zvar=obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex)
 442      zlev=obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
 443      headerIndex=obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
 444
 445      ilyr = obs_bodyElem_i(obsSpaceData,OBS_LYR,bodyIndex)
 446      varLevel = vnl_varLevelFromVarnum(bufrCode)
 447      zpt= col_getHeight(columnTrlOnTrlLev,ilyr  ,headerIndex,varLevel)
 448      zpb= col_getHeight(columnTrlOnTrlLev,ilyr+1,headerIndex,varLevel)
 449      zwb  = (zpt-zlev)/(zpt-zpb)
 450      zwt  = 1.d0 - zwb
 451
 452      select case (bufrCode)
 453      case (bufr_neal) ! Aladin HLOS wind observation
 454        ! Scan body indices for the needed attributes
 455        found = 0
 456        bodyIndexStart = obs_headElem_i(obsSpaceData, OBS_RLN, headerIndex)
 457        bodyIndexEnd   = obs_headElem_i(obsSpaceData, OBS_NLV, headerIndex) &
 458                       + bodyIndexStart - 1
 459        tempRef = 0.0d0
 460        dwdt    = 0.0d0
 461        presRef = 0.0d0
 462        dwdp    = 0.0d0
 463        BODY_SUPP: do bodyIndex2 = bodyIndexStart, bodyIndexEnd
 464
 465          value = obs_bodyElem_r(obsSpaceData, OBS_VAR, bodyIndex2)
 466          select case(obs_bodyElem_i(obsSpaceData, OBS_VNM, bodyIndex2))
 467          case(bufr_NEAZ)
 468            azimuth = value * MPC_RADIANS_PER_DEGREE_R8
 469            found = ibset(found,0)
 470
 471          case(bufr_nett)
 472            tempRef = value
 473            found = ibset(found,1)
 474
 475          case(bufr_neps)
 476            presRef = value
 477            found = ibset(found,2)
 478
 479          case(bufr_NEDWDP)
 480            dwdp = value
 481            found = ibset(found,3)
 482
 483          case(bufr_NEDWDT)
 484            dwdt = value
 485            found = ibset(found,4)
 486          end select
 487
 488          if (popcnt(found) == 5) exit BODY_SUPP
 489        end do BODY_SUPP
 490
 491        if (.not. btest(found,0))then
 492          ! The azimuth was not found.  The observation cannot be treated
 493          ! Set the assimilation flag to 0 to ignore this datum later.
 494          call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex,obs_notAssimilated)
 495          cycle BODY
 496        end if
 497
 498        ! Obtain the needed forecast data
 499        uuLyr =col_getElem(columnTrlOnTrlLev,ilyr,  headerIndex,'UU')
 500        uuLyr1=col_getElem(columnTrlOnTrlLev,ilyr+1,headerIndex,'UU')
 501        vvLyr =col_getElem(columnTrlOnTrlLev,ilyr,  headerIndex,'VV')
 502        vvLyr1=col_getElem(columnTrlOnTrlLev,ilyr+1,headerIndex,'VV')
 503        ttLyr =col_getElem(columnTrlOnTrlLev,ilyr,  headerIndex,'TT')
 504        ttLyr1=col_getElem(columnTrlOnTrlLev,ilyr+1,headerIndex,'TT')
 505        ppLyr =col_getPressure(columnTrlOnTrlLev,ilyr  ,headerIndex,'MM')
 506        ppLyr1=col_getPressure(columnTrlOnTrlLev,ilyr+1,headerIndex,'MM')
 507
 508        ! Interpolate forecast T, P to the observation location
 509        ttbg  = zwb*ttLyr1 + zwt*ttLyr
 510        ppbg  = zwb*ppLyr1 + zwt*ppLyr
 511
 512        ! Adjust zvar, the HLOS wind observation, if all attributes are available
 513        if (do_adjust_aladin .and. (popcnt(found) == 5)) then
 514          ! Adjust in situ the HLOS wind data from obsSpaceData to account for
 515          ! the differences between our T, P forecast fields and those of the NWP
 516          ! site that calculated the HLOS wind values.  The goal is to produce an
 517          ! HLOS wind observation as if it had been calculated by us.
 518          zvar = zvar + (ttbg - tempRef) * dwdt &
 519                      + (ppbg - presRef) * dwdp
 520        end if
 521
 522        ! Apply the nonlinear aladin observation operator
 523        trlValueBot= -vvLyr1*cos(azimuth) - uuLyr1*sin(azimuth)
 524        trlValueTop= -vvLyr *cos(azimuth) - uuLyr *sin(azimuth)
 525
 526        ! For aladin data, the temperature and pressure are really *reference*
 527        ! values.  They must not be assimilated.  Mark them so.
 528      case (bufr_nett)
 529        call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex,obs_notAssimilated)
 530        cycle BODY
 531      case (bufr_neps)
 532        call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex,obs_notAssimilated)
 533        cycle BODY
 534      case (bufr_nees)
 535        call utl_abort('oop_zzz_nl: CANNOT ASSIMILATE ES!!!')
 536
 537      case default
 538        ! These are the profiler observations
 539        levIndexTop = ilyr + col_getOffsetFromVarno(columnTrlOnTrlLev,bufrCode)
 540        levIndexBot = levIndexTop+1
 541        trlValueBot=col_getElem(columnTrlOnTrlLev,levIndexBot,headerIndex)
 542        trlValueTop=col_getElem(columnTrlOnTrlLev,levIndexTop,headerIndex)
 543      end select
 544
 545      zomp = zvar-(zwb*trlValueBot+zwt*trlValueTop)
 546      call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex,zomp)
 547
 548    enddo BODY
 549
 550  end subroutine oop_zzz_nl
 551
 552  !--------------------------------------------------------------------------
 553  ! oop_sfc_nl
 554  !--------------------------------------------------------------------------
 555  subroutine oop_sfc_nl( columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam,  &
 556                         destObsColumn )
 557    !:Purpose:  Computation of the residuals to the observations
 558    !            FOR SURFACE DATA (except ground-based GPS zenith delay).
 559    implicit none
 560
 561    ! Arguments:
 562    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 563    type(struct_obs)       , intent(inout) :: obsSpaceData
 564    logical                , intent(in)    :: beSilent
 565    character(len=*)       , intent(in)    :: cdfam ! family of observation
 566    integer                , intent(in)    :: destObsColumn
 567
 568    ! Locals:
 569    integer :: columnLevelIndex, bufrCode, headerIndex, bodyIndex
 570    integer :: ierr, nulnam, fnom, fclos
 571    real(8) :: obsValue, trlVirtTemp
 572    real(8) :: pCorrectionFactor, coeffA, coeffB
 573    real(8) :: obsHeight, deltaT, delTdelZ, trlLevelHeight
 574    real(8) :: trlValue
 575    real(8) :: trlUwind, trlVwind, squareSum, trlWindSpeed 
 576    character(len=4) :: varLevel
 577    logical, save :: firstCall = .true.
 578
 579    ! Namelist variables:
 580    logical, save :: adjustTemperature ! choose to adjust near-sfc temperature using lapse rate and height difference
 581
 582    namelist /namSurfaceObs/adjustTemperature
 583
 584    if (.not.beSilent) write(*,*) "Entering subroutine oop_sfc_nl"
 585
 586    ! Read in the namelist namSurfaceObs
 587    if (firstCall) then
 588      adjustTemperature = .true. ! default value
 589
 590      if (utl_isNamelistPresent('namSurfaceObs','./flnml')) then
 591        nulnam=0
 592        ierr=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
 593        read(nulnam,nml=namSurfaceObs,iostat=ierr)
 594        if (ierr /= 0) call utl_abort('oop_sfc_nl: Error reading namelist namSurfaceObs')
 595        if (.not. beSilent) write(*,nml=namSurfaceObs)
 596        ierr=fclos(nulnam)
 597      else if (.not. beSilent) then
 598        write(*,*)
 599        write(*,*) 'oop_sfc_nl: namSurfaceObs is missing in the namelist. The default value will be taken.'
 600      end if
 601      firstCall = .false.
 602    end if
 603
 604    ! loop over all header indices of the specified family with surface obs
 605    call obs_set_current_header_list(obsSpaceData,cdfam)
 606    HEADER: do
 607       headerIndex = obs_getHeaderIndex(obsSpaceData)
 608       if (headerIndex < 0) exit HEADER
 609       ! loop over all body indices for this headerIndex
 610       call obs_set_current_body_list(obsSpaceData, headerIndex)
 611       BODY: do 
 612          bodyIndex = obs_getBodyIndex(obsSpaceData)
 613          if (bodyIndex < 0) exit BODY
 614
 615          ! only process height level observations flagged to be assimilated
 616          if ( obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) /= 1 .or.  &
 617               obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) /= obs_assimilated ) cycle BODY
 618
 619          ! only process this set of surface observations
 620          bufrCode=obs_bodyElem_i (obsSpaceData,OBS_VNM,bodyIndex)
 621          if (bufrCode /= bufr_nets .and. bufrCode /= bufr_neps   .and.  &
 622              bufrCode /= bufr_neus .and. bufrCode /= bufr_nevs   .and.  &
 623              bufrCode /= bufr_ness .and. bufrCode /= bufr_nepn   .and.  &
 624              bufrCode /= bufr_vis  .and. bufrCode /= bufr_logVis .and.  &
 625              bufrCode /= bufr_gust .and.  &
 626              bufrCode /= bufr_radarPrecip .and.  &
 627              bufrCode /= bufr_logRadarPrecip .and. &
 628              bufrCode /= bufr_nefs ) cycle BODY
 629
 630          obsValue = obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex)
 631          obsHeight= obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
 632          ! obsHeigth = station height + obs validity height offset as defined in obsUtil_mod->surfvcord
 633          varLevel = vnl_varLevelFromVarnum(bufrCode)
 634
 635          if (bufrCode == bufr_nets .or. bufrCode == bufr_ness .or.  &
 636              bufrCode == bufr_neus .or. bufrCode == bufr_nevs .or.  &
 637              bufrCode == bufr_gust .or. bufrCode == bufr_vis  .or.  &
 638              bufrCode == bufr_logVis .or. &
 639              bufrCode == bufr_radarPrecip .or.  &
 640              bufrCode == bufr_logRadarPrecip ) then
 641
 642             ! T1.5m,(T-Td)1.5m,u10m,v10m
 643             ! In this section we always extrapolate linearly the trial
 644             ! field at the model surface to the height of the
 645             ! surface observation whether the observation is above or
 646             ! below the model surface.
 647             ! NOTE: For (T-Td)1.5m,u10m,v10m we do a zero order extrapolation
 648
 649             if (bufrCode == bufr_nets .and. adjustTemperature) then
 650               delTdelZ = temperatureLapseRate
 651             else
 652               delTdelZ = 0.0d0
 653             end if
 654
 655             if (bufrCode == bufr_ness) then
 656                trlValue = hutoes(col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'HU'),  &
 657                     col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'TT'),  &
 658                     col_getPressure(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'TH'))
 659             else
 660                columnLevelIndex = col_getNumLev(columnTrlOnTrlLev,varLevel) + col_getOffsetFromVarno(columnTrlOnTrlLev,bufrCode)
 661                trlValue = col_getElem(columnTrlOnTrlLev,columnLevelIndex,headerIndex)
 662             end if
 663             trlLevelHeight = col_getHeight(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,varLevel),headerIndex,varLevel)
 664
 665             call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex,  &
 666                  (obsValue-trlValue + delTdelZ*(obsHeight-trlLevelHeight)) )
 667
 668          else if ( bufrCode == bufr_neps .or. bufrCode == bufr_nepn ) then
 669
 670            ! Surface (PS) & mean sea level (PN) pressure cases
 671            ! Background surface pressure are corrected for the height difference with the 
 672            ! observation. For mean sea level observation, the observation height = 0.
 673
 674            ! 1) Temperature difference = lapse-rate (6.5 degree/km) * height difference (dz)
 675            deltaT = temperatureLapseRate*(obsHeight-col_getHeight(columnTrlOnTrlLev,0,headerIndex,'SF'))
 676
 677            ! 2) Compute the 1.5m background virtual temperature: Tv = T*(1+0.608*HU)
 678            trlVirtTemp = (1.0d0 + MPC_DELTA_R8 *  &
 679                   col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'HU')) *  &
 680                   col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'TH'),headerIndex,'TT')
 681            
 682            ! 3) Compute the correction coefficient
 683            ! The legacy code says...
 684            coeffA = (trlVirtTemp-deltaT)/trlVirtTemp
 685            coeffB = 1.0D0/(MPC_RGAS_DRY_AIR_R8*temperatureLapseRate/ec_rg)
 686            pCorrectionFactor = coeffA**coeffB
 687            ! However, the U.S. Standard Atmosphere (1976, U.S. Government Printing Office, Washington, D.C., 1976*)
 688            ! at page 12 says...
 689            ! coeffA = trlVirtTemp/(trlVirtTemp+deltaT)
 690            ! but the former was found to perform better (gives lower O-P values) than the latter by J-F Caron in 2018
 691
 692            ! 4) O-P, where P = P0 * pCorrectionFactor (Eq. 33a at page 12 of the U.S. Standard Atmosphere, 1976, 
 693            !                                           U.S. Government Printing Office, Washington, D.C., 1976*)
 694            call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex,  &
 695                               obsValue-(col_getElem(columnTrlOnTrlLev,1,headerIndex,'P0')*pCorrectionFactor))
 696
 697            ! (*) available at https://ntrs.nasa.gov/api/citations/19770009539/downloads/19770009539.pdf
 698
 699          else if (bufrCode == bufr_nefs) then 
 700
 701            trlUwind = col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'MM'),headerIndex,'UU')
 702            trlVwind = col_getElem(columnTrlOnTrlLev,col_getNumLev(columnTrlOnTrlLev,'MM'),headerIndex,'VV')
 703            squareSum = trlUwind**2 + trlVwind**2
 704            if ( squareSum > 1.d-10 ) then
 705              trlWindSpeed = sqrt(squareSum)
 706            else
 707              trlWindSpeed = 0.0
 708            end if
 709            call obs_bodySet_r(obsSpaceData,OBS_OMP,bodyIndex,  &
 710                              obsValue-trlWindSpeed)  
 711
 712          end if
 713
 714       end do BODY
 715
 716    end do HEADER
 717
 718  end subroutine oop_sfc_nl
 719
 720  !--------------------------------------------------------------------------
 721  ! oop_sst_nl
 722  !--------------------------------------------------------------------------
 723  subroutine oop_sst_nl( columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam,  &
 724                         destObsColumn )
 725    !:Purpose: Computation of Jo and the residuals to the observations
 726    !           for Sea Surface Temperature (SST) data.
 727    implicit none
 728
 729    ! Arguments:
 730    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 731    type(struct_obs)       , intent(inout) :: obsSpaceData
 732    logical                , intent(in)    :: beSilent
 733    character(len=*)       , intent(in)    :: cdfam        ! family of observation
 734    integer                , intent(in)    :: destObsColumn
 735
 736    ! Locals:
 737    integer          :: bufrCode, headerIndex, bodyIndex
 738    real(8)          :: obsValue
 739    character(len=4) :: varName
 740
 741    if (.not.beSilent) write(*,*) "Entering subroutine oop_sst_nl, family: ", trim(cdfam)
 742
 743    ! loop over all header indices of the specified family with surface obs
 744    call obs_set_current_header_list( obsSpaceData, cdfam )
 745
 746    HEADER: do
 747
 748      headerIndex = obs_getHeaderIndex( obsSpaceData )
 749      if ( headerIndex < 0 ) exit HEADER
 750
 751      ! loop over all body indices for this headerIndex
 752      call obs_set_current_body_list( obsSpaceData, headerIndex )
 753
 754      BODY: do
 755
 756        bodyIndex = obs_getBodyIndex( obsSpaceData )
 757        if ( bodyIndex < 0 ) exit BODY
 758
 759        ! only process observations flagged to be assimilated
 760        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) /= obs_assimilated ) cycle BODY
 761
 762        bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
 763
 764        if ( bufrCode /= bufr_sst ) cycle BODY
 765        
 766        if ( col_varExist(columnTrlOnTrlLev,'TM') ) then
 767          varName = 'TM'
 768        else
 769          varName = 'TG'
 770        end if
 771
 772        obsValue = obs_bodyElem_r( obsSpaceData, OBS_VAR, bodyIndex )
 773        call obs_bodySet_r( obsSpaceData, destObsColumn, bodyIndex, &
 774                            obsValue - ( col_getElem( columnTrlOnTrlLev, 1, headerIndex, varName ) ))
 775
 776      end do BODY
 777
 778    end do HEADER
 779
 780  end subroutine oop_sst_nl
 781
 782  !--------------------------------------------------------------------------
 783  ! oop_hydro_nl
 784  !--------------------------------------------------------------------------
 785  subroutine oop_hydro_nl(columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam,  &
 786                          destObsColumn)
 787    !:Purpose: To computate Jo and the residuals to the observations
 788    !           for hydrological data
 789    implicit none
 790
 791    ! Arguments:
 792    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 793    type(struct_obs)       , intent(inout) :: obsSpaceData
 794    logical                , intent(in)    :: beSilent
 795    character(len=*)       , intent(in)    :: cdfam        ! family of observation
 796    integer                , intent(in)    :: destObsColumn
 797
 798    ! Locals:
 799    integer          :: bufrCode, headerIndex, bodyIndex
 800    real(8)          :: obsValue
 801    character(len=4) :: varName
 802
 803    if (.not.beSilent) write(*,*) "Entering subroutine oop_hydro_nl, family: ", trim(cdfam)
 804
 805    ! loop over all header indices of the specified family with surface obs
 806    call obs_set_current_header_list( obsSpaceData, cdfam )
 807
 808    HEADER: do
 809      headerIndex = obs_getHeaderIndex( obsSpaceData )
 810      if ( headerIndex < 0 ) exit HEADER
 811
 812      ! loop over all body indices for this headerIndex
 813      call obs_set_current_body_list( obsSpaceData, headerIndex )
 814
 815      BODY: do
 816        bodyIndex = obs_getBodyIndex( obsSpaceData )
 817        if ( bodyIndex < 0 ) exit BODY
 818
 819        ! only process observations flagged to be assimilated
 820        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) /= obs_assimilated ) cycle BODY
 821
 822        bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
 823
 824        if ( bufrCode /= bufr_riverFlow ) cycle BODY
 825
 826        obsValue = obs_bodyElem_r( obsSpaceData, OBS_VAR, bodyIndex )
 827        varName = vnl_varNameFromVarNum(bufrCode)
 828        call obs_bodySet_r( obsSpaceData, destObsColumn, bodyIndex, &
 829                            obsValue - col_getElem(columnTrlOnTrlLev,1,headerIndex, varName_opt = varName) )
 830
 831      end do BODY
 832
 833    end do HEADER
 834
 835  end subroutine oop_hydro_nl
 836
 837  !--------------------------------------------------------------------------
 838  ! oop_ice_nl
 839  !--------------------------------------------------------------------------
 840  subroutine oop_ice_nl( columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam,  &
 841                         destObsColumn )
 842    !:Purpose: Computation of Jo and the residuals to the observations
 843    !           FOR SEA ICE CONCENTRATION DATA
 844    implicit none
 845
 846    ! Arguments:
 847    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 848    type(struct_obs)       , intent(inout) :: obsSpaceData
 849    logical                , intent(in)    :: beSilent
 850    character(len=*)       , intent(in)    :: cdfam        ! family of observation
 851    integer                , intent(in)    :: destObsColumn
 852
 853    ! Locals:
 854    integer          :: bufrCode, headerIndex, bodyIndex
 855    integer          :: obsDate, monthIndex
 856    integer          :: trackCellNum
 857    real(8)          :: obsValue, backValue
 858    real(8)          :: conc
 859    character(len=4) :: varName
 860    character(len=8) :: ccyymmdd
 861
 862    if (.not. beSilent) write(*,*) "Entering subroutine oop_ice_nl, family: ", trim(cdfam)
 863
 864    ! loop over all body indices
 865    call obs_set_current_body_list( obsSpaceData, cdfam )
 866
 867    BODY: do
 868
 869      bodyIndex = obs_getBodyIndex( obsSpaceData )
 870      if ( bodyIndex < 0 ) exit BODY
 871
 872      ! only process observations flagged to be assimilated
 873      if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) /= obs_assimilated ) cycle BODY
 874
 875      bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
 876
 877      headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
 878      varName = vnl_varNameFromVarNum(bufrCode)
 879
 880      select case (bufrCode)
 881      case(bufr_icec, bufr_icep)
 882        backValue = 100.0d0*col_getElem( columnTrlOnTrlLev, 1, headerIndex, varName )
 883      case(bufr_icev)
 884        backValue = 1.0d0*col_getElem( columnTrlOnTrlLev, 1, headerIndex, varName )
 885      case(bufr_ices)
 886        obsDate = obs_headElem_i( obsSpaceData, OBS_DAT, headerIndex ) 
 887        write(ccyymmdd, FMT='(i8.8)') obsDate
 888        read(ccyymmdd(5:6), FMT='(i2)') monthIndex
 889        conc = col_getElem( columnTrlOnTrlLev, 1, headerIndex, varName)
 890        trackCellNum = obs_headElem_i( obsSpaceData, OBS_FOV, headerIndex )
 891        backValue = (1.0d0-conc)*oer_ascatAnisOpenWater(trackCellNum,monthIndex) + &
 892                         conc*oer_ascatAnisIce(trackCellNum,monthIndex)
 893      case default
 894        cycle BODY
 895      end select
 896
 897      obsValue = obs_bodyElem_r( obsSpaceData, OBS_VAR, bodyIndex )
 898      call obs_bodySet_r( obsSpaceData, destObsColumn, bodyIndex, obsValue - backValue )
 899
 900    end do BODY
 901
 902  end subroutine oop_ice_nl
 903
 904  !--------------------------------------------------------------------------
 905  ! oop_raDvel_nl
 906  !--------------------------------------------------------------------------
 907  subroutine oop_raDvel_nl(columnTrlOnTrlLev, obsSpaceData, beSilent, cdfam, &
 908                           destObsColumn)
 909    !:Purpose: Computation of Jo and OMP for Radar Doppler velocity observations 
 910    implicit none
 911
 912    ! Arguments:
 913    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
 914    type(struct_obs)       , intent(inout) :: obsSpaceData
 915    logical                , intent(in)    :: beSilent
 916    character(len=*)       , intent(in)    :: cdfam        ! family of observation
 917    integer                , intent(in)    :: destObsColumn
 918
 919    ! Locals:
 920    integer :: bodyIndex, headerIndex, levelIndex, bufrCode
 921    real(8) :: observedDoppler, simulatedDoppler
 922    real(8) :: levelAltLow, levelAltHigh
 923    real(8) :: radarAltitude, beamAzimuth, beamElevation, obsAltitude
 924    real(8) :: uuLow, uuHigh, vvLow, vvHigh, uuInterpolated, vvInterpolated
 925    real(8) :: interpWeight
 926
 927    call obs_set_current_header_list(obsSpaceData, cdfam)
 928    if (.not.beSilent) write(*,*) 'Entering subroutine oop_raDvel_nl, family: ', trim(cdfam)
 929
 930    
 931    !
 932    ! Loop over all header indices of the 'RA' family with schema 'radvel':
 933    !
 934    HEADER: do  
 935
 936      headerIndex = obs_getHeaderIndex(obsSpaceData)  
 937      if (headerIndex < 0) exit HEADER
 938  
 939      radarAltitude = obs_headElem_r(obsSpaceData, OBS_ALT,  headerIndex)
 940      beamAzimuth   = obs_headElem_r(obsSpaceData, OBS_RZAM, headerIndex) 
 941      beamElevation = obs_headElem_r(obsSpaceData, OBS_RELE, headerIndex)
 942      call obs_set_current_body_list(obsSpaceData, headerIndex)
 943      !
 944      ! Loop over all body indices of the 'RA' family with schema 'radvel':
 945      !
 946      BODY: do
 947        bodyIndex = obs_getBodyIndex(obsSpaceData)
 948        if (bodyIndex < 0) exit BODY
 949        ! Check that this observation has the expected bufr element ID
 950        bufrCode = obs_bodyElem_i(obsSpaceData, OBS_VNM, bodyIndex)
 951        if (bufrCode /= bufr_radvel) cycle BODY
 952        ! only process observations flagged to be assimilated
 953        if (obs_bodyElem_i(obsSpaceData, OBS_ASS, bodyIndex) /= obs_assimilated) cycle BODY
 954
 955        obsAltitude  = obs_bodyElem_r(obsSpaceData, OBS_PPP, bodyIndex) 
 956
 957        ! Levels that bracket the observation from OBS_LYR
 958        !   note to self:   like in GEM, level=1 is the highest level
 959        levelIndex = obs_bodyElem_i(obsSpaceData, OBS_LYR, bodyIndex)
 960
 961        levelAltHigh = col_getHeight(columnTrlOnTrlLev, levelIndex,   headerIndex,'MM')
 962        levelAltLow  = col_getHeight(columnTrlOnTrlLev, levelIndex+1, headerIndex,'MM')
 963
 964        ! Vertical interpolation of model wind at observation height
 965        interpWeight = (obsAltitude - levelAltLow)/(levelAltHigh - levelAltLow)
 966        uuHigh = col_getElem(columnTrlOnTrlLev, levelIndex,   headerIndex, 'UU')
 967        uuLow  = col_getElem(columnTrlOnTrlLev, levelIndex+1, headerIndex, 'UU')
 968        vvHigh = col_getElem(columnTrlOnTrlLev, levelIndex,   headerIndex, 'VV')
 969        vvLow  = col_getElem(columnTrlOnTrlLev, levelIndex+1, headerIndex, 'VV')
 970        uuInterpolated = uuLow + interpWeight*(uuHigh - uuLow)
 971        vvInterpolated = vvLow + interpWeight*(vvHigh - vvLow)
 972
 973        ! Doppler velocity is the projection of wind along direction of radar beam
 974        ! Positive values indicates velocities "away" from the radar
 975        simulatedDoppler = uuInterpolated*sin(beamAzimuth) + vvInterpolated*cos(beamAzimuth)
 976
 977        observedDoppler = obs_bodyElem_r(obsSpaceData, OBS_VAR, bodyIndex)     
 978
 979        call obs_bodySet_r(obsSpaceData, destObsColumn, bodyIndex, observedDoppler-simulatedDoppler)
 980
 981      end do BODY
 982    end do HEADER
 983    if (.not. beSilent) write(*,*) 'Ending subroutine oop_raDvel_nl, family: ', trim(cdfam)
 984
 985  end subroutine oop_raDvel_nl
 986
 987  !--------------------------------------------------------------------------
 988  ! oop_gpsro_nl
 989  !--------------------------------------------------------------------------
 990  subroutine oop_gpsro_nl(columnTrlOnTrlLev, obsSpaceData, beSilent, destObsColumn)
 991    !:Purpose: Computation of Jo and the residuals to the GPSRO observations
 992    !
 993    !:Note: gps_struct1sw_v2 allows calculation of partial derivatives of refractivity 
 994    !        in gps_diff object w.r.t TT/HU/GZ/P0. The indirect dependency refractivity 
 995    !        to TT/HU/P0 through GZ is now attributed to direct dependency of refractivity on GZ.
 996    implicit none
 997
 998    ! Arguments:
 999    type(struct_columnData)  , intent(in)    :: columnTrlOnTrlLev
1000    type(struct_obs)         , intent(inout) :: obsSpaceData
1001    logical                  , intent(in)    :: beSilent
1002    integer                  , intent(in)    :: destObsColumn
1003
1004    ! Locals:
1005    type(struct_vco), pointer :: vco_hr
1006    real(8) :: pjob, pjo1
1007    real(8) :: zlat, lat
1008    real(8) :: zlon, lon
1009    real(8) :: zazm, azm
1010    integer :: isat, iclf
1011    real(8) :: rad, geo
1012    real(8), allocatable :: zpp(:)
1013    real(8), allocatable :: ztt(:)
1014    real(8), allocatable :: zhu(:)
1015    real(8), allocatable :: zHeight(:)
1016    real(8), allocatable :: zuu(:)
1017    real(8), allocatable :: zvv(:)
1018    real(8) :: zp0, zmt
1019    real(8) :: hnh1, zobs, zmhx, zoer, zinc
1020    integer headerIndex, idatyp, bodyIndex
1021    integer jl, ngpslev, nwndlev
1022    logical  assim, firstheader, ldsc
1023    integer :: nh, nh1, iProfile, varNum
1024    type(gps_profile)           :: prf
1025    real(8)       , allocatable :: h   (:),azmv(:)
1026    type(gps_diff), allocatable :: rstv(:)
1027    integer :: Vcode
1028
1029    if (.not.beSilent) write(*,*)'ENTER oop_gpsro_nl'
1030
1031    vco_hr => col_getVco(columnTrlOnTrlLev)
1032    vcode = vco_hr%vcode
1033
1034    !
1035    ! Initializations
1036    !
1037    ngpslev=col_getNumLev(columnTrlOnTrlLev,'TH')
1038    nwndlev=col_getNumLev(columnTrlOnTrlLev,'MM')
1039    allocate(zpp(ngpslev))
1040    allocate(ztt(ngpslev))
1041    allocate(zhu(ngpslev))
1042    allocate(zHeight(ngpslev))
1043    allocate(zuu(ngpslev))
1044    allocate(zvv(ngpslev))
1045
1046    allocate( h    (gps_ro_maxprfsize) )
1047    allocate( azmv (gps_ro_maxprfsize) )
1048    allocate( rstv (gps_ro_maxprfsize) )
1049
1050    !
1051    ! Loop over all header indices of the 'RO' family:
1052    !
1053    call obs_set_current_header_list(obsSpaceData,'RO')
1054    firstheader = .true.
1055
1056    HEADER: do
1057       headerIndex = obs_getHeaderIndex(obsSpaceData)
1058       if (headerIndex < 0) exit HEADER
1059       !
1060       ! Process only refractivity data (codtyp 169)
1061       !
1062       idatyp = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
1063       if ( idatyp /= 169 ) cycle HEADER
1064       !
1065       ! Scan for requested data values of the profile, and count them
1066       !
1067       assim = .false.
1068       nh = 0
1069       call obs_set_current_body_list(obsSpaceData, headerIndex)
1070       BODY: do 
1071          bodyIndex = obs_getBodyIndex(obsSpaceData)
1072          if (bodyIndex < 0) exit BODY
1073          if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) then
1074             assim = .true.
1075             nh = nh + 1
1076          end if
1077       end do BODY
1078       !
1079       ! If no assimilations are requested, skip to next header
1080       !
1081       if (.not.assim) cycle HEADER
1082       iProfile = gps_iprofile_from_index(headerIndex)
1083       varNum   = gps_vRO_IndexPrf(iProfile, 2)
1084       !
1085       ! Basic geometric variables of the profile:
1086       !
1087       isat = obs_headElem_i(obsSpaceData,OBS_SAT,headerIndex)
1088       iclf = obs_headElem_i(obsSpaceData,OBS_ROQF,headerIndex)
1089       rad  = obs_headElem_r(obsSpaceData,OBS_TRAD,headerIndex)
1090       geo  = obs_headElem_r(obsSpaceData,OBS_GEOI,headerIndex)
1091       azm  = obs_headElem_r(obsSpaceData,OBS_AZA,headerIndex)
1092       zmt  = col_getHeight(columnTrlOnTrlLev,0,headerIndex,'SF')
1093       !
1094       ! Profile at the observation location:
1095       !
1096       zlat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
1097       zlon = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
1098       lat  = zlat * MPC_DEGREES_PER_RADIAN_R8
1099       lon  = zlon * MPC_DEGREES_PER_RADIAN_R8
1100       zazm = azm / MPC_DEGREES_PER_RADIAN_R8
1101       zp0  = col_getElem(columnTrlOnTrlLev,1,headerIndex,'P0')
1102       do jl = 1, ngpslev
1103          !
1104          ! Profile x
1105          !
1106          zpp(jl) = col_getPressure(columnTrlOnTrlLev,jl,headerIndex,'TH')
1107          ztt(jl) = col_getElem(columnTrlOnTrlLev,jl,headerIndex,'TT') - MPC_K_C_DEGREE_OFFSET_R8
1108          zhu(jl) = col_getElem(columnTrlOnTrlLev,jl,headerIndex,'HU')
1109          zHeight(jl) = col_getHeight(columnTrlOnTrlLev,jl,headerIndex,'TH')
1110       end do
1111
1112       if (Vcode == 5002) then
1113          ! case with top thermo level above top momentum level (Vcode=5002)
1114          do jl = 1, nwndlev
1115             zuu(jl) = col_getElem(columnTrlOnTrlLev,jl  ,headerIndex,'UU')
1116             zvv(jl) = col_getElem(columnTrlOnTrlLev,jl  ,headerIndex,'VV')
1117          end do
1118       elseif (Vcode == 5005 .or. Vcode == 5100 .or. Vcode == 21001) then
1119          ! case without top thermo above top momentum level or unstaggered (Vcode=5001/4/5)
1120          do jl = 1, nwndlev-1
1121             zuu(jl) = col_getElem(columnTrlOnTrlLev,jl+1,headerIndex,'UU')
1122             zvv(jl) = col_getElem(columnTrlOnTrlLev,jl+1,headerIndex,'VV')
1123          end do
1124          zuu(nwndlev) = zuu(nwndlev-1)
1125          zvv(nwndlev) = zuu(nwndlev-1)
1126       else 
1127          ! case with Vcode /= 5002 and Vcode /= 5005 and Vcode /= 5100
1128          call utl_abort('oop_gpsro_nl: invalid vertical coord!')
1129       end if
1130       zuu(ngpslev) = zuu(nwndlev)
1131       zvv(ngpslev) = zuu(nwndlev)
1132       !
1133       ! GPS profile structure:
1134       !
1135       call gps_struct1sw_v2(ngpslev,zLat,zLon,zAzm,zMT,Rad,geo,zP0,zPP,zTT,zHU,zUU,zVV,zHeight,prf)
1136       ldsc=.not.btest(iclf,16-3)
1137       !
1138       ! Prepare the vector of all the observations:
1139       !
1140       nh1 = 0
1141       !
1142       ! Loop over all body indices for this headerIndex:
1143       ! (start at the beginning of the list)
1144       !
1145       call obs_set_current_body_list(obsSpaceData, headerIndex)
1146       BODY_2: do
1147          bodyIndex = obs_getBodyIndex(obsSpaceData)
1148          if (bodyIndex < 0) exit BODY_2
1149          if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) then
1150             nh1      = nh1 + 1
1151             h(nh1)   = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
1152             azmv(nh1)= zazm
1153          end if
1154       end do BODY_2
1155       !
1156       ! Apply the observation operator:
1157       !
1158       ! varNum = bufr_nebd (15037) or varNum = bufr_nerf (15036) for GPS-RO
1159       iProfile = gps_iprofile_from_index(headerIndex)
1160       if (varNum == bufr_nebd) then
1161          call gps_bndopv1(h, azmv, nh, prf, rstv)
1162       else
1163          call gps_refopv (h,       nh, prf, rstv)
1164       end if
1165       !
1166       ! Perform the (H(x)-Y)/S operation:
1167       !
1168       nh1 = 0
1169       pjob = 0.d0
1170       !
1171       ! Loop over all body indices for this headerIndex:
1172       ! (start at the beginning of the list)
1173       !
1174       call obs_set_current_body_list(obsSpaceData, headerIndex)
1175       BODY_3: do 
1176          bodyIndex = obs_getBodyIndex(obsSpaceData)
1177          if (bodyIndex < 0) exit BODY_3
1178          if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) then
1179             nh1 = nh1 + 1
1180             !
1181             ! Altitude:
1182             !
1183             hnh1= obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
1184             if (varNum == bufr_nebd) hnh1=hnh1-rad
1185             !
1186             ! Observation operator H(x)
1187             !
1188             zmhx = rstv(nh1)%var
1189             !
1190             ! Observation value    Y
1191             !
1192             zobs = obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex)
1193             !
1194             ! Observation error    S
1195             !
1196             zoer = obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex)
1197             !
1198             ! Normalized increment
1199             !
1200             zinc = (zmhx - zobs) / zoer
1201             !                           
1202             ! Datum contribution to Jo:
1203             !
1204             pjo1 = 0.5d0 * zinc * zinc
1205             !
1206             ! Per profile (PJOB) cumulatives:
1207             !
1208             pjob= pjob + pjo1
1209             !
1210             if (firstheader .and. .not.beSilent) then
1211                write(*,  &
1212                     '(A9,i10,3f7.2,f11.1,4f12.6,15f12.4)') 'DOBSGPSRO',  &
1213                     headerIndex,lat,lon,azm,hnh1,zobs,zoer,  &
1214                     zmhx,zinc,pjob,prf%gst(ngpslev)%var  
1215             end if
1216             call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex, zobs - zmhx)
1217          end if
1218       end do BODY_3
1219
1220       if ( .not.beSilent ) write(*,'(A9,i10,2f7.2,f18.10,f12.4,2I6)')  &
1221            'GPSRO_JO',headerIndex,lat,lon,pjob,zmt,isat,ldsc
1222       firstheader = .false.
1223    end do HEADER
1224
1225    deallocate( rstv )
1226    deallocate( azmv )
1227    deallocate( h    )
1228
1229    deallocate(zvv)
1230    deallocate(zuu)
1231    deallocate(zhu)
1232    deallocate(zHeight)
1233    deallocate(ztt)
1234    deallocate(zpp)
1235
1236    if (.not.beSilent) write(*,*)'EXIT oop_gpsro_nl'
1237
1238  end subroutine oop_gpsro_nl
1239
1240  !--------------------------------------------------------------------------
1241  ! oop_gpsgb_nl
1242  !--------------------------------------------------------------------------
1243  subroutine oop_gpsgb_nl( columnTrlOnTrlLev, obsSpaceData, beSilent, &
1244                           destObsColumn, analysisMode_opt )
1245    !
1246    !:Purpose: Computation of the residuals to the GB-GPS ZTD observations
1247    !
1248    implicit none
1249
1250    ! Arguments:
1251    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
1252    type(struct_obs)       , intent(inout) :: obsSpaceData
1253    logical                , intent(in)    :: beSilent
1254    integer                , intent(in)    :: destObsColumn
1255    logical,       optional, intent(in)    :: analysisMode_opt
1256
1257    ! Locals:
1258    real(8), allocatable :: zpp (:)
1259    real(8), allocatable :: ztt (:)
1260    real(8), allocatable :: zhu (:)
1261    real(8), allocatable :: zHeight (:)
1262    real(8) :: zlat, lat, zlon, lon
1263    real(8) :: zp0, zmt, zdzmin
1264    real(8) :: zobs, zoer, zinc, zhx, zlev
1265    real(8) :: zdz, zpsobs, zpsmod, zpwmod, zpomp, zpomps
1266    real(8) :: ztdomp(gps_gb_maxdata)
1267    real(8) :: bias, std
1268    integer :: headerIndex, bodyIndex, ioneobs, idatyp, bufrCode, index_ztd, iztd
1269    integer :: jl, nlev_T, nobs2p
1270    integer :: icount1, icount2, icount3, icount, icountp
1271    logical  :: assim, llrej, analysisMode, lfsl
1272    character(len=12) :: cstnid
1273    type(gps_profilezd)    :: prf
1274    type(gps_diff)         :: ztdopv
1275    !
1276    !     PW lower limit (mm) and Ps O-P upper limit (Pa) for ZTD assimilation
1277    !       Note:  1 mb = 100 Pa --> 2.2 mm ZTD
1278    !
1279    real(8) :: zpwmin, zpompmx
1280    data zpwmin    /   2.0d0 /
1281    data zpompmx   / 200.0d0 /
1282    !
1283    !     Criteria to select single observation (1-OBS mode)
1284    !
1285    !     Minimum value for ZTD O-P (m)
1286    real(8) :: xompmin
1287    data xompmin   / 0.015d0 /
1288    ! Minimum value for background (trial) PW (mm)
1289    real(8) :: xpwmin
1290    data xpwmin    / 20.0d0  /
1291    ! Maximum height difference between observation and background surface (m)
1292    real(8) :: xdzmax
1293    data xdzmax    / 400.0d0 /
1294
1295    if (.not.beSilent) write(*,*)'ENTER oop_gpsgb_nl'
1296
1297    if (present(analysisMode_opt)) then
1298       analysisMode = analysisMode_opt
1299    else
1300       analysisMode = .true.
1301    end if
1302
1303    zpomps = 0.0d0
1304
1305    ! Ensure Jacobian-related arrays are not allocated to force them to be recalculated in oop_H
1306    if (allocated(oop_vZTD_Jacobian)) deallocate(oop_vZTD_Jacobian)
1307
1308    zdzmin = gps_gb_dzmin      
1309    nobs2p = 50
1310
1311    nlev_T = col_getNumLev(columnTrlOnTrlLev,'TH')
1312    if (gps_gb_ltestop .and. .not.beSilent) write(*,*) '  col_getNumLev[columnTrlOnTrlLev,TH] = ',nlev_T
1313
1314    !
1315    ! Initializations
1316    !
1317    allocate(ztt(nlev_T))
1318    allocate(zhu(nlev_T))
1319    allocate(zHeight(nlev_T))
1320    allocate(zpp(nlev_T))
1321
1322    if ( .not.beSilent ) then
1323      write(*, *) ' '
1324      write(*, *) ' '
1325      write(*,'(A11,A9,3A8,A9,4A8,2A9,A7,A10,A11)')  &
1326           'OOP_GPSGB_NL','CSTNID','ZLAT','ZLON','ZLEV','ZDZ','ZOBS','ZOER','ZHX','O-P',  &
1327           'ZPOMPS','ZPOMP','ZPWMOD','ZINC2'
1328    end if
1329
1330    icount  = 0
1331    icount1 = 0
1332    icount2 = 0
1333    icount3 = 0
1334    icountp = 0
1335    ioneobs = -1
1336
1337    ! loop over all header indices of the 'GP' family (all obs locations/times)
1338    call obs_set_current_header_list(obsSpaceData,'GP')
1339
1340    HEADER: do
1341       headerIndex = obs_getHeaderIndex(obsSpaceData)
1342       if (headerIndex < 0) exit HEADER
1343
1344       ! Process only GP data (codtyp 189)
1345       idatyp = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
1346       if ( idatyp /= 189 ) cycle HEADER
1347
1348       assim = .false.
1349       zpsobs = -100.0d0
1350       lfsl = .false.
1351       cstnid = obs_elem_c(obsSpaceData,'STID',headerIndex)
1352       if (index(cstnid,'FSL_') > 0 .or. index(cstnid,'-NOAA') > 0) lfsl = .true.
1353
1354       ! Scan for requested ZTD assimilation.
1355       ! Get GPS antenna height ZLEV and Ps(ZLEV) (ZPSOBS)
1356       !
1357       ! loop over all body indices for this headerIndex (observations at location/time)
1358       call obs_set_current_body_list(obsSpaceData, headerIndex)
1359       BODY: do 
1360          bodyIndex = obs_getBodyIndex(obsSpaceData)
1361          if (bodyIndex < 0) exit BODY
1362          bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
1363          if ( (bufrCode == bufr_nezd) .and. &
1364               (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) ) then
1365             zlev = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
1366             assim = .true.
1367             ! Index in body of ZTD datum (assume at most 1 per header)
1368             index_ztd = bodyIndex
1369             icount = icount + 1
1370          end if
1371          if ( bufrCode == bufr_neps ) then
1372             if ( (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) .or. gps_gb_llblmet ) then
1373                zpsobs = obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex)
1374                zpomps = obs_bodyElem_r(obsSpaceData,destObsColumn,bodyIndex)
1375             end if
1376          end if
1377       end do BODY
1378
1379       ! If no ZTD assimilation requested, jump to next header
1380       if (.not.assim) cycle HEADER
1381
1382       ! Profile at the observation location:
1383       lat  = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
1384       lon  = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
1385       zlat = lat * MPC_DEGREES_PER_RADIAN_R8
1386       zlon = lon * MPC_DEGREES_PER_RADIAN_R8
1387       zmt  = col_getHeight(columnTrlOnTrlLev,0,headerIndex,'SF')
1388       zp0  = col_getElem(columnTrlOnTrlLev,1,headerIndex,'P0')
1389       do jl = 1, nlev_T
1390          zpp(jl) = col_getPressure(columnTrlOnTrlLev,jl,headerIndex,'TH')
1391          ztt(jl) = col_getElem(columnTrlOnTrlLev,jl,headerIndex,'TT')-MPC_K_C_DEGREE_OFFSET_R8
1392          zhu(jl) = col_getElem(columnTrlOnTrlLev,jl,headerIndex,'HU')
1393          zHeight(jl) = col_getHeight(columnTrlOnTrlLev,jl,headerIndex,'TH')
1394       end do
1395       zdz = zlev - zmt
1396
1397       ! Fill GPS ZTD profile structure (PRF):
1398       call gps_structztd_v2(nlev_T,lat,lon,zmt,zp0,zpp,ztt,zhu,zHeight,gps_gb_lbevis,gps_gb_irefopt,prf)
1399
1400       ! Apply the GPS ZTD observation operator
1401       ! --> output is model ZTD (type gps_diff) and P at obs height ZLEV
1402       call gps_ztdopv(zlev,prf,gps_gb_lbevis,zdzmin,ztdopv,zpsmod,gps_gb_iztdop)
1403
1404       ! Get model profile PW
1405       call gps_pw(prf,zpwmod)
1406       ! ZTD (m)
1407       zhx    = ztdopv%var
1408
1409       ! If analysis mode, reject ZTD data for any of the following conditions:
1410       !    (1) the trial PW is too low (extremely dry) 
1411       !    and if gps_gb_LASSMET=true and for NOAA/FSL sites only:
1412       !      (2) Ps observation is missing or out of normal range
1413       !      (3) the ABS(Ps(obs)-Ps(mod)) difference is too large
1414       llrej = .false.
1415       zpomp = -9999.0D0
1416       if ( analysisMode ) then
1417          llrej = ( zpwmod < zpwmin )
1418          if ( gps_gb_lassmet .and. lfsl ) then
1419             if ( .not. llrej ) then
1420                if ( zpsobs > 40000.0d0 .and. zpsobs <= 110000.0d0 ) then
1421                   zpomp = zpsobs - zpsmod
1422                   llrej = ( abs(zpomp) > zpompmx )
1423                   if ( llrej ) icount3 = icount3 + 1
1424                else
1425                   llrej = .true.
1426                   icount2 = icount2 + 1
1427                end if
1428             else
1429                icount1 = icount1 + 1
1430             end if
1431          end if
1432       end if
1433
1434       if ( llrej ) then
1435          call obs_bodySet_i(obsSpaceData,OBS_ASS,index_ztd, obs_notAssimilated)
1436          if ( .not. gps_gb_lassmet ) icount1 = icount1 + 1
1437       end if
1438
1439       ! Perform the (H(x)-Y)/SDERR operation
1440       !
1441       ! loop over all body indices for this headerIndex
1442       call obs_set_current_body_list(obsSpaceData, headerIndex)
1443       BODY_2: do 
1444          bodyIndex = obs_getBodyIndex(obsSpaceData)
1445          if (bodyIndex < 0) exit BODY_2
1446          bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
1447          if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated .and.  &
1448               bufrCode == bufr_nezd ) then
1449             icountp = icountp + 1
1450             !
1451             ! Observation value    Y
1452             !
1453             zobs = obs_bodyElem_r(obsSpaceData,OBS_VAR,bodyIndex)
1454             !
1455             ! Observation error    SDERR
1456             !
1457             zoer = obs_bodyElem_r(obsSpaceData,OBS_OER,bodyIndex)
1458             if ( zoer <= 0.0d0 ) then
1459                write(*,*) ' Problem with ZTD observation error!'
1460                write(*,*) ' Station =',cstnid
1461                write(*,*) ' Error =', zoer
1462                call utl_abort('OOP_GPSGB_NL: ABORT! BAD ZTD OBSERR') 
1463             end if
1464
1465             ! Observation height (m)
1466             !
1467             zlev = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
1468             !
1469             ! Normalized increment ZINC
1470             !
1471             ztdomp(icountp) = zobs - zhx
1472             zinc  = (zhx - zobs) / zoer
1473             call obs_bodySet_r(obsSpaceData,destObsColumn,bodyIndex, zobs - zhx)
1474
1475             !
1476             ! Apply data selection criteria for 1-OBS Mode
1477             !
1478             if ( gps_gb_l1obs .and. ioneobs == -1 ) then
1479                if ( (zobs-zhx) > xompmin .and. zpwmod > xpwmin .and.  &
1480                     abs(zdz) < xdzmax ) then
1481                   ioneobs = headerIndex
1482                   write(*,*) 'SINGLE OBS SITE = ',cstnid
1483                end if
1484             end if
1485             !
1486             ! Print data for first NOBS2P observations
1487             !
1488             if ( icountp <= nobs2p .and. .not.beSilent ) then
1489                write(*,  &
1490                     '(A14,A9,3(1x,f7.2),1x,f8.2,4(1x,f8.5),2(1x,f8.4),2x,f5.2,1x,f9.2,1x,f10.5)')  &
1491                     'OOP_GPSGB_NL: ',cstnid,zlat,zlon,zlev,zdz,zobs,zoer/gps_gb_yzderrwgt,zhx,-zinc*zoer,  &
1492                     zpomps/100.d0,zpomp/100.d0,zpwmod,zinc/zoer
1493             end if
1494
1495          end if
1496
1497       end do BODY_2
1498
1499    end do HEADER
1500
1501    deallocate(ztt)
1502    deallocate(zhu)
1503    deallocate(zHeight)
1504    deallocate(zpp)
1505
1506    if ( .not. beSilent ) then
1507      write(*,*) ' '
1508      write(*,*) 'NUMBER OF GPS ZTD DATA FLAGGED FOR ASSIMILATION = ', icountp
1509      if ( icountp > 0 ) then
1510         bias = sum(ztdomp(1:icountp))/real(icountp,8)
1511         std = 0.d0
1512         do jl = 1, icountp
1513            std = std + (ztdomp(jl)-bias)**2
1514         end do
1515         write(*, *) '     MEAN O-P (BIAS) [mm] = ', bias*1000.d0
1516         if (icountp > 1) then
1517            std = sqrt(std/(real(icountp,8)-1.d0))
1518            write(*, *) '     STD  O-P        [mm] = ', std*1000.d0
1519         else
1520            write(*, *) '     STD  O-P        Uncomputable since number of GPS ZTD observations is 1'
1521         end if
1522         write(*, *) ' '
1523      end if
1524    end if
1525
1526    if ( gps_gb_l1obs .and. analysisMode ) then
1527       ! Set assim flag to 0 for all observations except for selected record (site/time)
1528       if ( ioneobs /= -1 ) then
1529          call obs_set_current_header_list(obsSpaceData,'GP')
1530          icountp = 1
1531          HEADER_1: do
1532             headerIndex = obs_getHeaderIndex(obsSpaceData)
1533             if (headerIndex < 0) exit HEADER_1
1534             if (headerIndex /= ioneobs ) then
1535                call obs_set_current_body_list(obsSpaceData, headerIndex)
1536                BODY_1: do 
1537                   bodyIndex = obs_getBodyIndex(obsSpaceData)
1538                   if (bodyIndex < 0) exit BODY_1
1539                   call obs_bodySet_i(obsSpaceData,OBS_ASS,bodyIndex, obs_notAssimilated)
1540                end do BODY_1
1541             end if
1542          end do HEADER_1
1543       else
1544          call utl_abort('ERROR: FAILED TO SELECT SINGLE OBSERVATION!')
1545       end if
1546    end if
1547
1548    gps_gb_numztd = icountp
1549
1550    if ( analysisMode .and. icount > 0 .and. .not.gps_gb_l1obs .and. .not.beSilent ) then
1551       write(*,*) ' '
1552       write(*,*) '-----------------------------------------'
1553       write(*,*) ' SUMMARY OF ZTD REJECTIONS IN OOP_GPSGB_NL '
1554       write(*,*) '-----------------------------------------'
1555       write(*,*) ' TOTAL NUMBER OF ZTD DATA ORIGINALLY FLAGGED FOR ASSMILATION = ', icount
1556       write(*,*) '       NUMBER OF ZTD DATA       REJECTED DUE TO LOW TRIAL PW = ', icount1
1557       write(*,*) '       NUMBER OF ZTD DATA       REJECETD DUE TO    NO PS OBS = ', icount2
1558       write(*,*) '       NUMBER OF ZTD DATA       REJECETD DUE TO LARGE PS O-P = ', icount3
1559       write(*,*) ' TOTAL NUMBER OF REJECTED ZTD DATA                           = ', icount1+icount2+icount3
1560       write(*,*) '       PERCENT   REJECTED                                    = ',   &
1561            (real(icount1+icount2+icount3,8) / real(icount,8))*100.0d0
1562       write(*, *) ' TOTAL NUMBER OF ASSIMILATED ZTD DATA                        = ', icountp
1563       write(*,*) ' '
1564    end if
1565
1566    if ( icount > 0 .and. gps_gb_numztd > 0) then
1567
1568       if ( analysisMode ) then
1569          if ( .not.beSilent ) write(*,*) ' Number of GPS ZTD data to be assimilated (gps_gb_numZTD) = ', gps_gb_numztd
1570       else
1571          if ( .not.beSilent ) write(*,*) ' Number of GPS ZTD data for background check (gps_gb_numZTD) = ', gps_gb_numztd
1572       end if
1573
1574       if ( .not.beSilent ) write(*,*) ' Allocating and setting gps_ZTD_Index(gps_gb_numZTD)...'
1575       if (allocated(gps_ZTD_Index)) deallocate(gps_ZTD_Index)
1576       allocate(gps_ZTD_Index(gps_gb_numztd))
1577       iztd = 0
1578       call obs_set_current_header_list(obsSpaceData,'GP')
1579       HEADER_2: do
1580          headerIndex = obs_getHeaderIndex(obsSpaceData)
1581          if (headerIndex < 0) exit HEADER_2
1582          idatyp = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
1583          if ( idatyp == 189 ) then
1584             call obs_set_current_body_list(obsSpaceData, headerIndex)
1585             BODY_3: do 
1586                bodyIndex = obs_getBodyIndex(obsSpaceData)
1587                if (bodyIndex < 0) exit BODY_3
1588                bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
1589                if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated .and.  &
1590                     bufrCode == bufr_nezd ) then  
1591                   iztd = iztd + 1
1592                   gps_ZTD_Index(iztd) = headerIndex
1593                end if
1594             end do BODY_3
1595          end if
1596       end do HEADER_2
1597
1598       if ( iztd /= gps_gb_numztd ) then
1599          call utl_abort('ERROR: gps_ZTD_Index init: iztd /= gps_gb_numZTD!')
1600       end if
1601
1602    end if
1603
1604    if (.not.beSilent) write(*,*)'EXIT oop_gpsgb_nl'
1605
1606  end subroutine oop_gpsgb_nl
1607
1608  !--------------------------------------------------------------------------
1609  ! oop_tovs_nl
1610  !--------------------------------------------------------------------------
1611  subroutine oop_tovs_nl( columnTrl, obsSpaceData, datestamp, beSilent,  &
1612                          bgckMode_opt, option_opt, sourceObs_opt, destObs_opt )
1613    !:Purpose: Computation of the residuals to the tovs observations
1614    !           option_opt: defines input state:
1615    !              - 'HR': High Resolution background state,
1616    !              - 'LR': Low  Resolution background state, (CURRENTLY NOT SUPPORTED)
1617    !              - 'MO': Model state. (CURRENTLY NOT SUPPORTED)
1618    implicit none
1619
1620    ! Arguments:
1621    type(struct_columnData),    intent(in)    :: columnTrl
1622    type(struct_obs),           intent(inout) :: obsSpaceData
1623    integer,                    intent(in)    :: datestamp
1624    logical,                    intent(in)    :: beSilent
1625    logical,          optional, intent(in)    :: bgckMode_opt
1626    character(len=*), optional, intent(in)    :: option_opt       ! only valid value is HR
1627    integer,          optional, intent(in)    :: sourceObs_opt ! usually set to OBS_VAR
1628    integer,          optional, intent(in)    :: destObs_opt   ! usually set to OBS_OMP
1629
1630    ! Locals:
1631    integer :: jdata, sourceObs, destObs
1632    logical :: llprint,bgckMode
1633    character(len=2) :: option
1634    integer :: channelIndex, tovsIndex
1635    real(pre_obsReal) :: zdtb, obsPRM
1636    integer :: idatyp, channelNumber
1637    integer :: headerIndex, bodyIndex
1638
1639    if (.not.obs_famExist(obsSpaceData,'TO', localMPI_opt = .true. )) return
1640
1641    ! 0. set default values if bgckMode, option and source/dest columns not specified
1642    !
1643
1644    if (.not.beSilent) write(*,*) "Entering subroutine oop_tovs_nl"
1645
1646    if (present(bgckMode_opt)) then
1647       bgckMode = bgckMode_opt
1648    else
1649       bgckMode = .false.
1650    end if
1651
1652    if (present(option_opt)) then
1653       option = option_opt(1:2)
1654    else
1655       option = 'HR'
1656    end if
1657    if ( option /= 'HR' ) call utl_abort('oop_tovs_nl: Invalid option for input state')
1658
1659    if (present(sourceObs_opt)) then
1660       sourceObs = sourceObs_opt
1661    else
1662       sourceObs = OBS_VAR
1663    end if
1664
1665    if (present(destObs_opt)) then
1666       destObs = destObs_opt
1667    else
1668       destObs = OBS_OMP
1669    end if
1670
1671    ! 1.   Prepare atmospheric profiles for all tovs observation points for use in rttov
1672    ! .    -----------------------------------------------------------------------------
1673    call tvs_fillProfiles(columnTrl,obsSpaceData,datestamp,"nl",beSilent)
1674
1675    if ( .not.beSilent ) write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
1676
1677    ! 2.   Compute radiance
1678    ! .    ----------------
1679    call tvs_rttov(obsSpaceData,bgckMode,beSilent)
1680    if ( .not.beSilent ) write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
1681
1682    ! 3.   Compute the residuals
1683    ! .    ----------------------------
1684    if ( option == 'HR' .or. option == 'LR' ) then
1685       do jdata=1,obs_numbody(obsSpaceData)
1686          call obs_bodySet_r(obsSpaceData,OBS_PRM,jdata, obs_bodyElem_r(obsSpaceData,sourceObs,jdata))
1687       end do
1688    end if
1689
1690    ! loop over all header indices of the 'TO' family
1691    call obs_set_current_header_list(obsSpaceData,'TO')
1692    HEADER: do
1693      headerIndex = obs_getHeaderIndex(obsSpaceData)
1694      if (headerIndex < 0) exit HEADER
1695
1696      ! Extract general information for this observation point
1697      !      ------------------------------------------------------
1698
1699      ! process only radiance data to be assimilated?
1700      idatyp = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
1701      if ( .not. tvs_isIdBurpTovs(idatyp) ) then
1702        write(*,*) 'oop_tovs_nl: warning unknown radiance codtyp present check NAMTOVSINST', idatyp
1703        cycle HEADER
1704      end if
1705      tovsIndex = tvs_tovsIndex(headerIndex)
1706      if ( tovsIndex == -1 ) cycle HEADER
1707
1708      ! Set the body list
1709      ! (& start at the beginning of the list)
1710      call obs_set_current_body_list(obsSpaceData, headerIndex)
1711      BODY: do
1712        bodyIndex = obs_getBodyIndex(obsSpaceData)
1713        if ( bodyIndex < 0 ) exit BODY
1714
1715        ! Only consider if flagged for assimilation
1716        if ( obs_bodyElem_i(obsSpaceData,obs_ASS,bodyIndex) /= obs_assimilated ) cycle BODY
1717
1718        call tvs_getChannelNumIndexFromPPP( obsSpaceData, headerIndex, bodyIndex, &
1719                                            channelNumber, channelIndex )
1720
1721        if ( channelIndex == 0 ) call utl_abort('oop_tovs_nl: error with channel number')
1722
1723        zdtb = obs_bodyElem_r(obsSpaceData,OBS_PRM,bodyIndex) - &
1724             tvs_radiance (tovsIndex) % bt(channelIndex)
1725        if ( tvs_debug ) then
1726          obsPRM = obs_bodyElem_r(obsSpaceData,OBS_PRM,bodyIndex)
1727          write(*,'(a,i4,2f8.2,f6.2)') ' channelNumber,sim,obs,diff= ', &
1728               channelNumber,  tvs_radiance (tovsIndex) % bt(channelIndex), &
1729               obsPRM, -zdtb
1730        end if
1731        call obs_bodySet_r(obsSpaceData,destObs,bodyIndex, zdtb)
1732
1733        ! inflate OBS_OER for all-sky assimilation
1734        call oer_inflateErrAllsky(obsSpaceData, bodyIndex, destObs, beSilent_opt=.true.)
1735
1736      end do BODY
1737
1738    end do HEADER
1739
1740    if (option == 'HR') then
1741      llprint = .true.
1742    else
1743      llprint = .false.
1744    end if
1745    if ( beSilent ) llprint = .false.
1746    if ( llprint ) call tvs_printDetailledOmfStatistics(obsSpaceData)
1747
1748  end subroutine oop_tovs_nl
1749
1750  !--------------------------------------------------------------------------
1751  ! oop_chm_nl
1752  !--------------------------------------------------------------------------
1753  subroutine oop_chm_nl( columnTrlOnTrlLev, obsSpaceData, destObsColumn )
1754    !:Purpose: Computation of the residuals to the observations
1755    !           for all observations of the CH (chemical constituents) family.
1756    !           The array columnTrlOnTrlLev contains the input model array.
1757    !           Stores OmP in OBS_OMP in obsSpaceData.
1758    implicit none
1759
1760    ! Arguments:
1761    type(struct_columnData), intent(in)    :: columnTrlOnTrlLev
1762    type(struct_obs)       , intent(inout) :: obsSpaceData
1763    integer                , intent(in)    :: destObsColumn
1764    
1765    if (.not.obs_famExist(obsSpaceData,'CH', localMPI_opt = .true. )) return
1766
1767    if ( destObsColumn /= obs_omp ) then
1768      write(*,*) 'oop_chm_nl: WARNING: Storing results in an obs column other than OBS_OMP. Not fully implemented.'
1769    end if
1770
1771    call oopc_CHobsoperators( columnTrlOnTrlLev,obsSpaceData,'nl', & ! 'nl' for non-linear operator
1772                              destObsColumn_opt=destObsColumn )
1773
1774  end subroutine oop_chm_nl
1775
1776  !--------------------------------------------------------------------------
1777  ! oop_Htl
1778  !--------------------------------------------------------------------------
1779  subroutine oop_Htl( columnAnlInc, columnTrlOnAnlIncLev, obsSpaceData, &
1780                      min_nsim, initializeLinearization_opt )
1781    !
1782    !:Purpose: Compute simulated observations from profiled model increments.
1783    !           It returns Hdx in OBS_WORK. Calls the several linear observation operators.
1784    implicit none
1785
1786    ! Arguments:
1787    type(struct_columnData),   intent(in)    :: columnAnlInc
1788    type(struct_columnData),   intent(inout) :: columnTrlOnAnlIncLev
1789    type(struct_obs)       ,   intent(inout) :: obsSpaceData
1790    integer,                   intent(in)    :: min_nsim
1791    logical,         optional, intent(in)    :: initializeLinearization_opt 
1792
1793    ! Locals:
1794    type(struct_vco), pointer :: vco_anl
1795    logical, save :: initializeLinearization = .true.
1796
1797    if ( mmpi_myid == 0 ) then
1798      write(*,*) 'OOP_Htl - Linearized observation operators'
1799    end if
1800
1801    vco_anl => col_getVco(columnTrlOnAnlIncLev)
1802
1803    ! Re-linearlize if it is asked for
1804    if ( present(initializeLinearization_opt) ) then
1805      initializeLinearization = initializeLinearization_opt
1806    end if
1807
1808    if ( initializeLinearization ) then
1809      ! Find interpolation layer in model profiles (used by several operators)
1810      if ( col_getNumLev(columnTrlOnAnlIncLev,'MM') > 1 ) call oop_vobslyrs(columnTrlOnAnlIncLev, obsSpaceData, beSilent=.false.)
1811
1812      ! Initialize some operators needed by linearized H
1813      call subasic_obs(columnTrlOnAnlIncLev)
1814
1815      initializeLinearization = .false.
1816    end if
1817
1818    call oop_Hpp()           ! fill in OBS_WORK : Hdx
1819
1820    call oop_Hsf()           ! fill in OBS_WORK : Hdx
1821
1822    call oop_Hto()           ! fill in OBS_WORK : Hdx
1823
1824    call oop_Hro( initializeLinearization_opt=initializeLinearization_opt )
1825
1826    if ( gps_gb_numZTD > 0 ) then 
1827      call oop_Hgp( initializeLinearization_opt=initializeLinearization_opt )
1828    end if
1829
1830    call oop_Hchm()          ! fill in OBS_WORK : Hdx
1831
1832    call oop_Hsst()          ! fill in OBS_WORK : Hdx
1833
1834    call oop_Hice()          ! fill in OBS_WORK : Hdx
1835
1836    call oop_Hhydro()        ! fill in OBS_WORK : Hdx
1837
1838    call oop_HheightCoordObs()      ! fill in OBS_WORK : Hdx
1839 
1840  contains
1841    
1842    !--------------------------------------------------------------------------
1843
1844    subroutine subasic_obs( columnTrlOnAnlIncLev )
1845      !:Purpose:   Initialise background state dependant factors
1846      !             and vectors for use in TLM and adjoint of
1847      !             non-linear operator
1848      implicit none
1849
1850      ! Arguments:
1851      type(struct_columnData), intent(inout) :: columnTrlOnAnlIncLev
1852
1853      ! Locals:
1854      type(struct_vco), pointer :: vco_anl
1855      integer :: jlev,columnIndex,nlev_T,vcode_anl,status
1856      real(8) :: zhu,one
1857
1858      if ( .not.col_varExist(columnTrlOnAnlIncLev,'TT') .or. .not.col_varExist(columnTrlOnAnlIncLev,'HU') ) return
1859
1860      write(*,*) 'subasic_obs: setting up linearized Tv operator'
1861
1862      vco_anl => col_getVco(columnTrlOnAnlIncLev)
1863      one=1.0D0
1864      nlev_T = col_getNumLev(columnTrlOnAnlIncLev,'TH')
1865      Vcode_anl = vco_anl%vCode
1866
1867      if ( Vcode_anl /= 5002 .and. Vcode_anl /= 5005 ) then
1868         call utl_abort('subasic_obs: invalid vertical coord!')
1869      end if
1870
1871      ! initialize virtual temperature operator
1872
1873      !$OMP PARALLEL DO PRIVATE(jlev,columnIndex,zhu)
1874      do jlev = 1, nlev_T
1875         do columnIndex=1,col_getNumCol(columnTrlOnAnlIncLev)
1876            zhu=col_getElem(columnTrlOnAnlIncLev,jlev,columnIndex,'HU')
1877            columnTrlOnAnlIncLev%oltv(1,jlev,columnIndex) = phf_fottva(zhu,one)
1878            columnTrlOnAnlIncLev%oltv(2,jlev,columnIndex) = phf_folnqva(zhu,col_getElem(columnTrlOnAnlIncLev,  &
1879                 jlev,columnIndex,'TT'),one)
1880         end do
1881      end do
1882      !$OMP END PARALLEL DO
1883
1884    end subroutine subasic_obs
1885
1886    !--------------------------------------------------------------------------
1887
1888    subroutine oop_Hpp()
1889      !: Purpose: Compute simulated Upper Air observations from profiled model
1890      !            increments.
1891      !            It returns Hdx in OBS_WORK
1892      !            Interpolate vertically the contents of commvo to
1893      !            the pressure levels of the observations.
1894      !            A linear interpolation in ln(p) is performed.
1895      implicit none
1896
1897      ! Locals:
1898      integer levIndexBot,levIndexTop
1899      integer headerIndex,INDEX_FAMILY,layerIndex
1900      integer J,bodyIndex,bufrCode,nlev_T
1901      real(8) ZDADPS
1902      real(8) ZWB,ZWT,ZLTV
1903      real(8) ZLEV,ZPT,ZPB
1904      real(8) delPT,delPB
1905      real(8) anlIncValueBot,anlIncValueTop,trlValueBot,trlValueTop
1906      integer, parameter :: numFamily=3
1907      character(len=2) :: list_family(numFamily)
1908      character(len=4) :: varLevel
1909
1910      list_family(1) = 'UA'
1911      list_family(2) = 'AI'
1912      list_family(3) = 'SW'
1913
1914      FAMILY: do index_family=1,numFamily
1915
1916         call obs_set_current_body_list(obsSpaceData,list_family(index_family))
1917         BODY: do 
1918            bodyIndex = obs_getBodyIndex(obsSpaceData)
1919            if (bodyIndex < 0) exit BODY
1920
1921            if (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated .and. &
1922                obs_bodyElem_i(obsSpaceData,OBS_XTR,bodyIndex) == 0               .and. &
1923                obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) == 2 ) then
1924               headerIndex = obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
1925               ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
1926               bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
1927               varLevel = vnl_varLevelFromVarnum(bufrCode)
1928               layerIndex   = obs_bodyElem_i(obsSpaceData,OBS_LYR,bodyIndex)
1929               levIndexTop  = layerIndex + col_getOffsetFromVarno(columnTrlOnAnlIncLev,bufrCode)
1930               levIndexBot  = levIndexTop+1
1931               ZPT    = col_getPressure(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,varLevel)
1932               ZPB    = col_getPressure(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,varLevel)
1933               delPT  = col_getPressure(columnAnlInc,layerIndex  ,headerIndex,varLevel)
1934               delPB  = col_getPressure(columnAnlInc,layerIndex+1,headerIndex,varLevel)
1935               ZWB  = LOG(ZLEV/ZPT)/LOG(ZPB/ZPT)
1936               ZWT  = 1.0D0 - ZWB
1937
1938               ZDADPS   = ( LOG(ZLEV/ZPB)*delPT/ZPT -   &
1939                    LOG(ZLEV/ZPT)*delPB/ZPB )/  &
1940                    LOG(ZPB/ZPT)**2
1941
1942               if ( bufrCode == bufr_nees ) then
1943                  anlIncValueBot=hutoes_tl(col_getElem(columnAnlInc,layerIndex+1,headerIndex,'HU'), &
1944                       col_getElem(columnAnlInc,layerIndex+1,headerIndex,'TT'), &
1945                       delPB, &
1946                       col_getElem(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'HU'), &
1947                       col_getPressure(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'TH'))
1948                  anlIncValueTop=hutoes_tl(col_getElem(columnAnlInc,layerIndex  ,headerIndex,'HU'), &
1949                       col_getElem(columnAnlInc,layerIndex  ,headerIndex,'TT'), &
1950                       delPT, &
1951                       col_getElem(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'HU'), &
1952                       col_getPressure(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'TH'))
1953                  trlValueBot=hutoes(col_getElem(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'HU'), &
1954                       col_getElem(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'TT'), &
1955                       col_getPressure(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'TH'))
1956                  trlValueTop=hutoes(col_getElem(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'HU'), &
1957                       col_getElem(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'TT'), &
1958                       col_getPressure(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'TH'))
1959               else
1960                  anlIncValueBot=col_getElem(columnAnlInc,levIndexBot,headerIndex)
1961                  anlIncValueTop=col_getElem(columnAnlInc,levIndexTop,headerIndex)
1962                  trlValueBot=col_getElem(columnTrlOnAnlIncLev,levIndexBot,headerIndex)
1963                  trlValueTop=col_getElem(columnTrlOnAnlIncLev,levIndexTop,headerIndex)
1964               end if
1965               call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex,   &
1966                    ZWB*anlIncValueBot + ZWT*anlIncValueTop+  &
1967                    (trlValueBot - trlValueTop)*  &
1968                    ZDADPS)
1969
1970            end if
1971
1972         end do BODY
1973
1974      end do FAMILY
1975
1976    end subroutine oop_Hpp
1977
1978    !--------------------------------------------------------------------------
1979
1980    subroutine oop_Hsf()
1981      !:Purpose: Compute simulated surface observations from profiled model
1982      !           increments.
1983      !           It returns Hdx in OBS_WORK
1984      !
1985      implicit none
1986
1987      ! Locals:
1988      integer :: levIndexBot,levIndexTop
1989      integer :: headerIndex,layerIndex
1990      integer :: J, bodyIndex, bufrCode, INDEX_FAMILY, nlev, nLev_T
1991      real(8) :: coeffA, coeffB
1992      real(8) :: ZWB, ZWT, ZLTV, trlVirtTemp
1993      real(8) :: ZPT, ZPB, ZDELPS, ZDELTV, deltaT, obsHeight
1994      real(8) :: anlIncValue
1995      real(8) :: delP
1996      real(8) :: anlIncUwind, anlIncVwind, trlUwind, trlVwind, squareSum, trlWindSpeed, anlIncWindSpeed
1997      integer, parameter :: numFamily=5
1998      character(len=2) :: list_family(numFamily)
1999      character(len=4) :: varLevel
2000
2001      !     Temperature lapse rate for extrapolation of height below model surface
2002      coeffB   = 1.0d0/(MPC_RGAS_DRY_AIR_R8*temperatureLapseRate/ec_rg)
2003      !
2004      !
2005      list_family(1) = 'UA'
2006      list_family(2) = 'SF'
2007      list_family(3) = 'SC'
2008      list_family(4) = 'GP'
2009      list_family(5) = 'RA'
2010
2011      FAMILY: do index_family=1,numFamily
2012
2013         call obs_set_current_body_list(obsSpaceData, list_family(index_family))
2014         BODY: do
2015            bodyIndex = obs_getBodyIndex(obsSpaceData)
2016            if (bodyIndex < 0) exit BODY
2017
2018            ! Process all data within the domain of the model
2019            bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
2020            if ( bufrCode == bufr_nezd .or. bufrCode == bufr_radvel ) cycle BODY
2021            if (    (obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) == 1) &
2022                 .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) &
2023                 .and. (bufrCode == bufr_nets .or. bufrCode == bufr_neps  &
2024                 .or. bufrCode == bufr_nepn .or. bufrCode == bufr_ness  &
2025                 .or. bufrCode == bufr_neus .or. bufrCode == bufr_nevs  &
2026                 .or. bufrCode == bufr_vis  .or. bufrCode == bufr_logVis  &
2027                 .or. bufrCode == bufr_gust .or. bufrCode == bufr_nefs &
2028                 .or. bufrCode == bufr_radarPrecip .or. bufrCode == bufr_logRadarPrecip  &
2029                 .or. obs_bodyElem_i(obsSpaceData,OBS_XTR,bodyIndex) == 0) ) then
2030
2031              varLevel    = vnl_varLevelFromVarnum(bufrCode)
2032              nlev        = col_getNumLev(columnAnlInc,varLevel)
2033              nlev_T      = col_getNumLev(columnAnlInc,'TH')
2034              headerIndex = obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
2035              bufrCode    = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
2036              layerIndex  = obs_bodyElem_i(obsSpaceData,OBS_LYR,bodyIndex)
2037              obsHeight   = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
2038              levIndexTop = nlev - 1 + col_getOffsetFromVarno(columnTrlOnAnlIncLev,bufrCode)
2039              levIndexBot = levIndexTop + 1
2040
2041              if (bufrCode == bufr_nets .OR. bufrCode == bufr_ness .OR.  &
2042                  bufrCode == bufr_neus .OR. bufrCode == bufr_nevs .OR. &
2043                  bufrCode == bufr_vis  .or. bufrCode == bufr_logVis  .or.  &
2044                  bufrCode == bufr_gust .or.  &
2045                  bufrCode == bufr_radarPrecip .or. bufrCode == bufr_logRadarPrecip) THEN
2046                if (bufrCode == bufr_ness ) THEN
2047                  delP = col_getPressure(columnAnlInc,nlev_T,headerIndex,'TH')
2048                  anlIncValue = hutoes_tl(col_getElem(columnAnlInc,nlev_T,headerIndex,'HU'), &
2049                                       col_getElem(columnAnlInc,nlev_T,headerIndex,'TT'), &
2050                                       delP, &
2051                                       col_getElem(columnTrlOnAnlIncLev,nlev_T,headerIndex,'HU'), &
2052                                       col_getPressure(columnTrlOnAnlIncLev,nlev,headerIndex,varLevel))
2053                else
2054                  anlIncValue=col_getElem(columnAnlInc,levIndexBot,headerIndex)
2055                end if
2056                call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex,anlIncValue)
2057              else if (bufrCode == bufr_nefs) then
2058                 anlIncUwind=col_getElem(columnAnlInc,col_getNumLev(columnAnlInc,varLevel),headerIndex,'UU') 
2059                 anlIncVwind=col_getElem(columnAnlInc,col_getNumLev(columnAnlInc,varLevel),headerIndex,'VV') 
2060                 trlUwind=col_getElem(columnTrlOnAnlIncLev,col_getNumLev(columnTrlOnAnlIncLev,varLevel),headerIndex,'UU') 
2061                 trlVwind=col_getElem(columnTrlOnAnlIncLev,col_getNumLev(columnTrlOnAnlIncLev,varLevel),headerIndex,'VV') 
2062                 squareSum=trlUwind**2+trlVwind**2
2063                 if ( squareSum .gt. 1.d-10 ) then 
2064                   trlWindSpeed=sqrt(squareSum)
2065                   anlIncWindSpeed=( trlUwind * anlIncUwind + trlVwind * anlIncVwind ) / trlWindSpeed
2066                 else
2067                   trlWindSpeed=0.0
2068                   anlIncWindSpeed=0.0 
2069                 end if
2070                 call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex,anlIncWindSpeed)  
2071              else if (bufrCode == bufr_neps .OR. bufrCode == bufr_nepn) THEN
2072                ZLTV  = columnTrlOnAnlIncLev%OLTV(1,nlev_T,headerIndex)*col_getElem(columnAnlInc,nlev_T,headerIndex,'TT')  & 
2073                      + columnTrlOnAnlIncLev%OLTV(2,nlev_T,headerIndex)*col_getElem(columnAnlInc,nlev_T,headerIndex,'HU')
2074                trlVirtTemp  = columnTrlOnAnlIncLev%OLTV(1,nlev_T,headerIndex)*col_getElem(columnTrlOnAnlIncLev,nlev_T,headerIndex,'TT')
2075                deltaT= temperatureLapseRate*(obsHeight-col_getHeight(columnTrlOnAnlIncLev,0,headerIndex,'SF'))
2076                coeffA  = ((trlVirtTemp-deltaT)/trlVirtTemp)
2077                ZDELPS= (col_getElem(columnAnlInc,1,headerIndex,'P0')*coeffA**coeffB)
2078                ZDELTV= ((col_getElem(columnTrlOnAnlIncLev,1,headerIndex,'P0')*coeffB*coeffA**(coeffB-1))  &
2079                     *(deltaT/(trlVirtTemp*trlVirtTemp)*ZLTV))
2080                call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex, ZDELPS+ZDELTV)
2081              else
2082                
2083                call utl_abort('oop_Hsf: You have entered the twilight zone!')
2084
2085              end if
2086
2087            end if
2088
2089         end do BODY
2090
2091      end do FAMILY
2092
2093    end subroutine oop_Hsf
2094
2095    !--------------------------------------------------------------------------
2096
2097    subroutine oop_Hsst()
2098      !:Purpose: Compute simulated sea surface temperature observations 
2099      !           from profiled model increments.
2100      !           It returns Hdx in OBS_WORK
2101      implicit none
2102
2103      ! Locals:
2104      integer :: headerIndex, bodyIndex, bufrCode
2105      real(8) :: anlIncValueBot
2106      character(len=4) :: varName
2107
2108      call obs_set_current_body_list( obsSpaceData, 'TM' )
2109
2110      !$OMP PARALLEL DO PRIVATE(bodyIndex, bufrCode, varName, headerIndex, anlIncValueBot)
2111      BODY: do bodyIndex = 1, obs_numBody( obsSpaceData )
2112        if ( obs_getFamily(obsSpaceData, bodyIndex_opt=bodyIndex) /= 'TM' ) cycle BODY
2113
2114        bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
2115        if ( bufrCode /= bufr_sst ) cycle BODY
2116
2117        if ( col_varExist(columnAnlInc,'TM') ) then
2118          varName = 'TM'
2119        else
2120          varName = 'TG'
2121        end if
2122
2123        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) == obs_assimilated ) then
2124
2125          headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
2126          anlIncValueBot = col_getElem(columnAnlInc, 1, headerIndex, varName_opt = varName )
2127          call obs_bodySet_r( obsSpaceData, OBS_WORK, bodyIndex, anlIncValueBot )
2128        end if
2129
2130      end do BODY
2131      !$OMP END PARALLEL DO
2132
2133    end subroutine oop_Hsst
2134
2135    !--------------------------------------------------------------------------
2136
2137    subroutine oop_Hhydro()
2138      !:Purpose: Compute simulated hydrological observations 
2139      !           from profiled model increments.
2140      !           It returns Hdx in OBS_WORK
2141      implicit none
2142
2143      ! Locals:
2144      integer :: headerIndex, bodyIndex, bufrCode
2145      real(8) :: anlIncValueBot
2146      character(len=4) :: varName
2147
2148      call obs_set_current_body_list( obsSpaceData, 'HY' )
2149
2150      BODY: do
2151        bodyIndex = obs_getBodyIndex( obsSpaceData )
2152        if (bodyIndex < 0) exit BODY
2153
2154        ! Process all data within the domain of the model
2155        bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
2156
2157        if ( bufrCode /= bufr_riverFlow ) cycle BODY
2158
2159        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) == obs_assimilated ) then
2160          headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
2161          varName     = vnl_varNameFromVarNum(bufrCode)
2162          anlIncValueBot  = col_getElem(columnAnlInc, 1, headerIndex,varName_opt = varName)
2163          call obs_bodySet_r( obsSpaceData, OBS_WORK, bodyIndex, anlIncValueBot )
2164        end if
2165
2166      end do BODY
2167
2168    end subroutine oop_Hhydro
2169
2170    !--------------------------------------------------------------------------
2171
2172    subroutine oop_Hice()
2173      !:Purpose: Compute simulated sea ice concentration observations 
2174      !           from profiled model increments.
2175      !           It returns Hdx in OBS_WORK
2176      implicit none
2177
2178      ! Locals:
2179      integer          :: headerIndex, bodyIndex, bufrCode
2180      integer          :: idate, imonth
2181      integer          :: trackCellNum
2182      real(8)          :: anlIncValue, scaling
2183      character(len=4) :: varName
2184
2185      call obs_set_current_body_list( obsSpaceData, 'GL' )
2186
2187      BODY: do
2188
2189        bodyIndex = obs_getBodyIndex( obsSpaceData )
2190        if (bodyIndex < 0) exit BODY
2191
2192        ! Process all data within the domain of the model
2193        scaling = oop_iceScaling(obsSpaceData, bodyIndex)
2194
2195        if (scaling == 0.0d0) cycle BODY
2196
2197        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) == obs_assimilated ) then
2198          bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
2199          varName = vnl_varNameFromVarNum(bufrCode)
2200          headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
2201          anlIncValue = scaling*col_getElem( columnAnlInc, 1, headerIndex, varName_opt = varName )
2202          call obs_bodySet_r( obsSpaceData, OBS_WORK, bodyIndex, anlIncValue )
2203        end if
2204
2205      end do BODY
2206
2207    end subroutine oop_Hice
2208
2209    !--------------------------------------------------------------------------
2210
2211    subroutine oop_Hto()
2212      !:Purpose: Compute simulated radiances observations from profiled model
2213      !          increments.
2214      !          It returns Hdx in OBS_WORK
2215      implicit none
2216
2217      ! Locals:
2218      integer :: datestamp
2219
2220      if (.not.obs_famExist(obsSpaceData,'TO', localMPI_opt = .true. )) return
2221
2222      !     1.   Prepare atmospheric profiles for all tovs observation points for use in rttov
2223      !     .    -----------------------------------------------------------------------------
2224      !
2225      if (min_nsim == 1) then
2226        datestamp = tim_getDatestamp()
2227        call tvs_fillProfiles(columnTrlOnAnlIncLev, obsSpaceData, datestamp, "tlad", .false.)
2228      end if
2229
2230      !     2.   Compute radiance
2231      !     .    ----------------
2232      !
2233      call tvslin_rttov_tl(columnAnlInc, columnTrlOnAnlIncLev, obsSpaceData)
2234
2235
2236    end subroutine oop_Hto
2237
2238
2239    subroutine oop_HheightCoordObs()
2240      !
2241      !:Purpose: Compute simulated geometric-height based observations
2242      !           such as Radar Doppler velocity.
2243      !           It returns Hdx in OBS_WORK
2244      implicit none
2245
2246      ! Locals:
2247      integer :: bodyIndex, headerIndex, levelIndex, bufrCode, layerIndex, familyIndex
2248      integer :: bodyIndexStart, bodyIndexEnd, bodyIndex1
2249      real(8) :: levelAltLow, levelAltHigh, HDX, Azimuth, obsAltitude
2250      real(8) :: uuLow, uuHigh, vvLow, vvHigh, vInterpWeightHigh, vInterpWeightLow
2251      real(8) :: anlIncValueLow, anlIncValueHigh, trlValueLow, trlValueHigh
2252      real(8), pointer :: du_column(:), dv_column(:), height_column(:)
2253      integer, parameter :: NUMFAMILY=3
2254      character(len=2) :: listFamily(NUMFAMILY), cfam
2255
2256      listFamily(1) = 'RA' ! Doppler velocity (Radial Wind) burf_radvel
2257      listFamily(2) = 'PR' ! Dew point difference           burf_nees
2258      listFamily(3) = 'AL' ! Aladin HLOS wind               burf_neal
2259      ! Loop over all family
2260      FAMILY: do familyIndex=1, NUMFAMILY
2261
2262        ! Loop over all header indices 
2263        call obs_set_current_header_list(obsSpaceData, listFamily(familyIndex))
2264        HEADER: do  
2265          headerIndex = obs_getHeaderIndex(obsSpaceData)
2266          if ( headerIndex < 0 ) exit HEADER
2267
2268          if ( listFamily(familyIndex) == 'RA' ) then
2269
2270            ! Azimuth of the radar beam
2271            azimuth   = obs_headElem_r(obsSpaceData, OBS_RZAM, headerIndex)
2272
2273          end if
2274
2275          ! Local vector state
2276          du_column  => col_getColumn(columnAnlInc, headerIndex, 'UU') 
2277          dv_column  => col_getColumn(columnAnlInc, headerIndex, 'VV')
2278
2279          ! Loop over all body indices 
2280          call obs_set_current_body_list(obsSpaceData, headerIndex)
2281          BODY: do
2282            bodyIndex = obs_getBodyIndex(obsSpaceData)
2283            if ( bodyIndex < 0 ) exit BODY
2284
2285            ! only process observations flagged to be assimilated
2286            if ( obs_bodyElem_i(obsSpaceData, OBS_ASS, bodyIndex) /= obs_assimilated ) cycle BODY
2287
2288            ! Check that this observation has the expected bufr element ID
2289            bufrCode = obs_bodyElem_i(obsSpaceData, OBS_VNM, bodyIndex)
2290            if ( bufrCode == BUFR_logRadarPrecip ) cycle BODY
2291
2292            if  ( bufrCode == bufr_radvel ) then
2293              ! Operator Hx for tangential wind is linear in x
2294              ! that is:
2295              !    h(x) = Hx
2296              ! and
2297              ! h(xb) + Hdx  =  Hxb + Hdx 
2298              !
2299              ! H includes vertical interpolation  
2300              !   and projection of U and V wind components along the direction of the beam
2301              !
2302              ! In matrix form for one Doppler velocity observation:
2303              !   VDoppler = Hx = [ iwHigh*sin(az) iwHigh*cos(az) iwLow*sin(az) iwLow*cos(az) ][ uuHigh ]
2304              !                                                                                [ vvHigh ]
2305              !                                                                                [ uuLow  ] 
2306              !                                                                                [ vvLow  ] 
2307              ! such that
2308              !   VDoppler =  iwHigh*sin(az)*uuHigh 
2309              !             + iwHigh*cos(az)*vvHigh
2310              !             + iwLow *sin(az)*uuLow 
2311              !             + iwLow *cos(az)*vvLow
2312              !
2313              ! With
2314              !   az     = beam azimuth (radians, met convention) 
2315              !   iwHigh = Interpolation Weight for model level just above observation
2316              !   iwLow  =                 "                         below     "
2317              !   uuHigh and vvHigh = wind components on model level just above observation
2318              !   uuLow  and vvLow  =                 "                   below     "
2319              !
2320              ! The dependence of model levels on surface pressure is neglected here
2321
2322              ! OBS_LYR returns the index of the model level just above the observation
2323              !   level=1 is the highest level such that level+1 is lower 
2324              levelIndex = obs_bodyElem_i(obsSpaceData, OBS_LYR, bodyIndex)
2325              levelAltHigh = col_getHeight(columnTrlOnAnlIncLev, levelIndex,   headerIndex, 'MM')
2326              levelAltLow  = col_getHeight(columnTrlOnAnlIncLev, levelIndex+1, headerIndex, 'MM')
2327              obsAltitude = obs_bodyElem_r(obsSpaceData, OBS_PPP, bodyIndex) 
2328           
2329              !vertical interpolation weights 
2330              vInterpWeightHigh = (obsAltitude - levelAltLow)/(levelAltHigh - levelAltLow)
2331              vInterpWeightLow = 1.0D0 - vInterpWeightHigh
2332
2333              HDX =  vInterpWeightHigh*sin(azimuth)*du_column(levelIndex)   &
2334                   + vInterpWeightHigh*cos(azimuth)*dv_column(levelIndex)   &
2335                   + vInterpWeightLow *sin(azimuth)*du_column(levelIndex+1) &
2336                   + vInterpWeightLow *cos(azimuth)*dv_column(levelIndex+1)
2337                
2338              ! Store HDX in OBS_WORK  
2339              call obs_bodySet_r(obsSpaceData, OBS_WORK, bodyIndex, HDX)
2340              
2341            else 
2342
2343              cfam = obs_getfamily(obsSpaceData,headerIndex_opt=headerIndex)
2344              write(*,*) 'CANNOT ASSIMILATE OBSERVATION!!!', &
2345                         'bufrCode =', bufrCode, 'cfam =',  cfam
2346              call utl_abort('oop_HheightCoordObs')
2347
2348            end if
2349          end do BODY
2350        end do HEADER
2351      end do FAMILY
2352
2353    end subroutine oop_HheightCoordObs
2354
2355    !--------------------------------------------------------------------------
2356
2357    subroutine oop_Hro( initializeLinearization_opt )
2358      !
2359      !:Purpose: Compute the tangent operator for GPSRO observations.
2360      !
2361      implicit none
2362
2363      ! Arguments:
2364      logical, optional, intent(in) :: initializeLinearization_opt 
2365
2366      ! Locals:
2367      real(8) :: ZMHXL
2368      real(8) :: DX(gps_ncvmx)
2369      integer :: IDATYP
2370      integer :: JL, JV, NGPSLEV, NWNDLEV
2371      integer :: headerIndex, bodyIndex, iProfile
2372      logical :: ASSIM
2373      integer :: NH, NH1
2374
2375      ! Initializations
2376      NGPSLEV = col_getNumLev(columnAnlInc,'TH')
2377      NWNDLEV = col_getNumLev(columnAnlInc,'MM')
2378
2379      ! call to calculate the GPSRO Jacobians
2380      call oop_calcGPSROJacobian( columnTrlOnAnlIncLev, obsSpaceData, &
2381                                  initializeLinearization_opt=initializeLinearization_opt )
2382
2383      ! Loop over all header indices of the 'RO' family (Radio Occultation)
2384      ! Set the header list (start at the beginning of the list)
2385      call obs_set_current_header_list(obsSpaceData,'RO')
2386      HEADER: do
2387         headerIndex = obs_getHeaderIndex(obsSpaceData)
2388         if (headerIndex < 0) exit HEADER
2389
2390         ! Process only refractivity data (codtyp 169)
2391         IDATYP = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
2392         DATYP: if ( IDATYP == 169 ) THEN
2393
2394            ! Scan for requested data values of the profile, and count them
2395            ASSIM = .FALSE.
2396            NH = 0
2397
2398            ! Loop over all body indices for this headerIndex:
2399            ! (start at the beginning of the list)
2400            call obs_set_current_body_list(obsSpaceData, headerIndex)
2401            BODY: do 
2402               bodyIndex = obs_getBodyIndex(obsSpaceData)
2403               if (bodyIndex < 0) exit BODY
2404               if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) THEN
2405                  ASSIM = .TRUE.
2406                  NH = NH + 1
2407               end if
2408            end do BODY
2409
2410            ! If assimilations are requested, prepare and apply the observation operator
2411            ASSIMILATE: if (ASSIM) THEN
2412               iProfile=gps_iprofile_from_index(headerIndex)
2413
2414               ! Local vector state
2415               do JL = 1, NGPSLEV
2416                  DX (        JL) = col_getElem(columnAnlInc,JL,headerIndex,'TT')
2417                  DX (NGPSLEV+JL) = col_getElem(columnAnlInc,JL,headerIndex,'HU')
2418               end do
2419               DX (2*NGPSLEV+1:3*NGPSLEV) = col_getColumn(columnAnlInc,headerIndex,'Z_T')
2420               DX (3*NGPSLEV+1:4*NGPSLEV) = col_getColumn(columnAnlInc,headerIndex,'P_T')
2421
2422               ! Perform the (H(xb)DX-Y') operation
2423               ! Loop over all body indices for this headerIndex:
2424               NH1 = 0
2425               call obs_set_current_body_list(obsSpaceData, headerIndex)
2426               BODY_3: do 
2427                  bodyIndex = obs_getBodyIndex(obsSpaceData)
2428                  if (bodyIndex < 0) exit BODY_3
2429                  if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) THEN
2430                     NH1 = NH1 + 1
2431
2432                     ! Evaluate H(xb)DX
2433                     ZMHXL = 0.d0
2434                     do JV = 1, 4*NGPSLEV
2435                        ZMHXL = ZMHXL + oop_vRO_Jacobian(iProfile,NH1,JV) * DX(JV)
2436                     end do
2437
2438                     ! Store in CMA
2439                     call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex, ZMHXL)
2440                  end if
2441               end do BODY_3
2442            end if ASSIMILATE
2443         end if DATYP
2444      end do HEADER
2445
2446      
2447    end subroutine oop_Hro
2448
2449    !--------------------------------------------------------------------------
2450
2451    subroutine oop_Hgp( initializeLinearization_opt )
2452      !
2453      !:Purpose: Compute H'dx for all GPS ZTD observations
2454      !           oop_Hgp TL of DOBSGPSGB (Jo for GB-GPS ZTD observations)
2455      implicit none
2456
2457      ! Arguments:
2458      logical, optional, intent(in) :: initializeLinearization_opt 
2459
2460      ! Locals:
2461      real(8) :: ZHX
2462      real(8) :: DX(gps_ncvmx)
2463      integer :: headerIndex, bodyIndex
2464      integer :: JL, NFLEV, status, iztd, icount
2465      logical :: ASSIM
2466      character(len=12) :: cstnid
2467
2468      NFLEV  = col_getNumLev(columnTrlOnAnlIncLev,'TH')
2469
2470      icount = 0
2471
2472      ! call to calculate the GPSGB Jacobians
2473      call oop_calcGPSGBJacobian( columnTrlOnAnlIncLev, obsSpaceData, &
2474                                  initializeLinearization_opt=initializeLinearization_opt )
2475
2476      ! loop over all header indices of the 'GP' family (GPS observations)
2477      call obs_set_current_header_list(obsSpaceData,'GP')
2478      HEADER: do
2479         headerIndex = obs_getHeaderIndex(obsSpaceData)
2480         if (headerIndex < 0) exit HEADER
2481
2482         ! Scan for ZTD assimilation at this location
2483         ASSIM = .FALSE.
2484         ! loop over all body indices for this headerIndex
2485         call obs_set_current_body_list(obsSpaceData, headerIndex)
2486         BODY: do 
2487            bodyIndex = obs_getBodyIndex(obsSpaceData)
2488            if (bodyIndex < 0) exit BODY
2489
2490            if (   (obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex) == 189) &
2491                 .and. (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == bufr_nezd) &
2492                 .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) ) then
2493               ASSIM = .TRUE.
2494            end if
2495         end do BODY
2496
2497         ! If ZTD assimilation, apply the TL observation operator
2498         if ( ASSIM ) THEN
2499            iztd = gps_iztd_from_index(headerIndex)
2500            if ( iztd < 1 .or. iztd > gps_gb_numZTD ) then
2501               call utl_abort('oop_Hgp: ERROR: index from gps_iztd_from_index() is out of range!')
2502            end if
2503
2504            ! Local vector state (analysis increments)
2505            do JL = 1, NFLEV
2506               DX (JL)        = col_getElem(columnAnlInc,JL,headerIndex,'TT')
2507               DX (NFLEV+JL)  = col_getElem(columnAnlInc,JL,headerIndex,'HU')
2508            end do
2509            DX (2*NFLEV+1:3*NFLEV) = col_getColumn(columnAnlInc,headerIndex,'Z_T')
2510            DX (3*NFLEV+1:4*NFLEV) = col_getColumn(columnAnlInc,headerIndex,'P_T')
2511
2512            ! Evaluate H'(xb)*dX
2513            ZHX = 0.D0
2514            do JL = 1, 4*NFLEV
2515               ZHX = ZHX + oop_vZTD_Jacobian(iztd,JL)*DX(JL)
2516            end do
2517
2518            ! Store ZHX = H'dx in OBS_WORK
2519            ! loop over all body indices for this headerIndex
2520            call obs_set_current_body_list(obsSpaceData, headerIndex)
2521            BODY_2: do 
2522               bodyIndex = obs_getBodyIndex(obsSpaceData)
2523               if (bodyIndex < 0) exit BODY_2
2524
2525               if (   (obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex) == 189) &
2526                    .and. (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == bufr_nezd) &
2527                    .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) ) then
2528                  call obs_bodySet_r(obsSpaceData,OBS_WORK,bodyIndex, ZHX)
2529                  icount = icount + 1
2530                  if ( icount <= 3 .and. gps_gb_LTESTOP ) then
2531                    cstnid = obs_elem_c(obsSpaceData,'STID',headerIndex)
2532                    write(*,*) iztd, cstnid
2533                    write(*,*) 'JAC(ncv) = ', (oop_vZTD_Jacobian(iztd,JL),JL=1,4*NFLEV)
2534                    write(*,*) 'DTT(JL)  = ', (DX(JL),JL=1,NFLEV)
2535                    write(*,*) 'DHU(JL)  = ', (DX(JL),JL=NFLEV+1,2*NFLEV)
2536                    write(*,*) 'DAL(JL)  = ', (DX(JL),JL=2*NFLEV+1,3*NFLEV)
2537                    write(*,*) 'DP (JL)  = ', (DX(JL),JL=3*NFLEV+1,4*NFLEV)
2538                    write(*,*) 'ZHX (mm) = ', ZHX*1000.D0
2539                  end if
2540               end if
2541            end do BODY_2
2542
2543         end if ! ASSIM
2544
2545      end do HEADER
2546
2547      !      WRITE(*,*) 'oop_Hgp: Number of ZTD data locations with obs_bodySet_r(OBS_OMA) = ', icount
2548
2549      !      WRITE(*,*)'EXIT oop_Hgp'
2550
2551      
2552    end subroutine oop_Hgp
2553
2554    !--------------------------------------------------------------------------
2555
2556    subroutine oop_Hchm()
2557      !:Purpose: Compute simulated chemical constituents observations from profiled model
2558      !           increments, and returns Hdx in OBS_WORK
2559      implicit none
2560
2561      if (.not.obs_famExist(obsSpaceData,'CH', localMPI_opt = .true. )) return
2562      
2563      call oopc_CHobsoperators(columnTrlOnAnlIncLev,obsSpaceData,'tl',columnAnlInc_opt=columnAnlInc) ! 'tl' for tangent linear operator
2564
2565    end subroutine oop_Hchm
2566
2567  end subroutine oop_Htl
2568
2569  !--------------------------------------------------------------------------
2570  ! oop_Had
2571  !--------------------------------------------------------------------------
2572  subroutine oop_Had(columnAnlInc, columnTrlOnAnlIncLev, obsSpaceData, initializeLinearization_opt)
2573    !
2574    !:Purpose: Call the several adjoint of observation operators
2575    implicit none
2576
2577    ! Arguments:
2578    type(struct_columnData), intent(inout) :: columnAnlInc
2579    type(struct_columnData), intent(in)    :: columnTrlOnAnlIncLev
2580    type(struct_obs)       , intent(inout) :: obsSpaceData
2581    logical, optional      , intent(in)    :: initializeLinearization_opt 
2582
2583    ! Locals:
2584    type(struct_vco), pointer :: vco_anl
2585    logical, save :: initializeLinearization = .true.
2586
2587    if ( mmpi_myid == 0 ) then
2588      write(*,*)'OOP_HT- Adjoint of linearized observation operators'
2589    end if
2590
2591    vco_anl => col_getVco(columnTrlOnAnlIncLev)
2592
2593    ! Re-linearlize if it is asked for
2594    if ( present(initializeLinearization_opt) ) then
2595      initializeLinearization = initializeLinearization_opt
2596    end if
2597
2598    !     Find interpolation layer in model profiles (used by several operators)
2599    if ( initializeLinearization ) then
2600      if ( col_getNumLev(columnTrlOnAnlIncLev,'MM') > 1 ) call oop_vobslyrs(columnTrlOnAnlIncLev, obsSpaceData, beSilent=.false.)
2601      initializeLinearization = .false.
2602    end if
2603
2604    call oop_HTchm
2605
2606    if ( gps_gb_numZTD > 0 ) then
2607      call oop_HTgp( initializeLinearization_opt=initializeLinearization_opt )
2608    end if
2609
2610    call oop_HTheighCoordObs()
2611
2612    call oop_HTro( initializeLinearization_opt=initializeLinearization_opt )
2613
2614    call oop_HTto
2615
2616    call oop_HTsf
2617
2618    call oop_HTpp
2619
2620    call oop_HTsst
2621
2622    call oop_HTice
2623
2624    call oop_HThydro()
2625
2626
2627  CONTAINS
2628
2629    !--------------------------------------------------------------------------
2630
2631    subroutine oop_HTpp
2632      !:Purpose: Adjoint of the "vertical" interpolation, based on vint3d,
2633      !           for "UPPER AIR" data files.
2634      implicit none
2635
2636      ! Locals:
2637      integer levIndexBot,levIndexTop,bufrCode
2638      real(8) ZRES
2639      real(8) ZWB,ZWT
2640      real(8) ZLEV,ZPT,ZPB,ZDADPS,ZPRESBPB,ZPRESBPT
2641      integer headerIndex,layerIndex,nlev_T
2642      integer bodyIndex,INDEX_FAMILY
2643      real(8) trlValueTop,trlValueBot
2644      real(8), pointer :: all_column(:),tt_column(:),hu_column(:),p_column(:)
2645      real(8) :: delPT,delPB
2646      integer, parameter :: numFamily=3
2647      character(len=2) :: list_family(numFamily)
2648      character(len=4) :: varLevel
2649
2650      list_family(1) = 'UA'
2651      list_family(2) = 'AI'
2652      list_family(3) = 'SW'
2653
2654      FAMILY: do index_family=1,numFamily
2655
2656         call obs_set_current_body_list(obsSpaceData,list_family(index_family))
2657         BODY: do 
2658            bodyIndex = obs_getBodyIndex(obsSpaceData)
2659            if (bodyIndex < 0) exit BODY
2660
2661             ! Process all data within the domain of the model
2662            if (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated .and. &
2663                obs_bodyElem_i(obsSpaceData,OBS_XTR,bodyIndex) == 0               .and. &
2664                obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) == 2 ) then
2665 
2666              headerIndex = obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
2667               ZRES = obs_bodyElem_r(obsSpaceData,OBS_WORK,bodyIndex)
2668               ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
2669               bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
2670               varLevel = vnl_varLevelFromVarnum(bufrCode)
2671               layerIndex   = obs_bodyElem_i(obsSpaceData,OBS_LYR,bodyIndex)
2672               levIndexTop  = layerIndex  + col_getOffsetFromVarno(columnTrlOnAnlIncLev,bufrCode)
2673               levIndexBot  = levIndexTop+1
2674               ZPT  = col_getPressure(columnTrlOnAnlIncLev,layerIndex,headerIndex,varLevel)
2675               ZPB  = col_getPressure(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,varLevel)
2676               delPT= col_getPressure(columnAnlInc,layerIndex  ,headerIndex,varLevel)
2677               delPB= col_getPressure(columnAnlInc,layerIndex+1,headerIndex,varLevel)
2678               ZWB  = LOG(ZLEV/ZPT)/LOG(ZPB/ZPT)
2679               ZWT  = 1.0D0 - ZWB
2680
2681               ZDADPS   = ( LOG(ZLEV/ZPB)*delPT/ZPT -   &
2682                    LOG(ZLEV/ZPT)*delPB/ZPB )/  &
2683                    LOG(ZPB/ZPT)**2
2684
2685               all_column => col_getColumn(columnAnlInc,headerIndex)
2686               tt_column  => col_getColumn(columnAnlInc,headerIndex,'TT')
2687               hu_column  => col_getColumn(columnAnlInc,headerIndex,'HU')
2688               if ( varLevel == 'TH' ) then
2689                 p_column => col_getColumn(columnAnlInc,headerIndex,'P_T')
2690               else if ( varLevel == 'MM' ) then
2691                 p_column => col_getColumn(columnAnlInc,headerIndex,'P_M')
2692               end if
2693
2694               if (bufrCode.eq.bufr_nees) then
2695                  call hutoes_ad(hu_column(layerIndex+1),  &
2696                       tt_column(layerIndex+1),  &
2697                       p_column(layerIndex+1),   &
2698                       ZWB*ZRES,         &
2699                       col_getElem(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'HU'),      &
2700                       col_getPressure(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'TH'))
2701                  call hutoes_ad(hu_column(layerIndex  ),  &
2702                       tt_column(layerIndex  ),  &
2703                       p_column(layerIndex),     &
2704                       ZWT*ZRES,         &
2705                       col_getElem(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'HU'),      &
2706                       col_getPressure(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'TH'))
2707                  trlValueBot=hutoes(col_getElem(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'HU'),  &
2708                       col_getElem(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'TT'),  &
2709                       col_getPressure(columnTrlOnAnlIncLev,layerIndex+1,headerIndex,'TH'))
2710                  trlValueTop=hutoes(col_getElem(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'HU'),  &
2711                       col_getElem(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'TT'),  &
2712                       col_getPressure(columnTrlOnAnlIncLev,layerIndex  ,headerIndex,'TH'))
2713               else
2714                  all_column(levIndexBot) = all_column(levIndexBot) + ZWB*ZRES
2715                  all_column(levIndexTop) = all_column(levIndexTop) + ZWT*ZRES
2716                  trlValueBot=col_getElem(columnTrlOnAnlIncLev,levIndexBot,headerIndex)
2717                  trlValueTop=col_getElem(columnTrlOnAnlIncLev,levIndexTop,headerIndex)
2718               end if
2719               p_column(layerIndex  ) = p_column(layerIndex  ) + (trlValueBot - trlValueTop) * &
2720                                (LOG(ZLEV/ZPB)/ZPT/LOG(ZPB/ZPT)**2) * ZRES
2721               p_column(layerIndex+1) = p_column(layerIndex+1) - (trlValueBot - trlValueTop) * &
2722                                (LOG(ZLEV/ZPT)/ZPB/LOG(ZPB/ZPT)**2) * ZRES
2723            end if
2724
2725         end do BODY
2726
2727      end do FAMILY
2728
2729    end subroutine oop_HTpp
2730
2731    !--------------------------------------------------------------------------
2732
2733    subroutine oop_HTsf
2734      !:Purpose: based on surfc1dz to build the adjoint of the
2735      !          vertical interpolation for SURFACE data files.
2736      implicit none
2737
2738      ! Locals:
2739      integer :: levIndexBot,levIndexTop
2740      real(8) :: ZRES
2741      real(8) :: ZWB, ZWT,coeffA,coeffB,ZATV,trlVirtTemp
2742      real(8) :: obsHeight, ZPT, ZPB, ZDADPS, ZDELPS, ZDELTV, deltaT
2743      real(8) :: trlValueBot
2744      real(8) :: trlUwind, trlVwind, sumSquare, trlWindSpeed, anlIncWindSpeed 
2745      integer :: headerIndex, layerIndex, nlev, nlev_T
2746      integer :: bodyIndex, bufrCode, INDEX_FAMILY
2747      real(8), pointer :: all_column(:), tt_column(:), hu_column(:), ps_column(:), p_column(:)
2748      real(8), pointer :: du_column(:), dv_column(:)
2749      real(8) :: dPdPsfc
2750      integer, parameter :: numFamily=5
2751      character(len=2) :: list_family(numFamily)
2752      character(len=4) :: varLevel
2753
2754      !- Temperature lapse rate for extrapolation of height below model surface
2755      coeffB   = 1.0d0/(MPC_RGAS_DRY_AIR_R8*temperatureLapseRate/ec_rg)
2756
2757      !
2758      !-   1. Fill in COMMVO by using the adjoint of the "vertical" interpolation
2759      !
2760      list_family(1) = 'UA'
2761      list_family(2) = 'SF'
2762      list_family(3) = 'SC'
2763      list_family(4) = 'GP'
2764      list_family(5) = 'RA'
2765
2766      FAMILY: do index_family=1,numFamily
2767
2768         call obs_set_current_body_list(obsSpaceData, list_family(index_family))
2769         BODY: do
2770            bodyIndex = obs_getBodyIndex(obsSpaceData)
2771            if (bodyIndex < 0) exit BODY
2772
2773            ! Process all data within the domain of the model
2774            bufrCode = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
2775            if ( bufrCode == bufr_nezd .or. bufrCode == bufr_radvel ) cycle BODY
2776            if (    (obs_bodyElem_i(obsSpaceData,OBS_VCO,bodyIndex) == 1) &
2777                 .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) &
2778                 .and. (bufrCode == bufr_nets .or. bufrCode == bufr_neps  &
2779                 .or. bufrCode == bufr_nepn .or. bufrCode == bufr_ness  &
2780                 .or. bufrCode == bufr_neus .or. bufrCode == bufr_nevs  &
2781                 .or. bufrCode == bufr_vis  .or. bufrCode == bufr_logVis  &
2782                 .or. bufrCode == bufr_gust .or. bufrCode == bufr_nefs &
2783                 .or. bufrCode == bufr_radarPrecip .or. bufrCode == bufr_logRadarPrecip  &
2784                 .or. obs_bodyElem_i(obsSpaceData,OBS_XTR,bodyIndex) == 0) ) then
2785
2786               varLevel    = vnl_varLevelFromVarnum(bufrCode)
2787               nlev        = col_getNumLev(columnAnlInc,varLevel)
2788               nlev_T      = col_getNumLev(columnAnlInc,'TH')
2789               headerIndex = obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
2790               bufrCode    = obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex)
2791               layerIndex  = obs_bodyElem_i(obsSpaceData,OBS_LYR,bodyIndex)
2792               obsHeight   = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
2793               levIndexTop = nlev - 1 + col_getOffsetFromVarno(columnTrlOnAnlIncLev,bufrCode)
2794               levIndexBot = levIndexTop+1
2795               ZRES        = obs_bodyElem_r(obsSpaceData,OBS_WORK,bodyIndex)
2796               
2797               if (bufrCode == bufr_nets .or. bufrCode == bufr_ness .or.  &
2798                   bufrCode == bufr_neus .or. bufrCode == bufr_nevs .or. & 
2799                   bufrCode == bufr_vis  .or. bufrCode == bufr_logVis  .or.  &
2800                   bufrCode == bufr_gust .or.  &
2801                   bufrCode == bufr_radarPrecip .or. bufrCode == bufr_logRadarPrecip) then
2802                 if ( bufrCode == bufr_ness ) then
2803                   tt_column  => col_getColumn(columnAnlInc,headerIndex,'TT')
2804                   hu_column  => col_getColumn(columnAnlInc,headerIndex,'HU')
2805                   p_column   => col_getColumn(columnAnlInc,headerIndex,'P_T')
2806                   call hutoes_ad(hu_column(nlev_T),  &
2807                        tt_column(nlev_T),  &
2808                        p_column(nlev_T),   &
2809                        ZRES,               &
2810                        col_getElem(columnTrlOnAnlIncLev,nlev_T,headerIndex,'HU'),  &
2811                        col_getPressure(columnTrlOnAnlIncLev,nlev_T,headerIndex,'TH'))
2812                 else
2813                   all_column => col_getColumn(columnAnlInc,headerIndex) 
2814                   all_column(levIndexBot) = all_column(levIndexBot) + ZRES
2815                 end if
2816               else if ( bufrCode == bufr_nefs ) then
2817                   du_column  => col_getColumn(columnAnlInc,headerIndex,'UU') 
2818                   dv_column  => col_getColumn(columnAnlInc,headerIndex,'VV')
2819                   anlIncWindSpeed=obs_bodyElem_r(obsSpaceData,OBS_WORK,bodyIndex) 
2820                   sumSquare = col_getElem(columnTrlOnAnlIncLev,nlev,headerIndex,'UU')**2 + &
2821                               col_getElem(columnTrlOnAnlIncLev,nlev,headerIndex,'VV')**2 
2822                   if ( sumSquare > 1.0d-10 ) then
2823                     trlWindSpeed=sqrt(sumSquare)
2824                     du_column(nlev) = du_column(nlev) + &
2825                          anlIncWindSpeed*col_getElem(columnTrlOnAnlIncLev,nlev,headerIndex,'UU')/trlWindSpeed
2826                     dv_column(nlev) = dv_column(nlev) + &
2827                          anlIncWindSpeed*col_getElem(columnTrlOnAnlIncLev,nlev,headerIndex,'VV')/trlWindSpeed
2828                   else
2829                     trlWindSpeed=0.
2830                   end if
2831               else if ( bufrCode == bufr_neps .or. bufrCode == bufr_nepn ) then
2832                 tt_column  => col_getColumn(columnAnlInc,headerIndex,'TT')
2833                 hu_column  => col_getColumn(columnAnlInc,headerIndex,'HU')
2834                 ps_column  => col_getColumn(columnAnlInc,headerIndex,'P0')
2835                 trlVirtTemp = columnTrlOnAnlIncLev%OLTV(1,nlev_T,headerIndex)*col_getElem(columnTrlOnAnlIncLev,nlev_T,headerIndex,'TT')
2836                 deltaT = temperatureLapseRate*(obsHeight-col_getHeight(columnTrlOnAnlIncLev,0,headerIndex,'SF'))
2837                 coeffA = ((trlVirtTemp-deltaT)/trlVirtTemp)
2838                 ZDELTV = (col_getElem(columnTrlOnAnlIncLev,1,headerIndex,'P0')*coeffB*coeffA**(coeffB-1))  &
2839                      *(deltaT/(trlVirtTemp*trlVirtTemp))
2840                 ZDELPS = coeffA**coeffB
2841                 ZATV   = ZDELTV*ZRES
2842                 ps_column(1)= ps_column(1) + ZDELPS*ZRES
2843                 tt_column(nlev_T) = tt_column(nlev_T) + &
2844                                     columnTrlOnAnlIncLev%OLTV(1,nlev_T,headerIndex)*ZATV
2845                 hu_column(nlev_T) = hu_column(nlev_T) + &
2846                                     columnTrlOnAnlIncLev%OLTV(2,nlev_T,headerIndex)*ZATV
2847               else
2848
2849                 call utl_abort('oop_HTsf: You have entered the twilight zone!')
2850
2851               end if
2852
2853             end if
2854
2855         end do BODY
2856
2857      end do FAMILY
2858
2859    end subroutine oop_HTsf
2860
2861    !--------------------------------------------------------------------------
2862
2863    subroutine oop_HTsst
2864      !:Purpose: Adjoint of the "vertical" interpolation for SST data
2865      implicit none
2866
2867      ! Locals:
2868      real(8) :: residual
2869      integer :: headerIndex, bodyIndex, bufrCode 
2870      real(8), pointer :: columnTG(:)
2871      character(len=4) :: varName
2872
2873      call obs_set_current_body_list( obsSpaceData, 'TM' )
2874
2875      !$OMP PARALLEL DO PRIVATE(bodyIndex, bufrCode, varName, headerIndex, residual, columnTG)
2876      BODY: do bodyIndex = 1, obs_numBody( obsSpaceData )
2877        if ( obs_getFamily(obsSpaceData, bodyIndex_opt=bodyIndex) /= 'TM' ) cycle BODY
2878
2879        bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
2880        if ( bufrCode /= bufr_sst ) cycle BODY
2881
2882        if ( col_varExist(columnAnlInc,'TM') ) then
2883          varName = 'TM'
2884        else
2885          varName = 'TG'
2886        end if
2887
2888        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) == obs_assimilated ) then
2889
2890          headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
2891          residual = obs_bodyElem_r( obsSpaceData, OBS_WORK, bodyIndex )
2892          columnTG => col_getColumn(columnAnlInc, headerIndex, varName_opt = varName ) 
2893          columnTG(1) = columnTG(1) + residual
2894
2895        end if
2896
2897      end do BODY
2898      !$OMP END PARALLEL DO
2899
2900    end subroutine oop_HTsst
2901
2902    !--------------------------------------------------------------------------
2903
2904    subroutine oop_HThydro
2905      !:Purpose: Adjoint of the "vertical" interpolation for Hydrological data
2906      implicit none
2907
2908      ! Locals:
2909      real(8) :: residual
2910      integer :: headerIndex, bodyIndex, bufrCode 
2911      real(8), pointer :: columnHY(:)
2912      character(len=4) :: varName
2913
2914      call obs_set_current_body_list( obsSpaceData, 'HY' )
2915
2916      BODY: do
2917
2918        bodyIndex = obs_getBodyIndex( obsSpaceData )
2919        if (bodyIndex < 0) exit BODY
2920
2921        ! Process all data within the domain of the model
2922        bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
2923
2924        if ( bufrCode /= bufr_riverFlow ) cycle BODY
2925
2926        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) == obs_assimilated ) then
2927
2928          headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
2929          residual = obs_bodyElem_r( obsSpaceData, OBS_WORK, bodyIndex )
2930          varName = vnl_varNameFromVarNum(bufrCode)
2931          columnHY => col_getColumn(columnAnlInc, headerIndex, varName_opt = varName ) 
2932          columnHY(1) = columnHY(1) + residual
2933        end if
2934
2935      end do BODY
2936
2937    end subroutine oop_HThydro
2938
2939    !--------------------------------------------------------------------------
2940
2941    subroutine oop_HTice
2942      !:Purpose: Adjoint of the "vertical" interpolation for ICE data
2943      implicit none
2944
2945      ! Locals:
2946      real(8)          :: residual, scaling
2947      integer          :: headerIndex, bodyIndex, bufrCode
2948      real(8), pointer :: columnGL(:)
2949      character(len=4) :: varName
2950
2951      call obs_set_current_body_list( obsSpaceData, 'GL' )
2952
2953      BODY: do
2954
2955        bodyIndex = obs_getBodyIndex( obsSpaceData )
2956        if (bodyIndex < 0) exit BODY
2957
2958        ! Process all data within the domain of the model
2959
2960        scaling = oop_iceScaling(obsSpaceData, bodyIndex)
2961
2962        if (scaling == 0.0d0) cycle BODY
2963
2964        if ( obs_bodyElem_i( obsSpaceData, OBS_ASS, bodyIndex ) == obs_assimilated ) then
2965          residual = scaling*obs_bodyElem_r( obsSpaceData, OBS_WORK, bodyIndex )
2966          bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
2967          varName = vnl_varNameFromVarNum(bufrCode)
2968          headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
2969          columnGL => col_getColumn( columnAnlInc, headerIndex, varName_opt = varName )
2970          columnGL(1) = columnGL(1) + residual
2971        end if
2972
2973      end do BODY
2974
2975    end subroutine oop_HTice
2976
2977    !--------------------------------------------------------------------------
2978
2979    subroutine oop_HTto
2980      !:Purpose: Adjoint of computation of residuals to the tovs observations
2981      implicit none
2982
2983      ! Locals:
2984      integer :: datestamp
2985
2986      if (.not.obs_famExist(obsSpaceData,'TO', localMPI_opt = .true. )) return
2987
2988      ! Adjoint of computing radiance
2989      datestamp = tim_getDatestamp()
2990      call tvs_fillProfiles(columnTrlOnAnlIncLev,obsSpaceData,datestamp,"tlad",.false.)
2991
2992      call tvslin_rttov_ad(columnAnlInc,columnTrlOnAnlIncLev,obsSpaceData)
2993
2994    end subroutine oop_HTto
2995
2996    !--------------------------------------------------------------------------
2997
2998    subroutine oop_HTro ( initializeLinearization_opt ) 
2999      !
3000      !:Purpose: Compute the adjoint operator for GPSRO observations.
3001      implicit none
3002
3003      ! Arguments:
3004      logical, optional, intent(in) :: initializeLinearization_opt 
3005
3006      ! Locals:
3007      real(8) :: DPJO0(gps_ncvmx)
3008      real(8) :: DPJO1(gps_ncvmx)
3009      real(8) :: ZINC
3010      real(8), pointer :: tt_column(:),hu_column(:),height_column(:),p_column(:)
3011      integer :: IDATYP
3012      integer :: JL, NGPSLEV
3013      integer :: headerIndex, bodyIndex, iProfile
3014      logical :: ASSIM, LUSE
3015      integer :: NH, NH1
3016
3017      ! Initializations
3018      NGPSLEV=col_getNumLev(columnAnlInc,'TH')
3019
3020      ! call to calculate the GPSRO Jacobians
3021      call oop_calcGPSROJacobian( columnTrlOnAnlIncLev, obsSpaceData, &
3022                                  initializeLinearization_opt=initializeLinearization_opt )
3023
3024      ! Loop over all header indices of the 'RO' family (Radio Occultation)
3025      ! Set the header list (start at the beginning of the list)
3026      call obs_set_current_header_list(obsSpaceData,'RO')
3027      HEADER: do
3028         headerIndex = obs_getHeaderIndex(obsSpaceData)
3029         if (headerIndex < 0) exit HEADER
3030
3031         DPJO0 = 0.d0
3032
3033         ! Process only refractivity data (codtyp 169)
3034         IDATYP = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
3035         DATYP: if ( IDATYP == 169 ) THEN
3036
3037            ! Scan for requested data values of the profile, and count them
3038            ASSIM = .FALSE.
3039            NH = 0
3040
3041            ! Loop over all body indices for this headerIndex:
3042            ! (start at the beginning of the list)
3043            call obs_set_current_body_list(obsSpaceData, headerIndex)
3044            BODY: do 
3045               bodyIndex = obs_getBodyIndex(obsSpaceData)
3046               if (bodyIndex < 0) exit BODY
3047
3048               LUSE=( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated )
3049               if ( LUSE ) THEN
3050                  ASSIM = .TRUE.
3051                  NH = NH + 1
3052               end if
3053            end do BODY
3054
3055            ! If assimilations are requested, prepare and apply the observation operator
3056            ASSIMILATE: if (ASSIM) THEN
3057               iProfile=gps_iprofile_from_index(headerIndex)
3058
3059               ! Perform the (H(xb)DX-Y')/S operation
3060               NH1 = 0
3061
3062               ! Loop over all body indices for this headerIndex:
3063               ! (start at the beginning of the list)
3064               call obs_set_current_body_list(obsSpaceData, headerIndex)
3065               BODY_3: do 
3066                  bodyIndex = obs_getBodyIndex(obsSpaceData)
3067                  if (bodyIndex < 0) exit BODY_3
3068
3069                  LUSE=( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated )
3070                  if ( LUSE ) THEN
3071                     NH1 = NH1 + 1
3072
3073                     ! Normalized increment
3074                     ZINC = obs_bodyElem_r(obsSpaceData,OBS_WORK,bodyIndex)
3075
3076                     ! O-F Tested criteria:
3077                     DPJO1(1:4*NGPSLEV) = ZINC * oop_vRO_Jacobian(iProfile,NH1,1:4*NGPSLEV)
3078
3079                     ! Accumulate the gradient of the observation cost function:
3080                     DPJO0(1:4*NGPSLEV) = DPJO0(1:4*NGPSLEV) + DPJO1(1:4*NGPSLEV)
3081                  end if
3082               end do BODY_3
3083            end if ASSIMILATE
3084         end if DATYP
3085
3086         ! Store H* (HX - Z)/SIGMA in COMMVO
3087         tt_column => col_getColumn(columnAnlInc,headerIndex,'TT')
3088         hu_column => col_getColumn(columnAnlInc,headerIndex,'HU')
3089         height_column => col_getColumn(columnAnlInc,headerIndex,'Z_T')
3090         p_column => col_getColumn(columnAnlInc,headerIndex,'P_T')
3091         do JL = 1, NGPSLEV
3092            tt_column(JL) = DPJO0(JL)
3093            hu_column(JL) = DPJO0(JL+NGPSLEV)
3094            height_column(JL) = DPJO0(JL+2*NGPSLEV)
3095            p_column(JL)  = DPJO0(JL+3*NGPSLEV)
3096         end do
3097      end do HEADER
3098      
3099    end subroutine oop_HTro
3100
3101
3102    !--------------------------------------------------------------------------
3103
3104    subroutine oop_HTgp( initializeLinearization_opt ) 
3105      !
3106      !:Purpose: Compute Ht*grad(Jo) for all GPS ZTD observations
3107      !
3108      !:Note:  ZTD Jacobians are computed and stored in oop_Hgp (first iter.)
3109      implicit none
3110
3111      ! Arguments:
3112      logical, optional, intent(in) :: initializeLinearization_opt 
3113
3114      ! Locals:
3115      real(8) :: DPJO0(gps_ncvmx)
3116      real(8) :: JAC(gps_ncvmx)
3117      real(8) :: ZINC
3118      integer :: JL, NFLEV, iztd
3119      integer :: headerIndex, bodyIndex, icount
3120      logical :: ASSIM
3121      real(8), pointer :: tt_column(:),hu_column(:),height_column(:),p_column(:)
3122
3123      NFLEV  = col_getNumLev(columnTrlOnAnlIncLev,'TH')
3124
3125      ! call to calculate the GPSGB Jacobians
3126      call oop_calcGPSGBJacobian( columnTrlOnAnlIncLev, obsSpaceData, &
3127                                  initializeLinearization_opt=initializeLinearization_opt )
3128
3129      ! loop over all header indices of the 'GP' family (GPS observations)
3130      ! Set the header list & start at the beginning of the list
3131      call obs_set_current_header_list(obsSpaceData,'GP')
3132
3133      icount = 0
3134
3135      HEADER: do
3136         headerIndex = obs_getHeaderIndex(obsSpaceData)
3137         if (headerIndex < 0) exit HEADER
3138
3139         DPJO0(:) = 0.0D0
3140         JAC(:)   = 0.0D0
3141
3142         ! Scan for requested ZTD assimilation
3143         ASSIM = .FALSE.
3144
3145         ! loop over all body indices (still in the 'GP' family)
3146         ! Set the body list & start at the beginning of the list)
3147         call obs_set_current_body_list(obsSpaceData, headerIndex)
3148         BODY: do 
3149            bodyIndex = obs_getBodyIndex(obsSpaceData)
3150            if (bodyIndex < 0) exit BODY
3151
3152            if (   (obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex) == 189) &
3153                 .and. (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == bufr_nezd) &
3154                 .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) ) then
3155               ASSIM = .TRUE.
3156            end if
3157         end do BODY
3158         !
3159         if (ASSIM) THEN
3160
3161            icount = icount + 1
3162            iztd = gps_iztd_from_index(headerIndex)
3163            if ( iztd < 1 .or. iztd > gps_gb_numZTD ) then
3164               call utl_abort('oop_HTgp: ERROR: index from gps_iztd_from_index() is out of range!')
3165            end if
3166
3167            do JL = 1, 4*NFLEV
3168               JAC(JL) = oop_vZTD_Jacobian(iztd,JL)
3169            end do
3170
3171            ! Get Ht*grad(HeaderIndex) = Ht*(H'dx - d)/sigma_o^2
3172            ! loop over all body indices (still in the 'GP' family)
3173            ! Start at the beginning of the list)
3174            call obs_set_current_body_list(obsSpaceData, headerIndex)
3175            BODY_2: do 
3176               bodyIndex = obs_getBodyIndex(obsSpaceData)
3177               if (bodyIndex < 0) exit BODY_2
3178               if (   (obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex ) == 189) &
3179                    .and. (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == bufr_nezd) &
3180                    .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) ) then
3181                  ZINC = obs_bodyElem_r(obsSpaceData,OBS_WORK,bodyIndex)
3182
3183                  ! Accumulate the gradient of the observation cost function
3184                  DPJO0(1:4*NFLEV) = ZINC * oop_vZTD_Jacobian(iztd,:)
3185               end if
3186            end do BODY_2
3187
3188            ! Store Ht*grad(HeaderIndex) in COMMVO
3189            tt_column     => col_getColumn(columnAnlInc,headerIndex,'TT')
3190            hu_column     => col_getColumn(columnAnlInc,headerIndex,'HU')
3191            height_column => col_getColumn(columnAnlInc,headerIndex,'Z_T')
3192            p_column      => col_getColumn(columnAnlInc,headerIndex,'P_T')
3193            do JL = 1, NFLEV
3194               tt_column(JL) = DPJO0(JL)
3195               hu_column(JL) = DPJO0(JL+NFLEV)
3196               height_column(JL) = DPJO0(JL+2*NFLEV)
3197               p_column (JL) = DPJO0(JL+3*NFLEV)
3198            end do
3199
3200         end if ! ASSIM
3201
3202      end do HEADER
3203
3204    end subroutine oop_HTgp
3205
3206    ! --------------------------------------------------------------------------
3207
3208    subroutine oop_HTheighCoordObs()
3209      !:Purpose: Compute the adjoint operator of the
3210      !           vertical interpolation of geometric-height based data.
3211      !           Including Radar Doppler velocity data.
3212      !
3213      !  delta x is updated by this routine 
3214      implicit none
3215
3216      ! Locals:
3217      integer :: bodyIndex, headerIndex, levelIndex, bufrCode, familyIndex
3218      integer :: bodyIndexStart, bodyIndexEnd, bodyIndex1
3219      real(8) :: levelAltLow, levelAltHigh, azimuth, obsAltitude, HDX 
3220      real(8) :: anlIncValueLow, anlIncValueHigh, trlValueLow, trlValueHigh
3221      real(8) :: vInterpWeightLow, vInterpWeightHigh
3222      real(8), pointer :: du_column(:), dv_column(:), height_column(:)
3223      integer, parameter :: NUMFAMILY=3
3224      character(len=2) :: listFamily(NUMFAMILY), cfam
3225     
3226      listFamily(1) = 'RA' ! Doppler velocity (Radial Wind) burf_radvel
3227      listFamily(2) = 'PR' ! Dew point difference           burf_nees
3228      listFamily(3) = 'AL' ! Aladin HLOS wind               burf_neal
3229      
3230      ! Loop over all families
3231      FAMILY: do familyIndex = 1, NUMFAMILY
3232
3233        ! Loop over header indices 
3234        call obs_set_current_header_list(obsSpaceData, listFamily(familyIndex))
3235        HEADER: do  
3236          headerIndex = obs_getHeaderIndex(obsSpaceData)  
3237          if ( headerIndex < 0 ) exit HEADER
3238
3239          if  ( listFamily(familyIndex) == 'RA' ) then
3240            ! Azimuth of the radar beam
3241            azimuth   = obs_headElem_r(obsSpaceData, OBS_RZAM, headerIndex)
3242          end if
3243
3244          ! Local vector state
3245          du_column  => col_getColumn(columnAnlInc, headerIndex, 'UU') 
3246          dv_column  => col_getColumn(columnAnlInc, headerIndex, 'VV')
3247
3248          ! Loop over body indices 
3249          call obs_set_current_body_list(obsSpaceData, headerIndex)
3250          BODY: do
3251            bodyIndex = obs_getBodyIndex(obsSpaceData)
3252            if ( bodyIndex < 0 ) exit BODY
3253            ! only process observations flagged to be assimilated
3254            if ( obs_bodyElem_i(obsSpaceData, OBS_ASS, bodyIndex) /= obs_assimilated ) cycle BODY
3255
3256            ! Check that this observation has the expected bufr element ID
3257            bufrCode = obs_bodyElem_i(obsSpaceData, OBS_VNM, bodyIndex)
3258            if ( bufrCode == BUFR_logRadarPrecip ) cycle BODY
3259
3260            if ( bufrCode == bufr_radvel ) then
3261              ! OBS_LYR returns the index of the model level just above the observation
3262              !   level=1 is the highest level such that level+1 is lower 
3263              levelIndex = obs_bodyElem_i(obsSpaceData, OBS_LYR, bodyIndex)
3264              levelAltHigh = col_getHeight(columnTrlOnAnlIncLev, levelIndex,   headerIndex, 'MM')
3265              levelAltLow  = col_getHeight(columnTrlOnAnlIncLev, levelIndex+1, headerIndex, 'MM')
3266              obsAltitude = obs_bodyElem_r(obsSpaceData, OBS_PPP, bodyIndex) 
3267
3268              ! HDX from store
3269              HDX = obs_bodyElem_r(obsSpaceData, OBS_WORK, bodyIndex)
3270              vInterpWeightHigh = (obsAltitude - levelAltLow)/(levelAltHigh - levelAltLow)
3271              vInterpWeightLow = 1.0D0 - vInterpWeightHigh
3272
3273              ! see oop_HheightCoordObs for the description of the H operator for radar Doppler velocity
3274              ! 
3275              ! In matrix form, adjoint of H operator for one observation:
3276              !
3277              !  delta x         = Ht TangentialWindResidual
3278              !
3279              !  [ deltaUUHigh ] = [ iwHigh*sin(az) ][ residual ]  
3280              !  [ deltaVVigh  ]   [ iwHigh*cos(az) ]
3281              !  [ deltaUULow  ]   [ iwLow *sin(az) ]
3282              !  [ deltaVVLow  ]   [ iwLow *cos(az) ]
3283              !
3284              ! With
3285              !   az     = beam azimuth (radians, met convention) 
3286              !   iwHigh = Interpolation Weight for model level just above observation
3287              !   iwLow  =                 "                         below     "
3288              !   residual = OmP - Hdx
3289              !   deltaUUHigh and deltaVVHigh = wind components on model level just above observation
3290              !   deltaUULow  and deltaVVLow  =                 "                   below     "
3291
3292              !delta x is updated
3293              du_column(levelIndex  ) = du_column(levelIndex  ) + vInterpWeightHigh*HDX*sin(azimuth)
3294              dv_column(levelIndex  ) = dv_column(levelIndex  ) + vInterpWeightHigh*HDX*cos(azimuth)
3295              du_column(levelIndex+1) = du_column(levelIndex+1) + vInterpWeightLow *HDX*sin(azimuth)
3296              dv_column(levelIndex+1) = dv_column(levelIndex+1) + vInterpWeightLow *HDX*cos(azimuth)
3297
3298            else
3299              
3300              cfam = obs_getfamily(obsSpaceData,headerIndex_opt=headerIndex)
3301              write(*,*) 'CANNOT ASSIMILATE OBSERVATION!!!', &
3302                         'bufrCode =', bufrCode, 'cfam =',  cfam
3303              call utl_abort('oop_HTheighCoordObs')
3304
3305            end if
3306          end do BODY
3307        end do HEADER
3308      end do FAMILY
3309
3310    end subroutine oop_HTheighCoordObs
3311
3312    !--------------------------------------------------------------------------
3313
3314    subroutine oop_HTchm
3315      !:Purpose: Compute H^T * R^-1 (OmP-Hdx) for all CH observations  
3316      implicit none
3317      
3318      if (.not.obs_famExist(obsSpaceData,'CH', localMPI_opt = .true. )) return
3319      
3320      call oopc_CHobsoperators(columnTrlOnAnlIncLev,obsSpaceData,'adjoint', & 
3321                               columnAnlInc_opt=columnAnlInc) ! 'adjoint' for adjoint of the tangent linear operator
3322
3323    end subroutine oop_HTchm
3324
3325  end subroutine oop_Had
3326
3327  !--------------------------------------------------------------------------
3328  ! HUtoES
3329  !--------------------------------------------------------------------------
3330  function HUtoES( hu, tt, pressure ) result( es )
3331    !:Purpose:
3332    !          to calculate the dew point depression from specific
3333    !          humidity, temperature and pressure.  No ice phase
3334    !          is permitted and the pressure vector is given.
3335    implicit none
3336
3337    ! Arguments:
3338    real(8), intent(in) :: hu
3339    real(8), intent(in) :: tt
3340    real(8), intent(in) :: pressure
3341    ! Result:
3342    real(8)             :: es
3343
3344    ! Locals:
3345    real(8) :: husat, td
3346
3347    ! get the saturated vapor pressure from specific humidity
3348    husat = phf_foefq8(hu,pressure)
3349
3350    ! now the dewpoint temperature
3351    td = phf_fotw8(husat)
3352
3353    ! finally the dewpoint depression
3354    es = min(tt-td,MPC_MAXIMUM_ES_R8)
3355
3356  end function HUtoES
3357
3358  !--------------------------------------------------------------------------
3359  ! HUtoES_tl
3360  !--------------------------------------------------------------------------
3361  function HUtoES_tl(HU_inc,TT_inc,P_inc,HU_trl,PRES_trl) result(ES_inc)
3362    !:Purpose: TLM VERSION
3363    !           to calculate the dew point depression from specific
3364    !           humidity, temperature and pressure.  No ice phase
3365    !           is permitted and the pressure vector is given.
3366    implicit none
3367
3368    ! Arguments:
3369    real(8), intent(in) :: HU_inc
3370    real(8), intent(in) :: TT_inc
3371    real(8), intent(in) :: P_inc
3372    real(8), intent(in) :: HU_trl
3373    real(8), intent(in) :: PRES_trl
3374    ! Result:
3375    real(8)             :: ES_inc
3376
3377    ! Locals:
3378    real(8) :: ZE, ZTD, dTDdE, ZQBRANCH
3379    real(8) :: dESdLQ, dESdTT, dESdP
3380
3381    dESdTT = 1.0d0
3382
3383    !- Forward calculations of saturation vapour pressure and dewpoint temperature
3384    !  and adjoint of vapour pressure from adjoint of dewpoint temperature
3385    ZE   = phf_FOEFQ8(HU_trl, PRES_trl)
3386    ZTD  = phf_FOTW8 (ZE)
3387    dTDdE= phf_FODTW8(ZTD,ZE)
3388
3389    !- adjoint of temp. specific humidity and surface pressure due to changes in vapour pressure
3390    ZQBRANCH = phf_FQBRANCH(HU_trl)
3391
3392    dESdLQ = - ZQBRANCH*phf_FOEFQA(1.0d0,dTDdE,HU_trl,PRES_trl)
3393
3394    dESdP  = - ZQBRANCH*phf_FOEFQPSA(1.0d0,dTDdE,HU_trl,1.0d0)-  &
3395               (1.D0-ZQBRANCH)*(dTDdE*1.0d0)
3396
3397    ES_inc =  dESdLQ*HU_inc/HU_trl + dESdP*P_inc + dESdTT*TT_inc
3398
3399  end function HUtoES_tl
3400
3401  !--------------------------------------------------------------------------
3402  ! HUtoES_ad
3403  !--------------------------------------------------------------------------
3404  subroutine HUtoES_ad(HU_inc,TT_inc,P_inc,ES_inc,HU_trl,PRES_trl)
3405    ! Purpose: ADJOINT VERSION
3406    !          to calculate the dew point depression from specific
3407    !          humidity, temperature and pressure.  No ice phase
3408    !          is permitted and the pressure vector is given.
3409    implicit none
3410
3411    ! Arguments:
3412    real(8), intent(inout) :: HU_inc
3413    real(8), intent(inout) :: TT_inc
3414    real(8), intent(inout) :: P_inc
3415    real(8), intent(in)    :: ES_inc
3416    real(8), intent(in)    :: HU_trl
3417    real(8), intent(in)    :: PRES_trl
3418
3419    ! Locals:
3420    real(8) :: ZE,ZTD,dTDdE,ZQBRANCH
3421    real(8) :: dESdLQ,dESdTT,dESdP
3422
3423    dESdTT = 1.0d0
3424   
3425    !- Forward calculations of saturation vapour pressure and dewpoint temperature
3426    !  and adjoint of vapour pressure from adjoint of dewpoint temperature
3427    ZE = phf_FOEFQ8(HU_trl, PRES_trl)
3428
3429    ZTD=phf_FOTW8(ZE)
3430    dTDdE=phf_FODTW8(ZTD,ZE)
3431
3432    !- adjoint of temp. specific humidity and surface pressure due to changes in vapour pressure
3433    ZQBRANCH = phf_FQBRANCH(HU_trl)
3434    dESdLQ = - ZQBRANCH*phf_FOEFQA(1.0d0,dTDdE,HU_trl,PRES_trl)
3435
3436    dESdP  = - ZQBRANCH*phf_FOEFQPSA(1.0d0,dTDdE,HU_trl,1.0d0)-  &
3437               (1.D0-ZQBRANCH)*(dTDdE*1.0d0)
3438
3439    ! TLM: ES_inc =  dESdLQ*HU_inc/HU_trl + dESdP*P_inc + dESdTT*TT_inc
3440    ! ADJOINT:
3441    HU_inc = HU_inc + dESdLQ*ES_inc/HU_trl
3442    P_inc  = P_inc  + dESdP *ES_inc
3443    TT_inc = TT_inc + dESdTT*ES_inc
3444
3445  end subroutine HUtoES_ad
3446
3447  !--------------------------------------------------------------------------
3448  ! oop_calcGPSROJacobian
3449  !--------------------------------------------------------------------------
3450  subroutine oop_calcGPSROJacobian( columnTrlOnAnlIncLev, obsSpaceData, initializeLinearization_opt )
3451    !
3452    !:Purpose: Calculating the Jacobians of refractivity for oop_Hro/oop_HTro
3453    implicit none
3454
3455    ! Arguments:
3456    type(struct_columnData), intent(in)    :: columnTrlOnAnlIncLev
3457    type(struct_obs)       , intent(inout) :: obsSpaceData
3458    logical,       optional, intent(in)    :: initializeLinearization_opt 
3459
3460    ! Locals:
3461    real(8) :: zlat, lat
3462    real(8) :: zlon, lon
3463    real(8) :: zazm, azm
3464    integer :: isat
3465    real(8) :: rad, geo, zp0
3466    real(8), allocatable :: zpp(:), ztt(:), zhu(:), zHeight(:), zuu(:), zvv(:)
3467    real(8) :: zmt
3468    integer :: IDATYP, varNum
3469    integer :: jl, ngpslev, nwndlev
3470    integer :: headerIndex, bodyIndex, iProfile
3471    logical :: ASSIM
3472    logical, save :: initializeLinearization = .true.
3473    integer :: nh, nh1
3474    type(gps_profile)           :: prf
3475    real(8)       , allocatable :: h   (:),azmv(:)
3476    type(gps_diff), allocatable :: rstv(:)
3477
3478    ! Re-compute the Jacobian for re-linearized state
3479    if ( present(initializeLinearization_opt) ) then
3480      initializeLinearization = initializeLinearization_opt
3481    end if
3482
3483    if ( .not. initializeLinearization ) return
3484
3485    write(*,*) 'ENTER oop_calcGPSROJacobian'
3486
3487    initializeLinearization = .false.
3488
3489    ! Initializations
3490    ngpslev = col_getNumLev(columnTrlOnAnlIncLev,'TH')
3491    nwndlev = col_getNumLev(columnTrlOnAnlIncLev,'MM')
3492
3493    allocate(zpp (ngpslev))
3494    allocate(ztt (ngpslev))
3495    allocate(zhu (ngpslev))
3496    allocate(zHeight (ngpslev))
3497    allocate(zuu (ngpslev))
3498    allocate(zvv (ngpslev))
3499
3500    if ( .not. allocated(oop_vRO_Jacobian) ) then
3501      allocate( oop_vRO_Jacobian(gps_numroprofiles,gps_ro_maxprfsize,4*ngpslev) )
3502      allocate( oop_vRO_lJac    (gps_numROProfiles) )
3503      oop_vRO_Jacobian = 0.d0
3504      oop_vRO_lJac = .False.
3505    end if
3506
3507    allocate( h    (gps_ro_maxprfsize) )
3508    allocate( azmv (gps_ro_maxprfsize) )
3509    allocate( rstv (gps_ro_maxprfsize) )
3510
3511    ! Loop over all header indices of the 'RO' family (Radio Occultation)
3512    ! Set the header list (start at the beginning of the list)
3513    call obs_set_current_header_list(obsSpaceData,'RO')
3514    HEADER: do
3515      headerIndex = obs_getHeaderIndex(obsSpaceData)
3516      if (headerIndex < 0) exit HEADER
3517
3518      ! Process only refractivity data (codtyp 169)
3519      IDATYP = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
3520      DATYP: if ( IDATYP == 169 ) then
3521        ! Scan for requested data values of the profile, and count them
3522        assim = .false.
3523        nh = 0
3524
3525        ! Loop over all body indices for this headerIndex:
3526        ! (start at the beginning of the list)
3527        call obs_set_current_body_list(obsSpaceData, headerIndex)
3528        BODY: do 
3529          bodyIndex = obs_getBodyIndex(obsSpaceData)
3530          if (bodyIndex < 0) exit BODY
3531          if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) then
3532            assim = .true.
3533            nh = nh + 1
3534          endif
3535        enddo BODY
3536
3537        ! If assimilations are requested, prepare and apply the observation operator
3538        ASSIMILATE: if (assim) then
3539          iProfile = gps_iprofile_from_index(headerIndex)
3540          if (oop_vRO_lJac(iProfile)) cycle                  ! If already done, end this HEADER
3541          varNum = gps_vRO_IndexPrf(iProfile, 2)
3542
3543          ! Profile at the observation location:
3544          ! Basic geometric variables of the profile:
3545          zlat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
3546          zlon = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
3547          isat = obs_headElem_i(obsSpaceData,OBS_SAT,headerIndex)
3548          rad  = obs_headElem_r(obsSpaceData,OBS_TRAD,headerIndex)
3549          geo  = obs_headElem_r(obsSpaceData,OBS_GEOI,headerIndex)
3550          azm = obs_headElem_r(obsSpaceData,OBS_AZA,headerIndex)
3551          zmt  = col_getHeight(columnTrlOnAnlIncLev,0,headerIndex,'SF')
3552          lat  = zlat * MPC_DEGREES_PER_RADIAN_R8
3553          lon  = zlon * MPC_DEGREES_PER_RADIAN_R8
3554          zazm = azm / MPC_DEGREES_PER_RADIAN_R8
3555          zp0  = col_getElem(columnTrlOnAnlIncLev,1,headerIndex,'P0')
3556          do jl = 1, ngpslev
3557            ! Profile x_b
3558            zpp(jl) = col_getPressure(columnTrlOnAnlIncLev,jl,headerIndex,'TH')
3559            ztt(jl) = col_getElem(columnTrlOnAnlIncLev,jl,headerIndex,'TT') - MPC_K_C_DEGREE_OFFSET_R8
3560            zhu(jl) = col_getElem(columnTrlOnAnlIncLev,jl,headerIndex,'HU')
3561            zHeight(jl) = col_getHeight(columnTrlOnAnlIncLev,jl,headerIndex,'TH')
3562          enddo
3563
3564          if ((col_getPressure(columnTrlOnAnlIncLev,1,headerIndex,'TH') + 1.0d-4) <  &
3565               col_getPressure(columnTrlOnAnlIncLev,1,headerIndex,'MM')) then
3566            ! case with top thermo level above top momentum level (Vcode=5002)
3567            do jl = 1, nwndlev
3568              zuu(jl) = col_getElem(columnTrlOnAnlIncLev,jl,headerIndex,'UU')
3569              zvv(jl) = col_getElem(columnTrlOnAnlIncLev,jl,headerIndex,'VV')
3570            enddo
3571          else
3572            ! case without top thermo above top momentum level or unstaggered (Vcode=5001/4/5)
3573            do jl = 1, nwndlev-1
3574              zuu(jl) = col_getElem(columnTrlOnAnlIncLev,jl+1,headerIndex,'UU')
3575              zvv(jl) = col_getElem(columnTrlOnAnlIncLev,jl+1,headerIndex,'VV')
3576            enddo
3577            zuu(nwndlev) = zuu(nwndlev-1)
3578            zvv(nwndlev) = zuu(nwndlev-1)
3579          endif
3580          zuu(ngpslev) = zuu(nwndlev)
3581          zvv(ngpslev) = zuu(nwndlev)
3582
3583          ! GPS profile structure:
3584          call gps_struct1sw_v2(ngpslev,zlat,zlon,zazm,zmt,rad,geo,zp0,zpp,ztt,zhu,zuu,zvv,zHeight,prf)
3585
3586          ! Prepare the vector of all the observations:
3587          nh1 = 0
3588          call obs_set_current_body_list(obsSpaceData, headerIndex)
3589          BODY_2: do 
3590            bodyIndex = obs_getBodyIndex(obsSpaceData)
3591            if (bodyIndex < 0) exit BODY_2
3592            if ( obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated ) then
3593              nh1      = nh1 + 1
3594              h(nh1)   = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
3595              azmv(nh1)= zazm
3596            endif
3597          enddo BODY_2
3598
3599          ! Apply the observation operator:
3600          ! varNum = bufr_nebd (15037) or varNum = bufr_nerf (15036) for GPS-RO
3601          if (varNum == bufr_nebd) then
3602            call gps_bndopv1(h, azmv, nh, prf, rstv)
3603          else
3604            call gps_refopv (h, nh, prf, rstv)
3605          end if
3606          do nh1 = 1, nh
3607            oop_vRO_Jacobian(iprofile,nh1,1:4*ngpslev)= rstv(nh1)%dvar(1:4*ngpslev)
3608          end do
3609          oop_vRO_lJac(iProfile) = .True.
3610        endif ASSIMILATE
3611      endif DATYP
3612    enddo HEADER
3613
3614    deallocate( rstv )
3615    deallocate( azmv )
3616    deallocate( h    )
3617
3618    deallocate(zvv)
3619    deallocate(zuu)
3620    deallocate(zHeight)
3621    deallocate(zhu)
3622    deallocate(ztt)
3623    deallocate(zpp)
3624
3625    write(*,*) 'EXIT oop_calcGPSROJacobian'
3626    
3627
3628  end subroutine oop_calcGPSROJacobian
3629
3630  !--------------------------------------------------------------------------
3631  ! oop_calcGPSGBJacobian
3632  !--------------------------------------------------------------------------
3633  subroutine oop_calcGPSGBJacobian( columnTrlOnAnlIncLev, obsSpaceData, initializeLinearization_opt )
3634    !
3635    !:Purpose: Calculating the Jacobians of ZTD for oop_Hgp/oop_HTgp
3636    implicit none
3637
3638    ! Arguments:
3639    type(struct_columnData), intent(in)    :: columnTrlOnAnlIncLev
3640    type(struct_obs)       , intent(inout) :: obsSpaceData
3641    logical, optional      , intent(in)    :: initializeLinearization_opt 
3642
3643    ! Locals:
3644    real(8) :: ZLAT, Lat
3645    real(8) :: ZLON, Lon
3646    real(8), allocatable :: ZTTB(:)
3647    real(8), allocatable :: ZHUB(:)
3648    real(8), allocatable :: zHeight(:)
3649    real(8), allocatable :: ZPPB(:)
3650    real(8) :: ZP0B, ZPSMOD, ZPWMOD, ZPWMOD2, dZTD
3651    real(8) :: ZMT
3652    real(8) :: sfcfield
3653    real(8) :: dxq1, dxq2, dxq3
3654    real(8) :: ZLEV, ZDZMIN
3655    real(8) :: JAC(gps_ncvmx)
3656    integer :: headerIndex, bodyIndex
3657    integer :: JL, NFLEV, iztd, icount, vcode
3658    logical :: ASSIM
3659    type(gps_profilezd) :: PRF, PRF2
3660    type(gps_diff)      :: ZTDOPV, ZTDOPV2
3661    type(struct_vco), pointer :: vco_anl
3662    character(len=12) :: cstnid
3663    logical, save :: initializeLinearization = .true.
3664
3665    ! Re-compute the Jacobian for re-linearized state
3666    if ( present(initializeLinearization_opt) ) then
3667      initializeLinearization = initializeLinearization_opt
3668    end if
3669
3670    if ( .not. initializeLinearization ) return
3671
3672    write(*,*) 'ENTER oop_calcGPSGBJacobian'
3673
3674    initializeLinearization = .FALSE.
3675
3676    vco_anl => col_getVco(columnTrlOnAnlIncLev)
3677    vcode = vco_anl%vCode
3678
3679    ZDZMIN = gps_gb_DZMIN                     ! from modgpsztd_mod
3680
3681    NFLEV  = col_getNumLev(columnTrlOnAnlIncLev,'TH')
3682
3683    ! Initializations
3684    if ( .not. allocated(gps_ZTD_Index) ) call utl_abort('oop_calcGPSGBJacobian: ERROR:  gps_ZTD_Index not allocated!')
3685    if ( allocated(oop_vZTD_Jacobian) ) write(*,*) 'oop_calcGPSGBJacobian: WARNING, oop_vZTD_Jacobian is already allocated. Re-allocating'
3686    call utl_reallocate(oop_vZTD_Jacobian,gps_gb_numZTD,4*NFLEV)
3687    oop_vZTD_Jacobian(:,:) = 0.0d0
3688
3689    allocate(ZTTB(NFLEV))
3690    allocate(ZHUB(NFLEV))
3691    allocate(zHeight(NFLEV))
3692    allocate(ZPPB(NFLEV))
3693
3694    write(*,*) 'oop_calcGPSGBJacobian: Storing Jacobians for GPS ZTD data ...'
3695    write(*,*) '   INFO: Analysis grid iversion = ', vcode
3696    write(*,*) '         col_getNumLev[columnTrlOnAnlIncLev,TH] = ', NFLEV
3697    write(*,*) '         gps_gb_numZTD = ', gps_gb_numZTD
3698
3699    icount = 0
3700
3701    ! loop over all header indices of the 'GP' family (GPS observations)
3702    call obs_set_current_header_list(obsSpaceData,'GP')
3703    HEADER_0: do
3704      headerIndex = obs_getHeaderIndex(obsSpaceData)
3705      if (headerIndex < 0) exit HEADER_0
3706
3707      ! Scan for ZTD assimilation at this location
3708      ASSIM = .FALSE.
3709
3710      ! loop over all body indices for this headerIndex
3711      call obs_set_current_body_list(obsSpaceData, headerIndex)
3712      BODY_0: do 
3713        bodyIndex = obs_getBodyIndex(obsSpaceData)
3714        if (bodyIndex < 0) exit BODY_0
3715        if (   (obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex) == 189) &
3716             .and. (obs_bodyElem_i(obsSpaceData,OBS_VNM,bodyIndex) == bufr_nezd) &
3717             .and. (obs_bodyElem_i(obsSpaceData,OBS_ASS,bodyIndex) == obs_assimilated) ) then
3718          ZLEV = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
3719          ASSIM = .TRUE.
3720        end if
3721      end do BODY_0
3722
3723      if ( ASSIM ) THEN
3724        ! LR background profile at the observation location x :
3725        icount = icount + 1
3726        Lat  = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
3727        ZLAT = Lat * MPC_DEGREES_PER_RADIAN_R8
3728        Lon  = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
3729        ZLON = Lon * MPC_DEGREES_PER_RADIAN_R8
3730        ZP0B = col_getElem(columnTrlOnAnlIncLev,1,headerIndex,'P0')
3731        do JL = 1, NFLEV
3732          ZTTB(JL) = col_getElem(columnTrlOnAnlIncLev,JL,headerIndex,'TT') - MPC_K_C_DEGREE_OFFSET_R8
3733          ZHUB(JL) = col_getElem(columnTrlOnAnlIncLev,JL,headerIndex,'HU')
3734          ZPPB(JL) = col_getPressure(columnTrlOnAnlIncLev,JL,headerIndex,'TH')
3735          zHeight(JL) = col_getHeight(columnTrlOnAnlIncLev,JL,headerIndex,'TH')
3736        end do
3737        if ( abs(ZPPB(NFLEV)-ZP0B) > 0.1 ) then
3738          write(*,*) ' oop_calcGPSGBJacobian: ERROR: |ZPPB(NFLEV)-ZP0B| > 0.1'
3739          write(*,*) '          ZPPB(NFLEV), ZP0B =', ZPPB(NFLEV), ZP0B
3740        end if
3741        ZMT = col_getHeight(columnTrlOnAnlIncLev,0,headerIndex,'SF')
3742
3743        call gps_structztd_v2(NFLEV,Lat,Lon,ZMT,ZP0B,ZPPB,ZTTB,ZHUB,zHeight,gps_gb_LBEVIS,gps_gb_IREFOPT,PRF)
3744        call gps_ztdopv(ZLEV,PRF,gps_gb_LBEVIS,ZDZMIN,ZTDopv,ZPSMOD,gps_gb_IZTDOP)
3745
3746        ! Observation Jacobian H'(xb)            
3747        JAC = ZTDopv%DVar
3748        iztd = gps_iztd_from_index(headerIndex)
3749        do JL = 1, 4*NFLEV
3750           oop_vZTD_Jacobian(iztd,JL) = JAC(JL)
3751        end do
3752
3753        if ( icount <= 3 .and. gps_gb_LTESTOP ) then
3754          write(*,*) '--------------------------------------------------------- '
3755          cstnid = obs_elem_c(obsSpaceData,'STID',headerIndex)
3756          write(*,*) iztd, cstnid, 'ZTDopv (m) = ', ZTDopv%Var
3757          call gps_pw(PRF,ZPWMOD)
3758
3759          sfcfield = ZP0B + 50.0d0
3760          call gps_structztd_v2(NFLEV,Lat,Lon,ZMT,sfcfield,ZPPB,ZTTB,ZHUB,zHeight,gps_gb_LBEVIS,gps_gb_IREFOPT,PRF2)
3761          call gps_ztdopv(ZLEV,PRF2,gps_gb_LBEVIS,ZDZMIN,ZTDopv2,ZPSMOD,gps_gb_IZTDOP)
3762          write(*,*) ' ZTD Operator Test:  dP0 = +50 Pa'
3763          write(*,*) ' dZTD NL     = ', ZTDopv2%Var - ZTDopv%Var
3764          write(*,*) ' dZTD Linear = ', oop_vZTD_Jacobian(iztd,4*NFLEV)*50.0d0
3765          write(*,*) ' '
3766
3767          ! q dx 
3768          dxq1 = 0.44D-01*ZHUB(64)
3769          dxq2 = 0.44D-01*ZHUB(65)
3770          dxq3 = 0.44D-01*ZHUB(66)
3771          ZHUB(64) = ZHUB(64) - dxq1
3772          ZHUB(65) = ZHUB(65) - dxq2
3773          ZHUB(66) = ZHUB(66) - dxq3
3774          call gps_structztd_v2(NFLEV,Lat,Lon,ZMT,ZP0B,ZPPB,ZTTB,ZHUB,zHeight,gps_gb_LBEVIS,gps_gb_IREFOPT,PRF2)
3775          call gps_ztdopv(ZLEV,PRF2,gps_gb_LBEVIS,ZDZMIN,ZTDopv2,ZPSMOD,gps_gb_IZTDOP)
3776          call gps_pw(PRF2,ZPWMOD2)
3777          write(*,*) ' ZTD Operator Test:  dQ = -0.44E-01*Q JL = 64,65,66'
3778          write(*,*) ' dPW (mm)    = ', ZPWMOD2 - ZPWMOD
3779          write(*,*) ' dZTD NL     = ', ZTDopv2%Var - ZTDopv%Var
3780          dZTD = oop_vZTD_Jacobian(iztd,64+NFLEV)*(-dxq1) + oop_vZTD_Jacobian(iztd,65+NFLEV)*(-dxq2) + &
3781               oop_vZTD_Jacobian(iztd,66+NFLEV)*(-dxq3)
3782          write(*,*) ' dZTD Linear = ', dZTD
3783          write(*,*) ' '
3784          ZHUB(64) = ZHUB(64) + dxq1
3785          ZHUB(65) = ZHUB(65) + dxq2
3786          ZHUB(66) = ZHUB(66) + dxq3
3787
3788          ! temperature dx                   
3789          ZTTB(64) = ZTTB(64) + 2.0d0
3790          ZTTB(65) = ZTTB(65) + 2.0d0
3791          ZTTB(66) = ZTTB(66) + 2.0d0
3792          call gps_structztd_v2(NFLEV,Lat,Lon,ZMT,ZP0B,ZPPB,ZTTB,ZHUB,zHeight,gps_gb_LBEVIS,gps_gb_IREFOPT,PRF2)
3793          call gps_ztdopv(ZLEV,PRF2,gps_gb_LBEVIS,ZDZMIN,ZTDopv2,ZPSMOD,gps_gb_IZTDOP)
3794          write(*,*) ' ZTD Operator Test:  dTT = +2.0K JL = 64,65,66'
3795          write(*,*) ' dZTD NL     = ', ZTDopv2%Var - ZTDopv%Var
3796          dZTD = oop_vZTD_Jacobian(iztd,64)*2.0d0 + oop_vZTD_Jacobian(iztd,65)*2.0d0 + &
3797               oop_vZTD_Jacobian(iztd,66)*2.0d0
3798          write(*,*) ' dZTD Linear = ', dZTD
3799          write(*,*) '--------------------------------------------------------- '              
3800        end if
3801
3802      end if
3803
3804    end do HEADER_0
3805
3806    deallocate(ZTTB)
3807    deallocate(ZHUB)
3808    deallocate(zHeight)
3809    deallocate(ZPPB)
3810
3811    write(*,*) 'oop_calcGPSGBJacobian:   Number of ZTD data (icount) = ', icount
3812    write(*,*) '           Expected number (gps_gb_numZTD) = ', gps_gb_numZTD
3813    write(*,*) '           Last iztd                       = ', iztd
3814    write(*,*) '           gps_ZTD_Index(1)                = ', gps_ZTD_Index(1)
3815    write(*,*) '           gps_ZTD_Index(iztd)             = ', gps_ZTD_Index(iztd)
3816
3817    if ( icount /= gps_gb_numZTD ) then
3818      call utl_abort('oop_calcGPSGBJacobian: ERROR: icount /= gps_gb_numZTD!')
3819    end if
3820    if ( icount /= iztd ) then
3821      call utl_abort('oop_calcGPSGBJacobian: ERROR: icount /= iztd!')
3822    end if
3823    if ( gps_gb_numZTD /= iztd ) then
3824      call utl_abort('oop_calcGPSGBJacobian: ERROR: gps_gb_numZTD /= iztd!')
3825    end if
3826
3827    write(*,*) 'EXIT oop_calcGPSGBJacobian'
3828    
3829  end subroutine oop_calcGPSGBJacobian
3830
3831  function oop_iceScaling(obsSpaceData, bodyIndex) result(scaling)
3832    !
3833    !:Purpose: Calculate the scaling factor for
3834    !           ice related observations to convert from
3835    !           model space to observation space, i.e.
3836    !           H(iceConc) = scaling*iceConc + constant
3837    !
3838    implicit none
3839
3840    ! Arguments:
3841    type(struct_obs), intent(in) :: obsSpaceData
3842    integer,          intent(in) :: bodyIndex
3843    ! Result:
3844    real(8) :: scaling
3845
3846    ! Locals:
3847    integer          :: bufrCode
3848    integer          :: headerIndex, obsDate, monthIndex
3849    integer          :: trackCellNum
3850    character(len=8) :: ccyymmdd
3851
3852    bufrCode = obs_bodyElem_i( obsSpaceData, OBS_VNM, bodyIndex )
3853
3854    select case (bufrCode)
3855    case(BUFR_ICEC, BUFR_ICEP)
3856       scaling = 100.0d0
3857    case(BUFR_ICEV)
3858       scaling = 1.0d0
3859    case(BUFR_ICES)
3860       headerIndex = obs_bodyElem_i( obsSpaceData, OBS_HIND, bodyIndex )
3861       obsDate = obs_headElem_i( obsSpaceData, OBS_DAT, headerIndex ) 
3862       write(ccyymmdd, FMT='(i8.8)') obsDate
3863       read(ccyymmdd(5:6), FMT='(i2)') monthIndex
3864       trackCellNum = obs_headElem_i( obsSpaceData, OBS_FOV, headerIndex )
3865       scaling = oer_ascatAnisIce(trackCellNum,monthIndex) - &
3866            oer_ascatAnisOpenWater(trackCellNum,monthIndex)
3867    case default
3868       scaling = 0.0d0
3869    end select
3870
3871  end function oop_iceScaling
3872
3873end module obsOperators_mod