MObservationOperator.F90 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  1. ! First include the set of model-wide compiler flags
  2. #include "tm5.inc"
  3. ! #ifdef TROPNLL2DP
  4. ! #define WRITE_WARNING(notok_string) call fortranlog(notok_string,len(notok_string),2)
  5. ! #else
  6. ! Macro
  7. #define WRITE_WARNING(notok_string) write (*,'(a)') notok_string
  8. #define IF_ERROR_RETURN(action) if (status> 0) then; TRACEBACK; action; return; end if
  9. #define TRACEBACK write (gol,'("in ",a," (",a,i6,")")') rname, __FILE__, __LINE__ ; call goErr
  10. #define IF_NOTOK_RETURN(action) if (status/=0) then; TRACEBACK; action; return; end if
  11. ! #endif
  12. module MObservationOperator
  13. !
  14. ! Computes the model predicted NO2 observation (observation operator)
  15. !
  16. ! Use:
  17. ! After reading a track of NO2 (and cloud data),
  18. ! call "GetOMFHx" to obtain a structure "ObsFcInfo" with the model
  19. ! forecast, observation operator, vertical columns and related parameters
  20. !
  21. ! Contains:
  22. ! subroutine GetOMFHx( amfLut, no2Tr, TM5Data, no2pcf, ObsFcInfo )
  23. !
  24. ! Henk Eskes, Folkert Boersma, Bram Maasakkers KNMI, 2000-2003-2005-2012-2015
  25. ! printing, error handling
  26. use GO, only : gol, goPr, goErr
  27. use pqf_module
  28. implicit none
  29. private
  30. public :: InitOMFHx, GetOMFHx
  31. ! --------------------------------------------------------------
  32. ! Temperature of the cross section used in the DOAS fit
  33. ! --------------------------------------------------------------
  34. ! Temperature of NO2 cross section used in DOAS fit = 220 K
  35. ! Vandaele et al., 1998
  36. real, parameter :: Tdoasfit = 220.0
  37. ! Coefficient in the fit of the T dependence of the cross section amplitude
  38. ! real, parameter :: Tx = 11.39 ! K This is the old T correction result, DOMINO-2
  39. ! Update by Marina Zara, TN-OMIE-KNMI-982, March 2016, for DOMINO-3
  40. ! correction factor = 1 - 0.00316 (T-Tdoasfit) + 3.39e-6 (T-Tdoasfit)^2
  41. real, parameter :: Tcorrection_Acoeff = -0.00316
  42. real, parameter :: Tcorrection_Bcoeff = 3.39e-6
  43. ! Provide error message below this value of the tropospheric AMF
  44. real, parameter :: amfTropCutOff = 0.1
  45. ! Undefined value
  46. ! real, parameter :: undef = -999.99
  47. real, parameter :: nf_fill_float = 9.9692099683868690e+36
  48. ! The following parameters are taken from the .rc file
  49. ! Is cloud radiance fraction taken from the retrieval, or calculated in GetOMFHx ?
  50. logical :: useCloudRadianceFractionFromFile
  51. ! Is the terrain pressure taken from the retrieval file, or calculated in GetOMFHx ?
  52. logical :: useTerrainPressureFromFile
  53. ! Apply stripe corrections (in the case of OMI/TROPOMI)
  54. logical :: doApplyStripeCorrection
  55. ! Flag to identify first call to the routine GetOmFHx
  56. logical :: firstcall = .true.
  57. ! Is the albedo taken from the OMI file
  58. ! logical, parameter :: useOMIAlbedo = .true.
  59. character(len=*), parameter :: mname = 'MObservationOperator'
  60. contains
  61. subroutine InitOMFHx( rcFile )
  62. ! -----------------------------------------------------------------------
  63. ! Initialise the observation operator module:
  64. ! read settings from .rc file
  65. ! Henk Eskes, KNMI, Aug 2016
  66. ! -----------------------------------------------------------------------
  67. use GO, only : TrcFile, Init, Done, ReadRc
  68. use MStripeCorrection, only : stripeCorr_averaging_time
  69. implicit none
  70. ! in/out
  71. character(*), intent(in) :: rcFile
  72. ! local
  73. type(TrcFile) :: rcF
  74. integer :: status
  75. real :: aveTime
  76. character(len=*), parameter :: rname = trim(mname)//'/InitOMFHx'
  77. ! open rcfile:
  78. call Init( rcF, rcfile, status )
  79. IF_NOTOK_RETURN(status=1)
  80. ! Is cloud radiance fraction taken from the retrieval, or calculated in GetOMFHx ?
  81. call ReadRc( rcF, 'domino.useCloudRadianceFractionFromFile', useCloudRadianceFractionFromFile, status, default=.true. )
  82. IF_NOTOK_RETURN(status=1)
  83. ! Is the terrain pressure taken from the retrieval file, or calculated in GetOMFHx ?
  84. call ReadRc( rcF, 'domino.useTerrainPressureFromFile', useTerrainPressureFromFile, status, default=.false. )
  85. IF_NOTOK_RETURN(status=1)
  86. ! Apply stripe corrections (in the case of OMI/TROPOMI)
  87. call ReadRc( rcF, 'domino.doApplyStripeCorrection', doApplyStripeCorrection, status, default=.false. )
  88. IF_NOTOK_RETURN(status=1)
  89. if ( doApplyStripeCorrection ) then
  90. ! This number defines over what period the stripe correction is averaged
  91. ! If set to 1.0, stripeCorr = stripeCorr_last
  92. ! Unit: days
  93. call ReadRc( rcF, 'domino.StripeCorrectionAveragingTime', aveTime, status, default=7.0 )
  94. IF_NOTOK_RETURN(status=1)
  95. stripeCorr_averaging_time = aveTime
  96. else
  97. print '(a)', 'InitOMFHx: No stripe correction applied'
  98. end if
  99. ! close rcfile:
  100. call Done( rcF, status )
  101. IF_NOTOK_RETURN(status=1)
  102. end subroutine InitOMFHx
  103. subroutine GetOMFHx( amfLut, no2Tr, TM5Data, no2pcf, ObsFcInfo, isForecast, outputdir, debug )
  104. !=======================================================================
  105. !
  106. ! GetOMFHx
  107. ! Determine Observation, model forecast and relevant parameters
  108. ! Data is stored in structure "ObsFcInfo"
  109. !
  110. ! Input:
  111. ! amfLut : structure with the AMF lookup-table
  112. ! no2Tr : NO2 SLC + cloud data of type(TObsTrack)
  113. ! TM5Data : data structure contains all TM fields and params needed
  114. ! no2pcf : 3D field of NO2 partial columns for each TM grid cell (10^15 molecules per cm^2)
  115. ! isForecast : true for forecast, false for analysis
  116. ! outputdir : directory for stripe correction output
  117. ! debug : true = provide additional debug output
  118. !
  119. ! Output:
  120. ! "ObsFcInfo" - of type "TObsFcInfo"
  121. ! Contains output of the vertical column retrieval:
  122. ! grid cell indices, forecast, air mass factors
  123. ! vertical columns, error estimates and
  124. ! the corresponding observation operator vectors
  125. !
  126. ! Henk Eskes, Folkert Boersma, Ruud Dirksen, Bram Maasakkers
  127. ! KNMI, 2002 - 2015
  128. !=======================================================================
  129. ! --- TM5 model arrays and variables ---
  130. use MTM5Data, only : TTM5Data
  131. ! --- Structures ---
  132. ! structure containing observation input (slant columns)
  133. use MTObsTrack, only : TObsTrack
  134. ! structure containing observation, forecast and the observation operator
  135. use MTObsFcInfo, only : TObsFcInfo
  136. ! --- Assimilation interfaces ---
  137. use physconstants, only : pi, Earth_rad, grav
  138. use Mrweight, only : rweight
  139. use MTAmf, only : TAmfLut
  140. use MAmfGet, only : GetAmf, GetAmf_vectorised, GetAmfGeo, FitWindowCentre
  141. use MGridTools, only : getTMCellIndex, getTMCellIndex4
  142. use MNO2RetrievalError, only : NO2ErrEstimate, modelStratVcdError
  143. ! --- Error message codes ---
  144. use pqf_module
  145. ! --- OMI stripe correction codes ---
  146. use MStripeCorrection, only : ReadStripeCorrection, ComputeStripeCorrection
  147. use MStripeCorrection, only : stripeCorr, stripeCorrAvailable
  148. implicit none
  149. ! --- in/out ---
  150. type(TAmfLut), intent(inout) :: amfLut
  151. type(TObsTrack), intent(inout) :: no2Tr
  152. type(TTM5Data), intent(in) :: TM5Data
  153. real,dimension(TM5Data%im,TM5Data%jm,TM5Data%lm),intent(in) :: no2pcf
  154. type(TObsFcInfo), intent(inout) :: ObsFcInfo
  155. logical, intent(in) :: isForecast
  156. character(len=*), intent(in) :: outputdir
  157. logical, intent(in) :: debug
  158. ! local: parameters
  159. integer, parameter :: nrtape = 55982
  160. real, parameter :: gamma = 6.5 ! K/km lapse rate
  161. character(*), parameter :: rname = 'GetOMFHx'
  162. ! local: allocatable arrays
  163. real, dimension(:), allocatable :: no2collev, amflev, amfclearlev, amfcloudlev
  164. integer, dimension(:), allocatable :: ixc, iyc, ltropopause
  165. real, dimension(:), allocatable :: cell_oro
  166. integer, dimension(:,:), allocatable :: ix4a, iy4a
  167. real, dimension(:,:), allocatable :: w4a
  168. real, dimension(:,:), allocatable :: phybrid, phybrTop, phybrBot
  169. real, dimension(:,:), allocatable :: amfclearrel, amfcloudrel
  170. real, dimension(:,:), allocatable :: Tlayer, sigmacorrlev
  171. real, dimension(:), allocatable :: sza, vza, aza
  172. real, dimension(:), allocatable :: cloudfrac, cloudpres, surfpres
  173. real, dimension(:), allocatable :: albcloud, albclear, cldradfraction
  174. real, dimension(:), allocatable :: rclear, rcloud, amfgeo
  175. logical, dimension(:), allocatable :: isSnowIce
  176. ! only used in debug
  177. real, dimension(:), allocatable :: phybrid_mean, sigmacorrlev_mean
  178. ! local
  179. real :: gdxc, gdyc
  180. real :: no2gvc, no2slcfc, no2vcdfc
  181. real :: amftotal
  182. real :: satvcd, satscd, rsat
  183. real :: no2slcfctrop, no2vcdfctrop, amftroptotal, amfclear
  184. real :: no2slcfctropclear
  185. real :: no2slcfcstrat, no2vcdfcstrat, satvcdtrop, satslctrop
  186. real :: no2vcdfcnoghost, amftropnoghost
  187. real :: abovecloudfraction
  188. real :: errVcdTotal, errVcdTropTotal, errVcdStratTotal
  189. real :: errVcdTotalAK, errVcdTropTotalAK
  190. real :: spres_in, dT, pow, peff
  191. real :: cos_sza, cos_vza, dphi
  192. real :: dTemp
  193. integer :: status
  194. integer :: cntSceneScheme, nstripe
  195. integer :: NObs, iObs, iObs_print, i, i2, l
  196. integer :: nPixelsWithErrors, nSmallAmftroptotal, nPeff1050
  197. ! test difference between CRF computed and in the file
  198. real :: crf_m0, crf_m1, crf_m2, crf_dif
  199. ! test of amf
  200. real,dimension(100) :: amf_test
  201. ! for error messages
  202. character(256) :: errmessage
  203. ! begin code
  204. print '(a)', 'GetOMFHx: start computing vertical columns, AMFs, kernels'
  205. if ( doApplyStripeCorrection .and. firstcall ) then
  206. ! read the OMI slant column stripe corrections from file
  207. ! only once when routine is called for the first time
  208. call ReadStripeCorrection ( outputdir, TM5Data%date, no2Tr%dimGroundPixel, status )
  209. if ( status /= 0 ) then
  210. print '(a)', "GetOmFHx: WARNING, failed to read stripe correction file "
  211. end if
  212. end if
  213. print '(a,60f7.3)', "GetOmFHx: stripeCorr = ", stripeCorr(:)
  214. if ( doApplyStripeCorrection .and. stripeCorrAvailable ) then
  215. print '(a)', "GetOmFHx: stripeCorr will be applied "
  216. else
  217. print '(a)', "GetOmFHx: stripeCorr will NOT be applied "
  218. end if
  219. ObsFcInfo%count = no2Tr%count
  220. NObs = no2Tr%count ! number of observations in this track
  221. ! allocate helper arrays
  222. allocate(cloudfrac(Nobs)) ; allocate(cloudpres(Nobs))
  223. allocate(sza(Nobs)) ; allocate(vza(Nobs))
  224. allocate(aza(Nobs)) ; allocate(surfpres(Nobs))
  225. allocate(albcloud(Nobs)) ; allocate(albclear(Nobs))
  226. allocate(rcloud(Nobs)) ; allocate(rclear(Nobs))
  227. allocate(cldradfraction(Nobs)) ; allocate(isSnowIce(NObs))
  228. allocate(ix4a(Nobs,4)) ; allocate(iy4a(Nobs,4))
  229. allocate(w4a(Nobs,4)) ; allocate(ixc(Nobs))
  230. allocate(iyc(Nobs)) ; allocate(cell_oro(Nobs))
  231. allocate(ltropopause(Nobs)) ; allocate(Tlayer(Nobs,TM5Data%lm))
  232. allocate(phybrid(Nobs,TM5Data%lm)) ; allocate(phybrTop(Nobs,TM5Data%lm))
  233. allocate(phybrBot(Nobs,TM5Data%lm)) ; allocate(amfclearrel(Nobs,TM5Data%lm))
  234. allocate(amfcloudrel(Nobs,TM5Data%lm)) ; allocate(amfgeo(Nobs))
  235. allocate(sigmacorrlev(Nobs,TM5Data%lm))
  236. allocate ( amfclearlev(TM5Data%lm) ) ; allocate(amfcloudlev(TM5Data%lm))
  237. allocate ( amflev(TM5Data%lm) )
  238. allocate ( no2collev(TM5Data%lm) )
  239. if ( debug ) then
  240. ! extra mean debug ouput of sigmacorrlev
  241. allocate(phybrid_mean(TM5Data%lm)) ; allocate(sigmacorrlev_mean(TM5Data%lm))
  242. end if
  243. ! Copies of no2Tr
  244. cloudfrac(:) = no2Tr%cloudFraction(1:NObs)
  245. cloudpres(:) = no2Tr%cloudTopPressure(1:NObs)
  246. sza(:) = no2Tr%solarZenithAngle(1:NObs)
  247. vza(:) = no2Tr%viewingZenithAngle(1:NObs)
  248. albcloud(:) = no2Tr%cloudAlbedo(1:NObs)
  249. ! Cloud model switch in case of Ice and Snow
  250. ! For ice or snow: use scene pressure and scene albedo instead
  251. ! snowIceFlag - 0: snow free; 1-100: % sea ice cover; 101: ice; 103: snow; 255: ocean
  252. cntSceneScheme = 0
  253. do iObs = 1, NObs
  254. ! Swith to "scene" retrieval in case of snow or ice cover > 50 %
  255. isSnowIce(iObs) = ( (no2Tr%snowIceFlag(iObs) > 50) .and. (no2Tr%snowIceFlag(iObs) < 200) )
  256. if ( isSnowIce(iObs) ) then
  257. cntSceneScheme = cntSceneScheme + 1
  258. cloudfrac(iObs) = 1.0
  259. cloudpres(iObs) = no2Tr%scenePressure(iObs)
  260. albcloud(iObs) = min(1.0,no2Tr%sceneAlbedo(iObs))
  261. end if
  262. end do
  263. if ( debug ) then
  264. print '(a,i6,a,i6)', ' Switch to scene albedo/pressure for ',cntSceneScheme,' pixels out of ',NObs
  265. end if
  266. ! Use surface albedo map provided in L2 file
  267. !if ( useOMIAlbedo) albclear(iObs) = no2Tr%surfaceAlbedo(iObs)
  268. albclear(:) = no2Tr%surfaceAlbedo(1:NObs)
  269. ! Determine relative azimuth angles from the Sun and viewing azimuth
  270. do iObs = 1, NObs
  271. dphi = no2Tr%viewingAzimuthAngle(iObs) - no2Tr%solarAzimuthAngle(iObs) - 180.0;
  272. if ( dphi < 0.0 ) dphi = dphi + 360.0;
  273. if ( dphi >= 360.0 ) dphi = dphi - 360.0;
  274. if ( dphi >= 180.0 ) dphi = 360.0 - dphi;
  275. aza(iObs) = dphi ! in degree, range [0,180)
  276. end do
  277. iObs_print = max ( nObs/2, 1 ) ! middle of the orbit
  278. if ( debug ) then
  279. iObs = iObs_print
  280. print*,'NObs, iobs = ',NObs,iObs
  281. print*,' cloudfrac = ',cloudfrac(iObs)
  282. print*,' cloudpres = ',cloudpres(iObs)
  283. print*,' cloudalb = ',albcloud(iObs)
  284. print*,' cloud CRF = ',no2Tr%cloudRadianceFraction(iObs)
  285. print*,' albsurf = ',no2Tr%surfaceAlbedo(iObs)
  286. print*,' terrheight = ',no2Tr%terrainHeight(iObs)
  287. print*,' terrpres = ',no2Tr%terrainPressure(iObs)
  288. print*,' sza = ',sza(iObs)
  289. print*,' vza = ',vza(iObs)
  290. print*,' aza = ',aza(iObs)
  291. print*,' lat,lon = ',no2Tr%latitude(iObs),no2Tr%longitude(iObs)
  292. print*,' slc = ',no2Tr%no2slc(iObs)
  293. print*,' slc err = ',no2Tr%no2slcerror(iObs)
  294. print*,' pixelFlag = ',no2Tr%pixelFlag(iObs)
  295. print*,' snowIceFlag = ',no2Tr%snowIceFlag(iObs)
  296. print*,' pixelInd = ',no2Tr%pixelIndex(iObs)
  297. print*,' scanline = ',no2Tr%scanLineIndex(iObs)
  298. print*,' min-max albcloud = ',minval(albcloud),maxval(albcloud)
  299. print*,' min-max cloudpres = ',minval(cloudpres),maxval(cloudpres)
  300. print*,' min-max sceneAlbedo = ',minval(no2Tr%sceneAlbedo),maxval(no2Tr%sceneAlbedo)
  301. print*,' min-max scenePressure = ',minval(no2Tr%scenePressure),maxval(no2Tr%scenePressure)
  302. print*,'file = ',no2Tr%orbitParts(1)%filename
  303. end if
  304. ! initialise the flag to the flag obtained from the SLC file
  305. ObsFcInfo%flag(1:nObs) = no2Tr%pixelFlag(1:nObs)
  306. ! input range checks
  307. call CheckValueRanges ( nObs, cloudFrac, no2Tr%cloudRadianceFraction, albcloud, albclear, &
  308. sza, vza, aza, no2Tr%no2slc, no2Tr%longitude, no2Tr%latitude, ObsFcInfo%flag )
  309. ! clip values
  310. where ( albcloud > 0.99 ) albcloud = 0.99
  311. where ( cloudfrac > 1.0 ) cloudfrac = 1.0
  312. where ( cloudfrac < 0.0 ) cloudfrac = 0.0
  313. where ( albclear > 1.0 ) albclear = 1.0
  314. where ( albclear < 0.0 ) albclear = 0.0
  315. if ( debug ) then
  316. print *, 'TM5data, im, jm, lm ',TM5Data%im,TM5Data%jm,TM5Data%lm
  317. print *, 'TM5Data '
  318. print *, ' min max t = ',minval(TM5Data%t),maxval(TM5Data%t)
  319. print *, ' min-max ps = ',minval(TM5Data%ps),maxval(TM5Data%ps)
  320. print *, ' min-max oro = ',minval(TM5Data%oro),maxval(TM5Data%oro)
  321. print *, ' min-max no2 = ',minval(TM5Data%no2),maxval(TM5Data%no2)
  322. print *, ' min-max ltropo = ',minval(TM5Data%ltropo),maxval(TM5Data%ltropo)
  323. end if
  324. ! Compute model quantities @ observation locations
  325. do iObs = 1, NObs
  326. ! skip to next iObs if (flag = error), accept warnings
  327. if ( iand(ObsFcInfo%flag(iObs), 255) /= 0 ) cycle
  328. ! Find the four TM5 grid points closest to the pixel centre
  329. call GetTMCellIndex4(TM5Data%im,TM5Data%jm,no2Tr%longitude(iObs),no2Tr%latitude(iObs),ix4a(iObs,:),iy4a(iObs,:),w4a(iObs,:))
  330. ! Find the TM5 grid cell containing the pixel
  331. call GetTMCellIndex(TM5Data%im,TM5Data%jm,no2Tr%longitude(iObs),no2Tr%latitude(iObs),ixc(iObs),iyc(iObs),gdxc,gdyc)
  332. ! Store the grid cell indices
  333. ObsFcInfo%icell(iObs) = ixc(iObs)
  334. ObsFcInfo%jcell(iObs) = iyc(iObs)
  335. ! Determine surface pressure (in hPa)
  336. surfpres(iObs) = w4a(iObs,1) * TM5Data%ps(ix4a(iObs,1),iy4a(iObs,1)) + &
  337. w4a(iObs,2) * TM5Data%ps(ix4a(iObs,2),iy4a(iObs,2)) + &
  338. w4a(iObs,3) * TM5Data%ps(ix4a(iObs,3),iy4a(iObs,3)) + &
  339. w4a(iObs,4) * TM5Data%ps(ix4a(iObs,4),iy4a(iObs,4))
  340. surfpres(iObs) = 0.01 * surfpres(iObs) ! Pa -> hPa
  341. ! Calculate the terrainheight according to the model (in meter "m")
  342. cell_oro(iObs) = w4a(iObs,1) * TM5Data%oro(ix4a(iObs,1),iy4a(iObs,1)) + &
  343. w4a(iObs,2) * TM5Data%oro(ix4a(iObs,2),iy4a(iObs,2)) + &
  344. w4a(iObs,3) * TM5Data%oro(ix4a(iObs,3),iy4a(iObs,3)) + &
  345. w4a(iObs,4) * TM5Data%oro(ix4a(iObs,4),iy4a(iObs,4))
  346. ! Tropoause level
  347. ltropopause(iObs) = TM5Data%ltropo(ixc(iObs),iyc(iObs))
  348. ! Temperature of the layers
  349. ! Sgphx now works similar to the call in emission_nox.F90 !JDM
  350. ! KFB - 01-10-2009; correct the surface pressure by using the real surface elevation of the OMI pixel
  351. ! Need temperature here
  352. do l = 1, TM5Data%lm
  353. ! determine layer temperature
  354. Tlayer(iObs,l) = w4a(iObs,1) * TM5Data%t(ix4a(iObs,1),iy4a(iObs,1),l) + &
  355. w4a(iObs,2) * TM5Data%t(ix4a(iObs,2),iy4a(iObs,2),l) + &
  356. w4a(iObs,3) * TM5Data%t(ix4a(iObs,3),iy4a(iObs,3),l) + &
  357. w4a(iObs,4) * TM5Data%t(ix4a(iObs,4),iy4a(iObs,4),l)
  358. end do
  359. if ( debug .and. (iObs == iObs_print) ) then
  360. print*,'Observations debug output'
  361. print*,' iObs = ',iObs
  362. print*,' ix4a = ',ix4a(iObs,:)
  363. print*,' iy4a = ',iy4a(iObs,:)
  364. print*,' ixc = ',minval(ixc),maxval(ixc)
  365. print*,' min max iyc = ',minval(iyc),maxval(iyc)
  366. print*,' min max w4a = ',minval(w4a),maxval(w4a)
  367. print*,' min max cell_oro = ',minval(cell_oro),maxval(cell_oro)
  368. print*,' min max albclear = ',minval(albclear),maxval(albclear)
  369. print*,' min max surfpres = ',minval(surfpres),maxval(surfpres)
  370. print*,' min max cloudpres = ',minval(cloudpres),maxval(cloudpres)
  371. print*,' size modelterrainheight = ',size(ObsFcInfo%modelterrainheight)
  372. print*,' size cell_oro = ',size(cell_oro)
  373. end if
  374. end do ! loop over observations, iObs
  375. if ( debug ) then
  376. ! test profile of mean T correction
  377. phybrid_mean(:) = 0.0
  378. sigmacorrlev_mean(:) = 0.0
  379. end if
  380. ! Count number of times pressure rescaling exceeds threshold
  381. nPeff1050 = 0
  382. if ( .not. useCloudRadianceFractionFromFile ) then
  383. ! Initialise CRF counters
  384. print *, 'Initialise CRF counters'
  385. crf_m0 = 0.0
  386. crf_m1 = 0.0
  387. crf_m2 = 0.0
  388. end if
  389. ! surface pressure, cloud pressure, pressure levels, radiance levels, amf_geo
  390. do iObs = 1, NObs
  391. ! skip to next iObs if error flag is set
  392. if ( iand(ObsFcInfo%flag(iObs), 255) /= 0 ) cycle
  393. if ( useTerrainPressureFromFile ) then
  394. ! In case the provided terrain pressure has sufficient resolution
  395. surfpres(iObs) = no2Tr%terrainPressure(iObs)
  396. else
  397. ! Correct the model surface pressure to account for differences in terrain height
  398. ! KFB - 01-10-2009; following equation (5) in Zhou et al., AMT, 2009.
  399. ! JDMTH, using the average terrain height instead of the value at the center of the pixel
  400. dT = Tlayer(iObs,1)/( Tlayer(iObs,1) + 0.001*gamma*(cell_oro(iObs)-no2Tr%terrainHeight(iObs)) )
  401. pow = (-1.0*grav)/(287.*0.001*gamma)
  402. peff = surfpres(iObs) * (dT**pow)
  403. if ( peff > 1050.0 ) then
  404. nPeff1050 = nPeff1050 + 1
  405. peff = 1050.0
  406. end if
  407. surfpres(iObs) = peff
  408. end if
  409. ! clip the cloud pressures, domain [surfpres, 130.]
  410. cloudpres(iObs) = min(surfpres(iObs),cloudpres(iObs))
  411. cloudpres(iObs) = max(130.01,cloudpres(iObs))
  412. ! range checks for cloud and surface pressure
  413. if ( (cloudpres(iObs) < 0.0) .or. (cloudpres(iObs) > 1100.0) ) ObsFcInfo%flag(iObs) = PQF_E_CLOUD_ERROR
  414. if ( (surfpres(iObs) < 0.0) .or. (surfpres(iObs) > 1100.0) ) ObsFcInfo%flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  415. ! determine mid-level TM5 pressures (in hPa), with the corrected model surface pressure
  416. do l = 1, TM5Data%lm
  417. ! top/bottom and midlevel pressure of the layers (in hPa)
  418. phybrTop(iObs,l) = 0.01*( TM5Data%hyai(l+1) + 100.0*surfpres(iObs)*TM5Data%hybi(l+1) )
  419. phybrBot(iObs,l) = 0.01*( TM5Data%hyai(l) + 100.0*surfpres(iObs)*TM5Data%hybi(l) )
  420. phybrid(iObs,l) = 0.5*( phybrTop(iObs,l) + phybrBot(iObs,l) )
  421. end do
  422. if ( phybrid(iObs,ltropopause(iObs)) > 500. ) then
  423. write ( errmessage, *) 'Warning - Tropopause is too low: ', no2Tr%longitude(iObs), no2Tr%latitude(iObs), phybrid(iObs,ltropopause(iObs))
  424. WRITE_WARNING( trim(errmessage) )
  425. end if
  426. ! compute NO2 cross section temperature correction term
  427. ! sigmacorr = sigma(T)/sigma(Tdoasfit)
  428. ! the term Tx is the result of a least-squares fit of the fitted
  429. ! slant column vs. cross section temperature
  430. do l = 1, TM5Data%lm
  431. ! old formula used in DOMINO-2: (Tx = 11.39)
  432. ! sigmacorrlev(iObs,l) = ( Tdoasfit - Tx ) / ( Tlayer(iObs,l) - Tx )
  433. dTemp = Tlayer(iObs,l) - Tdoasfit
  434. sigmacorrlev(iObs,l) = 1.0 + Tcorrection_Acoeff * dTemp + Tcorrection_Bcoeff * dTemp * dTemp
  435. end do
  436. if ( debug ) then
  437. ! determine orbit-mean of the T-correction factor
  438. do l = 1, TM5Data%lm
  439. phybrid_mean(l) = phybrid_mean(l) + phybrid(iObs,l)
  440. sigmacorrlev_mean(l) = sigmacorrlev_mean(l) + sigmacorrlev(iObs,l)
  441. end do
  442. end if
  443. ! determine theoretical clear-sky and cloud-covered radiance levels, and cloud radiance fraction
  444. ! estimate radiation intensities for cloud and no cloud conditions, used for the error estimate
  445. ! rcloud, rclear are needed anyway for the error estimate,
  446. ! even if cldradfraction is taken from input
  447. call rweight( FitWindowCentre,cloudpres(iObs),albcloud(iObs),sza(iObs),vza(iObs),aza(iObs),rcloud(iObs) )
  448. call rweight( FitWindowCentre,surfpres(iObs),albclear(iObs),sza(iObs),vza(iObs),aza(iObs),rclear(iObs) )
  449. ! flagging
  450. if ( (rclear(iObs) <= 0.0) .or. (rcloud(iObs) <= 0.0) ) ObsFcInfo%flag(iObs) = PQF_E_REFLECTANCE_RANGE_ERROR
  451. ! cloud radiance fraction
  452. if ( useCloudRadianceFractionFromFile ) then
  453. ! Use value from the retrieval
  454. if ( isSnowIce(iObs) ) then
  455. cldradfraction(iObs) = 1.0 ! Fully covered snow-ice "scene"
  456. else
  457. cldradfraction(iObs) = no2Tr%cloudRadianceFraction(iObs)
  458. end if
  459. ! In this case:
  460. ! rclear = f(1-crf)/[(1-f)crf] rcloud
  461. ! clipping
  462. if ( cldradfraction(iObs) > 1.0 ) cldradfraction(iObs) = 1.0
  463. if ( cldradfraction(iObs) < 0.0 ) cldradfraction(iObs) = 0.0
  464. else
  465. ! Estimated satellite-observed radiance (normalization factor)
  466. rsat = (1.0-cloudfrac(iObs))*rclear(iObs) + cloudfrac(iObs)*rcloud(iObs)
  467. ! Compute radiance fraction originating from the cloudy part of the pixel
  468. cldradfraction(iObs) = cloudfrac(iObs)*rcloud(iObs) / rsat
  469. ! Determine CRF difference with file (for snow/ice free pixels)
  470. if ( .not. isSnowIce(iObs) ) then
  471. !crf_dif = cldradfraction(iObs)
  472. crf_dif = cldradfraction(iObs) - no2Tr%cloudRadianceFraction(iObs)
  473. crf_m0 = crf_m0 + 1.0
  474. crf_m1 = crf_m1 + crf_dif
  475. crf_m2 = crf_m2 + crf_dif*crf_dif
  476. end if
  477. end if
  478. ! fill "amfgeo", the geometrical air mass factor
  479. ! NOTE: the AMF lookup table should contain AMF/AMFgeo, where AMFgeo has exactly the same form as here
  480. cos_sza = cos(sza(iObs)*pi/180.0)
  481. cos_vza = cos(vza(iObs)*pi/180.0)
  482. call GetAmfGeo ( cos_vza, cos_sza, amfGeo(iObs) )
  483. if ( debug .and. (iObs == iObs_print) ) then
  484. print*,'Geometry debug output, iObs = ',iObs
  485. print*,' no2Tr%terrainPressure(iObs) = ',no2Tr%terrainPressure(iObs)
  486. print*,' surfpres(iObs) = ',surfpres(iObs)
  487. print*,' cloudpres(iObs) = ',cloudpres(iObs)
  488. print'(a,34F8.2)',' phybrid = ',phybrid(iObs,:)
  489. print'(a,34F6.3)',' sigmacorrlev = ',sigmacorrlev(iObs,:)
  490. print*,' rclear,rcloud = ',rclear(iObs),rcloud(iObs)
  491. print*,' cldradfraction = ',cldradfraction(iObs)
  492. print*,' amfGeo = ',amfGeo(iObs)
  493. end if
  494. end do ! iObs
  495. ! Determine CRF difference with file
  496. if ( .not. useCloudRadianceFractionFromFile ) then
  497. ! print *,'crf_m0,1,2 = ',crf_m0, crf_m1, crf_m2
  498. print '(a,F10.8,a,F10.8)', ' mean CRF difference, bias(computed-file) = ', crf_m1/crf_m0, &
  499. ', var = ', sqrt( crf_m2/crf_m0 - (crf_m1/crf_m0)**2 )
  500. end if
  501. if ( nPeff1050 > 0 ) then
  502. write ( errmessage, '(a,i7,a)') 'WARNING GetOMFHx: peff set to 1050 for ',nPeff1050,' observations'
  503. WRITE_WARNING( trim(errmessage) )
  504. end if
  505. if ( debug ) then
  506. ! Mean T-correction output
  507. phybrid_mean(:) = phybrid_mean(:) / real(NObs)
  508. sigmacorrlev_mean(:) = sigmacorrlev_mean(:) / real(NObs)
  509. print*,' '
  510. print '(a,34F8.2)', ' mean phybrid = ',phybrid_mean(:)
  511. print '(a,34F6.3)', ' mean sigmacorr = ',sigmacorrlev_mean(:)
  512. end if
  513. ! read sensitivities from the AMF lookup table: clear case
  514. call GetAmf_vectorised( nObs,TM5Data%lm,phybrid,aza,vza,sza,albclear,surfpres,amfLut,amfclearrel,obsFcInfo%flag,debug )
  515. if ( debug ) print*,'GetAmf_vectorised done (clear)'
  516. if ( debug ) then
  517. iObs = iObs_print
  518. do l = 1, TM5Data%lm
  519. call GetAmf(phybrid(iObs,l),aza(iObs),vza(iObs),sza(iObs),albclear(iObs),surfpres(iObs),amfLut,amf_test(l),status)
  520. end do
  521. print '(a,34f6.3)', ' amf (debug) = ',amf_test(1:TM5Data%lm)
  522. end if
  523. ! read sensitivities from the AMF lookup table: 100% cloud case
  524. call GetAmf_vectorised( nObs,TM5Data%lm,phybrid,aza,vza,sza,albcloud,cloudpres,amfLut,amfcloudrel,obsFcInfo%flag,.false. )
  525. if ( debug ) print*,'GetAmf_vectorised done (cloudy)'
  526. ! count the number of flagged pixels and pixels with small AMF
  527. nPixelsWithErrors = 0
  528. nSmallAmftroptotal = 0
  529. ! number of pixels where SCD is corrected
  530. nstripe = 0
  531. ! main retrieval loop
  532. mainObsLoop: do iObs = 1, NObs
  533. ! skip observation when error
  534. if ( iand(ObsFcInfo%flag(iObs), 255) /= 0 ) then
  535. nPixelsWithErrors = nPixelsWithErrors + 1
  536. cycle
  537. end if
  538. no2gvc = 0.0 ; no2slcfc = 0.0
  539. no2vcdfc = 0.0 ; no2collev = 0.0
  540. no2slcfctrop = 0.0 ; no2vcdfctrop = 0.0
  541. no2slcfctropclear = 0.0
  542. ! the height-dependent AMF for clear sky
  543. do l = 1, TM5Data%lm
  544. amfclearlev(l) = sigmacorrlev(iObs,l)*amfclearrel(iObs,l)*amfgeo(iObs)
  545. end do
  546. ! the height-dependent AMF for 100% cloud cover
  547. amfcloudlev(:) = 0.0
  548. do l = 1, TM5Data%lm
  549. if ( cloudpres(iObs) >= (phybrTop(iObs,l)-1.0e-5) ) then
  550. if ( cloudpres(iObs) >= (phybrBot(iObs,l)-1.0e-5) ) then
  551. ! model level above the cloud top
  552. amfcloudlev(l) = sigmacorrlev(iObs,l)*amfcloudrel(iObs,l)*amfgeo(iObs)
  553. else
  554. ! model level contains cloud top
  555. abovecloudfraction = (cloudpres(iObs) - phybrTop(iObs,l))/(phybrBot(iObs,l) - phybrTop(iObs,l))
  556. !Nieuwe code die clipt & melding schrijft: RuudDirksen 19 juni 2009
  557. spres_in = phybrBot(iObs,l)+1.0e-5
  558. if ( spres_in .lt. 119.82 ) then
  559. write ( errmessage, '(a,3(2x,f8.4))' ) &
  560. 'GetOMFHx: Clipped cloud pressure; p lvl c_f c_p:',spres_in,cloudfrac(iObs),cloudpres(iObs)
  561. WRITE_WARNING( trim(errmessage) )
  562. spres_in = 119.82
  563. end if
  564. !call GetAmf(phybrid(iObs,l),aza(iObs),vza(iObs),sza(iObs),albcloud(iObs),phybrid(iObs,l)+1.0e-5,amfcloudrel(iObs,l))
  565. call GetAmf(phybrid(iObs,l),aza(iObs),vza(iObs),sza(iObs),albcloud(iObs),spres_in,amfLut,amfcloudrel(iObs,l),status)
  566. ! set flag if status = error
  567. if ( status > 0 ) obsFcInfo%flag(iObs) = PQF_E_LUT_RANGE_ERROR
  568. ! set amf for the level containing cloud top
  569. amfcloudlev(l) = sigmacorrlev(iObs,l)*amfcloudrel(iObs,l)*amfgeo(iObs)*abovecloudfraction
  570. end if
  571. end if
  572. end do ! l, loop over vertical levels
  573. ! skip observation when flagged in the loop above
  574. if ( iand(ObsFcInfo%flag(iObs), 255) /= 0 ) then
  575. nPixelsWithErrors = nPixelsWithErrors + 1
  576. cycle
  577. end if
  578. ! loop over vertical TM5 hybrid levels
  579. do l = 1, TM5Data%lm
  580. ! determine amount of NO2 in this layer (kg/m^2)
  581. ! compute NO2 subcolumns for the observation and for layer "l" using the "no2pcf" model field
  582. ! unit 10^15 molec/cm^2
  583. no2collev(l) = w4a(iObs,1) * no2pcf(ix4a(iObs,1),iy4a(iObs,1),l) + &
  584. w4a(iObs,2) * no2pcf(ix4a(iObs,2),iy4a(iObs,2),l) + &
  585. w4a(iObs,3) * no2pcf(ix4a(iObs,3),iy4a(iObs,3),l) + &
  586. w4a(iObs,4) * no2pcf(ix4a(iObs,4),iy4a(iObs,4),l)
  587. ! determine AMF weighted with cloud fraction and radiation intensity
  588. ! amflev(iObs,l) = (1.0-cloudfrac(iObs))*rclear(iObs)*amfclearlev(iObs,l)/rsat(iObs) + &
  589. ! cloudfrac(iObs) *rcloud(iObs)*amfcloudlev(iObs,l)/rsat(iObs)
  590. amflev(l) = (1.0-cldradfraction(iObs))*amfclearlev(l) + &
  591. cldradfraction(iObs) *amfcloudlev(l)
  592. ! debug
  593. if ( debug .and. (iObs == iObs_print) .and. (l == TM5Data%lm) ) then
  594. print '(a,i6)', 'TM5 profile debug output, iObs = ',iObs
  595. print '(a,2f10.4)', ' crf, amfgeo = ', cldradfraction(iObs), amfgeo(iObs)
  596. print '(a,34f8.4)', ' no2collev(:) = ',no2collev(:)
  597. print '(a,34f8.4)', ' amfclearlev(:) = ',amfclearlev(:)
  598. print '(a,34f8.4)', ' amfcloudlev(:) = ',amfcloudlev(:)
  599. print '(a,34f8.4)', ' amflev(:) = ',amflev(:)
  600. end if
  601. ! compute model-predicted NO2 slant & vertical column
  602. ! no2collev = NO2 mass in grid cell (units 10^15 molecules cm^-2)
  603. no2slcfc = no2slcfc + amflev(l)*no2collev(l)
  604. no2vcdfc = no2vcdfc + no2collev(l)
  605. ! compute tropospheric model slant & vertical column
  606. if ( l <= ltropopause(iObs) ) then
  607. no2slcfctrop = no2slcfctrop + amflev(l)*no2collev(l)
  608. no2slcfctropclear = no2slcfctropclear + amfclearlev(l)*no2collev(l)
  609. no2vcdfctrop = no2vcdfctrop + no2collev(l)
  610. end if
  611. ! compute ghost vertical column "GVC", the vertical column below the cloud top.
  612. ! Burrows et al., J. Atmos. Sci., 56, 151-175, 1999.
  613. if ( cloudpres(iObs) >= phybrTop(iObs,l)-1.0e-5 ) then
  614. if ( cloudpres(iObs) >= phybrBot(iObs,l)-1.0e-5 ) then
  615. ! model level above the cloud top, no need to update ghost column
  616. else
  617. ! model level contains cloud top
  618. abovecloudfraction = (cloudpres(iObs) - phybrTop(iObs,l))/(phybrBot(iObs,l) - phybrTop(iObs,l))
  619. no2gvc = no2gvc + (1. - abovecloudfraction )*no2collev(l)
  620. end if
  621. else
  622. ! model level below cloud top
  623. no2gvc = no2gvc + no2collev(l)
  624. end if
  625. ! ghost column only defined for cloud fractions > 0
  626. if ( cloudfrac(iObs) < 0.001 ) no2gvc = 0.
  627. end do ! loop over TM5 hybrid levels
  628. ! checks to avoid division by 0
  629. if ( no2vcdfc <= 1.0e-8 ) then
  630. write ( errmessage, '(a,i6,2x,f8.2,2x,f8.2)' ) &
  631. 'GetOMFHx: no2vcdfc <=0 for ',iObs,no2Tr%latitude(iObs),no2Tr%longitude(iObs)
  632. WRITE_WARNING( trim(errmessage) )
  633. ObsFcInfo%flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  634. nPixelsWithErrors = nPixelsWithErrors + 1
  635. cycle
  636. end if
  637. if ( no2vcdfctrop <= 1.0e-10 ) then
  638. write ( errmessage, '(a,i6,2x,f8.2,2x,f8.2)' ) &
  639. 'GetOMFHx: no2vcdfctrop <=0 for ',iObs,no2Tr%latitude(iObs),no2Tr%longitude(iObs)
  640. WRITE_WARNING( trim(errmessage) )
  641. ObsFcInfo%flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  642. nPixelsWithErrors = nPixelsWithErrors + 1
  643. cycle
  644. end if
  645. ! total tropospheric air-mass factor
  646. amftroptotal = no2slcfctrop / no2vcdfctrop
  647. ! too small tropospheric AMFs not allowed, provide error
  648. if ( amftroptotal < amfTropCutOff ) then
  649. nSmallAmftroptotal = nSmallAmftroptotal + 1
  650. nPixelsWithErrors = nPixelsWithErrors + 1
  651. ObsFcInfo%flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  652. cycle
  653. end if
  654. ! total air-mass factor
  655. amftotal = no2slcfc / no2vcdfc ! model SLC / model VCD
  656. ! total tropospheric air-mass factor, assuming the pixel is cloud free
  657. amfclear = no2slcfctropclear / no2vcdfctrop
  658. ! Observed slant column: correct for stripes
  659. satscd = no2Tr%no2SLC(iObs)
  660. if ( doApplyStripeCorrection .and. stripeCorrAvailable ) then
  661. ! apply the OMI slant column stripe corrections to the DOAS slant column
  662. if ( stripeCorr( no2Tr%pixelIndex(iObs) ) < 0.9*nf_fill_float ) then
  663. ! number of pixels where SCD is corrected
  664. nstripe = nstripe + 1
  665. satscd = satscd - stripeCorr( no2Tr%pixelIndex(iObs) )
  666. end if
  667. end if
  668. ! retrieved total vertical NO2 column
  669. satvcd = satscd / amftotal
  670. ! estimated NO2 vertical tropospheric column based on the tropospheric air-mass factor
  671. ! Total model vertical column
  672. no2slcfcstrat = no2slcfc - no2slcfctrop
  673. no2vcdfcstrat = no2vcdfc - no2vcdfctrop
  674. ! Ghost column quantities:
  675. ! For stratospheric clouds, take trop. column as ghost column
  676. if ( no2gvc > no2vcdfctrop ) no2gvc = no2vcdfctrop
  677. ! Calculate the model predicted NO2 VCD without the ghostcolumn contribution
  678. no2vcdfcnoghost = (1.0 - cldradfraction(iObs) ) * no2vcdfctrop + &
  679. cldradfraction(iObs) * (no2vcdfctrop-no2gvc)
  680. ! calculate the tropospheric AMF without incorporating the ghostcolumn
  681. amftropnoghost = no2slcfctrop / no2vcdfcnoghost
  682. ! --- Compute error estimates ---
  683. satslctrop = satscd - no2slcfcstrat
  684. satvcdtrop = satslctrop / amftroptotal
  685. call NO2ErrEstimate( TM5Data%lm, &
  686. cloudfrac(iObs), cloudpres(iObs), rclear(iObs), rcloud(iObs), &
  687. amflev(:), amfclearlev(:), amfcloudlev(:), &
  688. albclear(iObs), albcloud(iObs), amfgeo(iObs), &
  689. ix4a(iObs,:), iy4a(iObs,:), w4a(iObs,:), &
  690. TM5Data%hyai, TM5Data%hybi, surfpres(iObs), sza(iObs), vza(iObs), aza(iObs), &
  691. no2collev(:), ltropopause(iObs), &
  692. amftotal, amftroptotal, &
  693. satvcd, satvcdtrop, &
  694. amfLut, &
  695. errVcdTotal, errVcdTropTotal, errVcdStratTotal, &
  696. errVcdTotalAK, errVcdTropTotalAK, status )
  697. if ( status > 0 ) then
  698. write ( errmessage, '(a)' ) 'ERROR GetOMFHx: error occured in NO2ErrEstimate'
  699. WRITE_WARNING( trim(errmessage) )
  700. ObsFcInfo%flag(iObs) = PQF_E_GENERIC_EXCEPTION
  701. nPixelsWithErrors = nPixelsWithErrors + 1
  702. cycle
  703. end if
  704. ! Retrieval was successful at this point, but warnings may still occur
  705. ! When the cloudradiance fraction exceeds 50%, raise troposphericcolumnflag
  706. ! the tropospheric column value is considered unreliable because more than half of the
  707. ! light/spectrum comes from above the cloud.
  708. ! HenkEskes: in DOMINO-3 there is no longer a warning for CRF > 0.5
  709. ! where (ObsFcInfo%cloudradfraction > 50.0 ) ObsFCInfo%fltrop = -1
  710. ! When the pixel is covered by snow the cloud parameters are unreliable.
  711. ! snow covered pixels have surface albedo of 0.6
  712. ! if ( albclear(iObs) > 0.59 ) ObsFCInfo%flag(iObs) = ior( ObsFCInfo%flag(iObs), PQF_W_SNOW_ICE_WARNING )
  713. if ( isSnowIce(iObs) ) ObsFCInfo%flag(iObs) = ior( ObsFCInfo%flag(iObs), PQF_W_SNOW_ICE_WARNING )
  714. ! show results
  715. if ( debug .and. ( iObs == iObs_print ) ) then
  716. print '(a,i6,a,8i4,4f6.3,2f8.1)', 'iObs = ', iObs, ' i, j, w, lon, lat ', ix4a(iObs,:), iy4a(iObs,:), w4a(iObs,:), no2Tr%longitude(iObs),no2Tr%latitude(iObs)
  717. print '(a,3f8.2)', ' vcdfc, satvcd, amf ',no2vcdfc, satvcd, amftotal
  718. print '(a,3f8.2)', ' vcdtrop, sig, sig_AK ', satvcdtrop, errVcdTropTotal, errVcdTropTotalAK
  719. print '(a,2f8.2)', ' vcdstrat, sig ', no2vcdfcstrat, errVcdStratTotal
  720. print '(a,3f8.2)', ' amftotal, amftrop, amfclear ', amftotal, amftroptotal, amfclear
  721. print '(a,2f8.2)', ' amfstrat, amfgeo ', no2slcfcstrat / no2vcdfcstrat, amfgeo(iObs)
  722. print '(a,50f6.2)', ' AK ', amflev(:)/amftotal
  723. print '(a,f8.2)', ' modelterrainhgt ', cell_oro(iObs)
  724. print '(a,i4)', ' l_tropo ', ltropopause(iObs)
  725. end if
  726. ! store retrieval data in structure "ObsFcInfo"
  727. ! vcd, amf
  728. ObsFcInfo%no2vcd(iObs) = satvcd
  729. ObsFcInfo%no2vcdsum(iObs) = satvcdtrop + no2vcdfcstrat
  730. obsFcInfo%no2vcdsumsig(iObs) = sqrt ( errVcdStratTotal**2 + errVcdTropTotal**2)
  731. ObsFcInfo%no2vcdsig(iObs) = errVcdTotal
  732. ObsFcInfo%no2vcdsigak(iObs) = errVcdTotalAK
  733. ObsFcInfo%no2vcdtrop(iObs) = satvcdtrop
  734. ObsFcInfo%no2vcdtropsig(iObs) = errVcdTropTotal
  735. ObsFcInfo%no2vcdtropsigak(iObs) = errVcdTropTotalAK
  736. ObsFcInfo%no2vcdstrat(iObs) = no2vcdfcstrat
  737. ObsFcInfo%no2vcdstratsig(iObs) = errVcdStratTotal
  738. ObsFcInfo%amf(iObs) = amftotal
  739. ObsFcInfo%amftrop(iObs) = amftroptotal
  740. ObsFcInfo%amfgeo(iObs) = amfgeo(iObs)
  741. ObsFcInfo%amfstrat(iObs) = no2slcfcstrat / no2vcdfcstrat
  742. ObsFcInfo%amfclear(iObs) = amfclear
  743. ObsFcInfo%avkernel(:,iObs) = amflev(:)/amftotal
  744. ObsFcInfo%ghostcol(iObs) = no2gvc
  745. ! model derived quantities
  746. ObsFcInfo%psurf(iObs) = surfpres(iObs) ! in hPa
  747. ObsFcInfo%no2vcdfc(iObs) = no2vcdfc
  748. ObsFcInfo%no2vcdfctrop(iObs) = no2vcdfctrop
  749. ObsFcInfo%no2slcfc(iObs) = no2slcfc
  750. ObsFcInfo%no2slcfctrop(iObs) = no2slcfctrop
  751. ObsFcInfo%no2slcstrat(iObs) = no2slcfcstrat
  752. ObsFcInfo%levtropopause(iObs) = ltropopause(iObs)
  753. ! model orography in "m"
  754. ObsFcInfo%modelTerrainHeight(iObs) = cell_oro(iObs)
  755. ! other
  756. ObsFcInfo%cloudradfraction(iObs)= cldradfraction(iObs) ! range [0,1]
  757. end do mainObsLoop ! iObs: loop over observations
  758. ! Has the cloud radiance fraction been computed in this module?
  759. ObsFcInfo%cloudRadFraction_computed = (.not. useCloudRadianceFractionFromFile)
  760. ! number of pixels where SCD is corrected
  761. print '(a,i7)', ' GetOMFHx: nr of pixels for which SCD is stripe corrected ', nstripe
  762. print '(a,2i6)', ' GetOMFHx: min-max pixelIndex = ', minval(no2Tr%pixelIndex(:)) , maxval(no2Tr%pixelIndex(:))
  763. ! report on the number of errors found (pixels removed)
  764. if ( nPixelsWithErrors > 0 ) then
  765. write ( errmessage, '(a,i6)' ) &
  766. 'GetOMFHx: WARNING, nr of pixels with error flag =', nPixelsWithErrors
  767. WRITE_WARNING( trim(errmessage) )
  768. end if
  769. if ( nSmallAmftroptotal > 0 ) then
  770. write ( errmessage, '(a,i6)' ) &
  771. 'GetOMFHx: WARNING, nr of pixels with too small amftroptotal =', nSmallAmftroptotal
  772. WRITE_WARNING( trim(errmessage) )
  773. end if
  774. ! deallocate temporary arrays
  775. if (allocated(no2collev)) deallocate(no2collev)
  776. if (allocated(cloudfrac)) deallocate(cloudfrac)
  777. if (allocated(cloudpres)) deallocate(cloudpres)
  778. if (allocated(sza)) deallocate(sza)
  779. if (allocated(vza)) deallocate(vza)
  780. if (allocated(aza)) deallocate(aza)
  781. if (allocated(surfpres)) deallocate(surfpres)
  782. if (allocated(albcloud)) deallocate(albcloud)
  783. if (allocated(albclear)) deallocate(albclear)
  784. if (allocated(rclear)) deallocate(rclear)
  785. if (allocated(rcloud)) deallocate(rcloud)
  786. if (allocated(cldradfraction)) deallocate(cldradfraction)
  787. if (allocated(isSnowIce)) deallocate(isSnowIce)
  788. if (allocated(amfgeo)) deallocate(amfgeo)
  789. if (allocated(ix4a)) deallocate(ix4a)
  790. if (allocated(iy4a)) deallocate(iy4a)
  791. if (allocated(w4a)) deallocate(w4a)
  792. if (allocated(ixc)) deallocate(ixc)
  793. if (allocated(iyc)) deallocate(iyc)
  794. if (allocated(cell_oro)) deallocate(cell_oro)
  795. if (allocated(ltropopause)) deallocate(ltropopause)
  796. if (allocated(Tlayer)) deallocate(Tlayer)
  797. if (allocated(phybrid)) deallocate(phybrid)
  798. if (allocated(phybrTop)) deallocate(phybrTop)
  799. if (allocated(phybrBot)) deallocate(phybrBot)
  800. if (allocated(amfclearrel)) deallocate(amfclearrel)
  801. if (allocated(amfcloudrel)) deallocate(amfcloudrel)
  802. if (allocated(sigmacorrlev)) deallocate(sigmacorrlev)
  803. if (allocated(amfclearlev)) deallocate(amfclearlev)
  804. if (allocated(amfcloudlev)) deallocate(amfcloudlev)
  805. if (allocated(amflev)) deallocate(amflev)
  806. if ( debug ) then
  807. ! only for extra mean ouput of sigmacorrlev
  808. if ( allocated(phybrid_mean) ) deallocate(phybrid_mean)
  809. if ( allocated(sigmacorrlev_mean) ) deallocate(sigmacorrlev_mean)
  810. end if
  811. ! new stripe correction
  812. if ( doApplyStripeCorrection .and. isForecast ) then
  813. ! check if this is a Pacific orbit,
  814. ! compute a new OMI slant column stripe correction,
  815. ! and save to file when a new correction was computed
  816. ! this is skipped in analysis mode
  817. call ComputeStripeCorrection ( outputdir, TM5Data%date, no2Tr, nObs, ObsFcInfo%no2slcfc, ObsFcInfo%flag, status )
  818. end if
  819. if ( status > 0 ) then
  820. write ( errmessage, '(a)' ) 'ERROR GetOMFHx: error occured in ComputeStripeCorrection'
  821. WRITE_WARNING( trim(errmessage) )
  822. end if
  823. if ( debug ) print*, 'This is the end of GetOMFHx'
  824. firstcall = .false.
  825. end subroutine GetOMFHx
  826. subroutine CheckValueRanges ( nObs, cloudFraction, cloudRadianceFraction, albcloud, albclear, &
  827. sza, vza, aza, no2slc, longitude, latitude, flag )
  828. !
  829. ! perform range checks for a list of input variables
  830. !
  831. implicit none
  832. integer, intent(in) :: nObs
  833. real, intent(in), dimension(nObs) :: cloudFraction, cloudRadianceFraction, albcloud, albclear
  834. real, intent(in), dimension(nObs) :: sza, vza, aza, no2slc, longitude, latitude
  835. integer, intent(inout), dimension(nObs) :: flag
  836. integer :: iObs
  837. character(256) :: errmessage
  838. integer :: nCloudFrac, nCloudRF, nAlbCloud, nAlbClear, nSZA, nVZA, nAZA, nSLC, nLon, nLat
  839. ! start code
  840. nCloudFrac = 0
  841. nCloudRF = 0
  842. nAlbCloud = 0
  843. nAlbClear = 0
  844. nSZA = 0
  845. nVZA = 0
  846. nAZA = 0
  847. nSLC = 0
  848. nLon = 0
  849. nLat = 0
  850. pixelloop: do iObs = 1, NObs
  851. ! only check when flag <> error
  852. if ( iand(flag(iObs), 255) == 0 ) then
  853. if ( (cloudFraction(iObs) < -0.1) .or. (cloudFraction(iObs) > 1.2) ) then
  854. flag(iObs) = PQF_E_CLOUD_ERROR
  855. nCloudFrac = nCloudFrac + 1
  856. end if
  857. if ( useCloudRadianceFractionFromFile ) then
  858. if ( (cloudRadianceFraction(iObs) < -0.1) .or. (cloudRadianceFraction(iObs) > 1.2) ) then
  859. flag(iObs) = PQF_E_CLOUD_ERROR
  860. nCloudRF = nCloudRF + 1
  861. end if
  862. end if
  863. if ( (albcloud(iObs) < 0.01) .or. (albcloud(iObs) > 1.1) ) then
  864. flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  865. nAlbCloud = nAlbCloud + 1
  866. end if
  867. if ( (albclear(iObs) < -0.1) .or. (albclear(iObs) > 1.1) ) then
  868. flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  869. nAlbClear = nAlbClear + 1
  870. end if
  871. if ( (sza(iObs) > 89.0) .or. (sza(iObs) < 0.) ) then
  872. flag(iObs) = PQF_E_SZA_RANGE_ERROR
  873. nSZA = nSZA + 1
  874. end if
  875. if ( (vza(iObs) > 89.0) .or. (vza(iObs) < 0.) ) then
  876. flag(iObs) = PQF_E_VZA_RANGE_ERROR
  877. nVZA = nVZA + 1
  878. end if
  879. if ( abs(aza(iObs)) > 360.0 ) then
  880. flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  881. nAZA = nAZA + 1
  882. end if
  883. if ( (no2slc(iObs) > 1000.0) .or. (no2slc(iObs) < -1.0) ) then
  884. flag(iObs) = PQF_E_GENERIC_RANGE_ERROR
  885. nSLC = nSLC + 1
  886. end if
  887. if ( (longitude(iObs) < -360.001) .and. (longitude(iObs) > 360.001) ) then
  888. flag(iObs) = PQF_E_GEOLOCATION_ERROR
  889. nLon = nLon + 1
  890. end if
  891. if ( (latitude(iObs) < -90.0) .and. (latitude(iObs) > 90.0) ) then
  892. flag(iObs) = PQF_E_GEOLOCATION_ERROR
  893. nLat = nLat + 1
  894. end if
  895. end if
  896. end do pixelloop
  897. if ( nCloudFrac > 0 ) then
  898. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - cloud fraction out of range: number of pixels =', nCloudFrac
  899. WRITE_WARNING( trim(errmessage) )
  900. end if
  901. if ( nCloudRF > 0 ) then
  902. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - cloud radiance fraction out of range: number of pixels =', nCloudRF
  903. WRITE_WARNING( trim(errmessage) )
  904. end if
  905. if ( nAlbCloud > 0 ) then
  906. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - cloud albedo out of range: number of pixels =', nAlbCloud
  907. WRITE_WARNING( trim(errmessage) )
  908. end if
  909. if ( nAlbClear > 0 ) then
  910. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - clear albedo out of range: number of pixels =', nAlbClear
  911. WRITE_WARNING( trim(errmessage) )
  912. end if
  913. if ( nSZA > 0 ) then
  914. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - SZA out of range: number of pixels =', nSZA
  915. WRITE_WARNING( trim(errmessage) )
  916. end if
  917. if ( nVZA > 0 ) then
  918. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - VZA out of range: number of pixels =', nVZA
  919. WRITE_WARNING( trim(errmessage) )
  920. end if
  921. if ( nAZA > 0 ) then
  922. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - Rel azimuth angle out of range: number of pixels =', nAZA
  923. WRITE_WARNING( trim(errmessage) )
  924. end if
  925. if ( nSLC > 0 ) then
  926. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - slant column out of range: number of pixels =', nSLC
  927. WRITE_WARNING( trim(errmessage) )
  928. end if
  929. if ( nLon > 0 ) then
  930. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - longitude out of range: number of pixels =', nLon
  931. WRITE_WARNING( trim(errmessage) )
  932. end if
  933. if ( nLat > 0 ) then
  934. write ( errmessage, '(a,i6)') 'CheckValueRanges: WARNING - latitude out of range: number of pixels =', nLat
  935. WRITE_WARNING( trim(errmessage) )
  936. end if
  937. end subroutine CheckValueRanges
  938. end module MObservationOperator