PM.F90 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #define TRACEBACK write (gol,'("in ",a," (",a,i6,")")') rname, __FILE__, __LINE__ ; call goErr
  2. #define IF_NOTOK_RETURN(action) if (status/=0) then; TRACEBACK; action; return; end if
  3. #define IF_ERROR_RETURN(action) if (status> 0) then; TRACEBACK; action; return; end if
  4. #include "tm5.inc"
  5. !-----------------------------------------------------------------------
  6. !
  7. ! PM module to calculate particulate matter (PM1, PM2.5, PM10, ...)
  8. ! For station output
  9. ! Implemented by Joost Aan de Brugh
  10. !
  11. !-----------------------------------------------------------------------
  12. module PM ! {{{
  13. use GO, only : gol, goErr, goPr
  14. ! You can specifiy which PM instances you want in your station output.
  15. ! For instance: PM2.5 and PM10
  16. ! You will get SO4, BC, POM, SS, DU, NO3, NH4 and water
  17. ! Where NO3 and NH4 come from EQSAM, and are considered to be in the accumulation mode.
  18. implicit none
  19. private
  20. Public :: PM_Init
  21. Public :: PM_Done
  22. Public :: PM_Step
  23. Public :: PM_Write
  24. integer :: nsizelimits
  25. Real, Dimension(:), Allocatable :: sizelimits ! Length: Number of size limits (~2)
  26. Integer, Dimension(:), Allocatable :: stations_count ! Length: Number of stations (~20)
  27. Real, Dimension(:,:,:), Allocatable :: stations_pm ! Length: Size limits, PM tracers, stations (~2,8,~20)
  28. Real, Dimension(:,:,:,:), Allocatable :: stations_pm_tobewritten ! Length: Size limits, PM tracers, stations, one. This is for the HDF, using start=(/0,0,0,io_record) (~2,8,~20,1)
  29. Real, Dimension(:,:), Allocatable :: modfrac ! Length: Number of M7 modes, number of size limits (7,~2)
  30. Integer :: io_record
  31. Integer :: io_nrecords
  32. character(len=*), parameter :: mname = 'PM'
  33. Integer, Parameter :: npmtracers = 9
  34. Real, Parameter :: hr2 = 0.5*sqrt(2.0)
  35. ! }}}
  36. contains
  37. Subroutine PM_Init(pmsizelimits,status) ! {{{
  38. Use user_output_station, only : io_hdf,sds_station_pm,n_stat,n_stat2 ! To put in the PM attributes.
  39. Use mo_aero_m7, only : nmod
  40. use file_hdf, only : WriteAttribute, Init, Done, SetDim
  41. Use Dims, Only : itaue,itaui,ndyn_max
  42. Use Partools, only : myid,root
  43. ! --- in/out --------------------------------
  44. Character (len=200) :: pmsizelimits
  45. Integer, Intent(out) :: status
  46. ! --- var ------------------------------
  47. Integer :: ipos,i,cnt,idx,wlidx
  48. Character(len=200) :: checkstring
  49. Logical :: last
  50. Integer :: j
  51. ! --- const ------------------------------
  52. character(len=*), parameter :: rname = mname//'/PM_Init'
  53. io_nrecords = abs(itaue-itaui)/ndyn_max ! Like in the station file
  54. ipos = 1
  55. last = .False.
  56. cnt = 0
  57. Do While (.Not. last)
  58. i = index(pmsizelimits(ipos:),' ')
  59. If (i == 0) Then
  60. last = .True. ! This last sizelimit has to be counted.
  61. Else
  62. checkstring = pmsizelimits(ipos:ipos+i-1)
  63. if(trim(checkstring) == '') Exit ! If you have trailing spaces, you do not want to count this one.
  64. ipos = ipos + i
  65. End If
  66. cnt = cnt + 1
  67. End Do
  68. ! Okay, we counted the number of sizelimits, not very nice, but I do not know how it can be done nice, like
  69. nsizelimits = cnt
  70. ! Allocate all stuff. The stations_count could have been allocated beforehand, but this looks more logical, having all Allocate-statements in one batch.
  71. Allocate(sizelimits(cnt))
  72. Allocate(stations_count(n_stat+n_stat2))
  73. Allocate(stations_pm(nsizelimits,npmtracers,n_stat+n_stat2))
  74. Allocate(stations_pm_tobewritten(nsizelimits,npmtracers,n_stat+n_stat2,1))
  75. Allocate(modfrac(nmod,nsizelimits))
  76. stations_count = 0
  77. stations_pm = 0.0
  78. ! Fill up the sizelimits array. I will be surprised if it works.
  79. ipos = 1
  80. Do idx=1,cnt
  81. i = index(pmsizelimits(ipos:),' ')
  82. If (i == 0) Then
  83. Read(pmsizelimits(ipos:),*) sizelimits(idx)
  84. Else
  85. Read(pmsizelimits(ipos:ipos+i-1),*) sizelimits(idx)
  86. ipos = ipos + i
  87. End If
  88. End Do
  89. If (myid .eq. root) Then
  90. io_record = 0
  91. call Init(sds_station_pm, io_hdf, 'PM', (/nsizelimits,npmtracers,n_stat+n_stat2,io_nrecords/), 'real(8)', status)
  92. IF_NOTOK_RETURN(status=1)
  93. call SetDim(sds_station_pm, 0, 'nsizelimits', 'Size limit', (/ (j, j=1,nsizelimits) /) , status)
  94. IF_NOTOK_RETURN(status=1)
  95. call SetDim(sds_station_pm, 1, 'npmtracers', 'PM tracer number', (/ (j, j=1,npmtracers) /) , status)
  96. IF_NOTOK_RETURN(status=1)
  97. call SetDim(sds_station_pm, 2, 'n_stations', 'station number', (/ (j, j=1,n_stat+n_stat2) /) , status)
  98. IF_NOTOK_RETURN(status=1)
  99. call SetDim(sds_station_pm, 3, 'n_records', 'time slot #', (/ (j, j=1,io_nrecords) /) , status)
  100. IF_NOTOK_RETURN(status=1)
  101. call WriteAttribute(sds_station_pm, 'Unit', 'ug/m3, with size limits in um', status)
  102. IF_NOTOK_RETURN(status=1)
  103. Call WriteAttribute(sds_station_pm, 'Size limits', sizelimits,status)
  104. IF_NOTOK_RETURN(status=1)
  105. Call WriteAttribute(sds_station_pm, 'Tracers', 'SO4, BC, POM, SS, DU, NO3, NH4, H2O',status)
  106. IF_NOTOK_RETURN(status=1)
  107. End If
  108. ! Now the micrometers are written down, we can convert micrometers diameter to meters radius. One micrometer diameter is 7.e-6 meter radius.
  109. sizelimits = sizelimits * 5.e-7
  110. ! ok
  111. status = 0
  112. End Subroutine PM_Init
  113. ! ================================================================ }}}
  114. subroutine PM_Done(status ) ! {{{
  115. Use user_output_station, only : sds_station_pm
  116. Use Partools, only : myid,root
  117. Use file_hdf, only : Done
  118. Implicit None
  119. ! --- in/out --------------------------------
  120. integer, intent(out) :: status
  121. ! --- var ------------------------------
  122. ! --- const ------------------------------
  123. character(len=*), parameter :: rname = mname//'/PM_Done'
  124. ! --- begin --------------------------------
  125. ! Deallocate a hell of a lot of disastrous things.
  126. Deallocate(sizelimits)
  127. Deallocate(stations_count)
  128. Deallocate(stations_pm)
  129. Deallocate(stations_pm_tobewritten)
  130. Deallocate(modfrac)
  131. If (myid .eq. root) Then
  132. call Done(sds_station_pm, status)
  133. IF_NOTOK_RETURN(status=1)
  134. End If
  135. ! ok
  136. status = 0
  137. end subroutine PM_Done ! }}}
  138. Subroutine PM_Step(region,status) ! {{{
  139. Use user_output_station, only : stat,n_stat,n_stat2
  140. Use tracer_data, only : mass_dat
  141. ! I need to convert (/kg air) to (/m3 air)
  142. Use chem_param, only : iso4nus, isoanus, iso4ais, ibcais, ipomais, iso4acs, ibcacs, ipomacs, issacs, iduacs, iso4cos, ibccos, &
  143. ipomcos, isscos, iducos, ibcaii, ipomaii, iduaci, iducoi, ino3_a, inh4
  144. Use mo_aero_m7, only : nmod, cmedr2mmedr, sigma
  145. Use m7_data, only : h2o_mode, rw_mode
  146. ! To rewrite ug / cell to ug/m3
  147. Use global_data, only : region_dat
  148. Use Meteodata, only : gph_dat
  149. Use Dims, only : im,jm,lm
  150. ! --- in/out --------------------------------
  151. Integer, Intent(In) :: region
  152. Integer, Intent(out) :: status
  153. ! --- var ------------------------------
  154. Integer :: isl,imod,i_stat
  155. Real :: z,rad
  156. ! For converting mass to mass/volume
  157. Real :: volume
  158. ! --- const ------------------------------
  159. Integer, Parameter :: ipm_so4 = 1
  160. Integer, Parameter :: ipm_bc = 2
  161. Integer, Parameter :: ipm_pom = 3
  162. Integer, Parameter :: ipm_ss = 4
  163. Integer, Parameter :: ipm_du = 5
  164. Integer, Parameter :: ipm_no3 = 6
  165. Integer, Parameter :: ipm_nh4 = 7
  166. Integer, Parameter :: ipm_h2o = 8
  167. Integer, Parameter :: ipm_soa = 9
  168. Real, Dimension(nmod) :: lnsigma = log(sigma)
  169. character(len=*), parameter :: rname = mname//'/PM_Step'
  170. ! Fraction belonging to the PM class for each mode, modfrac
  171. Do i_stat = 1,n_stat+n_stat2
  172. If (stat(i_stat)%region /= region) Cycle ! This method is evaluated per region, so stations not in the right region can go cycling.
  173. stations_count(i_stat) = stations_count(i_stat) + 1
  174. Do imod=1,nmod
  175. ! Calculate the median radius of the volume weighted distribution.
  176. rad = rw_mode(region,imod)%d3(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls)*cmedr2mmedr(imod)
  177. If (rad .eq. 0.0) Then
  178. modfrac(imod,:) = 1.0 ! Should not matter, because if the radius is zero, there are no aerosols. But in principle, it would mean that all aerosols are infinitely small, so they would fit in any PM-class.
  179. Else
  180. Do isl=1,nsizelimits
  181. ! Uses the normal distribution formula. We know the Z-value of the size limit.
  182. z = (log(sizelimits(isl)) - log(rad)) / lnsigma(imod)
  183. modfrac(imod,isl) = 0.5 + 0.5 * ERF(z*hr2)
  184. End Do
  185. End If
  186. End Do
  187. ! And the volume. This is the nicest and cleanest line of code of this whole module.
  188. volume = (gph_dat(region)%data(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls+1) - &
  189. gph_dat(region)%data(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls ) ) * region_dat(region)%dxyp(stat(i_stat)%js)
  190. ! First each processor gathers their own contributions, then we allreduce all output and write it down.
  191. ! Without MPI, we strip the If-statement and always resolve the 'tracer' way.
  192. ! Parallel over tracers: we need the rm_t, which is over i,j, l from 1 to lm and t from 1 to ntracetloc. We check whether the tracer is active on this processor.
  193. stations_pm(:,ipm_so4,i_stat) = stations_pm(:,ipm_so4,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iso4nus-offsetn) * modfrac(1,:) / volume
  194. stations_pm(:,ipm_so4,i_stat) = stations_pm(:,ipm_so4,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iso4ais-offsetn) * modfrac(2,:) / volume
  195. stations_pm(:,ipm_so4,i_stat) = stations_pm(:,ipm_so4,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iso4acs-offsetn) * modfrac(3,:) / volume
  196. stations_pm(:,ipm_so4,i_stat) = stations_pm(:,ipm_so4,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iso4cos-offsetn) * modfrac(4,:) / volume
  197. stations_pm(:,ipm_bc,i_stat) = stations_pm(:,ipm_bc,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ibcais-offsetn) * modfrac(2,:) / volume
  198. stations_pm(:,ipm_bc,i_stat) = stations_pm(:,ipm_bc,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ibcacs-offsetn) * modfrac(3,:) / volume
  199. stations_pm(:,ipm_bc,i_stat) = stations_pm(:,ipm_bc,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ibccos-offsetn) * modfrac(4,:) / volume
  200. stations_pm(:,ipm_bc,i_stat) = stations_pm(:,ipm_bc,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ibcaii-offsetn) * modfrac(5,:) / volume
  201. stations_pm(:,ipm_pom,i_stat) = stations_pm(:,ipm_pom,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ipomais-offsetn) * modfrac(2,:) / volume
  202. stations_pm(:,ipm_pom,i_stat) = stations_pm(:,ipm_pom,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ipomacs-offsetn) * modfrac(3,:) / volume
  203. stations_pm(:,ipm_pom,i_stat) = stations_pm(:,ipm_pom,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ipomcos-offsetn) * modfrac(4,:) / volume
  204. stations_pm(:,ipm_pom,i_stat) = stations_pm(:,ipm_pom,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ipomaii-offsetn) * modfrac(5,:) / volume
  205. stations_pm(:,ipm_ss,i_stat) = stations_pm(:,ipm_ss,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,issacs-offsetn) * modfrac(3,:) / volume
  206. stations_pm(:,ipm_ss,i_stat) = stations_pm(:,ipm_ss,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,isscos-offsetn) * modfrac(4,:) / volume
  207. stations_pm(:,ipm_du,i_stat) = stations_pm(:,ipm_du,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iduacs-offsetn) * modfrac(3,:) / volume
  208. stations_pm(:,ipm_du,i_stat) = stations_pm(:,ipm_du,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iducos-offsetn) * modfrac(4,:) / volume
  209. stations_pm(:,ipm_du,i_stat) = stations_pm(:,ipm_du,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iduaci-offsetn) * modfrac(6,:) / volume
  210. stations_pm(:,ipm_du,i_stat) = stations_pm(:,ipm_du,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,iducoi-offsetn) * modfrac(7,:) / volume
  211. stations_pm(:,ipm_no3,i_stat) = stations_pm(:,ipm_no3,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,ino3_a-offsetn) * modfrac(3,:) / volume
  212. stations_pm(:,ipm_nh4,i_stat) = stations_pm(:,ipm_nh4,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,inh4-offsetn) * modfrac(3,:) / volume
  213. stations_pm(:,ipm_h2o,i_stat) = stations_pm(:,ipm_h2o,i_stat) + h2o_mode(region,1)%d3(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls) * modfrac(1,:) / volume
  214. stations_pm(:,ipm_h2o,i_stat) = stations_pm(:,ipm_h2o,i_stat) + h2o_mode(region,2)%d3(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls) * modfrac(2,:) / volume
  215. stations_pm(:,ipm_h2o,i_stat) = stations_pm(:,ipm_h2o,i_stat) + h2o_mode(region,3)%d3(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls) * modfrac(3,:) / volume
  216. stations_pm(:,ipm_h2o,i_stat) = stations_pm(:,ipm_h2o,i_stat) + h2o_mode(region,4)%d3(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls) * modfrac(4,:) / volume
  217. stations_pm(:,ipm_soa,i_stat) = stations_pm(:,ipm_soa,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,isoanus-offsetn) * modfrac(1,:) / volume
  218. stations_pm(:,ipm_soa,i_stat) = stations_pm(:,ipm_soa,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,isoaais-offsetn) * modfrac(2,:) / volume
  219. stations_pm(:,ipm_soa,i_stat) = stations_pm(:,ipm_soa,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,isoaacs-offsetn) * modfrac(3,:) / volume
  220. stations_pm(:,ipm_soa,i_stat) = stations_pm(:,ipm_soa,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,isoacos-offsetn) * modfrac(4,:) / volume
  221. stations_pm(:,ipm_soa,i_stat) = stations_pm(:,ipm_soa,i_stat) + mass_dat(region)%rm_t(stat(i_stat)%is,stat(i_stat)%js,stat(i_stat)%ls,isoaaii-offsetn) * modfrac(5,:) / volume
  222. End Do
  223. ! ok
  224. status = 0
  225. End Subroutine PM_Step
  226. ! ================================================================ }}}
  227. Subroutine PM_Write(status) ! {{{
  228. use file_hdf, only : WriteData
  229. Use user_output_station, only : sds_station_pm,stat,n_stat,n_stat2
  230. Use Partools, only : myid, root, tracer_active,offsetn,offsetl,lmloc
  231. ! --- in/out --------------------------------
  232. Integer, Intent(out) :: status
  233. ! --- var ------------------------------
  234. Integer :: i_stat ! Just because I do not have Rebin. I want to divide a 3d object by a 1d object. It is possible to repeat dividing a 2d object by a scalar.
  235. ! --- const ------------------------------
  236. character(len=*), parameter :: rname = mname//'/PM_Write'
  237. stations_pm(:,:,:) = 1.e9*stations_pm(:,:,:)
  238. Do i_stat=1,n_stat+n_stat2
  239. stations_pm(:,:,i_stat) = stations_pm(:,:,i_stat) / stations_count(i_stat)
  240. End Do
  241. stations_pm_tobewritten(:,:,:,1) = stations_pm(:,:,:)
  242. ! Now the root continues. The rest only resets its values.
  243. If (myid .eq. root) then
  244. Call Writedata(sds_station_pm,stations_pm_tobewritten,status,start=(/0,0,0,io_record/))
  245. IF_NOTOK_RETURN(status=1)
  246. io_record = io_record + 1
  247. End If ! myid .eq. root
  248. ! Reset all output fields.
  249. stations_count = 0.0
  250. stations_pm = 0.0
  251. status = 0
  252. End Subroutine PM_Write
  253. ! ================================================================ }}}
  254. end module PM