!-------------------------------------- LICENCE BEGIN ------------------------------------
!Environment Canada - Atmospheric Science and Technology License/Disclaimer,
! version 3; Last Modified: May 7, 2008.
!This is free but copyrighted software; you can use/redistribute/modify it under the terms
!of the Environment Canada - Atmospheric Science and Technology License/Disclaimer
!version 3 or (at your option) any later version that should be found at:
!http://collaboration.cmc.ec.gc.ca/science/rpn.comm/license.html
!
!This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
!without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
!See the above mentioned License/Disclaimer for more details.
!You should have received a copy of the License/Disclaimer along with this software;
!if not, you can write to: EC-RPN COMM Group, 2121 TransCanada, suite 500, Dorval (Quebec),
!CANADA, H9P 1J3; or send e-mail to service.rpn@ec.gc.ca
!-------------------------------------- LICENCE END --------------------------------------
!
module obsOperators_mod 1,15
!
! Nonlinear observation operators (OONL)
!
use earthConstants_mod
use mathPhysConstants_mod
use obsSpaceData_mod
use columnData_mod
use bufr
! only for oonl_gpsgb
use modgps04profilezd
use modgps08ztdop
use modgpsztd_mod
! only for oonl_gpsro
use modgps01ctphys
, only : p_TC, p_knot
use modgps02wgs84grav
, only : gpsgravitysrf
use modgps03diff
use modgps04profile
, only : gpsprofile, gpsstruct1sw
use modgps08refop
, only : gpsrefopv
use modgps09bend
, only : gpsbndopv1
use modgpsro_mod
implicit none
save
private
! public procedures
public :: oonl_ppp, oonl_sfc, oonl_zzz, oonl_gpsro, oonl_gpsgb, oonl_tovs
contains
subroutine oonl_ppp(columnhr,obsSpaceData,jobs_out,cdfam) 3,25
!
!**s/r oonl_ppp - Computation of Jobs and y - H(x)
! for pressure-level observations
!
!* Purpose: -Interpolate vertically columnhr to
! the pressure levels of the observations. Then compute Jobs.
! A linear interpolation in ln(p) is performed.
!
!Arguments
! jobs_out: contribution to Jobs
! cdfam: family of obsservation
!
implicit none
type(struct_columnData) :: columnhr
type(struct_obs) :: obsSpaceData
real(8), optional :: jobs_out
character(len=*), optional :: cdfam
integer :: headerIndex,bodyIndex,ilyr
integer :: iass,ixtr,ivco,ivnm
real(8) :: zvar,zoer,jobs
real(8) :: zwb,zwt,zexp,zgamma,ztvg
real(8) :: zlev,zpt,zpb,zomp
real(8) :: columnVarB,columnVarT,lqtoes
character(len=4) :: varName
character(len=2) :: varType
real(8),pointer :: col_ptr(:),col_ptr_tt(:),col_ptr_hu(:)
!
! Temperature lapse rate for extrapolation of gz below model surface
!
Write(*,*) "Entering subroutine oonl_ppp"
zgamma = 0.0065D0 / GRAV
zexp = MPC_RGAS_DRY_AIR_R8*zgamma
jobs=0.d0
if(present(cdfam)) then
call obs_set_current_body_list
(obsSpaceData, cdfam)
else
call obs_set_current_body_list
(obsSpaceData)
endif
BODY: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY
! Only process pressure level observations flagged to be assimilated
iass=obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex)
ivco=obs_bodyElem_i
(obsSpaceData,OBS_VCO,bodyIndex)
if(iass.ne.1 .or. ivco.ne.2) cycle BODY
ixtr=obs_bodyElem_i
(obsSpaceData,OBS_XTR,bodyIndex)
ivnm=obs_bodyElem_i
(obsSpaceData,OBS_VNM,bodyIndex)
zvar=obs_bodyElem_r
(obsSpaceData,OBS_VAR,bodyIndex)
zlev=obs_bodyElem_r
(obsSpaceData,OBS_PPP,bodyIndex)
zoer=obs_bodyElem_r
(obsSpaceData,OBS_OER,bodyIndex)
headerIndex=obs_bodyElem_i
(obsSpaceData,OBS_HIND,bodyIndex)
if ( ixtr.eq.0 ) then
! Process all data within the domain of the model
ilyr =obs_bodyElem_i
(obsSpaceData,OBS_LYR,bodyIndex)
varName = vnl_varnameFromVarnum
(ivnm)
varType = vnl_varTypeFromVarnum
(ivnm)
zpt= col_getPressure
(columnhr,ilyr ,headerIndex,varType)
zpb= col_getPressure
(columnhr,ilyr+1,headerIndex,varType)
zwb = log(zlev/zpt)/log(zpb/zpt)
zwt = 1.d0 - zwb
if(ivnm.eq.bufr_nees) then
col_ptr_hu=>col_getColumn(columnhr,headerIndex,'HU')
col_ptr_tt=>col_getColumn(columnhr,headerIndex,'TT')
columnVarB=lqtoes
(col_ptr_hu(ilyr+1),col_ptr_tt(ilyr+1),zpb)
columnVarT=lqtoes
(col_ptr_hu(ilyr ),col_ptr_tt(ilyr ),zpt)
else
if(trim(varName).eq.'GZ') then
col_ptr=>col_getColumn(columnhr,headerIndex,varName,'TH')
else
col_ptr=>col_getColumn(columnhr,headerIndex,varName)
endif
columnVarB=col_ptr(ilyr+1)
columnVarT=col_ptr(ilyr )
endif
zomp = zvar-(zwb*columnVarB+zwt*columnVarT)
jobs = jobs + zomp*zomp/(zoer*zoer)
call obs_bodySet_r
(obsSpaceData,OBS_OMP,bodyIndex,zomp)
elseif (ixtr.eq.2) then
! Process only GZ that is data below model's orography
if(ivnm .eq. BUFR_NEGZ ) then
!
! Forward nonlinear model for geopotential data below model's orography
!
ztvg = (1.0d0 + MPC_DELTA_R8 * exp(col_getElem
(columnhr,col_getNumLev
(columnhr,'TH'),headerIndex,'HU')))* &
col_getElem
(columnhr,col_getNumLev
(columnhr,'TH'),headerIndex,'TT')
zomp = ( zvar - col_getMountain
(columnhr,headerIndex) - &
ztvg/zgamma*(1.D0-(zlev/col_getElem
(columnhr,1,headerIndex,'P0'))**zexp))
jobs = jobs + zomp*zomp/(zoer*zoer)
call obs_bodySet_r
(obsSpaceData,OBS_OMP,bodyIndex,zomp)
endif
endif
enddo body
if(present(jobs_out)) jobs_out=0.5d0*jobs
end subroutine oonl_ppp
subroutine oonl_sfc(columnhr,obsSpaceData,jobs_out,cdfam) 4,35
!
!**s/r oonl_sfc - Computation of Jo and the residuals to the observations
! FOR SURFACE DATA (except ground-based GPS zenith delay)
!
!* Purpose: -Interpolate vertically the contents of columnhr to
! the pressure levels of the observations. Then
! compute Jo.
! A linear interpolation in ln(p) is performed.
!
!Arguments
! jobs_out : contribution to Jo
! cdfam: family of observation
!
implicit none
type(struct_columnData) :: columnhr
type(struct_obs) :: obsSpaceData
real(8), optional :: jobs_out
character(len=*), optional :: cdfam
integer :: ipb,ipt,ivnm,headerIndex,bodyIndex
real(8) :: zvar,zcon,zexp,zgamma,ztvg
real(8) :: zlev,zhhh,zgamaz,zslope,gzhr
real(8) :: columnVarB,lqtoes,jobs
character(len=2) :: varType
!
! Temperature lapse rate for extrapolation of gz below model surface
!
Write(*,*) "Entering subroutine oonl_sfc"
zgamma = 0.0065d0 / GRAV
zexp = 1.0D0/(MPC_RGAS_DRY_AIR_R8*zgamma)
jobs=0.d0
! loop over all header indices of the 'SF' family
if(present(cdfam)) then
call obs_set_current_header_list
(obsSpaceData,cdfam)
else
call obs_set_current_header_list
(obsSpaceData)
endif
HEADER: do
headerIndex = obs_getHeaderIndex
(obsSpaceData)
if (headerIndex < 0) exit HEADER
! loop over all body indices for this headerIndex
call obs_set_current_body_list
(obsSpaceData, headerIndex)
BODY: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY
! only process height level observations flagged to be assimilated
if(obs_bodyElem_i
(obsSpaceData,OBS_VCO,bodyIndex).ne.1 .or. &
obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex).ne.1) cycle BODY
! only process this set of surface observations
ivnm=obs_bodyElem_i
(obsSpaceData,OBS_VNM,bodyIndex)
if( ivnm.ne.BUFR_NETS .and. ivnm.ne.BUFR_NEPS .and. &
ivnm.ne.BUFR_NEUS .and. ivnm.ne.BUFR_NEVS .and. &
ivnm.ne.BUFR_NESS .and. ivnm.ne.BUFR_NEPN ) cycle BODY
zvar = obs_bodyElem_r
(obsSpaceData,OBS_VAR,bodyIndex)
zlev = obs_bodyElem_r
(obsSpaceData,OBS_PPP,bodyIndex)
zhhh = zlev * grav
varType = vnl_varTypeFromVarnum
(ivnm)
if(ivnm.eq.BUFR_NETS .or. ivnm.eq.BUFR_NESS .or. &
ivnm.eq.BUFR_NEUS .or. ivnm.eq.BUFR_NEVS) then
! T2m,(T-TD)2m,US,VS
! In this section we always extrapolate linearly the trial
! field at the model surface to the height of the
! surface observation whether the observation is above or
! below the model surface.
! NOTE: For (T-TD)2m,US,VS we do a zero order extrapolation
if(ivnm.eq.BUFR_NETS) then
zslope = zgamma
else
zslope = 0.0d0
endif
ipt = col_getNumLev
(COLUMNHR,varType)-1 + col_getOffsetFromVarno
(columnhr,ivnm)
ipb = ipt + 1
if(ivnm.eq.bufr_ness) then
columnVarB=lqtoes
(col_getElem
(columnhr,col_getNumLev
(COLUMNHR,'TH'),headerIndex,'HU'), &
col_getElem
(columnhr,col_getNumLev
(COLUMNHR,'TH'),headerIndex,'TT'), &
col_getPressure
(columnhr,col_getNumLev
(COLUMNHR,'TH'),headerIndex,'TH'))
else
columnVarB=col_getElem
(columnhr,ipb,headerIndex)
endif
gzhr=col_getHeight
(columnhr,col_getNumLev
(columnhr,'TH'),headerIndex,'TH')
call obs_bodySet_r
(obsSpaceData,OBS_OMP,bodyIndex, &
(zvar-columnVarB + zslope*(zhhh-gzhr)) )
elseif(ivnm.eq.BUFR_NEPS .or. ivnm.eq.BUFR_NEPN) then
! Surface Pressure Mean sea level Pressure
! In this section we always extrapolate linearly the trial
! field at the model surface to the height of the
! surface observation whether the observation is above or
! below the model height
zgamaz= zgamma*(zhhh-col_getHeight
(columnhr,col_getNumLev
(columnhr,'TH'), &
headerIndex,'TH'))
ztvg = (1.0d0 + MPC_DELTA_R8 * &
exp(col_getElem
(columnhr,col_getNumLev
(columnhr,'TH'),headerIndex,'HU'))) * &
col_getElem
(columnhr,col_getNumLev
(columnhr,'TH'),headerIndex,'TT')
zcon = ((ztvg-zgamaz)/ztvg)
call obs_bodySet_r
(obsSpaceData,OBS_OMP,bodyIndex, &
zvar-(col_getElem
(columnhr,1,headerIndex,'P0')*zcon**zexp))
endif
! contribution to jobs
jobs = jobs +(obs_bodyElem_r
(obsSpaceData,OBS_OMP,bodyIndex)* &
obs_bodyElem_r
(obsSpaceData,OBS_OMP,bodyIndex)) / &
(obs_bodyElem_r
(obsSpaceData,OBS_OER,bodyIndex)* &
obs_bodyElem_r
(obsSpaceData,OBS_OER,bodyIndex))
enddo BODY
enddo HEADER
if(present(jobs_out)) jobs_out=0.5d0*jobs
end subroutine oonl_sfc
subroutine oonl_zzz(columnhr,obsSpaceData,jobs_out,cdfam) 1,23
!
!**s/r oonl_zzz - Computation of Jo and the residuals to the observations
! FOR UPPER AIR DATAFILES
!
!Author : J. St-James, CMDA/SMC July 2003
!
!Revision :
!
! Purpose: - Interpolate vertically the contents of commvo
! onto the heights (in meters) of the observations.
! Compute Jo.
! A linear interpolation in z is performed.
!
!Arguments
! jobs_out: CONTRIBUTION to Jo
! CDFAM: FAMILY OF OBSSERVATION
!
implicit none
type(struct_columnData) :: columnhr
type(struct_obs) :: obsSpaceData
real(8), optional :: jobs_out
character(len=*), optional :: cdfam
integer :: ipb,ipt,ivnm,ik,headerIndex,bodyIndex
real(8) :: zvar,zwb,zwt,zlev,zpt,zpb,jobs
character(len=2) :: varType, obsfam
Write(*,*) "Entering subroutine oonl_zzz"
jobs=0.d0
if(present(cdfam)) then
call obs_set_current_body_list
(obsSpaceData, cdfam)
else
write(*,*) 'oonl_zzz: WARNING, no family specified, assuming PR'
call obs_set_current_body_list
(obsSpaceData, 'PR')
endif
BODY: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY
! Process all height-level data within the domain of the model
if( obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex) .ne. 1 .or. &
obs_bodyElem_i
(obsSpaceData,OBS_XTR,bodyIndex) .ne. 0 .or. &
obs_bodyElem_i
(obsSpaceData,OBS_VCO,bodyIndex) .ne. 1 ) cycle BODY
! In case not specified, make sure only PR family is processed
obsfam = obs_getFamily
(obsSpaceData,bodyIndex=bodyIndex)
if( obsfam.ne.'PR' ) cycle BODY
headerIndex = obs_bodyElem_i
(obsSpaceData,OBS_HIND,bodyIndex)
zvar = obs_bodyElem_r
(obsSpaceData,OBS_VAR,bodyIndex)
zlev = obs_bodyElem_r
(obsSpaceData,OBS_PPP,bodyIndex)
ik = obs_bodyElem_i
(obsSpaceData,OBS_LYR,bodyIndex)
ivnm = obs_bodyElem_i
(obsSpaceData,OBS_VNM,bodyIndex)
ipt = ik + col_getOffsetFromVarno
(columnhr,ivnm)
ipb = ipt+1
varType = vnl_varTypeFromVarnum
(ivnm)
zpt= col_getHeight
(columnhr,ik ,headerIndex,varType)/RG
zpb= col_getHeight
(columnhr,ik+1,headerIndex,varType)/RG
zwb = (zpt-zlev)/(zpt-zpb)
zwt = 1.d0 - zwb
if(ivnm.eq.bufr_nees) call abort3d
('oonl_zzz: CANNOT ASSIMILATE ES!!!')
call obs_bodySet_r
(obsSpaceData,OBS_OMP,bodyIndex, &
zvar-zwb*col_getElem
(columnhr,ipb,headerIndex) &
- zwt*col_getElem
(columnhr,ipt,headerIndex))
! contribution to jobs
jobs = jobs + obs_bodyElem_r
(obsSpaceData,OBS_OMP,bodyIndex)* &
obs_bodyElem_r
(obsSpaceData,OBS_OMP,bodyIndex) / &
(obs_bodyElem_r
(obsSpaceData,OBS_OER,bodyIndex)* &
obs_bodyElem_r
(obsSpaceData,OBS_OER,bodyIndex))
enddo BODY
if(present(jobs_out)) jobs_out=0.5d0*jobs
end subroutine oonl_zzz
subroutine oonl_gpsro(columnhr,obsSpaceData,jobs_out) 1,35
!
!**s/r oonl_gpsro - Computation of Jo and the residuals to the GPSRO observations
!
!
!Author : J. M. Aparicio Jan 2004
! Adapted Nov 2012 for both refractivity and bending angle data
! -------------------
!* Purpose:
!
!Arguments
! jobs_out: total value of Jobs for GPSRO
!
implicit none
type(struct_columnData) :: columnhr
type(struct_obs) :: obsSpaceData
real(8), optional :: jobs_out
real(8) :: jobs, pjob, pjo1
real(8) :: zlat, lat, slat
real(8) :: zlon, lon
real(8) :: zazm, azm
integer :: iazm, isat, iclf, jj
real(8) :: rad, geo, rad1, wfgps
real(8), allocatable :: zpp(:)
real(8), allocatable :: zdp(:)
real(8), allocatable :: ztt(:)
real(8), allocatable :: zhu(:)
real(8), allocatable :: zuu(:)
real(8), allocatable :: zvv(:)
real(8) :: zp0, zmt
real(8) :: hnh1, zobs, zmhx, zoer, zinc
integer index_header, idatyp, index_body
integer jl, ngpslev, nwndlev, stat
logical assim, firstheader, ldsc
integer :: nh, nh1
type(gpsprofile) :: prf
real(8) , allocatable :: h (:),azmv(:)
type(gpsdiff), allocatable :: rstv(:),rstvp(:),rstvm(:)
write(*,*)'ENTER oonl_gpsro'
!
! Initializations
!
ngpslev=col_getNumLev
(columnhr,'TH')
nwndlev=col_getNumLev
(columnhr,'MM')
allocate(zpp(ngpslev))
allocate(zdp(ngpslev))
allocate(ztt(ngpslev))
allocate(zhu(ngpslev))
allocate(zuu(ngpslev))
allocate(zvv(ngpslev))
allocate( h (gpsro_maxprfsize) )
allocate( azmv (gpsro_maxprfsize) )
allocate( rstv (gpsro_maxprfsize) )
!if (levelgpsro.eq.1) then
! allocate( rstvp(gpsro_maxprfsize) )
! allocate( rstvm(gpsro_maxprfsize) )
!endif
jobs=0.0d0
!
! Loop over all header indices of the 'RO' family:
!
call obs_set_current_header_list
(obsSpaceData,'RO')
firstheader = .true.
HEADER: do
index_header = obs_getHeaderIndex
(obsSpaceData)
if (index_header < 0) exit HEADER
!
! Process only refractivity data (codtyp 169)
!
idatyp = obs_headElem_i
(obsSpaceData,OBS_ITY,index_header)
if ( idatyp .ne. 169 ) cycle HEADER
!
! Scan for requested data values of the profile, and count them
!
assim = .false.
nh = 0
call obs_set_current_body_list
(obsSpaceData, index_header)
BODY: do
index_body = obs_getBodyIndex(obsSpaceData)
if (index_body < 0) exit BODY
if ( obs_bodyElem_i
(obsSpaceData,OBS_ASS,index_body).eq.1 ) then
assim = .true.
nh = nh + 1
endif
enddo BODY
!
! If no assimilations are requested, skip to next header
!
if (.not.assim) cycle HEADER
!
! Basic geometric variables of the profile:
!
iazm = obs_headElem_i
(obsSpaceData,OBS_AZA,index_header)
isat = obs_headElem_i
(obsSpaceData,OBS_SAT,index_header)
iclf = obs_headElem_i
(obsSpaceData,OBS_ROQF,index_header)
rad = obs_headElem_r
(obsSpaceData,OBS_TRAD,index_header)
geo = obs_headElem_r
(obsSpaceData,OBS_GEOI,index_header)
zazm = 0.01d0*iazm / MPC_DEGREES_PER_RADIAN_R8
zmt = col_getHeight
(columnhr,ngpslev,index_header,'TH')/RG
wfgps=0.d0
do jj=1,numgpssats
if (isat.eq.igpssat(jj)) wfgps=wgps(jj)
enddo
!
! Profile at the observation location:
!
zlat = obs_headElem_r
(obsSpaceData,OBS_LAT,index_header)
zlon = obs_headElem_r
(obsSpaceData,OBS_LON,index_header)
lat = zlat * MPC_DEGREES_PER_RADIAN_R8
lon = zlon * MPC_DEGREES_PER_RADIAN_R8
azm = zazm * MPC_DEGREES_PER_RADIAN_R8
slat = sin(zlat)
zmt = zmt * RG / gpsgravitysrf
(slat)
zp0 = col_getElem
(columnhr,1,index_header,'P0')
do jl = 1, ngpslev
!
! Profile x
!
zpp(jl) = col_getPressure
(columnhr,jl,index_header,'TH')
zdp(jl) = col_getPressureDeriv
(columnhr,jl,index_header,'TH')
ztt(jl) = col_getElem
(columnhr,JL,index_header,'TT') - p_tc
zhu(jl) = col_getElem
(columnhr,JL,index_header,'HU')
zuu(jl) = 0.d0
zvv(jl) = 0.d0
enddo
do jl = 1, nwndlev
zuu(jl) = col_getElem
(columnhr,jl,index_header,'UU') * p_knot
zvv(jl) = col_getElem
(columnhr,JL,index_header,'VV') * p_knot
enddo
zuu(ngpslev) = zuu(nwndlev)
zvv(ngpslev) = zuu(nwndlev)
!
! GPS profile structure:
!
call gpsstruct1sw
(ngpslev,zLat,zLon,zAzm,zMT,Rad,geo,zPP,zDP,zTT,zHU,zUU,zVV,prf)
ldsc=.not.btest(iclf,16-3)
!
! Prepare the vector of all the observations:
!
nh1 = 0
!
! Loop over all body indices for this index_header:
! (start at the beginning of the list)
!
call obs_set_current_body_list
(obsSpaceData, index_header)
BODY_2: do
index_body = obs_getBodyIndex(obsSpaceData)
if (index_body < 0) exit BODY_2
IF ( obs_bodyElem_i
(obsSpaceData,OBS_ASS,index_body).eq.1 ) then
nh1 = nh1 + 1
h(nh1) = obs_bodyElem_r
(obsSpaceData,OBS_PPP,index_body)
azmv(nh1)= zazm
endif
enddo BODY_2
!
! Apply the observation operator:
!
if (levelgpsro.eq.1) then
call gpsbndopv1
(h , azmv, nh, prf, rstv)
!call gpsbndopv1(h+wfgps, azmv, nh, prf, rstvp)
!call gpsbndopv1(h-wfgps, azmv, nh, prf, rstvm)
!do nh1 = 1, nh
! rstv(nh1)=(rstvp(nh1)+rstv(nh1)+rstvm(nh1))/3.d0
!enddo
else
call gpsrefopv
(h, nh, prf, rstv)
endif
!
! Perform the (H(x)-Y)/S operation:
!
nh1 = 0
pjob = 0.d0
!
! Loop over all body indices for this index_header:
! (start at the beginning of the list)
!
call obs_set_current_body_list
(obsSpaceData, index_header)
BODY_3: do
index_body = obs_getBodyIndex(obsSpaceData)
if (index_body < 0) exit BODY_3
IF ( obs_bodyElem_i
(obsSpaceData,OBS_ASS,index_body).eq.1 ) then
nh1 = nh1 + 1
!
! Altitude:
!
hnh1= obs_bodyElem_r
(obsSpaceData,OBS_PPP,index_body)
if (levelgpsro.eq.1) hnh1=hnh1-rad
!
! Observation operator H(x)
!
zmhx = rstv(nh1)%var
!
! Observation value Y
!
zobs = obs_bodyElem_r
(obsSpaceData,OBS_VAR,index_body)
!
! Observation error S
!
zoer = obs_bodyElem_r
(obsSpaceData,OBS_OER,index_body)
!
! Normalized increment
!
zinc = (zmhx - zobs) / zoer
!
! Datum contribution to Jo:
!
pjo1 = 0.5d0 * zinc * zinc
!
! Total (PJO) and per profile (PJOB) cumulatives:
!
jobs = jobs + pjo1
pjob= pjob+ pjo1
!
if (firstheader) then
write(*, &
'(A9,i10,3f7.2,f11.1,4f12.6,15f12.4)') 'DOBSGPSRO', &
index_header,lat,lon,azm,hnh1,zobs,zoer, &
zmhx,zinc,pjob,prf%gst(ngpslev)%var
endif
call obs_bodySet_r
(obsSpaceData,OBS_OMP,index_body, zobs - zmhx)
endif
enddo BODY_3
write(*,'(A9,i10,2f7.2,f18.10,f12.4,2I6)') &
'GPSRO_JO',index_header,lat,lon,pjob,zmt,isat,ldsc
firstheader = .false.
enddo HEADER
!if (levelgpsro.eq.1) then
! deallocate( rstvm )
! deallocate( rstvp )
!endif
deallocate( rstv )
deallocate( azmv )
deallocate( h )
deallocate(zvv)
deallocate(zuu)
deallocate(zhu)
deallocate(ztt)
deallocate(zdp)
deallocate(zpp)
if(present(jobs_out)) jobs_out=jobs
write(*,*)'EXIT oonl_gpsro'
end subroutine oonl_gpsro
subroutine oonl_gpsgb(columnhr,obsSpaceData,jobs_out,analysisMode_in) 1,46
!
!**s/r oonl_gpsgb - Computation of Jo and the residuals to the GB-GPS ZTD observations
!
!
!Author : S. Macpherson ARMA/MRD
!Revisions:
! S. Macpherson Oct 2012
! -- conversion of 3dvar v11.2.2 version to Rev189 modular form.
! -- uses new (modified) GPS-RO modgps*.f90 for ZTD observation operator
! -- option to use old NL operator removed
! -- ZTD operator gpsZTDopv is found in MODIF modgps08refop.f90
! -- Uses columnData_mod.
!
! S. Macpherson Dec 2012 - Jan 2013
! -- update from Rev189 to Rev213
! -- new namelist parameters in modgpsztd_mod
! -- ZTD operator gpsZTDopv is found in NEW modgps08ztdop.cdk90
! -- ZETA (eta/hybrid values) and ZGZ profiles no longer needed.
! -- add filter for 1-OBS option (L1OBS=.true. in namelist)
! -- Set vGPSZTD_Index(numGPSZTD) for Jacobian storage
!
! S. Macpherson Jun 2013
! -- Use true implementation of ZDP (dP/dP0), although not needed here
!
!Arguments (out)
! jobs_out: total value of Jo for all GB-GPS (ZTD) observations
!
implicit none
type(struct_columnData) :: columnhr
type(struct_obs) :: obsSpaceData
real(8), optional :: jobs_out
logical, optional :: analysisMode_in
real(8), allocatable :: zpp (:)
real(8), allocatable :: zdp (:)
real(8), allocatable :: ztt (:)
real(8), allocatable :: zhu (:)
real(8) :: zlat, lat, zlon, lon, jobs
real(8) :: zmt, zdzmin
real(8) :: zobs, zoer, zinc, zhx, zlev
real(8) :: zdz, zpsobs, zpsmod, zpwmod, zpomp, zpomps
real(8) :: ztdomp(max_gps_data)
real(8) :: bias, std
integer :: headerIndex, bodyIndex, ioneobs, idatyp, ityp, index_ztd, iztd
integer :: jl, nlev_T, nobs2p, stat
integer :: icount1, icount2, icount3, icount, icountp
logical :: assim, llrej, analysisMode
type(gpsprofilezd) :: prf
type(gpsdiff) :: ztdopv
!
! PW lower limit (mm) and Ps O-P upper limit (Pa) for ZTD assimilation
! Note: 1 mb = 100 Pa --> 2.2 mm ZTD
!
real(8) :: zpwmin, zpompmx
data zpwmin / 2.0d0 /
data zpompmx / 200.0d0 /
!
! Criteria to select single observation (1-OBS mode)
!
! Minimum value for ZTD O-P (m)
real(8) :: xompmin
data xompmin / 0.015d0 /
! Minimum value for background (trial) PW (mm)
real(8) :: xpwmin
data xpwmin / 20.0d0 /
! Maximum height difference between observation and background surface (m)
real(8) :: xdzmax
data xdzmax / 400.0d0 /
write(*,*)'ENTER oonl_gpsgb'
if(present(analysisMode_in)) then
analysisMode = analysisMode_in
else
analysisMode = .true.
endif
! Ensure Jacobian-related arrays are not allocated to force them to be recalculated in oda_H
if(allocated(vGPSZTD_Jacobian)) deallocate(vGPSZTD_Jacobian)
if(allocated(vGPSZTD_lJac)) deallocate(vGPSZTD_lJac)
zdzmin = dzmin
nobs2p = 50
jobs = 0.d0
nlev_T = col_getNumLev
(columnhr,'TH')
if (ltestop) write(*,*) ' col_getNumLev
(columnhr,TH) = ',nlev_T
!
! Initializations
!
allocate(ztt(nlev_T))
allocate(zhu(nlev_T))
allocate(zdp(nlev_T))
allocate(zpp(nlev_T))
write(*, *) ' '
write(*, *) ' '
write(*,'(A11,A9,3A8,A9,4A8,2A9,A7,A10,A11)') &
'OONL_GPSGB','CSTNID','ZLAT','ZLON','ZLEV','ZDZ','ZOBS','ZOER','ZHX','O-P', &
'ZPOMPS','ZPOMP','ZPWMOD','Jobs','ZINC2'
icount = 0
icount1 = 0
icount2 = 0
icount3 = 0
icountp = 0
ioneobs = -1
! loop over all header indices of the 'GP' family (all obs locations/times)
call obs_set_current_header_list
(obsSpaceData,'GP')
HEADER: do
headerIndex = obs_getHeaderIndex
(obsSpaceData)
if (headerIndex < 0) exit HEADER
! Process only GP data (codtyp 189)
idatyp = obs_headElem_i
(obsSpaceData,OBS_ITY,headerIndex)
if ( idatyp .ne. 189 ) cycle HEADER
assim = .false.
zpsobs = -100.0d0
! Scan for requested ZTD assimilation.
! Get GPS antenna height ZLEV and Ps(ZLEV) (ZPSOBS)
!
! loop over all body indices for this headerIndex (observations at location/time)
call obs_set_current_body_list
(obsSpaceData, headerIndex)
BODY: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY
ityp = obs_bodyElem_i
(obsSpaceData,OBS_VNM,bodyIndex)
if ( (ityp .eq. BUFR_NEZD) .and. &
(obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex) .eq. 1) ) then
zlev = obs_bodyElem_r
(obsSpaceData,OBS_PPP,bodyIndex)
assim = .true.
! Index in body of ZTD datum (assume at most 1 per header)
index_ztd = bodyIndex
icount = icount + 1
endif
if ( ityp .eq. bufr_neps ) then
if ( (obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex) .eq. 1) .or. llblmet ) then
zpsobs = obs_bodyElem_r
(obsSpaceData,OBS_VAR,bodyIndex)
zpomps = obs_bodyElem_r
(obsSpaceData,OBS_OMP,bodyIndex)
endif
endif
enddo BODY
! If no ZTD assimilation requested, jump to next header
if (.not.assim) cycle HEADER
! Profile at the observation location:
lat = obs_headElem_r
(obsSpaceData,OBS_LAT,headerIndex)
lon = obs_headElem_r
(obsSpaceData,OBS_LON,headerIndex)
zlat = lat * MPC_DEGREES_PER_RADIAN_R8
zlon = lon * MPC_DEGREES_PER_RADIAN_R8
zmt = col_getHeight
(columnhr,nlev_T,headerIndex,'TH')/RG
do jl = 1, nlev_T
zpp(jl) = col_getPressure
(columnhr,jl,headerIndex,'TH')
! True implementation of ZDP (dP/dP0)
zdp(jl) = col_getPressureDeriv
(columnhr,jl,headerIndex,'TH')
ztt(jl) = col_getElem
(columnhr,jl,headerIndex,'TT')-MPC_K_C_DEGREE_OFFSET_R8
zhu(jl) = col_getElem
(columnhr,jl,headerIndex,'HU')
enddo
zdz = zlev - zmt
! Fill GPS ZTD profile structure (PRF):
call gpsstructztd
(nlev_T,lat,lon,zmt,zpp,zdp,ztt,zhu,lbevis,irefopt,prf)
! Apply the GPS ZTD observation operator
! --> output is model ZTD (type gpsdiff) and P at obs height ZLEV
call gpsztdopv
(zlev,prf,lbevis,zdzmin,ztdopv,zpsmod,iztdop)
! Get model profile PW
call gpspw
(prf,zpwmod)
! ZTD (m)
zhx = ztdopv%var
! If analysis mode, reject ZTD data for any of the following conditions:
! (1) the trial PW is too low (extremely dry)
! and if LASSMET=true
! (2) Ps observation is missing or out of normal range
! (3) the ABS(Ps(obs)-Ps(mod)) difference is too large
llrej = .false.
zpomp = -9999.0D0
if ( analysisMode ) then
llrej = ( zpwmod .lt. zpwmin )
if ( lassmet ) then
if ( .not. llrej ) then
if ( zpsobs .gt. 40000.0d0 .and. zpsobs .le. 110000.0d0 ) then
zpomp = zpsobs - zpsmod
llrej = ( abs(zpomp) .gt. zpompmx )
if ( llrej ) icount3 = icount3 + 1
else
llrej = .true.
icount2 = icount2 + 1
endif
else
icount1 = icount1 + 1
endif
endif
endif
if ( llrej ) then
call obs_bodySet_i
(obsSpaceData,OBS_ASS,index_ztd, 0)
if ( .not. lassmet ) icount1 = icount1 + 1
endif
! Perform the (H(x)-Y)/SDERR operation
!
! loop over all body indices for this headerIndex
call obs_set_current_body_list
(obsSpaceData, headerIndex)
BODY_2: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY_2
ityp = obs_bodyElem_i
(obsSpaceData,OBS_VNM,bodyIndex)
if ( obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex).eq.1 .and. &
ityp.eq.BUFR_NEZD ) then
icountp = icountp + 1
!
! Observation value Y
!
zobs = obs_bodyElem_r
(obsSpaceData,OBS_VAR,bodyIndex)
!
! Observation error SDERR
!
zoer = obs_bodyElem_r
(obsSpaceData,OBS_OER,bodyIndex)
if ( zoer .le. 0.0d0 ) then
write(*,*) ' Problem with ZTD observation error!'
write(*,*) ' Station =',obs_elem_c
(obsSpaceData,'STID',headerIndex)
write(*,*) ' Error =', zoer
call abort3d
('OONL_GPSGB: ABORT! BAD ZTD OBSERR')
endif
! Observation height (m)
!
zlev = obs_bodyElem_r
(obsSpaceData,OBS_PPP,bodyIndex)
!
! Normalized increment ZINC
!
ztdomp(icountp) = zobs - zhx
zinc = (zhx - zobs) / zoer
call obs_bodySet_r
(obsSpaceData,OBS_OMP,bodyIndex, zobs - zhx)
jobs = jobs + 0.5d0 * zinc * zinc
!
! Apply data selection criteria for 1-OBS Mode
!
if ( l1obs .and. ioneobs .eq. -1 ) then
if ( (zobs-zhx).gt.xompmin .and. zpwmod.gt.xpwmin .and. &
abs(zdz).lt.xdzmax ) then
ioneobs = headerIndex
write(*,*) 'SINGLE OBS SITE = ',obs_elem_c
(obsSpaceData,'STID',headerIndex)
endif
endif
!
! Print data for first NOBS2P observations
!
if ( icountp .le. nobs2p ) then
write(*, &
'(A12,A9,3(1x,f7.2),1x,f8.2,4(1x,f7.5),2(1x,f8.4),2x,f5.2,1x,f9.2,1x,f10.5)') &
'OONL_GPSGB: ',obs_elem_c
(obsSpaceData,'STID',headerIndex), &
zlat,zlon,zlev,zdz,zobs,zoer/yzderrwgt,zhx,-zinc*zoer, &
zpomps/100.d0,zpomp/100.d0,zpwmod,jobs,zinc/zoer
endif
endif
enddo BODY_2
enddo HEADER
deallocate(ztt)
deallocate(zhu)
deallocate(zdp)
deallocate(zpp)
write(*,*) ' '
write(*,*) 'NUMBER OF GPS ZTD DATA FLAGGED FOR ASSIMILATION = ', icountp
bias = sum(ztdomp(1:icountp))/real(icountp,8)
std = 0.d0
do jl = 1, icountp
std = std + (ztdomp(jl)-bias)**2
enddo
std = sqrt(std/(real(icountp,8)-1.d0))
write(*, *) ' MEAN O-P (BIAS) [mm] = ', bias*1000.d0
write(*, *) ' STD O-P [mm] = ', std*1000.d0
write(*, *) ' '
if ( l1obs .and. analysisMode ) then
! Set assim flag to 0 for all observations except for selected record (site/time)
if ( ioneobs .ne. -1 ) then
call obs_set_current_header_list
(obsSpaceData,'GP')
icountp = 1
HEADER_1: do
headerIndex = obs_getHeaderIndex
(obsSpaceData)
if (headerIndex < 0) exit HEADER_1
if (headerIndex .ne. ioneobs ) then
call obs_set_current_body_list
(obsSpaceData, headerIndex)
BODY_1: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY_1
call obs_bodySet_i
(obsSpaceData,OBS_ASS,bodyIndex, 0)
enddo BODY_1
endif
enddo HEADER_1
else
call abort3d
('ERROR: FAILED TO SELECT SINGLE OBSERVATION!')
endif
endif
numgpsztd = icountp
if ( analysisMode .and. icount .gt. 0 ) then
if ( .not.l1obs ) then
write(*,*) ' '
write(*,*) '-----------------------------------------'
write(*,*) ' SUMMARY OF ZTD REJECTIONS IN OONL_GPSGB '
write(*,*) '-----------------------------------------'
write(*,*) ' TOTAL NUMBER OF ZTD DATA ORIGINALLY FLAGGED FOR ASSMILATION = ', icount
write(*,*) ' NUMBER OF ZTD DATA REJECTED DUE TO LOW TRIAL PW = ', icount1
write(*,*) ' NUMBER OF ZTD DATA REJECETD DUE TO NO PS OBS = ', icount2
write(*,*) ' NUMBER OF ZTD DATA REJECETD DUE TO LARGE PS O-P = ', icount3
write(*,*) ' TOTAL NUMBER OF REJECTED ZTD DATA = ', icount1+icount2+icount3
write(*,*) ' PERCENT REJECTED = ', &
(real(icount1+icount2+icount3,8) / real(icount,8))*100.0d0
write(*, *) ' TOTAL NUMBER OF ASSIMILATED ZTD DATA = ', icountp
if ( icountp.gt.0 ) then
write(*, *) 'MEAN Jo = (jobs/numGPSZTD)*YZDERRWGT**2 = ',(jobs/real(icountp,8))*yzderrwgt**2
endif
write(*,*) ' '
endif
if (numgpsztd .gt. 0) then
write(*,*) ' Number of GPS ZTD data to be assimilated (numGPSZTD) = ', numgpsztd
write(*,*) ' Allocating and setting vGPSZTD_Index(numGPSZTD)...'
if(allocated(vgpsztd_index)) deallocate(vgpsztd_index)
allocate(vgpsztd_index(numgpsztd))
iztd = 0
call obs_set_current_header_list
(obsSpaceData,'GP')
HEADER_2: do
headerIndex = obs_getHeaderIndex
(obsSpaceData)
if (headerIndex < 0) exit HEADER_2
idatyp = obs_headElem_i
(obsSpaceData,OBS_ITY,headerIndex)
if ( idatyp .eq. 189 ) then
call obs_set_current_body_list
(obsSpaceData, headerIndex)
BODY_3: do
bodyIndex = obs_getBodyIndex(obsSpaceData)
if (bodyIndex < 0) exit BODY_3
ityp = obs_bodyElem_i
(obsSpaceData,OBS_VNM,bodyIndex)
if ( obs_bodyElem_i
(obsSpaceData,OBS_ASS,bodyIndex) .eq. 1 .and. &
ityp .eq. BUFR_NEZD ) then
iztd = iztd + 1
vgpsztd_index(iztd) = headerIndex
endif
enddo BODY_3
endif
enddo HEADER_2
if ( iztd .ne. numgpsztd ) then
call abort3d
('ERROR: vGPSZTD_Index init: iztd .ne. numGPSZTD!')
endif
endif
else
if ( icount .gt. 0 ) write(*,*) ' '
endif
if(present(jobs_out)) jobs_out=jobs
write(*,*)'EXIT oonl_gpsgb'
end subroutine oonl_gpsgb
subroutine oonl_tovs(columnghr,obsSpaceData,datestamp,limlvhu,bgckMode_in,jobs_out,option_in,source_obs_in,dest_obs_in) 1,7
!
!**s/r oonl_tovs - Computation of jobs and the residuals to the tovs observations
!
!
!author : j. halle *cmda/aes april 8, 2005
!
!arguments
! option_in: defines input state:
! 'HR': High Resolution background state,
! 'LR': Low Resolution background state, (CURRENTLY NOT SUPPORTED)
! 'MO': Model state. (CURRENTLY NOT SUPPORTED)
! jobs_out: total value of jobs for tovs
!
implicit none
type(struct_columnData) :: columnghr
type(struct_obs) :: obsSpaceData
integer :: datestamp
real(8) :: limlvhu
logical, optional :: bgckMode_in
real(8), optional :: jobs_out
character(len=*), optional :: option_in ! only valid value is HR
integer, optional, intent(in) :: source_obs_in ! usually set to OBS_VAR
integer, optional, intent(in) :: dest_obs_in ! usually set to OBS_OMP
real(8) :: jobs
integer :: jdata, source_obs, dest_obs
logical :: llprint,bgckMode
character(len=2) :: option
! 0. set default values if bgckMode, option and source/dest columns not specified
!
Write(*,*) "Entering subroutine oonl_tovs"
if(present(bgckMode_in)) then
bgckMode = bgckMode_in
else
bgckMode = .false.
endif
if(present(option_in)) then
option = option_in(1:2)
else
option = 'HR'
endif
if ( option .ne. 'HR' ) call abort3d
('oonl_tovs: Invalid option for input state')
if(present(source_obs_in)) then
source_obs = source_obs_in
else
source_obs = OBS_VAR
endif
if(present(dest_obs_in)) then
dest_obs = dest_obs_in
else
dest_obs = OBS_OMP
endif
! 1. Prepare atmospheric profiles for all tovs observation points for use in rttov
! . -----------------------------------------------------------------------------
call tovs_fill_profiles
(columnghr,obsSpaceData,datestamp,limlvhu,bgckMode)
! 2. Compute radiance
! . ----------------
call tovs_rttov
(columnghr,obsSpaceData,bgckMode)
! 3. Compute Jobs and the residuals
! . ----------------------------
if ( option .eq. 'HR' .or. option .eq. 'LR' ) then
do jdata=1,obs_numbody
(obsSpaceData)
call obs_bodySet_r
(obsSpaceData,OBS_PRM,jdata, obs_bodyElem_r
(obsSpaceData,source_obs,jdata))
enddo
endif
if(present(jobs_out) .and. option.eq.'HR') then
llprint = .true.
else
llprint = .false.
endif
jobs = 0.0d0
call tovs_calc_jo
(jobs,llprint,obsSpaceData,dest_obs)
if(present(jobs_out)) jobs_out=jobs
end subroutine oonl_tovs
end module obsOperators_mod