3 ! module_cam_mam_wateruptake.F
4 ! adapted from cam3 modal_aero_wateruptake.F90 by r.c.easter, june 2010
5 !Updated to CESM1.0.3 (CAM5.1.01) by Balwinder.Singh@pnnl.gov
7 !--------------------------------------------------------------
8 ! modal_aero_wateruptake.F90
11 !----------------------------------------------------------------------
14 ! !MODULE: modal_aero_wateruptake --- modal aerosol mode merging (renaming)
17 module modal_aero_wateruptake
20 use shr_kind_mod, only : r8 => shr_kind_r8
22 use cam_logfile, only: iulog
24 use module_cam_support, only: iulog
31 ! !PUBLIC MEMBER FUNCTIONS:
32 public modal_aero_wateruptake_sub
34 ! !PUBLIC DATA MEMBERS:
38 ! !DESCRIPTION: This module implements ...
42 ! RCE 07.04.13: Adapted from MIRAGE2 code
45 !----------------------------------------------------------------------
47 ! list private module data here
49 !----------------------------------------------------------------------
55 !----------------------------------------------------------------------
56 subroutine modal_aero_wateruptake_sub( &
58 iwaterup_flag, loffset, &
59 aero_mmr_flag, h2o_mmr_flag, &
60 deltat, h2ommr, t, pmid, pdel, cldn, &
61 raer, raertend, dotend, qaerwat, &
62 dgncur_a, dgncur_awet, wetdens &
67 ! following are local for now -- wait and see
68 ! maer, naer, wetrad, density )
70 !-----------------------------------------------------------------------
72 ! Purpose: Compute aerosol wet radius
74 ! Method: Kohler theory
78 !-----------------------------------------------------------------------
81 use mo_constants, only: pi
82 use pmgrid, only: plat, plon
83 use ppgrid, only: begchunk, endchunk, pcols, pver
85 use physconst, only: cpair, epsilo, gravit, mwdry, mwh2o, &
86 rair, rga, rhoh2o, rh2o, latvap
88 use constituents, only: pcnst, qmin
90 use wv_saturation, only: aqsat, qsat_water
92 use phys_grid, only: get_lat_all_p, get_lon_all_p
94 use abortutils, only : endrun
95 use cam_history, only : outfld
96 use spmd_utils, only : masterproc
98 use module_cam_support, only: pcnst => pcnst_runtime, &
100 endrun, masterproc, outfld
101 use physconst, only: pi
106 integer, intent(in) :: lchnk ! chunk index
107 integer, intent(in) :: ncol ! number of columns
108 integer, intent(in) :: nstep ! time step
109 integer, intent(in) :: iwaterup_flag
110 ! identifies call location from typhysbc (1,2)
111 integer, intent(in) :: loffset ! offset applied to modal aero "pointers"
113 logical, intent(in) :: aero_mmr_flag ! if .true., aerosol q are kg/kg-air
114 ! if .false., aerosol q are mol/mol-air
115 logical, intent(in) :: h2o_mmr_flag ! if .true., h2ommr is kg/kg-air
116 logical, intent(inout)::dotend(pcnst)
117 ! identifies species for which tendencies are computed
119 real(r8), intent(in) :: deltat ! time step (s)
120 real(r8), intent(in) :: h2ommr(pcols,pver) ! layer specific humidity
121 real(r8), intent(in) :: t(pcols,pver) ! layer temperatures (K)
122 real(r8), intent(in) :: pmid(pcols,pver) ! layer pressure (Pa)
123 real(r8), intent(in) :: pdel(pcols,pver) ! layer pressure thickness (Pa)
124 real(r8), intent(in) :: cldn(pcols,pver) ! layer cloud fraction (0-1)
125 real(r8), intent(in) :: raer(pcols,pver,pcnst)
126 ! aerosol species MRs (kg/kg and #/kg)
127 real(r8), intent(inout)::raertend(pcols,pver,pcnst)
128 ! aerosol MR tendencies (kg/kg/s)
129 ! only defined for aerosol water
130 real(r8), intent(out) :: qaerwat(pcols,pver,ntot_amode)
131 real(r8), intent(in) :: dgncur_a(pcols,pver,ntot_amode)
132 real(r8), intent(out) :: dgncur_awet(pcols,pver,ntot_amode)
133 real(r8), intent(out) :: wetdens(pcols,pver,ntot_amode)
135 ! following are local for now -- wait and see
136 ! real(r8), intent(out) :: maer(pcols,pver,ntot_amode)
137 ! ! aerosol wet mass MR (including water) (kg/kg-air)
138 ! real(r8), intent(out) :: naer(pcols,pver,ntot_amode)
139 ! ! aerosol number MR (bounded!) (#/kg-air)
140 ! real(r8), intent(out) :: wetrad(pcols,pver,ntot_amode)
141 ! ! wet radius of aerosol (m)
142 ! real(r8), intent(out) :: density(pcols,pver,ntot_amode)
143 ! ! wet mean density of aerosol (kg/m3)
149 integer l ! species index
150 integer lmass ! pointer for aerosol mass
151 integer ltype ! pointer for aerosol type
152 ! integer lwater ! pointer for water on aerosol
153 integer lat(pcols), lon(pcols) ! lat,lon indices
155 real(r8) density_water ! density of water (kg/m3)
156 real(r8) drydens(ntot_amode) ! dry particle density (kg/m^3)
157 real(r8) drymass(ntot_amode) ! single-particle-mean dry mass (kg)
158 real(r8) dryrad(pcols,pver,ntot_amode) ! dry volume mean radius of aerosol (m)
159 real(r8) dryvol(ntot_amode) ! single-particle-mean dry volume (m3)
160 real(r8) dryvolmr(ntot_amode) ! volume MR for aerosol mode (m3/kg)
162 real(r8) es(pcols,pver) ! saturation vapor pressure (Pa)
163 real(r8) hygro(ntot_amode) ! volume-weighted mean hygroscopicity (--)
164 real(r8) hystfac(ntot_amode) ! working variable for hysteresis
166 real(r8) qs(pcols,pver) ! saturation specific humidity
167 real(r8) qwater ! aerosol water MR
168 real(r8) rh(pcols,pver) ! relative humidity (0-1)
170 real(r8) v2ncur_a(pcols,pver,ntot_amode)
171 real(r8) wtrvol(ntot_amode) ! single-particle-mean water volume in wet aerosol (m3)
172 real(r8) wetvol(ntot_amode) ! single-particle-mean wet volume (m3)
174 real(r8) :: maer(pcols,pver,ntot_amode)
175 ! aerosol wet mass MR (including water) (kg/kg-air)
176 real(r8) :: naer(pcols,pver,ntot_amode)
177 ! aerosol number MR (bounded!) (#/kg-air)
178 real(r8) :: wetrad(pcols,pver,ntot_amode)
179 ! wet radius of aerosol (m)
181 character(len=3) :: trnum ! used to hold mode number (as characters)
183 !-----------------------------------------------------------------------
188 ! currently, this is one of several routines called from aerosol_wet_intr
189 ! so DO NOT re-initialize dotend=.false. and raertend=0.0
190 ! dotend(:) = .false.
192 ! lwater = lwaterptr_amode(m) - loffset
193 ! dotend(lwater) = .true.
194 ! raertend(1:ncol,:,lwater) = 0.0
200 density_water = rhoh2o ! is (kg/m3)
202 ! compute size-related factors
203 ! when numptr_amode(m)=0, earlier code set dryrad = prescribed volume.
204 ! this is no longer needed because
205 ! dryrad(i,k,m) = (1.0/v2ncur_a(i,k,m)/pi43)**third
208 hystfac(m) = 1.0 / max( 1.0e-5_r8, &
209 (rhdeliques_amode(m)-rhcrystal_amode(m)) )
212 ! main loops over i, k
214 ! call aqsat( t, pmid, es, qs, pcols, ncol, pver, 1, pver )
218 qs(i,k)=qsat_water(t(i,k),pmid(i,k))
220 if ( h2o_mmr_flag ) then
221 rh(i,k) = h2ommr(i,k)/qs(i,k)
223 rh(i,k) = h2ommr(i,k)*mwh2o/(mwdry*qs(i,k))
225 rh(i,k) = max(rh(i,k),0.0_r8)
226 rh(i,k) = min(rh(i,k),0.98_r8)
227 if (cldn(i,k) .lt. 1.0_r8) then
228 rh(i,k) = (rh(i,k) - cldn(i,k)) / (1.0_r8 - cldn(i,k)) ! clear portion
230 rh(i,k) = max(rh(i,k),0.0_r8)
233 ! compute dryvolmr, maer, naer for each mode
239 do l = 1, nspec_amode(m)
240 lmass = lmassptr_amode(l,m) - loffset
241 ltype = lspectype_amode(l,m)
242 if ( aero_mmr_flag ) then
243 duma = raer(i,k,lmass)
245 duma = raer(i,k,lmass)*(specmw_amode(ltype)/mwdry)
247 maer(i,k,m) = maer(i,k,m) + duma
248 dumb = duma/specdens_amode(ltype)
249 dryvolmr(m) = dryvolmr(m) + dumb
250 hygro(m) = hygro(m) + dumb*spechygro(ltype)
252 if (dryvolmr(m) > 1.0e-30_r8) then
253 hygro(m) = hygro(m)/dryvolmr(m)
255 hygro(m) = spechygro( lspectype_amode(1,m) )
258 ! naer = aerosol number (#/kg)
259 ! the new v2ncur_a replaces old coding here
260 v2ncur_a(i,k,m) = 1. / ( (pi/6.)* &
261 (dgncur_a(i,k,m)**3.)*exp(4.5*alnsg_amode(m)**2.) )
262 naer(i,k,m) = dryvolmr(m)*v2ncur_a(i,k,m)
263 enddo !m=1,ntot_amode
265 ! compute mean (1 particle) dry volume and mass for each mode
266 ! old coding is replaced because the new (1/v2ncur_a) is equal to
267 ! the mean particle volume
268 ! also moletomass forces maer >= 1.0e-30, so (maer/dryvolmr)
269 ! should never cause problems (but check for maer < 1.0e-31 anyway)
271 if (maer(i,k,m) .gt. 1.0e-31) then
272 drydens(m) = maer(i,k,m)/dryvolmr(m)
276 dryvol(m) = 1.0/v2ncur_a(i,k,m)
277 drymass(m) = drydens(m)*dryvol(m)
278 dryrad(i,k,m) = (dryvol(m)/pi43)**third
281 ! compute wet radius for each mode
283 call modal_aero_kohler( &
284 dryrad(i,k,m), hygro(m), rh(i,k), &
285 wetrad(i,k,m), 1, 1 )
287 wetrad(i,k,m)=max(wetrad(i,k,m),dryrad(i,k,m))
288 dgncur_awet(i,k,m) = dgncur_a(i,k,m)* &
289 (wetrad(i,k,m)/dryrad(i,k,m))
290 wetvol(m) = pi43*wetrad(i,k,m)*wetrad(i,k,m)*wetrad(i,k,m)
291 wetvol(m) = max(wetvol(m),dryvol(m))
292 wtrvol(m) = wetvol(m) - dryvol(m)
293 wtrvol(m) = max( wtrvol(m), 0.0_r8 )
295 ! apply simple treatment of deliquesence/crystallization hysteresis
296 ! for rhcrystal < rh < rhdeliques, aerosol water is a fraction of
297 ! the "upper curve" value, and the fraction is a linear function of rh
298 if (rh(i,k) < rhcrystal_amode(m)) then
299 wetrad(i,k,m) = dryrad(i,k,m)
300 wetvol(m) = dryvol(m)
302 else if (rh(i,k) < rhdeliques_amode(m)) then
303 wtrvol(m) = wtrvol(m)*hystfac(m) &
304 *(rh(i,k) - rhcrystal_amode(m))
305 wtrvol(m) = max( wtrvol(m), 0.0_r8 )
306 wetvol(m) = dryvol(m) + wtrvol(m)
307 wetrad(i,k,m) = (wetvol(m)/pi43)**third
310 ! compute aer. water tendency = (new_water - old_water)/deltat
311 ! [ either (kg-h2o/kg-air/s) or (mol-h2o/mol-air/s) ]
312 ! lwater = lwaterptr_amode(m) - loffset
313 if ( aero_mmr_flag ) then
316 #if (defined MIMIC_CAM3)
322 qwater = density_water*naer(i,k,m)*wtrvol(m)*duma
324 ! old_water (after modal_aero_calcsize) is
325 ! qwater_old = raer(i,k,lwater) + raertend(i,k,lwater)*deltat
326 ! and water tendency is
327 ! raertend(i,k,lwater) = raertend(i,k,lwater) &
328 ! + (qwater - qwater_old)/deltat
329 ! which is equivalent to
330 ! raertend(i,k,lwater) = (qwater - raer(i,k,lwater))/deltat
331 qaerwat(i,k,m) = qwater
333 ! compute aerosol wet density (kg/m3)
334 if (wetvol(m) > 1.0e-30_r8) then
335 wetdens(i,k,m) = (drymass(m) + density_water*wtrvol(m))/wetvol(m)
337 wetdens(i,k,m) = specdens_amode( lspectype_amode(1,m) )
349 write( trnum, '(i3.3)' ) m
350 call outfld( 'wat_a'//trnum(3:3), qaerwat(:,:,m), pcols, lchnk)
351 ! note - eventually we should change these from "dgnd_a0N" to "dgnd_aN"
352 call outfld( 'dgnd_a'//trnum(2:3), dgncur_a(:,:,m), pcols, lchnk)
353 call outfld( 'dgnw_a'//trnum(2:3), dgncur_awet(:,:,m), pcols, lchnk)
358 end subroutine modal_aero_wateruptake_sub
361 !-----------------------------------------------------------------------
362 subroutine modal_aero_kohler( &
363 rdry_in, hygro, s, rwet_out, im, imx )
365 ! calculates equlibrium radius r of haze droplets as function of
366 ! dry particle mass and relative humidity s using kohler solution
367 ! given in pruppacher and klett (eqn 6-35)
369 ! for multiple aerosol types, assumes an internal mixture of aerosols
374 integer :: im ! number of grid points to be processed
375 integer :: imx ! dimensioned number of grid points
376 real(r8) :: rdry_in(imx) ! aerosol dry radius (m)
377 real(r8) :: hygro(imx) ! aerosol volume-mean hygroscopicity (--)
378 real(r8) :: s(imx) ! relative humidity (1 = saturated)
379 real(r8) :: rwet_out(imx) ! aerosol wet radius (m)
382 integer, parameter :: imax=200
383 integer :: i, n, nsol
386 real(r8) :: p40(imax),p41(imax),p42(imax),p43(imax) ! coefficients of polynomial
387 real(r8) :: p30(imax),p31(imax),p32(imax) ! coefficients of polynomial
390 real(r8) :: r(imx) ! wet radius (microns)
391 real(r8) :: rdry(imax) ! radius of dry particle (microns)
392 real(r8) :: ss ! relative humidity (1 = saturated)
393 real(r8) :: slog(imax) ! log relative humidity
394 real(r8) :: vol(imax) ! total volume of particle (microns**3)
397 complex(r8) :: cx4(4,imax),cx3(3,imax)
399 real(r8), parameter :: eps = 1.e-4
400 real(r8), parameter :: mw = 18.
401 real(r8), parameter :: pi = 3.14159
402 real(r8), parameter :: rhow = 1.
403 real(r8), parameter :: surften = 76.
404 real(r8), parameter :: tair = 273.
405 real(r8), parameter :: third = 1./3.
406 real(r8), parameter :: ugascon = 8.3e7
409 ! effect of organics on surface tension is neglected
410 a=2.e4*mw*surften/(ugascon*tair*rhow)
413 rdry(i) = rdry_in(i)*1.0e6 ! convert (m) to (microns)
414 vol(i) = rdry(i)**3 ! vol is r**3, not volume
423 p41(i)=b/slog(i)-vol(i)
424 p40(i)=a*vol(i)/slog(i)
434 ! if(vol(i).le.1.e-20)then
435 if(vol(i).le.1.e-12)then
440 p=abs(p31(i))/(rdry(i)*rdry(i))
442 ! approximate solution for small particles
443 r(i)=rdry(i)*(1.+p*third/(1.-slog(i)*rdry(i)/a))
445 call makoh_quartic(cx4(1,i),p43(i),p42(i),p41(i),p40(i),1)
446 ! find smallest real(r8) solution
452 if(abs(xi).gt.abs(xr)*eps) cycle
454 if(xr.lt.rdry(i)*(1.-eps)) cycle
461 'ccm kohlerc - no real(r8) solution found (quartic)'
463 call wrf_message(iulog)
465 write(iulog,*)'roots =', (cx4(n,i),n=1,4)
467 call wrf_message(iulog)
469 write(iulog,*)'p0-p3 =', p40(i), p41(i), p42(i), p43(i)
471 call wrf_message(iulog)
473 write(iulog,*)'rh=',s(i)
475 call wrf_message(iulog)
477 write(iulog,*)'setting radius to dry radius=',rdry(i)
479 call wrf_message(iulog)
486 if(s(i).gt.1.-eps)then
487 ! save quartic solution at s=1-eps
490 p=abs(p31(i))/(rdry(i)*rdry(i))
492 r(i)=rdry(i)*(1.+p*third)
494 call makoh_cubic(cx3,p32,p31,p30,im)
495 ! find smallest real(r8) solution
501 if(abs(xi).gt.abs(xr)*eps) cycle
503 if(xr.lt.rdry(i)*(1.-eps)) cycle
510 'ccm kohlerc - no real(r8) solution found (cubic)'
512 call wrf_message(iulog)
514 write(iulog,*)'roots =', (cx3(n,i),n=1,3)
516 call wrf_message(iulog)
518 write(iulog,*)'p0-p2 =', p30(i), p31(i), p32(i)
520 call wrf_message(iulog)
522 write(iulog,*)'rh=',s(i)
524 call wrf_message(iulog)
526 write(iulog,*)'setting radius to dry radius=',rdry(i)
528 call wrf_message(iulog)
535 ! now interpolate between quartic, cubic solutions
536 r(i)=(r4*(1.-s(i))+r3*(s(i)-1.+eps))/eps
541 ! bound and convert from microns to m
543 r(i) = min(r(i),30._r8) ! upper bound based on 1 day lifetime
544 rwet_out(i) = r(i)*1.e-6
548 end subroutine modal_aero_kohler
551 !-----------------------------------------------------------------------
552 subroutine makoh_cubic( cx, p2, p1, p0, im )
554 ! solves x**3 + p2 x**2 + p1 x + p0 = 0
555 ! where p0, p1, p2 are real
557 integer, parameter :: imx=200
559 real(r8) :: p0(imx), p1(imx), p2(imx)
560 complex(r8) :: cx(3,imx)
563 real(r8) :: eps, q(imx), r(imx), sqrt3, third
564 complex(r8) :: ci, cq, crad(imx), cw, cwsq, cy(imx), cz(imx)
573 cwsq=0.5*(-1-ci*sqrt3)
577 ! completely insoluble particle
578 cx(1,i)=(-p0(i))**third
584 crad(i)=r(i)*r(i)+q(i)*q(i)*q(i)
585 crad(i)=sqrt(crad(i))
588 if (abs(cy(i)).gt.eps) cy(i)=cy(i)**third
593 cx(2,i)=-cw*cy(i)-cwsq*cz(i)
594 cx(3,i)=-cwsq*cy(i)-cw*cz(i)
599 end subroutine makoh_cubic
602 !-----------------------------------------------------------------------
603 subroutine makoh_quartic( cx, p3, p2, p1, p0, im )
605 ! solves x**4 + p3 x**3 + p2 x**2 + p1 x + p0 = 0
606 ! where p0, p1, p2, p3 are real
608 integer, parameter :: imx=200
610 real(r8) :: p0(imx), p1(imx), p2(imx), p3(imx)
611 complex(r8) :: cx(4,imx)
614 real(r8) :: third, q(imx), r(imx)
615 complex(r8) :: cb(imx), cb0(imx), cb1(imx), &
616 crad(imx), cy(imx), czero
624 q(i)=-p2(i)*p2(i)/36.+(p3(i)*p1(i)-4*p0(i))/12.
625 r(i)=-(p2(i)/6)**3+p2(i)*(p3(i)*p1(i)-4*p0(i))/48. &
626 +(4*p0(i)*p2(i)-p0(i)*p3(i)*p3(i)-p1(i)*p1(i))/16
628 crad(i)=r(i)*r(i)+q(i)*q(i)*q(i)
629 crad(i)=sqrt(crad(i))
632 if(cb(i).eq.czero)then
634 cx(1,i)=(-p1(i))**third
641 cy(i)=-cb(i)+q(i)/cb(i)+p2(i)/6
643 cb0(i)=sqrt(cy(i)*cy(i)-p0(i))
644 cb1(i)=(p3(i)*cy(i)-p1(i))/(2*cb0(i))
647 crad(i)=cb(i)*cb(i)-4*(cy(i)+cb0(i))
648 crad(i)=sqrt(crad(i))
649 cx(1,i)=(-cb(i)+crad(i))/2.
650 cx(2,i)=(-cb(i)-crad(i))/2.
653 crad(i)=cb(i)*cb(i)-4*(cy(i)-cb0(i))
654 crad(i)=sqrt(crad(i))
655 cx(3,i)=(-cb(i)+crad(i))/2.
656 cx(4,i)=(-cb(i)-crad(i))/2.
661 end subroutine makoh_quartic
663 !----------------------------------------------------------------------
665 end module modal_aero_wateruptake