module modal_aero_wateruptake ! RCE 07.04.13: Adapted from MIRAGE2 code use shr_kind_mod, only: r8 => shr_kind_r8 use physconst, only: pi, rhoh2o use ppgrid, only: pcols, pver use physics_types, only: physics_state use physics_buffer, only: physics_buffer_desc, pbuf_get_index, pbuf_old_tim_idx, pbuf_get_field use wv_saturation, only: qsat_water use rad_constituents, only: rad_cnst_get_info, rad_cnst_get_aer_mmr, rad_cnst_get_aer_props, & rad_cnst_get_mode_props, rad_cnst_get_mode_num use cam_history, only: addfld, add_default, phys_decomp, outfld use cam_logfile, only: iulog use ref_pres, only: top_lev => clim_modal_aero_top_lev use phys_control, only: phys_getopts use cam_abortutils, only: endrun implicit none private save public :: & modal_aero_wateruptake_init, & modal_aero_wateruptake_dr public :: modal_aero_wateruptake_reg real(r8), parameter :: third = 1._r8/3._r8 real(r8), parameter :: pi43 = pi*4.0_r8/3.0_r8 ! Physics buffer indices integer :: cld_idx = 0 integer :: dgnum_idx = 0 integer :: dgnumwet_idx = 0 integer :: sulfeq_idx = 0 integer :: wetdens_ap_idx = 0 integer :: qaerwat_idx = 0 logical, public :: modal_strat_sulfate = .false. ! If .true. then MAM sulfate surface area density used in stratospheric heterogeneous chemistry !=============================================================================== contains !=============================================================================== subroutine modal_aero_wateruptake_reg() use physics_buffer, only: pbuf_add_field, dtype_r8 use rad_constituents, only: rad_cnst_get_info integer :: nmodes call rad_cnst_get_info(0, nmodes=nmodes) call pbuf_add_field('DGNUMWET', 'global', dtype_r8, (/pcols, pver, nmodes/), dgnumwet_idx) call pbuf_add_field('WETDENS_AP', 'physpkg', dtype_r8, (/pcols, pver, nmodes/), wetdens_ap_idx) ! 1st order rate for direct conversion of strat. cloud water to precip (1/s) call pbuf_add_field('QAERWAT', 'physpkg', dtype_r8, (/pcols, pver, nmodes/), qaerwat_idx) if (modal_strat_sulfate) then call pbuf_add_field('MAMH2SO4EQ', 'global', dtype_r8, (/pcols, pver, nmodes/), sulfeq_idx) end if end subroutine modal_aero_wateruptake_reg !=============================================================================== !=============================================================================== subroutine modal_aero_wateruptake_init(pbuf2d) use time_manager, only: is_first_step use physics_buffer,only: pbuf_set_field use infnan, only : nan, assignment(=) type(physics_buffer_desc), pointer :: pbuf2d(:,:) real(r8) :: real_nan integer :: m, nmodes logical :: history_aerosol ! Output the MAM aerosol variables and tendencies character(len=3) :: trnum ! used to hold mode number (as characters) !---------------------------------------------------------------------------- real_nan = nan cld_idx = pbuf_get_index('CLD') dgnum_idx = pbuf_get_index('DGNUM') ! assume for now that will compute wateruptake for climate list modes only call rad_cnst_get_info(0, nmodes=nmodes) do m = 1, nmodes write(trnum, '(i3.3)') m call addfld('dgnd_a'//trnum(2:3), 'm', pver, 'A', & 'dry dgnum, interstitial, mode '//trnum(2:3), phys_decomp) call addfld('dgnw_a'//trnum(2:3), 'm', pver, 'A', & 'wet dgnum, interstitial, mode '//trnum(2:3), phys_decomp) call addfld('wat_a'//trnum(3:3), 'm', pver, 'A', & 'aerosol water, interstitial, mode '//trnum(2:3), phys_decomp) ! determine default variables call phys_getopts(history_aerosol_out = history_aerosol) if (history_aerosol) then call add_default('dgnd_a'//trnum(2:3), 1, ' ') call add_default('dgnw_a'//trnum(2:3), 1, ' ') call add_default('wat_a'//trnum(3:3), 1, ' ') endif end do if (is_first_step()) then ! initialize fields in physics buffer call pbuf_set_field(pbuf2d, dgnumwet_idx, 0.0_r8) if (modal_strat_sulfate) then ! initialize fields in physics buffer to NaN (not a number) ! so model will crash if used before initialization call pbuf_set_field(pbuf2d, sulfeq_idx, real_nan) endif endif end subroutine modal_aero_wateruptake_init !=============================================================================== subroutine modal_aero_wateruptake_dr(state, pbuf, list_idx_in, dgnumdry_m, dgnumwet_m, & qaerwat_m, wetdens_m) !----------------------------------------------------------------------- ! ! CAM specific driver for modal aerosol water uptake code. ! ! *** N.B. *** The calculation has been enabled for diagnostic mode lists ! via optional arguments. If the list_idx arg is present then ! all the optional args must be present. ! !----------------------------------------------------------------------- use time_manager, only: is_first_step use cam_history, only: outfld, fieldname_len use tropopause, only: tropopause_find, TROP_ALG_HYBSTOB, TROP_ALG_CLIMATE ! Arguments type(physics_state), target, intent(in) :: state ! Physics state variables type(physics_buffer_desc), pointer :: pbuf(:) ! physics buffer integer, optional, intent(in) :: list_idx_in real(r8), optional, target, intent(in) :: dgnumdry_m(:,:,:) real(r8), optional, pointer :: dgnumwet_m(:,:,:) real(r8), optional, pointer :: qaerwat_m(:,:,:) real(r8), optional, pointer :: wetdens_m(:,:,:) ! local variables integer :: lchnk ! chunk index integer :: ncol ! number of columns integer :: list_idx ! radiative constituents list index integer :: stat integer :: i, k, l, m integer :: itim_old integer :: nmodes integer :: nspec integer :: tropLev(pcols) character(len=fieldname_len+3) :: fieldname real(r8), pointer :: h2ommr(:,:) ! specific humidity real(r8), pointer :: t(:,:) ! temperatures (K) real(r8), pointer :: pmid(:,:) ! layer pressure (Pa) real(r8), pointer :: raer(:,:) ! aerosol species MRs (kg/kg and #/kg) real(r8), pointer :: cldn(:,:) ! layer cloud fraction (0-1) real(r8), pointer :: dgncur_a(:,:,:) real(r8), pointer :: dgncur_awet(:,:,:) real(r8), pointer :: wetdens(:,:,:) real(r8), pointer :: qaerwat(:,:,:) real(r8), allocatable :: maer(:,:,:) ! aerosol wet mass MR (including water) (kg/kg-air) real(r8), allocatable :: hygro(:,:,:) ! volume-weighted mean hygroscopicity (--) real(r8), allocatable :: naer(:,:,:) ! aerosol number MR (bounded!) (#/kg-air) real(r8), allocatable :: dryvol(:,:,:) ! single-particle-mean dry volume (m3) real(r8), allocatable :: so4dryvol(:,:,:) ! single-particle-mean so4 dry volume (m3) real(r8), allocatable :: drymass(:,:,:) ! single-particle-mean dry mass (kg) real(r8), allocatable :: dryrad(:,:,:) ! dry volume mean radius of aerosol (m) real(r8), allocatable :: wetrad(:,:,:) ! wet radius of aerosol (m) real(r8), allocatable :: wetvol(:,:,:) ! single-particle-mean wet volume (m3) real(r8), allocatable :: wtrvol(:,:,:) ! single-particle-mean water volume in wet aerosol (m3) real(r8), allocatable :: rhcrystal(:) real(r8), allocatable :: rhdeliques(:) real(r8), allocatable :: specdens_1(:) real(r8), pointer :: sulfeq(:,:,:) ! H2SO4 equilibrium mixing ratios over particles (mol/mol) real(r8), allocatable :: wtpct(:,:,:) ! sulfate aerosol composition, weight % H2SO4 real(r8), allocatable :: sulden(:,:,:) ! sulfate aerosol mass density (g/cm3) real(r8) :: dryvolmr(pcols,pver) ! volume MR for aerosol mode (m3/kg) real(r8) :: so4dryvolmr(pcols,pver) ! volume MR for sulfate aerosol in mode (m3/kg) real(r8) :: specdens, so4specdens real(r8) :: spechygro, spechygro_1 real(r8) :: duma, dumb real(r8) :: sigmag real(r8) :: alnsg real(r8) :: v2ncur_a real(r8) :: drydens ! dry particle density (kg/m^3) real(r8) :: rh(pcols,pver) ! relative humidity (0-1) real(r8) :: dmean, qh2so4_equilib, wtpct_mode, sulden_mode real(r8) :: es(pcols) ! saturation vapor pressure real(r8) :: qs(pcols) ! saturation specific humidity character(len=3) :: trnum ! used to hold mode number (as characters) character(len=32) :: spectype !----------------------------------------------------------------------- lchnk = state%lchnk ncol = state%ncol list_idx = 0 if (present(list_idx_in)) then list_idx = list_idx_in ! check that all optional args are present if (.not. present(dgnumdry_m) .or. .not. present(dgnumwet_m) .or. & .not. present(qaerwat_m) .or. .not. present(wetdens_m)) then call endrun('modal_aero_wateruptake_dr called for'// & 'diagnostic list but required args not present') end if end if ! loop over all aerosol modes call rad_cnst_get_info(list_idx, nmodes=nmodes) if (modal_strat_sulfate) then call pbuf_get_field(pbuf, sulfeq_idx, sulfeq ) endif allocate( & maer(pcols,pver,nmodes), & hygro(pcols,pver,nmodes), & naer(pcols,pver,nmodes), & dryvol(pcols,pver,nmodes), & so4dryvol(pcols,pver,nmodes),& drymass(pcols,pver,nmodes), & dryrad(pcols,pver,nmodes), & wetrad(pcols,pver,nmodes), & wetvol(pcols,pver,nmodes), & wtrvol(pcols,pver,nmodes), & wtpct(pcols,pver,nmodes), & sulden(pcols,pver,nmodes), & rhcrystal(nmodes), & rhdeliques(nmodes), & specdens_1(nmodes) ) maer(:,:,:) = 0._r8 hygro(:,:,:) = 0._r8 so4dryvol(:,:,:) = 0._r8 wtpct(:,:,:) = 75._r8 sulden(:,:,:) = 1.923_r8 if (list_idx == 0) then call pbuf_get_field(pbuf, dgnum_idx, dgncur_a ) call pbuf_get_field(pbuf, dgnumwet_idx, dgncur_awet ) if (is_first_step()) then dgncur_awet(:,:,:) = dgncur_a(:,:,:) end if else dgncur_a => dgnumdry_m dgncur_awet => dgnumwet_m end if !----------------------------------------------------------------------- ! get tropopause level !----------------------------------------------------------------------- if (modal_strat_sulfate) then call tropopause_find(state, tropLev, primary=TROP_ALG_HYBSTOB, backup=TROP_ALG_CLIMATE) endif h2ommr => state%q(:,:,1) t => state%t pmid => state%pmid do m = 1, nmodes dryvolmr(:,:) = 0._r8 so4dryvolmr(:,:) = 0._r8 ! get mode properties call rad_cnst_get_mode_props(list_idx, m, sigmag=sigmag, & rhcrystal=rhcrystal(m), rhdeliques=rhdeliques(m)) ! get mode info call rad_cnst_get_info(list_idx, m, nspec=nspec) do l = 1, nspec ! get species interstitial mixing ratio ('a') call rad_cnst_get_aer_mmr(list_idx, m, l, 'a', state, pbuf, raer) call rad_cnst_get_aer_props(list_idx, m, l, density_aer=specdens, & hygro_aer=spechygro, spectype=spectype) if (modal_strat_sulfate .and. (trim(spectype).eq.'sulfate')) then so4specdens=specdens end if if (l == 1) then ! save off these values to be used as defaults specdens_1(m) = specdens spechygro_1 = spechygro end if do k = top_lev, pver do i = 1, ncol duma = raer(i,k) ! kg/kg air maer(i,k,m) = maer(i,k,m) + duma dumb = duma/specdens ! m3/kg air dryvolmr(i,k) = dryvolmr(i,k) + dumb if (modal_strat_sulfate .and. (trim(spectype).eq.'sulfate')) then so4dryvolmr(i,k) = so4dryvolmr(i,k) + dumb end if hygro(i,k,m) = hygro(i,k,m) + dumb*spechygro end do end do end do alnsg = log(sigmag) do k = top_lev, pver do i = 1, ncol if (dryvolmr(i,k) > 1.0e-30_r8) then hygro(i,k,m) = hygro(i,k,m)/dryvolmr(i,k) else hygro(i,k,m) = spechygro_1 end if ! dry aerosol properties v2ncur_a = 1._r8 / ( (pi/6._r8)*(dgncur_a(i,k,m)**3._r8)*exp(4.5_r8*alnsg**2._r8) ) ! naer = aerosol number (#/kg) naer(i,k,m) = dryvolmr(i,k)*v2ncur_a ! compute mean (1 particle) dry volume and mass for each mode ! old coding is replaced because the new (1/v2ncur_a) is equal to ! the mean particle volume ! also moletomass forces maer >= 1.0e-30, so (maer/dryvolmr) ! should never cause problems (but check for maer < 1.0e-31 anyway) if (maer(i,k,m) .gt. 1.0e-31_r8) then drydens = maer(i,k,m)/dryvolmr(i,k) ! kg/m3 aerosol else drydens = 1.0_r8 end if dryvol(i,k,m) = 1.0_r8/v2ncur_a ! m3/particle drymass(i,k,m) = drydens*dryvol(i,k,m) ! kg/particle dryrad(i,k,m) = (dryvol(i,k,m)/pi43)**third ! m end do ! i = 1, ncol end do ! k = top_lev, pver if (modal_strat_sulfate) then do k = top_lev, pver do i = 1, ncol if (so4dryvolmr(i,k) .gt. 1.0e-31_r8) then so4dryvol(i,k,m) = dryvol(i,k,m)*so4dryvolmr(i,k)/dryvolmr(i,k) else so4dryvol(i,k,m) = 0.0_r8 end if dmean = dgncur_awet(i,k,m)*exp(1.5_r8*alnsg**2) call calc_h2so4_equilib_mixrat( t(i,k), pmid(i,k), h2ommr(i,k), dmean, & qh2so4_equilib, wtpct_mode, sulden_mode ) sulfeq(i,k,m) = qh2so4_equilib wtpct(i,k,m) = wtpct_mode sulden(i,k,m) = sulden_mode end do ! i = 1, ncol end do ! k = top_lev, pver fieldname = ' ' write(fieldname,fmt='(a,i1)') 'wtpct_a',m call outfld(fieldname,wtpct(1:ncol,1:pver,m), ncol, lchnk ) fieldname = ' ' write(fieldname,fmt='(a,i1)') 'sulfeq_a',m call outfld(fieldname,sulfeq(1:ncol,1:pver,m), ncol, lchnk ) fieldname = ' ' write(fieldname,fmt='(a,i1)') 'sulden_a',m call outfld(fieldname,sulden(1:ncol,1:pver,m), ncol, lchnk ) end if end do ! m = 1, nmodes ! relative humidity calc itim_old = pbuf_old_tim_idx() call pbuf_get_field(pbuf, cld_idx, cldn, start=(/1,1,itim_old/), kount=(/pcols,pver,1/) ) do k = top_lev, pver call qsat_water(t(:ncol,k), pmid(:ncol,k), es(:ncol), qs(:ncol)) do i = 1, ncol if (qs(i) > h2ommr(i,k)) then rh(i,k) = h2ommr(i,k)/qs(i) else rh(i,k) = 0.98_r8 endif rh(i,k) = max(rh(i,k), 0.0_r8) rh(i,k) = min(rh(i,k), 0.98_r8) if (cldn(i,k) .lt. 1.0_r8) then rh(i,k) = (rh(i,k) - cldn(i,k)) / (1.0_r8 - cldn(i,k)) ! clear portion end if rh(i,k) = max(rh(i,k), 0.0_r8) end do end do call modal_aero_wateruptake_sub( & ncol, nmodes, rhcrystal, rhdeliques, dryrad, & hygro, rh, dryvol, so4dryvol, so4specdens, tropLev, & wetrad, wetvol, wtrvol, sulden, wtpct) if (list_idx == 0) then call pbuf_get_field(pbuf, wetdens_ap_idx, wetdens) call pbuf_get_field(pbuf, qaerwat_idx, qaerwat) else allocate(wetdens(pcols,pver,nmodes), & qaerwat(pcols,pver,nmodes), stat=stat) if (stat > 0) then call endrun('modal_aero_wateruptake_dr: allocation FAILURE') end if end if do m = 1, nmodes do k = top_lev, pver do i = 1, ncol dgncur_awet(i,k,m) = dgncur_a(i,k,m) * (wetrad(i,k,m)/dryrad(i,k,m)) qaerwat(i,k,m) = rhoh2o*naer(i,k,m)*wtrvol(i,k,m) ! compute aerosol wet density (kg/m3) if (wetvol(i,k,m) > 1.0e-30_r8) then wetdens(i,k,m) = (drymass(i,k,m) + rhoh2o*wtrvol(i,k,m))/wetvol(i,k,m) else wetdens(i,k,m) = specdens_1(m) end if end do end do end do ! modes if (list_idx == 0) then do m = 1, nmodes ! output to history write( trnum, '(i3.3)' ) m call outfld( 'wat_a'//trnum(3:3), qaerwat(:,:,m), pcols, lchnk) call outfld( 'dgnd_a'//trnum(2:3), dgncur_a(:,:,m), pcols, lchnk) call outfld( 'dgnw_a'//trnum(2:3), dgncur_awet(:,:,m), pcols, lchnk) end do else ! for diagnostic calcs just return results dgnumwet_m => dgncur_awet qaerwat_m => qaerwat wetdens_m => wetdens end if deallocate( & maer, hygro, naer, dryvol, so4dryvol, drymass, dryrad, & wetrad, wetvol, wtrvol, wtpct, sulden, rhcrystal, rhdeliques, specdens_1 ) end subroutine modal_aero_wateruptake_dr !=============================================================================== subroutine modal_aero_wateruptake_sub( & ncol, nmodes, rhcrystal, rhdeliques, dryrad, & hygro, rh, dryvol, so4dryvol, so4specdens, troplev, & wetrad, wetvol, wtrvol, sulden, wtpct) !----------------------------------------------------------------------- ! ! Purpose: Compute aerosol wet radius ! ! Method: Kohler theory ! ! Author: S. Ghan ! !----------------------------------------------------------------------- ! Arguments integer, intent(in) :: ncol ! number of columns integer, intent(in) :: nmodes integer, intent(in) :: troplev(:) real(r8), intent(in) :: rhcrystal(:) real(r8), intent(in) :: rhdeliques(:) real(r8), intent(in) :: dryrad(:,:,:) ! dry volume mean radius of aerosol (m) real(r8), intent(in) :: hygro(:,:,:) ! volume-weighted mean hygroscopicity (--) real(r8), intent(in) :: rh(:,:) ! relative humidity (0-1) real(r8), intent(in) :: dryvol(:,:,:) ! dry volume of single aerosol (m3) real(r8), intent(in) :: so4dryvol(:,:,:) ! dry volume of sulfate in single aerosol (m3) real(r8), intent(in) :: so4specdens ! mass density sulfate in single aerosol (kg/m3) real(r8), intent(in) :: wtpct(:,:,:) ! sulfate aerosol composition, weight % H2SO4 real(r8), intent(in) :: sulden(:,:,:) ! sulfate aerosol mass density (g/cm3) real(r8), intent(out) :: wetrad(:,:,:) ! wet radius of aerosol (m) real(r8), intent(out) :: wetvol(:,:,:) ! single-particle-mean wet volume (m3) real(r8), intent(out) :: wtrvol(:,:,:) ! single-particle-mean water volume in wet aerosol (m3) ! local variables integer :: i, k, m real(r8) :: hystfac ! working variable for hysteresis !----------------------------------------------------------------------- ! loop over all aerosol modes do m = 1, nmodes hystfac = 1.0_r8 / max(1.0e-5_r8, (rhdeliques(m) - rhcrystal(m))) do k = top_lev, pver do i = 1, ncol if ( modal_strat_sulfate .and. (k Pa sulfequil = sulfequil * 1.01325e5_r8 ! Convert Pa ==> mol/mol sulfequil = sulfequil / pres ! Calculate Kelvin curvature factor for H2SO4 interactively with temperature: ! (g/mol)*(erg/cm2)/(K * g/cm3 * erg/mol/K) = cm akelvin = 2._r8 * wtmol_h2so4 * surf_tens_mode / (t * sulden * RGAS) expon = akelvin / r ! divide by mode radius (cm) expon = max(-100._r8, expon) expon = min(100._r8, expon) akas = exp( expon ) qh2so4_equilib = sulfequil * akas ! reduce H2SO4 equilibrium mixing ratio by Kelvin curvature factor return end subroutine calc_h2so4_equilib_mixrat !---------------------------------------------------------------------- subroutine calc_h2so4_wtpct( temp, pres, qh2o, wtpct ) !! This function calculates the weight % H2SO4 composition of !! sulfate aerosol, using Tabazadeh et. al. (GRL, 1931, 1997). !! Rated for T=185-260K, activity=0.01-1.0 !! !! Argument list input: !! temp = temperature (K) !! pres = atmospheric pressure (Pa) !! qh2o = water specific humidity (kg/kg) !! !! Output: !! wtpct = weight % H2SO4 in H2O/H2SO4 particle (0-100) !! !! @author Mike Mills !! @ version October 2013 use wv_saturation, only: qsat_water implicit none real(r8), intent(in) :: temp ! temperature (K) real(r8), intent(in) :: pres ! pressure (Pa) real(r8), intent(in) :: qh2o ! water vapor specific humidity (kg/kg) real(r8), intent(out) :: wtpct ! sulfate weight % H2SO4 composition ! Local declarations real(r8) :: atab1,btab1,ctab1,dtab1,atab2,btab2,ctab2,dtab2 real(r8) :: contl, conth, contt, conwtp real(r8) :: activ real(r8) :: es ! saturation vapor pressure over water (Pa) (dummy) real(r8) :: qs ! saturation specific humidity over water (kg/kg) ! calculate saturation specific humidity over pure water, qs (kg/kg) call qsat_water(temp, pres, es, qs) ! Activity = water specific humidity (kg/kg) / equilibrium water (kg/kg) activ = qh2o/qs if (activ.lt.0.05_r8) then activ = max(activ,1.e-6_r8) ! restrict minimum activity atab1 = 12.37208932_r8 btab1 = -0.16125516114_r8 ctab1 = -30.490657554_r8 dtab1 = -2.1133114241_r8 atab2 = 13.455394705_r8 btab2 = -0.1921312255_r8 ctab2 = -34.285174607_r8 dtab2 = -1.7620073078_r8 elseif (activ.ge.0.05_r8.and.activ.le.0.85_r8) then atab1 = 11.820654354_r8 btab1 = -0.20786404244_r8 ctab1 = -4.807306373_r8 dtab1 = -5.1727540348_r8 atab2 = 12.891938068_r8 btab2 = -0.23233847708_r8 ctab2 = -6.4261237757_r8 dtab2 = -4.9005471319_r8 elseif (activ.gt.0.85_r8) then activ = min(activ,1._r8) ! restrict maximum activity atab1 = -180.06541028_r8 btab1 = -0.38601102592_r8 ctab1 = -93.317846778_r8 dtab1 = 273.88132245_r8 atab2 = -176.95814097_r8 btab2 = -0.36257048154_r8 ctab2 = -90.469744201_r8 dtab2 = 267.45509988_r8 else write(iulog,*) 'calc_h2so4_wtpct: invalid activity: activ,qh2o,qs,temp,pres=',activ,qh2o,qs,temp,pres call endrun( 'calc_h2so4_wtpct error' ) return endif contl = atab1*(activ**btab1)+ctab1*activ+dtab1 conth = atab2*(activ**btab2)+ctab2*activ+dtab2 contt = contl + (conth-contl) * ((temp -190._r8)/70._r8) conwtp = (contt*98._r8) + 1000._r8 wtpct = (100._r8*contt*98._r8)/conwtp wtpct = min(max(wtpct,25._r8),100._r8) ! restrict between 1 and 100 % return end subroutine calc_h2so4_wtpct !---------------------------------------------------------------------- end module modal_aero_wateruptake