presProfileOperators_mod sourceΒΆ

   1module presProfileOperators_mod
   2  ! MODULE presProfileOperators_mod (prefix='ppo' category='8. Low-level utilities and constants')
   3  !
   4  !:Purpose:  Vertical interpolation, integration, and layer averaging subroutines.
   5  !           Includes the special routines designed to interpolate to the 
   6  !           (widely spaced) RTTOV pressure levels.
   7  !
   8  use utilities_mod
   9
  10  implicit none
  11  save
  12  private
  13
  14  ! public procedures
  15  
  16  ! Linear interpolation routine
  17  public :: ppo_lintv
  18  
  19  ! Stand-alone interpolator calling routine providing interpolation weights 
  20  ! For weights W(:,:) and initial vector X(:), the interpolated vector would be W*X
  21  ! Will call one of the following prodedures:
  22  !    ppo_layeravgInterpWgts     - weights for linear piecewise weighted averaging interpolation
  23  !    ppo_piecewiseLinearWgts    - weights for piecewise linear interpolation
  24  ! The first of routines is similar in content to those used with RTTOV pressure levels.
  25  public :: ppo_vertInterpWgts    ! Main call routine
  26
  27  ! Routines common to ppo_vertIntegWgts and ppo_vertAvgWgts indicated below.
  28  !     ppo_getLevelIndex - get the vertical input level index for level
  29  !                         within target layer and nearest specified layer boundary
  30  public :: ppo_vertLayersSetup
  31  
  32  ! Work arrays for ppo_vertInteg* and ppo_vertAvg*
  33  real(8), allocatable :: boundaries(:), weights(:,:)
  34  
  35  ! Stand-alone integration routines providing integration weights 
  36  ! For weights W(:,:) and initial vector X(:), the integrated values would be W*X  
  37  public :: ppo_vertIntegWgts
  38
  39  ! Stand-alone layer averaging routine (in lp(P)) providing weights 
  40  ! For weights W(:,:) and initial vector X(:), the average values would be W*X  
  41  public :: ppo_vertAvgWgts
  42  
  43  contains
  44
  45  !--------------------------------------------------------------------------
  46  ! PPO_LINTV
  47  !--------------------------------------------------------------------------
  48  SUBROUTINE PPO_LINTV (PVLEV,PVI,KNI, KNPROF,KNO,PPO,PVO)
  49      !
  50      !:Purpose: To perform the vertical interpolation in log of pressure and
  51      !          constant-value extrapolation of one-dimensional vectors. Input
  52      !          pressure levels can vary for each profile.
  53      !
  54      implicit none
  55
  56      ! Arguments:
  57      real(8) ,intent(in)  :: pvlev(kni,knprof) ! Vertical levels, pressure (source)
  58      real(8) ,intent(in)  :: pvi(kni,knprof)   ! Vector to be interpolated (source)
  59      integer ,intent(in)  :: kni               ! Number of input levels (source)
  60      integer ,intent(in)  :: knprof            ! Number of profiles
  61      integer ,intent(in)  :: kno               ! Number of output levels (destination)
  62      real(8) ,intent(in)  :: ppo(kno,knprof)   ! Vertical levels, pressure (destination)
  63      real(8) ,intent(out) :: pvo(kno,knprof)   ! Interpolated profiles (destination)
  64
  65      ! Locals:
  66      INTEGER  JI, JK, JO, profileIndex, IK, IORDER
  67      REAL(8)     ZPI (0:KNI+1,KNPROF)
  68      REAL(8)     ZPVI(0:KNI+1,KNPROF)
  69      INTEGER  IL  (KNO    ,KNPROF)      
  70      REAL(8) ZW1, ZW2
  71      REAL(8) ZP, XI, ZRT, ZP1, ZP2
  72
  73      !
  74      !**   1. Initialization for vertical extrapolation (extra dummy levels)
  75      !     .  --------------------------------------------------------------
  76      !
  77      
  78      ZPI(0,:)=2000.D0
  79      ZPI(KNI+1,:)=2000.D0
  80      
  81      !
  82      !**      1.1 Determine if input pressure levels are in ascending or
  83      !     .      descending order.
  84      !     .     -------------------------------------------------------
  85      !
  86      IF ( PVLEV(1,1)  <  PVLEV(KNI,1) ) THEN
  87         IORDER = 1
  88      ELSE
  89         IORDER = -1
  90      END IF
  91      !
  92      !**   2. Compute pressure levels pressure
  93      !     .  ------------------------------------------------
  94      !
  95
  96      !
  97      !**   2.1 Source levels
  98      !     .   -------------
  99      !
 100          
 101      ZPI(1:KNI,:) = PVLEV(1:KNI,:)
 102
 103      !
 104      !
 105      !*    3.  Interpolate in log of pressure or extrapolate with constant value
 106      !*    .   for each destination pressure level
 107      !     .   -----------------------------------------------------------------
 108      !
 109
 110      !
 111      !
 112      !*    .  3.1  Find the adjacent level below
 113      !     .       -----------------------------
 114      !
 115      !
 116      
 117      IL(:,:)=0
 118      !
 119      DO JI=1,KNI
 120         DO profileIndex = 1, KNPROF
 121            DO JO=1,KNO
 122              ZRT = PPO(JO,profileIndex)
 123               ZP = ZPI(JI,profileIndex)
 124               XI = SIGN(1.0D0,IORDER*(ZRT-ZP))
 125               IL(JO,profileIndex) = IL(JO,profileIndex) + MAX(0.0D0,XI)
 126            END DO
 127         END DO
 128      END DO
 129      !
 130      !
 131      !*    .  3.2  Fill extra levels, for constant value extrapolation
 132      !     .       ---------------------------------------------------
 133      !
 134      
 135      DO profileIndex = 1, KNPROF
 136         DO JK = 1, KNI
 137            ZPVI(JK,profileIndex) = PVI(JK,profileIndex)
 138         END DO
 139      END DO
 140      DO profileIndex = 1, KNPROF
 141         ZPVI(0    ,profileIndex) = PVI(1  ,profileIndex)
 142         ZPVI(KNI+1,profileIndex) = PVI(KNI,profileIndex)
 143      END DO
 144      !
 145      !
 146      !*    .  3.3  Interpolation/extrapolation
 147      !     .       ---------------------------
 148      !
 149      
 150      DO profileIndex = 1, KNPROF
 151        DO JO=1,KNO
 152          ZP = PPO(JO,profileIndex)
 153          IK = IL(JO,profileIndex)
 154          ZP1 = ZPI(IK  ,profileIndex)
 155          ZP2 = ZPI(IK+1,profileIndex)
 156          ZW1 = LOG(ZP/ZP2)/LOG(ZP1/ZP2)
 157          ZW2 = 1.D0 - ZW1
 158          PVO(JO,profileIndex) = ZW1*ZPVI(IK,profileIndex) +  ZW2*ZPVI(IK+1,profileIndex)
 159        END DO
 160      END DO
 161           
 162  END SUBROUTINE PPO_LINTV
 163
 164  !==========================================================================
 165  !---- Stand-alone interpolation routine providing interpolation weights ---
 166  ! For weights W(:,:) and initial vector X(:), the integrated values would be W*X.  
 167
 168  !--------------------------------------------------------------------------
 169  ! ppo_vertInterpWgts
 170  !--------------------------------------------------------------------------
 171  subroutine ppo_vertInterpWgts(pressInput,pressTarget,numInputLevs,numTargetLevs, &
 172                                wgts,kstart,kend,method_opt,skipType_opt,outbound_opt,success_opt)
 173    !
 174    !:Purpose: Determination of interpolation weights for interpolation to points 
 175    !          in a profile. Applies interpolation in log(Pressure).
 176    !
 177    !:Input:
 178    !       :pressInput:       pressure on reference column levels assumed to be in ascending order
 179    !       :pressTarget:      target pressure levels assumed to be in ascending order
 180    !       :numInputLevs:     number of input/reference levels
 181    !       :numTargetLevs:    number of target levels
 182    !       :method_opt:       Specified interpolation method
 183    !       :skipType_opt:     Skipping processing of specific target levels depending on case:
 184    !                          'default'  - extrapolation allowed and skipping application via input success_opt only
 185    !                          'noExtrap' - no extrapolation as well as additional skipping via input success_opt
 186    !                          'doAll&noExtrap'  - no extrapolation only (all other levels processed)
 187    !:InOut:
 188    !       :outbound_opt:     Flag set when beyond range of reference levels
 189    !       :success_opt:      LOgical indicating a valid target level
 190    !
 191    !:Output:
 192    !
 193    !       :wgts:             Interpolation coefficients/weights
 194    !       :kstart:           Index of first relevant referenence/input level for each target level
 195    !       :kend:             Index of last relevant referenence/input level for each target levbel
 196    ! 
 197    implicit none
 198
 199    ! Arguments:
 200    integer,                    intent(in)    :: numInputLevs  ! # of original/input vertical levels
 201    integer,                    intent(in)    :: numTargetLevs ! # of target vertical levels
 202    real(8),                    intent(in)    :: pressInput(numInputLevs)   ! pressure on reference column levels assumed in ascending order
 203    real(8),                    intent(in)    :: pressTarget(numTargetLevs) ! target pressure levels assumed to be in ascending order
 204    character(len=*), optional, intent(in)    :: method_opt   ! Specified interpolation method
 205    character(len=*), optional, intent(in)    :: skipType_opt ! Skipping processing of specific target levels depending on case
 206    integer,          optional, intent(inout) :: outbound_opt(numTargetLevs) ! Flag indicating if obs outside model vertical range (0 for no)
 207    logical,          optional, intent(inout) :: success_opt(numTargetLevs)  ! success of interpolation
 208    real(8),                    intent(out)   :: wgts(numTargetLevs,numInputLevs) ! Averaging weights
 209    integer,                    intent(out)   :: kstart(numTargetLevs) ! Index of first relevant original/input level for each target level
 210    integer,                    intent(out)   :: kend(numTargetLevs)   ! Index of last relevant original/input level for each target level
 211
 212    ! Locals:
 213    integer :: TargetIndex 
 214    real(8) :: logPressInput(numInputLevs),logPressTarget(numTargetLevs)
 215    real(8) :: pieceLinearWgts(numTargetLevs,2)
 216    logical :: success(numTargetLevs)
 217    character(len=20) :: method,skipType
 218
 219    if (present(method_opt)) then
 220      method = method_opt
 221    else
 222      method = 'default'
 223    end if
 224        
 225    if (present(skipType_opt)) then
 226      skipType = skipType_opt
 227    else
 228      skipType = 'default'
 229    end if 
 230 
 231    if (present(success_opt)) then
 232      if ( trim(skipType) == 'doAll&noExtrap' ) then
 233        success(:)=.true.
 234      else
 235        success(:) = success_opt(:)
 236      end if 
 237    else
 238      success(:) = .true.
 239    end if
 240    
 241    ! Flag to prevent extrapolation beyond the range of the reference column levels
 242        
 243    if ( trim(skipType) == 'noExtrap' .or. trim(skipType) == 'doAll&noExtrap' ) then
 244
 245      do TargetIndex=1,numTargetLevs
 246
 247        ! Check if target level is outside reference column range
 248        if ( PressTarget(TargetIndex) < PressInput(1) .or. &
 249             PressTarget(TargetIndex) > PressInput(numInputLevs) ) then
 250          success(TargetIndex)=.false.
 251          success_opt(TargetIndex)=.false.
 252          if (.not.present(outbound_opt)) cycle
 253          if (PressTarget(TargetIndex) < PressInput(1)) then
 254            outbound_opt(TargetIndex)=1
 255          else
 256            outbound_opt(TargetIndex)=2
 257          end if
 258        end if
 259      end do
 260    end if
 261        
 262    if ( trim(method) == 'default' ) then
 263
 264      ! Piecewise log-linear interpolation
 265      
 266      logPressInput(:) = log(PressInput(:))
 267      logPressTarget(:) = log(PressTarget(:))
 268
 269      call ppo_piecewiseLinearWgts(logPressTarget,logPressInput,numTargetLevs,numInputLevs, &
 270                                 pieceLinearWgts,kstart,success)
 271                                              
 272      kend(:) = kstart(:) + 1      
 273      do TargetIndex=1,numTargetLevs
 274        if (success(TargetIndex)) then
 275          kend(TargetIndex) = kstart(TargetIndex) + 1      
 276          wgts(TargetIndex,kstart(TargetIndex))=pieceLinearWgts(TargetIndex,1)
 277          wgts(TargetIndex,kend(TargetIndex))=pieceLinearWgts(TargetIndex,2)
 278        else
 279          kstart(TargetIndex) = 1
 280          kend(TargetIndex) = 1
 281          wgts(TargetIndex,1)=0.0d0
 282        end if
 283      end do
 284
 285    else if ( trim(method) == 'wgtAvg' .and. numTargetLevs > 1) then
 286    
 287      ! Piecewise weighted averaging according to distance 
 288      ! Involves all model levels within the profile range
 289      
 290      logPressInput(:) = log(PressInput(:))
 291      logPressTarget(:) = log(PressTarget(:))
 292      
 293      call ppo_layeravgInterpWgts(logPressTarget,logPressInput,numTargetLevs,numInputLevs, &
 294                                  wgts,kstart,kend,validLevel_opt=success)
 295
 296    else
 297      call utl_abort('ppo_vertInterpWgts: This interpolation observation operator is not recognized')
 298    end if
 299    
 300  end subroutine ppo_vertInterpWgts
 301  
 302  !------------ routines for interface ppo_linearInterpWgts ----------------
 303  
 304  !--------------------------------------------------------------------------
 305  ! ppo_piecewiseLinearWgts
 306  !--------------------------------------------------------------------------
 307  subroutine ppo_piecewiseLinearWgts(pvo,pvi,kno,kni,wgts,kstart,validLevel_opt)
 308    !
 309    !:Purpose: To obtain peacewise linear interpolation weigths.
 310    !          Assumes pv*(i) < pv*(i+1). Constant values extrapolation is applied.
 311    !
 312    implicit none
 313
 314    ! Arguments:
 315    real(8),           intent(in)  :: pvi(kni)     ! Vertical levels, pressure (source)
 316    integer,           intent(in)  :: kni          ! Number of input levels (source)
 317    integer,           intent(in)  :: kno          ! Number of output levels (destination)
 318    real(8),           intent(in)  :: pvo(kno)     ! Vertical levels, pressure (destination)
 319    real(8),           intent(out) :: wgts(kno,2)  ! Interpolation weights (destination)
 320    integer,           intent(out) :: kstart(kno)  ! Index i of pvlev level associated to pvo(j,1); pvo(j,2) is for pvlev level i+1
 321    logical, optional, intent(out) :: validLevel_opt(kno)
 322    
 323    ! Locals:
 324    integer :: pviIndex, pvoIndex, ik
 325    integer :: lowerlevel(0:KNO)     
 326    real(8) :: zw1, zp2
 327    logical :: validLevel(kno)
 328      
 329    if (present(validLevel_opt)) then
 330      validLevel(:) = validLevel_opt(:)
 331    else
 332      validLevel(:) = .true.
 333    end if
 334    
 335    ! Find the adjacent level below
 336    
 337    lowerlevel(:)=1
 338    lowerlevel(1:kno) = 0
 339    do pvoIndex=1,kno
 340      if (.not.validLevel(pvoIndex)) cycle 
 341      do pviIndex=max(1,lowerlevel(pvoIndex-1)),kni
 342        if ( pvo(pvoIndex) < pvi(pviIndex) ) then
 343          lowerlevel(pvoIndex) = pviIndex
 344          exit 
 345        end if
 346      end do
 347      if (pviIndex > kni .or. lowerlevel(pvoIndex) == 0 ) then
 348        lowerlevel(pvoIndex:kno) = kni+1
 349        exit
 350      end if
 351    end do
 352    
 353    ! Determine interpolation/extrapolation weights
 354  
 355    !$OMP PARALLEL DO PRIVATE(pvoIndex,ik,zp2,zw1)    
 356    do pvoIndex=1,kno
 357      if (.not.validLevel(pvoIndex)) then    
 358        kstart(pvoIndex)=1
 359        wgts(pvoIndex,1) = 0.0d0
 360        wgts(pvoIndex,2) = 0.0d0
 361        cycle
 362      end if
 363      
 364      ik = lowerlevel(pvoIndex)
 365      if (ik <=1 ) then
 366        kstart(pvoIndex)=1
 367        wgts(pvoIndex,1) = 1.0d0
 368        wgts(pvoIndex,2) = 0.0d0
 369      else if ( ik > kni ) then
 370        kstart(pvoIndex)=kni-1
 371        wgts(pvoIndex,1)=0.0d0
 372        wgts(pvoIndex,2)=1.0d0
 373      else
 374        zp2 = pvi(ik-1)
 375        zw1 = (pvo(pvoIndex)-zp2)/(pvi(ik)-zp2)
 376        wgts(pvoIndex,2) =zw1
 377        wgts(pvoIndex,1) = 1.0d0 - zw1
 378        kstart(pvoIndex) = ik - 1
 379      end if
 380    end do
 381    !$OMP END PARALLEL DO
 382           
 383  end subroutine ppo_piecewiseLinearWgts
 384
 385  !--------------------------------------------------------------------------
 386  ! ppo_layeravgInterpWgts
 387  !--------------------------------------------------------------------------
 388  subroutine ppo_layeravgInterpWgts(PX1,PX2,KN1,KN2,PZ,kstart,kend,PZS1_opt, &
 389                                     PZS2_opt,PZDPS_opt,rttov_opt,validLevel_opt)
 390    !
 391    !:Purpose: Determine profile interpolation weights by considering integrations 
 392    !          over of series of  segments [PX1(KI-1),PX1(KI+1)] using  piecewise 
 393    !          linear weighting with weights of zero at  KI-1 and KI+1 and 
 394    !          max weight at KI. KI ranges from 1 to KN1.
 395    !
 396    !          Can also provide gradient contributions from both 
 397    !          linear and non-linear components of interpolator. The non-linear 
 398    !          components (case PZS* is present) stem from vertical coordinate 
 399    !          independent variables (e.g. dependency on Ps). The gradients 
 400    !          contributions from the linear components are the interpolation weights.
 401    !
 402    !          For the interpolation model f(x) where
 403    !
 404    !                f(v,x) = F(v)*x
 405    !                       ~ F(vo)*xo + F(vo)*(x-xo) + (dF/dv)*xo*(v-vo)
 406    !                       = F(vo)*x + (dF/dv)*xo*(v-vo)                (eqn 1)
 407    ! 
 408    !          
 409    !                 F(vo):  array of interpolation weights
 410    !                         = array of gradients from the linear component
 411    !                 (dF/dv)*xo:  
 412    !                         array of gradients from linearized component.
 413    !                         (dF/dv) or (dF/dv)*(v-vo) provided when pzs* is present
 414    !
 415    !          Method:
 416    !             - Piecewise weighted interpolation in ln(P).
 417    !             - Journal reference:
 418    !               Rochon, Y.J., L. Garand, D.S. Turner, and S. Polavarapu.
 419    !               Jacobian mapping between coordinate systems in data assimilation,
 420    !               Q. J. of the Royal Met. Soc., vol 133, 1547-1558, 2007.
 421    !               (www.interscience.wiley.com) DOI:10.1002/qj.117
 422    !
 423    !               URL:
 424    !               http://www3.interscience.wiley.com/cgi-bin/fulltext/116320452/PDFSTART
 425    !  
 426    !:Comments:
 427    !                    
 428    !     Assumption: PX1(i)<PX1(i+1) & PX2(i)<PX2(i+1)
 429    !
 430    !     1) The input profile is now extrapolated at constant value.
 431    !
 432    !     The impact is of most practical significance for instruments where 
 433    !     the weighting function peaks at or near the surface such as SSMI.
 434    !
 435    !     This approach increases the weights of contribution from
 436    !     the lowest and highest input domain levels for output
 437    !     layers intersecting these input domain boundaries. It consists
 438    !     of applying contant value extrapolation by introducing  
 439    !     'fake' or 'virtual' layers. For the lowest level of the input domain, 
 440    !     as example, this implies creating a virtual surface layer which 
 441    !     extends to the lower boundary of the output domain layer which 
 442    !     contains the input domain surface. This increases the
 443    !     contributing weight of the surface which would be otherwise 
 444    !     understimated in the original code due to the interpolator 
 445    !     actually doing piecewise weighted averaging.
 446    !
 447    !     2) COmment out use of 'zb' for consistency with RTTOV-9 when 
 448    !     rttov_opt = .true.
 449    !     See the four lines ending with !C1 and version 7 comment above.
 450    !
 451    !     3) A major reduction in computational time results from only
 452    !     assigning values to the non-zero ranges of the 2D output
 453    !     arrays. These ranges of the 2D areas are identified by 'kstart'
 454    !     and 'kend'. Initialization to zero for values within these ranges 
 455    !     is done using 1D work arrays 'zpz' and 'zpzd', with the resulting
 456    !     values then being assigned to the related elements of 'PZ' and 
 457    !     'PZDPS'.
 458    !
 459    !     Therefore, elements of 'PZ' and 'PZDPS' outside these ranges 
 460    !     could be undefined (i.e. NaN if not 0.0) and should not be used.
 461    !
 462    !     Other notable reductions in computational time stem (a) from inlining
 463    !     of 'sublayer' code (applied to a reduced degree in 'layeravg' as
 464    !     compared to 'rttov_layeravg_*) and (b) from updating the start 
 465    !     position 'istart' of the loops over J. The latter was faciliated 
 466    !     by moving the loop over KI inside 'layeravg'.
 467    !
 468    !     These improvements were originally devised and implemented by 
 469    !     Deborah Salmond and Mats Hamrud (ECMWF) in the RTTOV-9 routines 
 470    !     'rttov_layeravg*'.
 471    !
 472    !     4) Contributors to improvements and changes to the original version 
 473    !     of 'layeravg' and to 'rttov_layeravg*': members of the RTTOV9 
 474    !     development team, namely Niels Bormann, Alan Geer, Deborah Salmond,
 475    !     and Mats Hamrud of ECMWF, Peter Rayer and Roger Saunders of the 
 476    !     Met Office and Pascal Brunel of Meteo-France, and Y.J. Rochon (EC).
 477    !                                
 478    implicit none
 479     
 480    ! Arguments:
 481    integer,           intent(in)  :: KN1          ! Dimension of PX1
 482    integer,           intent(in)  :: KN2          ! Dimension of other arrays
 483    real(8),           intent(in)  :: PX1(KN1)     ! Levels of output domain (e.g. lnP; in increasing values)
 484    real(8),           intent(in)  :: PX2(KN2)     ! Levels of input domain (e.g. lnP; in increasing values)
 485    real(8),           intent(out) :: PZ(KN1,KN2)  ! Resultant accumulated weighting factors for interp from input to output domain
 486    integer,           intent(out) :: KSTART(KN1)  ! Start index for relevant PZ row
 487    integer,           intent(out) :: KEND(KN1)    ! End index of relevant PZ row
 488    real(8), optional, intent(in)  :: PZS1_opt(KN1) ! dPX1/dv or perturbation dPX1/dv * delta(v) where PX1(v), e.g. v=Ps    
 489    real(8), optional, intent(in)  :: PZS2_opt(KN2) ! dPX2/dv or perturbation dPX2/dv * delta(v) where PX2(v), e.g. v=Ps
 490    real(8), optional, intent(out) :: PZDPS_opt(KN1,KN2) ! dF/dv or perturbations (dF/dv)*(v-vo): Resultant accumulated factors for the gradients w.r.t. v (or perturbations) associated to coordinates
 491    logical, optional, intent(in)  :: RTTOV_OPT  ! Commented out use of 'zb' when .true. for consistency with RTTOV-9
 492    logical, optional, intent(in)  :: validLevel_opt(kn1) ! Logical indicating validity of each output level
 493                  
 494    ! Locals:                
 495    logical  :: LGRADP,lgradp1,lgradp2,validLevel(kn1)
 496    integer  :: J,IC,ISTART,KI
 497    real(8)  :: Z1(0:KN1+1),ZW1,ZW2,ZSUM,ZB,ZBPS,ZWPS1,ZWPS2,ZDXD
 498    real(8)  :: PZ1(0:KN1+1),PZS2(KN2)
 499    real(8)  :: DZ(KN1+1),DZD(KN1+1),DXD(KN2+1)
 500    real(8)  :: ZPZ(KN2),ZPZD(KN2)
 501    real(8)  :: WX1,WX2,Y1,Y2,DY,DAD
 502    real(8), parameter :: WGT1=0.0d0, WGT2=1.0d0, WGT3=0.0d0
 503
 504    !- Set integration/averaging range boundaries for output domain.
 505    !  Range of integration for each layer ki is z1(ki-1) to z1(ki+1).
 506    !  Weighting function is linear with weights of 0.0 at z1(ki-1) and 
 507    !  z1(ki+1) and a weight of 1.0 at z1(ki).
 508
 509    z1(1:kn1)=px1(1:kn1)
 510    z1(0)=2.0*px1(1)-px1(2)
 511    z1(kn1+1)=2.0*px1(kn1)-px1(kn1-1)
 512
 513    if (present(pzs1_opt)) then 
 514      lgradp1=.true.
 515      lgradp=.true.     
 516      pz1(1:kn1)=pzs1_opt(1:kn1)
 517      pz1(0)=2.0*pzs1_opt(1)-pzs1_opt(2)
 518      pz1(kn1+1)=2.0*pzs1_opt(kn1)-pzs1_opt(kn1-1)
 519    else
 520      lgradp1=.false.
 521      lgradp=.false.
 522      pz1(0:kn1+1)=0.0d0
 523    end if
 524    if (present(pzs2_opt)) then 
 525      lgradp2=.true. 
 526      lgradp=.true.     
 527      pzs2(:)=pzs2_opt(:)
 528    else
 529      lgradp2=.false.
 530      pzs2(:)=0.0d0
 531    end if       
 532    if (present(validLevel_opt)) then
 533      validLevel(:) = validLevel_opt(:)
 534    else
 535      validLevel(:) = .true.
 536    end if
 537     
 538    !- Pre-calculate values (dzd and dxd) used by subroutine sublayer
 539
 540    dz(1:kn1+1)=z1(1:kn1+1)-z1(0:kn1)
 541    dzd(1:kn1+1)=1.0d0/dz(1:kn1+1)
 542
 543    dxd(2:kn2)=1.0d0/(px2(2:kn2)-px2(1:kn2-1))
 544    dxd(1)=dxd(2)
 545    dxd(kn2+1)=dxd(kn2)
 546
 547    !- Determine forward interpolator or TLM coefficients
 548       
 549    !- Loop over output domain levels for determining 
 550    !  contributing weights of input domain levels over 
 551    !  segments [PX1(KI-1),PX1(KI+1)]
 552
 553    istart=1
 554    do ki=1,kn1
 555
 556      if (.not.validLevel(ki)) then
 557         kstart(ki) = 1
 558         kend(ki) = 1
 559         pz(ki,:) = 0.0d0
 560         if (lgradp) pzdps_opt(ki,:)=0.0d0
 561         cycle
 562      end if         
 563      
 564      ! -- Consider constant value extrapolations cases for output domain 
 565      !    layers entirely outside the input domain.
 566
 567      if (z1(ki+1).le.px2(1)) then
 568
 569        ! pz(ki,1) is set to 1.0 to force constant value extrapolation
 570        ! of field (e.g. temperature) above highest input level.
 571
 572        pz(ki,1)=1.0d0
 573        if (lgradp) pzdps_opt(ki,1)=0.0
 574        kstart(ki)=1
 575        kend(ki)=1
 576        CYCLE
 577
 578      else if (z1(ki-1).ge.px2(kn2)) then
 579
 580        ! pz(ki:kni,kn2) is set to 1.0 to force constant value extrapolation 
 581        ! of field (e.g. temperature) below lowest input level.
 582 
 583        pz(ki:kn1,kn2)=1.0d0
 584        if (lgradp) pzdps_opt(ki:kn1,kn2)=0.0
 585        kstart(ki:kn1)=kn2
 586        kend(ki:kn1)=kn2
 587        return
 588      end if   
 589
 590      ! -- Consider piecewise averaging interpolation for output domain
 591      !    layers and layer segments within the input domain.
 592      !
 593      ! -- Loop over input layers within the (z1(ki-1),z1(ki)) and 
 594      !    (z1(ki),z1(ki+1)) integration ranges.
 595      !
 596      ! Accumulate contributions to integration components over the 
 597      !     different segments.
 598
 599      ic=0
 600      zpz(istart)=0.0d0
 601      if (lgradp) then
 602        zpzd(istart)=0.0d0
 603        do j=istart,kn2-1
 604          zpz(j+1)=0.0d0
 605          zpzd(j+1)=0.0d0
 606
 607          if (px2(j).ge.z1(ki+1)) exit
 608
 609          if (px2(j).lt.z1(ki).and.px2(j+1).gt.z1(ki-1)) then
 610
 611            ! Integration over the segment of the range (z1(ki-1),z1(ki))
 612            ! intersecting with the range (px2(j),px2(j+1))=(x1,x2)
 613
 614            call ppo_sublayerInterpWgts(z1(ki-1),z1(ki),dzd(ki),wgt1,wgt2, &
 615                 px2(j),px2(j+1),dxd(j+1),zpz(j),zpz(j+1), &
 616                 pz1(ki-1),pz1(ki), &
 617                 pzs2(j),pzs2(j+1),zpzd(j),zpzd(j+1),lgradp1,lgradp2)
 618            if (ic.eq.0) ic=j
 619          endif
 620
 621          if (px2(j+1).gt.z1(ki)) then
 622
 623            ! Integration over the segment of the range (z1(ki),z1(ki+1))
 624            ! intersecting with the range (px2(j),px2(j+1))=(x1,x2).
 625
 626            call ppo_sublayerInterpWgts(z1(ki),z1(ki+1),dzd(ki+1),wgt2,wgt3, &
 627                 px2(j),px2(j+1),dxd(j+1),zpz(j),zpz(j+1), &
 628                 pz1(ki),pz1(ki+1), &
 629                 pzs2(j),pzs2(j+1),zpzd(j),zpzd(j+1),lgradp1,lgradp2)
 630            if (ic.eq.0) ic=j
 631          endif
 632        enddo
 633
 634        if (ic.eq.0) then
 635          j=kn2    
 636          pz(ki,j)=1.0d0
 637          pzdps_opt(ki,j)=0.0
 638          kstart(ki)=j
 639          kend(ki)=j
 640          CYCLE
 641	else
 642          istart=ic
 643          kstart(ki)=istart
 644          kend(ki)=j	
 645        end if
 646
 647      else
 648
 649        ! Same as above but with a compressed subset of 'sublayer' inlined 
 650        ! for improved speed at least in setting interplation weights.
 651        ! This follows the corresponding change in 'rttov_layeravg' 
 652        ! by Deborah Salmond and Mats Hamrud (ECMWF) and takes advantage
 653        ! of the known wgt* values in each case.
 654        !
 655        ! See 'ppo_sublayerInterpWgts' for information on applied equations.
 656
 657        do j=istart,kn2-1
 658          zpz(j+1)=0.0d0
 659
 660          if (px2(j).ge.z1(ki+1)) exit
 661	  
 662          if (px2(j).lt.z1(ki).and.px2(j+1).gt.z1(ki-1)) then
 663
 664            ! Integration over the segment of the range (z1(ki-1),z1(ki))
 665            ! intersecting with the range (px2(j),px2(j+1))=(x1,x2)
 666
 667            if (z1(ki-1).lt.px2(j)) then
 668              y1=px2(j)
 669              wx1=(y1-z1(ki-1))
 670            else
 671              y1=z1(ki-1)
 672              wx1=0.0d0
 673            end if
 674            if (z1(ki).gt.px2(j+1)) then
 675              wx2=(px2(j+1)-z1(ki-1))
 676              dy=(px2(j+1)-y1)*dzd(ki)
 677              zpz(j)=zpz(j)+dy*wx1
 678              zpz(j+1)=zpz(j+1)+dy*wx2
 679            else
 680              dad=dxd(j+1)*dz(ki)*(z1(ki)-px2(j+1))
 681              dy=(z1(ki)-y1)*dzd(ki)
 682              zpz(j)=zpz(j)+dy*(wx1-dad)
 683              zpz(j+1)=zpz(j+1)+dy*(dz(ki)+dad)
 684            end if
 685
 686            if (ic.eq.0) ic=j
 687          endif
 688
 689          if (px2(j+1).gt.z1(ki)) then
 690
 691            ! Integration over the segment of the range (z1(ki),z1(ki+1))
 692            ! intersecting with the range (px2(j),px2(j+1))=(x1,x2).
 693
 694            if (z1(ki+1).gt.px2(j+1)) then
 695              y2=px2(j+1)
 696              wx2=z1(ki+1)-y2
 697            else
 698              y2=z1(ki+1)
 699              wx2=0.0d0
 700            end if
 701            if (z1(ki).lt.px2(j)) then
 702              wx1=z1(ki+1)-px2(j)
 703              dy=(y2-px2(j))*dzd(ki+1)
 704              zpz(j)=zpz(j)+dy*wx1
 705              zpz(j+1)=zpz(j+1)+dy*wx2
 706            else  
 707              dad=dxd(j+1)*dz(ki+1)*(z1(ki)-px2(j))
 708              dy=(y2-z1(ki))*dzd(ki+1)
 709              zpz(j)=zpz(j)+dy*(dz(ki+1)-dad)
 710              zpz(j+1)=zpz(j+1)+dy*(wx2+dad)
 711            end if
 712
 713            if (ic.eq.0) ic=j
 714          endif
 715        enddo
 716
 717        if (ic.eq.0) then
 718          j=kn2    
 719          pz(ki,j)=1.0d0
 720          kstart(ki)=j
 721          kend(ki)=j
 722          CYCLE
 723	else
 724          istart=ic
 725          kstart(ki)=istart
 726          kend(ki)=j	
 727        end if
 728
 729      end if
 730
 731      ! -- Consider constant value extrapolation contribution for output domain
 732      !    layers crossing lower or upper input domain boundaries.
 733      !
 734      ! -- Extend to below (and/or above) lowest (and/or highest) input levels
 735      !    if output layer intersects the corresponding input domain boundary.
 736      !
 737      !    A virtual layer covering the region between the input domain boundary 
 738      !    and the boundary of the output layer is added. This increases the
 739      !    contributing in weight of the input domain boundary roughly by the 
 740      !    relative thickness of the virtual layer to the entire output layer 
 741      !    thickness.
 742      !
 743      !    This follows updates by Alan Geer (ECMWF) in rttov_layeravg 
 744      !    of RTTOV-9. It is done for two reasons:
 745      !
 746      !       1) Piecewise weighted averaging using only the region
 747      !          within the input domain will tend to underestimate the impact
 748      !          of the input level boundary.
 749      !
 750      !       2) The regression coefficients of the output domain
 751      !          model are based on complete output domain layers, this being
 752      !          consistent with RTTOV convention. 
 753    
 754      if (z1(ki+1).gt.px2(kn2).and.z1(ki-1).lt.px2(kn2)) then
 755
 756        ! Increase contribution from lowest input level.
 757
 758        zw1=0.0d0
 759        zw2=0.0d0
 760        zwps1=0.0d0
 761        zwps2=0.0d0
 762        zb=z1(ki+1)
 763        zbps=pz1(ki+1)
 764        if (present(rttov_opt)) then
 765          if (rttov_opt) then
 766            if (z1(ki+1).gt.2*px2(kn2)-px2(kn2-1).and.ki.eq.kn1) then     !C1
 767              zb=2*px2(kn2)-px2(kn2-1)                                    !C1
 768              zbps=2*pzs2(kn2)-pzs2(kn2-1)                                !C1
 769            end if                                                        !C1
 770          end if                                                       
 771        end if
 772        zdxd=1.0d0/(zb-px2(kn2))
 773        if (z1(ki).lt.zb) then
 774          call ppo_sublayerInterpWgts(z1(ki),z1(ki+1),dzd(ki+1),wgt2,wgt3, &
 775                          px2(kn2),zb,zdxd,zw1,zw2,  &
 776                          pz1(ki),pz1(ki+1), &
 777                          pzs2(kn2),zbps,zwps1,zwps2,lgradp1,lgradp2)
 778        end if
 779        if (z1(ki).gt.px2(kn2)) then
 780          call ppo_sublayerInterpWgts(z1(ki-1),z1(ki),dzd(ki),wgt1,wgt2, &
 781                          px2(kn2),zb,zdxd,zw1,zw2, &
 782                          pz1(ki-1),pz1(ki), &
 783                          pzs2(kn2),zbps,zwps1,zwps2,lgradp1,lgradp2)
 784        end if
 785        zpz(kn2)=zpz(kn2)+zw1+zw2
 786        if (lgradp) zpzd(kn2)=zpzd(kn2)+zwps1+zwps2
 787      end if       
 788      if (z1(ki-1).lt.px2(1).and.z1(ki+1).gt.px2(1)) then
 789
 790        ! Increase contribution from highest input level.
 791
 792        zw1=0.0d0
 793        zw2=0.0d0
 794        zwps1=0.0d0
 795        zwps2=0.0d0
 796        zb=z1(ki-1)
 797        zbps=pz1(ki-1)
 798        if (present(rttov_opt)) then
 799          if (rttov_opt) then
 800            if (z1(ki-1).lt.2*px2(1)-px2(2).and.ki.eq.1) then          !C1
 801              zb=2*px2(1)-px2(2)                                       !C1
 802              zbps=2*pzs2(1)-pzs2(2)                                   !C1
 803            end if                                                     !C1
 804          end if
 805        end if
 806        zdxd=1.0d0/(px2(1)-zb)
 807        if (z1(ki).gt.zb) then
 808          call ppo_sublayerInterpWgts(z1(ki-1),z1(ki),dzd(ki),wgt1,wgt2, &
 809                          zb,px2(1),zdxd,zw1,zw2, &
 810                          pz1(ki-1),pz1(ki), &
 811                          zbps,pzs2(1),zwps1,zwps2,lgradp1,lgradp2)
 812        end if
 813        if (z1(ki).lt.px2(1)) then
 814          call ppo_sublayerInterpWgts(z1(ki),z1(ki+1),dzd(ki+1),wgt2,wgt3, &
 815                          zb,px2(1),zdxd,zw1,zw2, &
 816                          pz1(ki),pz1(ki+1), &
 817                          zbps,pzs2(1),zwps1,zwps2,lgradp1,lgradp2)
 818        end if
 819        zpz(1)=zpz(1)+zw1+zw2
 820        if (lgradp) zpzd(1)=zpzd(1)+zwps1+zwps2
 821      end if
 822
 823      ! -- Normalize sum to unity (instead of calculating and dividing by
 824      !    weighting denominator)
 825
 826      zsum=1.0d0/sum(zpz(kstart(ki):kend(ki)))
 827      pz(ki,kstart(ki):kend(ki))=zpz(kstart(ki):kend(ki))*zsum
 828
 829      if (lgradp) then
 830
 831        ! Normalize and account for denominator gradients, i.e.
 832
 833        ! d[sum1(w*t)/sum2(w)]/dv = sum1[t*(dw/dv)]/sum2(w) 
 834        !                          -sum1[t*w]*sum2[(dw/dv)]/sum2(w)^2       
 835
 836        pzdps_opt(ki,kstart(ki):kend(ki))=(zpzd(kstart(ki):kend(ki)) & 
 837                             -pz(ki,kstart(ki):kend(ki)) &
 838                             *sum(zpzd(kstart(ki):kend(ki))))*zsum
 839      end if
 840
 841    end do
 842
 843  end subroutine ppo_layeravgInterpWgts
 844
 845  !--------------------------------------------------------------------------
 846  ! ppo_sublayerInterpWgts
 847  !--------------------------------------------------------------------------
 848  subroutine ppo_sublayerInterpWgts(z1,z2,dzd,wgt1,wgt2,x1,x2,dxd,w1,w2, &
 849                                     pzs1,pzs2,pxs1,pxs2,wps1,wps2,lgradpx,lgradpz)
 850    !
 851    !:Purpose: Determine weight coefficient contributions to w1 and w2 to assign
 852    !          to input domain (e.g. NWP model) variables at x1 and x2. Weights 
 853    !          are determined from integration over the intersecting segment (y1,y2) 
 854    !          of the ranges (x1,x2) for the input domain and (z1,z2) for the 
 855    !          output domain. Integrals are approximated via the trapezoidal rule:
 856    !
 857    !          integral of f(x)=w(x)*t(x) from y1 to y2
 858    !
 859    !                                      = (f(y1)+f(y2))/2*(y2-y1)
 860    !                                      = w(y1)*t(y1)+w(y2)*t(y2)
 861    !                                      = w1*t(x1)+w2*t(x2)
 862    !                                      = w1*t1+w2*t2     
 863    !
 864    !          This is synonomous to having an integrand linear in x.
 865    !
 866    !          In the above (and below) equation(s), w1 and w2 are contributions to 
 867    !          the input values.
 868    !
 869    !          The weights for linearized contributions of non-linear interpolator
 870    !          components, i.e. gradient w.r.t. the vertical coordinate 
 871    !          independent variable (e.g. v*=Ps), are calculated 
 872    !          when LGRADP* is .true.:
 873    !
 874    !                pzps = pzps + (df/dx1)*(dx1/dvx1)+(df/dx2)*(dx2/dvx2)
 875    !                            + (df/dz1)*(dz1/dvz1)+(df/dz2)*(dz2/dvz2)
 876    !
 877    !                     = pzpz + (dw1/dx1*t1+dw2/dx1*t2)*pxs1
 878    !                            + (dw1/dx2*t1+dw2/dx2*t2)*pxs2
 879    !                            + (dw1/dz1*t1+dw2/dz1*t2)*pzs1
 880    !                            + (dw1/dz2*t1+dw2/dz2*t2)*pzs2
 881    !
 882    !          This routine provides terms on the right-hand-side.
 883    !
 884    !          Note: pxs* and pzs* can be provided either as gradients or 
 885    !          perturbations.
 886    ! 
 887    !          Method:
 888    !          - Piecewise weighted interpolation in ln(P).
 889    !          - Journal reference:
 890    !            Rochon, Y.J., L. Garand, D.S. Turner, and S. Polavarapu.
 891    !            Jacobian mapping between coordinate systems in data assimilation,
 892    !            Q. J. of the Royal Met. Soc., vol 133, 1547-1558, 2007. 
 893    !            (www.interscience.wiley.com) DOI:10.1002/qj.117
 894    !
 895    !           URL: 
 896    !           http://www3.interscience.wiley.com/cgi-bin/fulltext/116320452/PDFSTART 
 897    !
 898    !:Comments:
 899    !
 900    !     Assumptions:
 901    !     
 902    !        1) x1<x2
 903    !
 904    !        2) z1<z2
 905    !
 906    !        3) The ranges (z1,z2) and (x1,x2) overlap. The overlap region
 907    !        will be identified as (y1,y2) with y1<y2.
 908    !
 909    !     1) w(y1) and w(y2) are obtained by linear interpolation of the linear
 910    !     weighting function w with w(z1)=wgt1 and w(z2)=wgt2.
 911    !                                      
 912    !     2) The w1 and w2 above are determined by expanding t(y1) and t(y2)
 913    !     as linear functions of t(x1)=t1 and t(x2)=t2.
 914    !
 915    !     3) The factor of 1/2 in
 916    !
 917    !                          (f(y1)+f(y2))/2*(y2-y1)
 918    !                           = w(y1)*t(y1)+w(y2)*t(y2)
 919    !
 920    !     is omitted as normalization is performed in the calling routine
 921    !     LAYERAVG.
 922    !
 923    !     4) The code version of the interpolator part of 'int_sublayerInterpWgts'
 924    !     provided for RTTOV-9 assumed the following conditions:
 925    !
 926    !        (wgt1,wgt2)=(0,1),  d1=(y1-x1)=0 from y1=x1 or
 927    !                            wy1=0 from wgt1=0 and y1=z1,
 928    !     or
 929    !
 930    !        (wgt1,wgt2)=(1,0),  d2=(y2-x2)=0 when y2=x2 or
 931    !                            wy2=0 from wgt2=0 and y2=z2
 932    !
 933    !     and took account of the implications on d* and wy*.
 934    !
 935    !     The version presented here has each step accompanied by
 936    !     related equations. It does not assume the above restrictions on  
 937    !     wgt1 and wgt2. This version is provided in the 
 938    !     comments section of the RTTOV-9 module 'rttov_sublayer'.
 939    !
 940    !     5)  When LGRADP*=.true., this routine provides terms needed for 
 941    !     the gradients w.r.t.the vertical coordinate independent variable, 
 942    !     e.g. Ps. 
 943    !                           
 944    implicit none
 945
 946    ! Arguments:                           
 947    logical, intent(in)    :: LGRADPz ! Output domain logical indicating if gradient w.r.t. vertical coordinate independent variable if required i.e. d/dv where P(v) (e.g. v=Ps).
 948    logical, intent(in)    :: LGRADPx ! Input domain logical indicating if gradient w.r.t. vertical coordinate independent variable if required i.e. d/dv where P(v) (e.g. v=Ps).
 949    real(8), intent(in)    :: z1   ! Upper level of output domain half-layer (z1<z2)
 950    real(8), intent(in)    :: z2   ! Lower level of output domain half-layer
 951    real(8), intent(in)    :: x1   ! Upper boundary of input layer (x1<x2)
 952    real(8), intent(in)    :: x2   ! Lower boundary of input layer
 953    real(8), intent(in)    :: wgt1 ! Weight at z1 (0.0 or 1.0 or ...)
 954    real(8), intent(in)    :: wgt2 ! Weight at z2 (1.0 or 0.0 or ...)
 955    real(8), intent(in)    :: pzs1 ! dz1/dvz or perturbation dz1/dvz * delta(vz)
 956    real(8), intent(in)    :: pzs2 ! dz2/dvz or perturbation dz2/dvz * delta(vz)
 957    real(8), intent(in)    :: pxs1 ! dx1/dvx or perturbation dx1/dvx * delta(vx) (required when LGRADPx=.true.)
 958    real(8), intent(in)    :: pxs2 ! dx2/dvx or perturbation dx2/dvx * delta(vx)
 959    real(8), intent(in)    :: dxd  ! 1.0/(x2-x1)=1.0/dx
 960    real(8), intent(in)    :: dzd  ! 1.0/(z2-z1)=1.0/dz
 961    real(8), intent(inout) :: w1 ! Starting (in) and updated (out) weight assigned to variable at upper level x1
 962    real(8), intent(inout) :: w2 ! Starting (in) and updated (out) weight assigned to variable at upper level x2
 963    real(8), intent(inout) :: wps1 ! Starting (in) and updated (out) value of (pxs1*dw1/dx1 + pxs2*dw1/dx2 +pzs1*dw1/dz1 + pzs2*dw1/dz2)
 964    real(8), intent(inout) :: wps2 ! Starting (in) and updated (out) value of pxs1*dw2/dx1 + pxs2*dw2/dx2 +pzs1*dw2/dz1 + pzs2*dw2/dz2) (required when LGRADP*=.true.)
 965     
 966    ! Locals:           
 967    real(8) :: y1  ! Upper boundary of integral range (y1<y2)
 968    real(8) :: y2  ! Lower boundary of integral range
 969    real(8) :: d1,d2,wx1,wx2,wy1,wy2,dy,dzdd
 970    real(8) :: a1,a2,dxy1,dxy2,dxyd1,dxyd2,c1,c2
 971    real(8) :: dydzdd,dy1z1,dy2z2,d1d1,d1d2
 972    integer :: ibot,itop
 973    real(8) :: zthreshold
 974
 975    !  1. Initialization
 976
 977    !- Set upper and lower boundaries of integration layer (y1,y2):
 978    !        (y1,y2) = ( max(x1,z1), min(x2,z2) )
 979    itop=0
 980    ibot=0
 981    y1=z1
 982    y2=z2
 983    if (y1.lt.x1) then
 984      y1=x1
 985      itop=1
 986    end if
 987    if (y2.gt.x2) then
 988      y2=x2
 989      ibot=1
 990    end if
 991    dy=y2-y1
 992
 993    !- Verify for negative and zero (and near-zero) y2-y1 values.
 994     
 995    zthreshold=epsilon(1.0d0)*100.0
 996    c1=abs(y2)*zthreshold
 997
 998    if (dy.lt.-c1) then
 999      write(*,*) 'y1,y2 = ',y1,y2 
1000      write(*,*) 'z1,z2 = ',z1,z2 
1001      write(*,*) 'x1,x2 = ',x1,x2
1002      call utl_abort("ppo_sublayerInterpWgts: dy is negative")
1003    else if (dy.lt.c1) then
1004      ! dy~0; weights can be taken as having values of zero.
1005      ! w1 and w2 unchanged.
1006      return
1007    end if
1008    
1009    !  2. Interpolation weights (equivalent to gradient contributions from
1010    !     linear component of interpolator)
1011
1012    !- Determine w(y1) and w(y2) of the integral f = w(y1)*t(y1)+w(y2)*t(y2)
1013    !                                              = wy1*t(y1)+wy2*t(y2)
1014
1015    !  Weights are obtained by linear interpolation of the linear
1016    !  weighting function w with w(z1)=wgt1 and w(z2)=wgt2.
1017
1018    dzdd=dzd*(wgt2-wgt1)
1019    dy1z1=y1-z1
1020    wx1=wgt1+dy1z1*dzdd
1021     
1022    dydzdd=dy*dzdd
1023    wx2=wx1+dydzdd    !  wx2=wgt1+(y2-z1)*dzdd=wgt2+(y2-z2)*dzdd
1024     
1025    wy1=dy*wx1
1026    wy2=dy*wx2
1027
1028   !- Determine contribution of w1 and w2 for f=w1*t(x1)+w2*t(x2). 
1029
1030   !  The w1 and w2 contributions above are determined by expanding t(y1)
1031   !  and t(y2) in f = w(y1)*t(y1)+w(y2)*t(y2) as linear functions of 
1032   !  t(x1)=t1 and t(x2)=t2.
1033   !
1034   !        t(y1) = t1+(y1-x1)*(t2-t1)/(x2-x1) = t1+(y1-x1)*dxd*(t2-t1)
1035   !        t(y2) = t2+(y2-x2)*(t2-t1)/(x2-x1) = t2+(y2-x2)*dxd*(t2-t1)
1036   !
1037   !  Therefore,
1038   !
1039   !        f = wy1*[t1+(y1-x1)*dxd*(t2-t1)]
1040   !           +wy2*[t2+(y2-x2)*dxd*(t2-t1)]
1041   !
1042   !          = t1*[wy1-wy1*(y1-x1)*dxd-wy2*(y2-x2)*dxd]
1043   !           +t2*[wy1*(y1-x1)*dxd+wy2+wy2*(y2-x2)*dxd]
1044   !
1045   !  Aside: Contribution to w1+w2 = wy1+wy2 = 2 * w[(y2+y1)/2]
1046 
1047    dxy1=y1-x1
1048    d1=dxy1*dxd
1049
1050    dxy2=y2-x2
1051    d2=dxy2*dxd
1052    
1053    d1d1=1.0d0-d1
1054    d1d2=1.0d0+d2  
1055
1056    w1=w1+wy1*d1d1-wy2*d2
1057    w2=w2+wy1*d1+wy2*d1d2  
1058
1059    !  Aside used below: wy1*d1d1-wy2*d2 = wy1 + wy2 - (wy1*d1d1-wy2*d2)
1060
1061    !  3. Provide gradient contributions from linearized terms of non-linear
1062    !     component of interpolator (i.e. vertical coordinate dependency)
1063
1064    if (LGRADPx) then
1065     
1066      !  -- 3.1 Provide linearized contribution of non-linear interpolator
1067      !     component to TLM - this part in relation to the input vertical  
1068      !     coordinate:
1069      !
1070      !                    (df/dx1)*(dx1/dv)+(df/dx2)*(dx2/dv)
1071      !                        =  (dw1/dx1*t1+dw2/dx1*t2)*pxs1
1072      !                         + (dw1/dx2*t1+dw2/dx2*t2)*pxs2
1073      !
1074      !     a1 and a2 are used below to denote dw*/dx1 and dw*/dx2.
1075      !
1076      !     Gradients of w1 and w2 with respect to x1 and x2 are done in two parts:
1077      !
1078      !          - Part I:  The gradients calc excludes the cases where one 
1079      !                     might have y1=x1 and or y2=x2.
1080      !          - Part II: The gradient terms related to y1=x1 and or y2=x2 are
1081      !                     added.
1082      !
1083      !  -- Gradient of f w.r.t x1
1084      !    
1085      !  -- Gradients of w1 and w2 w.r.t. x1:
1086      !
1087      !     PART I:
1088      !
1089      !         For this case:
1090      !
1091      !            d(d1)/dx1 = dxd*(-1+dxy1*dxd) 
1092      !            d(d2)/dx1 = dxd*dxy2*dxd
1093      !
1094      !            d(wy1)/dx1=0, d(wy2)/dx1=0
1095      !
1096      !         Note: d(dj)/dx* = 0.0 when dj=0.0 due to y1=x1 or y2=x2
1097      
1098      dxyd1=d1*dxd
1099      dxyd2=d2*dxd                                
1100      a1=wy1*(dxd-dxyd1)-wy2*dxyd2
1101      a2=-a1        ! a2=wy1*(-dxd+dxyd1)+wy2*dxyd2
1102
1103      !     PART II:
1104      !
1105      !        For the extra terms: (gradients w.r.t. y1=x1)
1106      !
1107      !            d(d1)/dy1 = dxd
1108      !            d(d2)/dy1 = 0
1109      !
1110      !            d(wy1)/dy1 = -wx1+dzdd*dy
1111      !            d(wy2)/dy1 = -wx2
1112      
1113      if (itop.eq.1) then
1114
1115        ! Case y1=x1 (d1=0)
1116
1117        c1=wy1*dxd
1118        c2=-wx1+dydzdd
1119        a1=a1-c1+c2+wx2*d2
1120        a2=a2+c1-wx2*d1d2
1121      end if
1122
1123      !  -- Add to accumulated gradient terms
1124
1125      wps1=wps1+a1*pxs1
1126      wps2=wps2+a2*pxs1
1127
1128      !  -- Gradient of f w.r.t. x2
1129      !
1130      !  -- Gradients of w1 and w2 w.r.t. x2:
1131      !
1132      !     PART I:
1133      !
1134      !         For this case:
1135      !
1136      !            d(d1)/dx2 = -dxd*dxy1*dxd
1137      !            d(d2)/dx2 = -dxd*(1+dxy2*dxd)
1138      !
1139      !            d(wy1)/dx2=0,  d(wy2)/dx2=0
1140      
1141      a1=wy1*dxyd1+wy2*(dxd+dxyd2)
1142      a2=-a1         ! a2=-wy1*dxyd1-wy2*(dxd+dxyd2)
1143                    
1144      !     PART II:
1145      !     
1146      !        For the extra terms: (gradients w.r.t. y2=x2)
1147      !
1148      !            d(d1)/dy2 = 0
1149      !            d(d2)/dy2 = dxd
1150      !            
1151      !            d(wy1)/dy2 = wx1
1152      !            d(wy2)/dy2 = wx2+dzdd*dy
1153                
1154      if (ibot.eq.1) then
1155
1156        ! Case y2=x2 (d2=0)
1157
1158        c1=wy2*dxd
1159        c2=wx2+dydzdd
1160        a1=a1-c1+wx1*d1d1
1161        a2=a2+c1+c2+wx1*d1
1162      end if
1163
1164      !  -- Add to accumulated gradient terms
1165
1166      wps1=wps1+a1*pxs2
1167      wps2=wps2+a2*pxs2
1168
1169    end if
1170
1171    if (LGRADPz) then
1172     
1173      !  -- 3.2 Provide linearized contribution of non-linear interpolator
1174      !     component to the TLM - this part in relation to the output vertical 
1175      !     coordinate:
1176      !
1177      !                    (df/dz1)*(dz1/dv)+(df/dz2)*(dz2/dv)
1178      !
1179      !                        =  (dw1/dz1*t1+dw2/dz1*t2)*pzs1
1180      !                         + (dw1/dz2*t1+dw2/dz2*t2)*pzs2
1181      !
1182      !     a1 and a2 are used below to denote dw*/dz1 and dw*/dz2.
1183      !
1184      !     Gradients of w1 and w2 with respect to z1 and z2 are done in two parts:
1185      !
1186      !          - Part I:  The gradients calc excludes the cases where one 
1187      !                     might have y1=z1 and or y2=z2.
1188      !          - Part II: The gradient terms related to y1=z1 and or y2=z2 are
1189      !                     added.
1190      !
1191      !  -- Gradient of f w.r.t z1
1192      !
1193      !  -- Gradients of w1 and w2 w.r.t. z1:
1194      !
1195      !     PART I: Excludes y1=z1 consideration
1196      !
1197      !        d(wy1)/dz1 = dy*dzdd*((y1-z1)*dzd-1)=dy*dzdd*(y1-z2)*dzd
1198      !        d(wy2)/dz1 = dy*dzdd*(y2-z2)*dzd
1199      
1200      dy2z2=y2-z2
1201      dxyd2=dydzdd*dzd
1202      dxyd1=dxyd2*dy1z1
1203      dxyd2=dxyd2*dy2z2
1204      a1=(dxyd1-dydzdd)*d1d1-dxyd2*d2
1205      a2=dxyd1+dxyd2-(dydzdd+a1) 
1206
1207      if (itop.eq.0) then 
1208
1209        ! PART II: y1=z1
1210        !
1211        !          d(d1)/dy1 = dxd
1212        !          d(d2)/dy1 = 0
1213        !
1214        !          d(wy1)/dy1 = -wx1+dzdd*dy 
1215        !          d(wy2)/dy1 = -wx2
1216
1217        c1=wy1*dxd
1218        c2=-wx1+dydzdd
1219        a1=a1-c1+c2*d1d1+wx2*d2
1220        a2=a2+c1+c2*d1-wx2*d1d2
1221      end if
1222
1223      !  -- Add to accumulated gradient terms
1224
1225      wps1=wps1+a1*pzs1
1226      wps2=wps2+a2*pzs1
1227
1228      !  -- Gradient of f w.r.t. z2
1229      !
1230      !  -- Gradients of w1 and w2 w.r.t. z2:
1231      !
1232      !     PART I: Excludes y2=z2 consideration
1233      !
1234      !         d(wy1)/dz2 = -dy*dzdd*(y1-z1)*dzd
1235      !         d(wy2)/dz2 = -dy*dzdd*(1+(y2-z2)*dzd)=-dy*dzdd*(y2-z1)*dzd
1236      
1237      a1=-dxyd1*d1d1+(dxyd2+dydzdd)*d2
1238      a2=-(dxyd1+dxyd2+dydzdd+a1)
1239
1240      if (ibot.eq.0) then
1241
1242        ! PART II: y2=z2
1243        !     
1244        !          d(d1)/dy2 = 0
1245        !          d(d2)/dy2 = dxd
1246        !            
1247        !          d(wy1)/dy2 = wx1
1248        !          d(wy2)/dy2 = wx2+dzdd*dy
1249        
1250        c1=wy2*dxd
1251        c2=wx2+dydzdd
1252        a1=a1-c1-c2*d2+wx1*d1d1
1253        a2=a2+c1+c2*d1d2+wx1*d1 
1254      end if
1255
1256      !  -- Add to accumulated gradient terms
1257
1258      wps1=wps1+a1*pzs2
1259      wps2=wps2+a2*pzs2
1260
1261    end if
1262
1263  end subroutine ppo_sublayerInterpWgts
1264
1265  !==========================================================================
1266  !--- Stand-alone integration (and averaging) routines providing weights ---
1267  ! For weights W(:,:) and initial vector X(:), the integrated (or average) 
1268  ! values would be W*X.  
1269
1270  !--------------------------------------------------------------------------
1271  ! ppo_vertLayersSetup
1272  !--------------------------------------------------------------------------
1273  subroutine ppo_vertLayersSetup(operatorType,pressInput,numInputLevs)
1274    !
1275    !:Purpose: Preliminary calculations for producing components required for
1276    !          vertical integration (or averaging) w.r.t. pressure for the full  
1277    !          vertical rangeor a set of target layers. To be called before 
1278    !          routine ppo_vertIntegWgts or ppo_vertAvgWgts.
1279    !
1280    !          Integration calculations are performed appling quadratic interpolation 
1281    !          in P between level. 
1282    !
1283    !:Input:
1284    !         :operatorType:       'integ' for intergration; 'avg' for averaging
1285    !         :pressInput:         Reference input levels
1286    !         :numInputLevs:       # of model vertical levels
1287    !
1288    !:Output:
1289    !         :boundaries(numInputLevs+1):  Input layer boundaries assuming
1290    !                                       provided input levels can be taken as
1291    !                                       mid-layer values.
1292    !         :weights:                     Second order Lagrangian
1293    !                                       interp integration weights or 
1294    !                                       unity for averaging weights
1295    !
1296    !:Comments:
1297    !         - This subroutine does the following:
1298    !
1299    !           - Setting of layer boundaries
1300    !           - If integration, determining integration weights associated 
1301    !             to second order Lagrangian interpolation. Otherwise, initialize
1302    !             weights to unity.
1303    !
1304    !         - Layer boundaries are taken as mid-point between provided levels in
1305    !           lnP coordinate. Layer values are set to be the values
1306    !           interpolated to the mid-point in P within the various layers.
1307    !           Interpolation in P is done quadratically. 
1308    !
1309    implicit none
1310
1311    ! Arguments:
1312    character(len=*), intent(in) :: operatorType       ! 'integ' for integration; 'avg' for averaging
1313    integer,          intent(in) :: numInputLevs             ! # of model vertical levels
1314    real(8),          intent(in) :: pressInput(numInputLevs) ! Reference input levels
1315
1316    ! Locals:
1317    integer   :: levelIndex
1318    real(8)   :: zp, zp1, zp2, zp3, zr1, zr2, zr3
1319
1320    ! Determine P boundaries of layers and save weights for
1321    ! use in setting integration (or averaging) weights.
1322    ! N.B.: Boundaries of layers set to mid-point between input levels
1323      
1324    ! Calculate layer boundaries
1325
1326    if (allocated(boundaries)) deallocate(boundaries)
1327    allocate(boundaries(numInputLevs+1))
1328    
1329    boundaries(1)=pressInput(1)
1330    boundaries(numInputLevs+1)= pressInput(numInputLevs)
1331
1332    !$OMP PARALLEL DO PRIVATE(levelIndex)    
1333    DO levelIndex = 2, numInputLevs
1334       boundaries(levelIndex)=sqrt(pressInput(levelIndex-1)*pressInput(levelIndex))
1335    END DO
1336    !$OMP END PARALLEL DO 
1337
1338    if (allocated(weights)) deallocate(weights)
1339    
1340    if ( trim(operatorType) == 'avg' ) then
1341    
1342      ! Initialize weights as scaling factors of unity.
1343
1344      allocate(weights(numInputLevs,1))      
1345      weights(:,:) = 1.0d0
1346    
1347    else
1348
1349      ! Set second degree Lagrangian interpolator weights
1350
1351      allocate(weights(numInputLevs,numInputLevs))            
1352      weights(:,:) = 0.0d0
1353    
1354      ! Interpolation to mid-layer level in P using
1355      ! second degree Lagrangian interpolator.
1356      ! N.B.: Integration is w.r.t. P
1357    
1358      ! Calculating for levelIndex=1
1359    
1360      zp1= pressInput(1)
1361      zp2= pressInput(2)
1362      zp3= pressInput(3)
1363      zp = (boundaries(2)+boundaries(1))/2.0
1364      zr1=(zp-zp2)*(zp-zp3)/(zp1-zp2)/(zp1-zp3)
1365      zr2=(zp-zp1)*(zp-zp3)/(zp2-zp1)/(zp2-zp3)
1366      zr3=(zp-zp2)*(zp-zp1)/(zp3-zp2)/(zp3-zp1)
1367      weights(1,1)=zr1
1368      weights(2,1)=zr2
1369      weights(3,1)=zr3
1370
1371      !$OMP PARALLEL DO PRIVATE(levelIndex,zp1,zp2,zp3,zp,zr1,zr2,zr3)    
1372      DO levelIndex=2,numInputLevs-1
1373        zp1=pressInput(levelIndex-1)
1374        zp2=pressInput(levelIndex)
1375        zp3=pressInput(levelIndex+1)
1376        zp=(boundaries(levelIndex+1)+boundaries(levelIndex))/2.0
1377        zr1=(zp-zp2)*(zp-zp3)/(zp1-zp2)/(zp1-zp3)
1378        zr2=(zp-zp1)*(zp-zp3)/(zp2-zp1)/(zp2-zp3)
1379        zr3=(zp-zp2)*(zp-zp1)/(zp3-zp2)/(zp3-zp1)
1380        weights(levelIndex-1,levelIndex)=zr1
1381        weights(levelIndex,levelIndex)=zr2
1382        weights(levelIndex+1,levelIndex)=zr3
1383      ENDDO
1384      !$OMP END PARALLEL DO 
1385    
1386      ! Calculating  for levelIndex=numInputLevs
1387    
1388      zp1= pressInput(numInputLevs-2)
1389      zp2= pressInput(numInputLevs-1)
1390      zp3= pressInput(numInputLevs)
1391      zp = (boundaries(numInputLevs+1)+boundaries(numInputLevs))/2.0
1392      zr1=(zp-zp2)*(zp-zp3)/(zp1-zp2)/(zp1-zp3)
1393      zr2=(zp-zp1)*(zp-zp3)/(zp2-zp1)/(zp2-zp3)
1394      zr3=(zp-zp2)*(zp-zp1)/(zp3-zp2)/(zp3-zp1)
1395      weights(numInputLevs-2,numInputLevs)=zr1
1396      weights(numInputLevs-1,numInputLevs)=zr2
1397      weights(numInputLevs,numInputLevs)=zr3
1398
1399    end if
1400
1401  end subroutine ppo_vertLayersSetup
1402
1403  !--------------------------------------------------------------------------
1404  ! ppo_vertIntegWgts
1405  !--------------------------------------------------------------------------
1406  subroutine ppo_vertIntegWgts(targetLayersTop,targetLayersBot,numInputLevs, &
1407                                numTargetLevs,kstart,kend,wgts,wgts_opt,skipType_opt, &
1408                                outbound_opt,success_opt,dealloc_opt)   
1409    !
1410    !:Purpose: To calculate integration weights "wgts" required for vertical integration w.r.t.
1411    !          pressure for the full vertical range or a set of target layers. 
1412    !          Given the calculated weights and a user intergrand array vector X, the integral 
1413    !          for a given layer i would be given by sum(wgts(i,:)*X(:))
1414    !
1415    !          Integration calculations are performed applying quadratic interpolation 
1416    !          in P between level.
1417    !
1418    !          Routine ppo_vertLayersSetup to be called beforehand to generate Lagrangian weights
1419    !          and related layer boundaries (arrays 'weights' and 'boundaries')
1420    ! 
1421    !:Input:
1422    !         :targetLayersTop:    top of target layers
1423    !         :targetLayersBot:    bottom of target layers
1424    !         :numInputLevs:       # of original/input vertical levels
1425    !         :numTargetLevs:      # of target vertical levels
1426    !         :kstart:             Index of first relevant original/input level for each target level
1427    !         :kend:               Index of last relevant original/input level for each target level
1428    !                              If kstart and kend are non-zero on input, 
1429    !                              the input are initial estimates of the values.
1430    !         :weights:            See routine ppo_vertLayersSetup
1431    !         :boundaries:         Boundaries of input layers
1432    !         :skipType_opt:       Skipping processing of specific target layers depending on case:
1433    !                              'default' - skipping application via input success_opt only
1434    !                              'doAll&noExtrap' - application of both success_opt and outbound_opt
1435    !         :outbound_opt:       Flag set when beyond range of reference/input levels
1436    !         :success_opt:        Logical indicating a valid target layer
1437    !         :dealloc_opt:        Logical indicating if deallocation is desired when done. (default: .false.)
1438    !
1439    !:Output:
1440    !         :wgts(numTargetLevs,numInputLevs):     Integration weights
1441    !         :wgts_opt(numTargetLevs,numInputLevs): Part of integrtation weights not related to resolution
1442    !
1443    implicit none
1444
1445    ! Arguments:
1446    integer,                    intent(in)    :: numInputLevs  ! # of original/input vertical levels
1447    integer,                    intent(in)    :: numTargetLevs ! # of target vertical levels
1448    real(8),                    intent(in)    :: targetLayersTop(numTargetLevs) ! top of target layers
1449    real(8),                    intent(in)    :: targetLayersBot(numTargetLevs) ! bottom of target layers
1450    real(8),                    intent(out)   :: wgts(numTargetLevs,numInputLevs) ! Averaging weights
1451    integer,                    intent(inout) :: kstart(numTargetLevs) ! Index of first relevant original/input level for each target level
1452    integer,                    intent(inout) :: kend(numTargetLevs)   ! Index of last relevant original/input level for each target level
1453    character(len=*), optional, intent(in)    :: skipType_opt ! Skipping processing of specific target layers depending on case
1454    logical,          optional, intent(in)    :: dealloc_opt ! Logical indicating if deallocation is desired when done
1455    real(8),          optional, intent(out)   :: wgts_opt(numTargetLevs,numInputLevs) ! Part of averaging weights not related to resolution
1456    integer,          optional, intent(inout) :: outbound_opt(numTargetLevs) ! Flag indicating if obs outside input vertical range (0 for no)
1457    logical,          optional, intent(inout) :: success_opt(numTargetLevs)  ! success of interpolation
1458
1459    ! Locals:
1460    integer :: TargetIndex   
1461    logical :: success(numTargetLevs)
1462    character(len=20) :: skipType
1463    integer, parameter :: ivweights=2  ! Order of Lagrangian interpolation.
1464    integer :: levelIndex,JK,ILMAX2,ILMIN2
1465    integer :: ILMIN, ILMAX
1466    real(8) :: zp, zp1, zp2, zp3, zr1, zr2, zr3, ptop, pbtm
1467    
1468    if (present(skipType_opt)) then
1469      skipType = skipType_opt
1470    else
1471      skipType = 'default'
1472    end if 
1473 
1474    if (present(success_opt)) then
1475      if ( trim(skipType) == 'doAll&noExtrap') then
1476        if (present(outbound_opt)) then
1477          where (outbound_opt(:) == 0)
1478            success(:) = .true.
1479          elsewhere
1480            success(:) = .false.
1481          end where
1482        else
1483          success(:) = .true.
1484        end if
1485      else
1486        success(:) = success_opt(:)
1487      end if 
1488    else
1489      success(:) = .true.
1490    end if
1491        
1492    do TargetIndex=1,numTargetLevs
1493
1494      if ( .not.success(TargetIndex) ) then
1495        wgts(TargetIndex,:) = 0.0D0
1496        wgts_opt(TargetIndex,:) = 0.0D0          
1497        kstart(TargetIndex)=1
1498        kend(TargetIndex)=1
1499        cycle
1500      end if
1501
1502      ptop = targetLayersTop(TargetIndex)
1503      pbtm = targetLayersBot(TargetIndex)
1504         
1505      ! Find the range of vertical levels over which to perform the integration
1506      ! and set the integration weights over this range.
1507          
1508      ilmin=1
1509      ilmax=numInputLevs
1510      if (ptop <= boundaries(1)*1.01 .and. &
1511          pbtm >= boundaries(numInputLevs+1)*0.99) then
1512
1513        ! Total column integration part
1514
1515        !$OMP PARALLEL DO PRIVATE(jk,levelIndex)                  
1516        do jk = 1,numInputLevs
1517          do levelIndex=max(1,jk-ivweights),min(numInputLevs,jk+ivweights)
1518            wgts(TargetIndex,jk)=wgts(TargetIndex,jk) &
1519	          +(boundaries(levelIndex+1) &
1520                  -boundaries(levelIndex))*weights(jk,levelIndex)
1521            if (present(wgts_opt)) &
1522              wgts_opt(TargetIndex,jk)=wgts_opt(TargetIndex,jk)+ &
1523	                               weights(jk,levelIndex)
1524          end do
1525        end do
1526        !$OMP END PARALLEL DO                 
1527             
1528      else
1529
1530        ! Partial column integration part (special treatment at boundaries)
1531     
1532        ! Identify input layer boundaries just within the target layer.
1533             
1534        ilmin = ppo_getLevelIndex(ptop, boundaries, 'top', numInputLevs+1)
1535        ilmax = ppo_getLevelIndex(pbtm, boundaries, 'btm', numInputLevs+1)
1536               
1537        if (ilmin == ilmax+1) then
1538
1539          ! Entire target layer within one input layer
1540                
1541          levelIndex=ilmax
1542          if (levelIndex < 3) levelIndex=3
1543          if (levelIndex > numInputLevs) levelIndex=numInputLevs
1544          zp1=boundaries(levelIndex-2)
1545          zp2=boundaries(levelIndex-1)
1546          zp3=boundaries(levelIndex)
1547          zp=(ptop+pbtm)/2.0
1548          zr1=(zp-zp2)*(zp-zp3)/(zp1-zp2)/(zp1-zp3)
1549          zr2=(zp-zp1)*(zp-zp3)/(zp2-zp1)/(zp2-zp3)
1550          zr3=(zp-zp2)*(zp-zp1)/(zp3-zp2)/(zp3-zp1)
1551                
1552          wgts(TargetIndex,levelIndex-2)=(pbtm-ptop)*zr1
1553          wgts(TargetIndex,levelIndex-1)=(pbtm-ptop)*zr2
1554          wgts(TargetIndex,levelIndex)=(pbtm-ptop)*zr3
1555          if (present(wgts_opt)) then
1556            wgts_opt(TargetIndex,levelIndex-2)=zr1
1557            wgts_opt(TargetIndex,levelIndex-1)=zr2
1558            wgts_opt(TargetIndex,levelIndex)=zr3
1559          end if
1560          ilmin=levelIndex-2
1561          ilmax=levelIndex
1562                  
1563        else
1564                
1565          ! Determine terms from the inner layers (excluding the lower and upper
1566          ! boundary layers when these layers not covering entire input layers)
1567                
1568          if (pbtm >= boundaries(numInputLevs)*0.99) then
1569            ilmax2=numInputLevs
1570          else
1571            ilmax2=ilmax-1
1572          end if
1573          if (ptop <= boundaries(1)*1.01) then
1574            ilmin=1
1575            ilmin2=ilmin
1576          else
1577            ilmin2=ilmin
1578          end if
1579          if (ilmin2 <= ilmax2) then
1580            !$OMP PARALLEL DO PRIVATE(jk,levelIndex)                  
1581            do jk = ilmin2,ilmax2
1582              do levelIndex=max(1,jk-ivweights),min(numInputLevs,jk+ivweights)
1583                wgts(TargetIndex,jk)=wgts(TargetIndex,jk)+(boundaries(levelIndex+1) &
1584                         -boundaries(levelIndex))*weights(jk,levelIndex)
1585                if (present(wgts_opt)) &
1586                  wgts_opt(TargetIndex,jk)=wgts_opt(TargetIndex,jk)+weights(jk,levelIndex)
1587              end do
1588            end do
1589            !$OMP END PARALLEL DO               
1590          end if
1591                
1592          ! Determine terms from the lower and upper boundary layers
1593          ! when these layers do not cover entire input layers.
1594                
1595          if (pbtm < boundaries(numInputLevs)*0.99) then
1596                     
1597            levelIndex=ilmax+1
1598            if (levelIndex > numInputLevs) levelIndex=numInputLevs
1599            if (levelIndex < 3) levelIndex=3
1600            zp1=boundaries(levelIndex-2)
1601            zp2=boundaries(levelIndex-1)
1602            zp3=boundaries(levelIndex)
1603            zp=(boundaries(ilmax)+pbtm)/2.0
1604            zr1=(zp-zp2)*(zp-zp3)/(zp1-zp2)/(zp1-zp3)
1605            zr2=(zp-zp1)*(zp-zp3)/(zp2-zp1)/(zp2-zp3)
1606            zr3=(zp-zp2)*(zp-zp1)/(zp3-zp2)/(zp3-zp1)
1607                   
1608            wgts(TargetIndex,levelIndex-2)=wgts(TargetIndex,levelIndex-2)+(pbtm - boundaries(ilmax))*zr1
1609            wgts(TargetIndex,levelIndex-1)=wgts(TargetIndex,levelIndex-1)+(pbtm - boundaries(ilmax))*zr2
1610            wgts(TargetIndex,levelIndex)=wgts(TargetIndex,levelIndex)+(pbtm - boundaries(ilmax))*zr3
1611           
1612            if (present(wgts_opt)) then
1613              wgts_opt(TargetIndex,levelIndex-2)=wgts_opt(TargetIndex,levelIndex-2)+zr1
1614              wgts_opt(TargetIndex,levelIndex-1)=wgts_opt(TargetIndex,levelIndex-1)+zr2
1615              wgts_opt(TargetIndex,levelIndex)=wgts_opt(TargetIndex,levelIndex)+zr3
1616            end if
1617            ilmax=levelIndex
1618                  
1619          end if
1620                  
1621          if (ptop > boundaries(1)*1.01) then
1622                     
1623            levelIndex=ilmin-1
1624            if (levelIndex < 1) levelIndex=1
1625            if (levelIndex > numInputLevs-2) levelIndex=numInputLevs-2
1626            zp1= boundaries(levelIndex)
1627            zp2= boundaries(levelIndex+1)
1628            zp3= boundaries(levelIndex+2)
1629            zp = (boundaries(ilmin)+ptop)/2.0
1630            zr1=(zp-zp2)*(zp-zp3)/(zp1-zp2)/(zp1-zp3)
1631            zr2=(zp-zp1)*(zp-zp3)/(zp2-zp1)/(zp2-zp3)
1632            zr3=(zp-zp2)*(zp-zp1)/(zp3-zp2)/(zp3-zp1)
1633                   
1634            wgts(TargetIndex,levelIndex)=wgts(TargetIndex,levelIndex)+(boundaries(ilmin)-ptop)*zr1
1635            wgts(TargetIndex,levelIndex+1)=wgts(TargetIndex,levelIndex+1)+(boundaries(ilmin)-ptop)*zr2
1636            wgts(TargetIndex,levelIndex+2)=wgts(TargetIndex,levelIndex+2)+(boundaries(ilmin)-ptop)*zr3
1637                   
1638            if (present(wgts_opt)) then
1639              wgts_opt(TargetIndex,levelIndex)=wgts_opt(TargetIndex,levelIndex)+zr1
1640              wgts_opt(TargetIndex,levelIndex+1)=wgts_opt(TargetIndex,levelIndex+1)+zr2
1641              wgts_opt(TargetIndex,levelIndex+2)=wgts_opt(TargetIndex,levelIndex+2)+zr3
1642 	    end if
1643            ilmin=levelIndex
1644            if (ilmax < levelIndex+2) ilmax=levelIndex+2
1645                   
1646          end if
1647          if (ilmin > ilmax-2) ilmin=ilmax-2
1648        end if
1649      end if
1650
1651      if (kstart(TargetIndex) > 0 .and. kend(TargetIndex) > 0) then
1652        if (abs(kstart(TargetIndex)-ilmin) > 1 .or. &
1653	    abs(kend(TargetIndex)-ilmax) > 1) then
1654	    
1655          write(*,*) 'ppo_vertIntegWgts: Suspected error in layer', &
1656	        ' identification: ',TargetIndex,kstart(TargetIndex),ilmin, &
1657	        kend(TargetIndex),ilmax
1658        end if
1659      end if
1660
1661      kstart(TargetIndex)=ilmin
1662      kend(TargetIndex)=ilmax
1663       
1664    end do
1665
1666    if (present(dealloc_opt)) then
1667      if (dealloc_opt) deallocate(weights,boundaries)
1668    end if
1669    
1670  end subroutine ppo_vertIntegWgts
1671
1672  !--------------------------------------------------------------------------
1673  ! ppo_getLevelIndex
1674  !--------------------------------------------------------------------------
1675  integer function ppo_getLevelIndex(level, layerBoundaryLevels, topbtm, numBoundaries)
1676    !
1677    !:Purpose: To get the vertical input level index for level
1678    !          within target layer and nearest specified layer boundary.  
1679    !
1680    implicit none
1681
1682    ! Arguments:
1683    integer,          intent(in) :: numBoundaries  ! Number of layer boundaries       
1684    real(8),          intent(in) :: level          ! Target layer index      
1685    real(8),          intent(in) :: layerBoundaryLevels(numBoundaries) ! Layer boundaries
1686    character(len=*), intent(in) :: topbtm         ! indicating whether we are looking for top or bottom level
1687
1688    ! Locals
1689    integer     :: ilev1, ilev2
1690    integer     :: levelIndex
1691
1692    ! Find the model levels adjacent to pressure level 
1693
1694    ! Default values
1695    
1696    if (level < 0.0d0) then
1697      if ((topbtm == 'btm') .or. (topbtm == 'BTM')) then
1698        ppo_getLevelIndex = numBoundaries
1699      endif
1700      if ((topbtm == 'top') .or. (topbtm == 'TOP')) then
1701        ppo_getLevelIndex = 1
1702      endif                                                  
1703    endif
1704      
1705    ilev1=0
1706    ilev2=1
1707    do levelIndex=1,numBoundaries
1708      if (level > layerBoundaryLevels(levelIndex)) then
1709        ilev1=levelIndex
1710        ilev2=levelIndex+1
1711      else
1712        exit
1713      endif
1714    enddo
1715
1716    ! Find the input level index
1717
1718    ! If we are looking for top level, the index is the level immediately 
1719    ! below. if looking for bottom level, the index is the one immediately 
1720    ! above.
1721    
1722    if ((topbtm == 'btm') .or. (topbtm == 'BTM')) then
1723      ppo_getLevelIndex=ilev1
1724    else if ((topbtm == 'top') .or. (topbtm == 'TOP')) then
1725      ppo_getLevelIndex=ilev2
1726    endif
1727
1728    if (ppo_getLevelIndex < 1) ppo_getLevelIndex=1
1729    if (ppo_getLevelIndex > numBoundaries) ppo_getLevelIndex=numBoundaries
1730  
1731  end function ppo_getLevelIndex
1732
1733  !--------------------------------------------------------------------------
1734  ! ppo_vertAvgWgts
1735  !--------------------------------------------------------------------------
1736  subroutine ppo_vertAvgWgts(targetLayersTop,targetLayersBot,numInputLevs, &
1737                             numTargetLevs,kstart,kend,wgts,wgts_opt,skipType_opt, &
1738                             outbound_opt,success_opt,dealloc_opt)   
1739    !
1740    !:Purpose: To calculate averaging weights "wgts" required for vertical averaging 
1741    !          w.r.t. ln(pressure) for the full vertical range or a set of target layers. 
1742    !          Given the calculated weights and a user input array vector X, the average 
1743    !          for a given layer i would be given by sum(wgts(i,:)*X(:))
1744    !
1745    !          Routine ppo_vertLayersSetup to be called beforehand to initial weigths
1746    !          and related layer boundaries (arrays 'weights' and 'boundaries')
1747    ! 
1748    !:Input:
1749    !         :targetLayersTop:    top of target layers
1750    !         :targetLayersBot:    bottom of target layers
1751    !         :numInputLevs:       # of original/input vertical levels
1752    !         :numTargetLevs:      # of target vertical levels
1753    !         :kstart:             Index of first relevant original/input level for each target level
1754    !         :kend:               Index of last relevant original/input level for each target level
1755    !                              If kstart and kend are non-zero on input, 
1756    !                              the input are initial estimates of the values.
1757    ! 
1758    !         :weights:            See routine ppo_vertLayersSetup
1759    !         :boundaries:         Boundaries of input layers
1760    !         :skipType_opt:       Skipping processing of specific target layers depending on case:
1761    !                              'default' - skipping application via input success_opt only
1762    !                              'doAll&noExtrap' - application of both success_opt and outbound_opt
1763    !         :outbound_opt:       Flag set when beyond range of reference/input levels
1764    !         :success_opt:        Logical indicating a valid target layer
1765    !         :dealloc_opt:        Logical indicating if deallocation is desired when done. (default: .false.)
1766    !
1767    !:Output:
1768    !         :wgts(numTargetLevs,numInputLevs):     Averaging weights
1769    !         :wgts_opt(numTargetLevs,numInputLevs): Part of averaging weights not related to resolution
1770    !
1771    implicit none
1772
1773    ! Arguments:
1774    integer,                    intent(in)    :: numInputLevs  ! # of original/input vertical levels
1775    integer,                    intent(in)    :: numTargetLevs ! # of target vertical levels
1776    real(8),                    intent(in)    :: targetLayersTop(numTargetLevs) ! top of target layers
1777    real(8),                    intent(in)    :: targetLayersBot(numTargetLevs) ! bottom of target layers
1778    real(8),                    intent(out)   :: wgts(numTargetLevs,numInputLevs) ! Averaging weights
1779    integer,                    intent(inout) :: kstart(numTargetLevs) ! Index of first relevant original/input level for each target level
1780    integer,                    intent(inout) :: kend(numTargetLevs)   ! Index of last relevant original/input level for each target level
1781    character(len=*), optional, intent(in)    :: skipType_opt ! Skipping processing of specific target layers depending on case
1782    logical,          optional, intent(in)    :: dealloc_opt ! Logical indicating if deallocation is desired when done
1783    real(8),          optional, intent(out)   :: wgts_opt(numTargetLevs,numInputLevs) ! Part of averaging weights not related to resolution
1784    integer,          optional, intent(inout) :: outbound_opt(numTargetLevs) ! Flag indicating if obs outside input vertical range (0 for no)
1785    logical,          optional, intent(inout) :: success_opt(numTargetLevs)  ! success of interpolation
1786
1787    ! Locals:
1788    integer :: TargetIndex   
1789    logical :: success(numTargetLevs)
1790    character(len=20) :: skipType
1791    integer :: levelIndex,ILMAX2,ILMIN2
1792    integer :: ILMIN, ILMAX
1793    real(8) :: SumWeights, TargetLayerThickWgt, ptop, pbtm
1794    
1795    if (present(skipType_opt)) then
1796      skipType = skipType_opt
1797    else
1798      skipType = 'default'
1799    end if 
1800 
1801    if (present(success_opt)) then
1802      if ( trim(skipType) == 'doAll&noExtrap') then
1803        if (present(outbound_opt)) then
1804          where (outbound_opt(:) == 0)
1805             success(:) = .true.
1806          elsewhere
1807             success(:) = .false.
1808          end where
1809        else
1810           success(:) = .true.
1811        end if
1812      else
1813        success(:) = success_opt(:)
1814      end if 
1815    else
1816      success(:) = .true.
1817    end if
1818        
1819    do TargetIndex=1,numTargetLevs
1820
1821      if ( .not.success(TargetIndex) ) then
1822        wgts(TargetIndex,:) = 0.0D0
1823        wgts_opt(TargetIndex,:) = 0.0D0          
1824        kstart(TargetIndex)=1
1825        kend(TargetIndex)=1
1826        cycle
1827      end if
1828
1829      ptop = targetLayersTop(TargetIndex)
1830      pbtm = targetLayersBot(TargetIndex)
1831      TargetLayerThickWgt=1.0D0/(min(pbtm,boundaries(numInputLevs+1))-max(ptop,boundaries(1)))
1832         
1833      ! Find the range of vertical levels over which to perform the averaging
1834      ! and set the averaging weights over this range.
1835          
1836      ilmin=1
1837      ilmax=numInputLevs
1838      if (ptop <= boundaries(1)*1.01 .and. pbtm >= boundaries(numInputLevs+1)*0.99) then
1839
1840        ! Total column averaging part
1841
1842        SumWeights=1.0D0/sum(weights(1:numInputLevs,1))
1843        !$OMP PARALLEL DO PRIVATE(levelIndex)                  
1844        do levelIndex = 1,numInputLevs
1845          wgts(TargetIndex,levelIndex)=(boundaries(levelIndex+1) &
1846                -boundaries(levelIndex))*TargetLayerThickWgt
1847          if (present(wgts_opt)) &
1848            wgts_opt(TargetIndex,levelIndex)=SumWeights
1849        end do
1850        !$OMP END PARALLEL DO                 
1851             
1852      else
1853
1854        ! Partial column averaging part (special treatment at boundaries)
1855     
1856        ! Identify the vertical input level indices for levels
1857        ! within target layer and nearest specified layer boundary. 
1858
1859        ilmin = ppo_getLevelIndex(ptop, boundaries, 'top', numInputLevs+1)
1860        ilmax = ppo_getLevelIndex(pbtm, boundaries, 'btm', numInputLevs+1)
1861               
1862        if (ilmin == ilmax+1) then
1863
1864          ! Entire target layer within one input layer
1865                
1866          levelIndex=ilmin
1867          if (levelIndex < 1) levelIndex=1
1868          if (levelIndex > numInputLevs) levelIndex=numInputLevs
1869
1870          wgts(TargetIndex,levelIndex)=1.0D0
1871          if (present(wgts_opt)) wgts_opt(TargetIndex,levelIndex)=1.0D0
1872	  
1873          ilmin=levelIndex
1874          ilmax=levelIndex+1
1875                  
1876        else
1877                
1878          ! Determine terms from the inner layers (excluding the lower and upper
1879          ! boundary layers when these layers not covering entire input layers)
1880                
1881          if (pbtm >= boundaries(numInputLevs)*0.99) then
1882            ilmax2=numInputLevs
1883          else
1884            ilmax2=ilmax-1
1885          end if
1886          if (ptop <= boundaries(1)*1.01) then
1887            ilmin=1
1888            ilmin2=ilmin
1889          else
1890            ilmin2=ilmin
1891          end if	
1892	    
1893          SumWeights=1.0D0/sum(weights(ilmin:ilmax,1))
1894	  
1895          if (ilmin2 <= ilmax2) then
1896            !$OMP PARALLEL DO PRIVATE(levelIndex)                  
1897            do levelIndex = ilmin2,ilmax2
1898              wgts(TargetIndex,levelIndex)= &
1899	        (boundaries(levelIndex+1)-boundaries(levelIndex))*TargetLayerThickWgt
1900              if (present(wgts_opt)) wgts_opt(TargetIndex,levelIndex)=SumWeights
1901            end do
1902            !$OMP END PARALLEL DO               
1903          end if
1904                
1905          ! Determine terms from the lower and upper boundary layers
1906          ! when these layers do not cover entire input layers.
1907                
1908          if (pbtm < boundaries(numInputLevs)*0.99) then
1909                     
1910            levelIndex=ilmax+1
1911            if (levelIndex > numInputLevs) levelIndex=numInputLevs
1912            if (levelIndex < 1) levelIndex=1
1913                   
1914            wgts(TargetIndex,levelIndex)= &
1915	      (pbtm - boundaries(ilmax))*TargetLayerThickWgt
1916            
1917            if (present(wgts_opt)) wgts_opt(TargetIndex,levelIndex)=SumWeights
1918
1919            ilmax=levelIndex
1920                  
1921          end if
1922                  
1923          if (ptop > boundaries(1)*1.01) then
1924                     
1925            levelIndex=ilmin-1
1926            if (levelIndex < 1) levelIndex=1
1927            if (levelIndex > numInputLevs) levelIndex=numInputLevs
1928                   
1929            wgts(TargetIndex,levelIndex)= &
1930	      (boundaries(ilmin)-ptop)*TargetLayerThickWgt
1931
1932            if (present(wgts_opt)) wgts_opt(TargetIndex,levelIndex)=SumWeights
1933	      
1934            ilmin=levelIndex
1935            if (ilmax < levelIndex+1) ilmax=levelIndex+1
1936                   
1937          end if
1938          if (ilmin > ilmax-1) ilmin=ilmax-1
1939        end if
1940      end if
1941
1942      kstart(TargetIndex)=ilmin
1943      kend(TargetIndex)=ilmax
1944       
1945    end do
1946
1947    if (present(dealloc_opt)) then
1948      if (dealloc_opt) deallocate(weights,boundaries)
1949    end if
1950    
1951  end subroutine ppo_vertAvgWgts
1952   
1953end module presProfileOperators_mod