Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion schemes/chemistry/prescribed_ozone.meta
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
intent = in
[ boltz ]
standard_name = boltzmann_constant
units = J K-1
units = J K-1 molecule-1
Comment thread
nusbaume marked this conversation as resolved.
Outdated
type = real | kind = kind_phys
dimensions = ()
intent = in
Expand Down
356 changes: 356 additions & 0 deletions schemes/chemistry/prescribed_volcanic_aerosol.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
! Manages reading and interpolation of prescribed volcanic aerosol concentrations.
!
! This module uses CCPP constituents (non-advected) to store prescribed volcanic aero
! fields:
! 1) volcanic aerosol mass mixing ratio (from prescribed dataset)
! 2) geometric-mean wet aerosol radius (derived from mass)
!
! Based on original CAM version from: Francis Vitt
module prescribed_volcanic_aerosol
use ccpp_kinds, only: kind_phys

! CAM-SIMA host model dependency to read aerosol data
use tracer_data, only: trfile ! data information and file read state
use tracer_data, only: trfld ! tracer data container

implicit none
private

! public CCPP-compliant subroutines
public :: prescribed_volcanic_aerosol_register
public :: prescribed_volcanic_aerosol_init
public :: prescribed_volcanic_aerosol_run

! fields to store tracer_data state and information
type(trfld), pointer :: tracer_data_fields(:)
type(trfile) :: tracer_data_file

! module state variables
logical :: has_prescribed_volcaero = .false.

! Constituent names
character(len=*), parameter :: volcaero_const_name = 'VOLC_MMR'
character(len=*), parameter :: volcrad_const_name = 'VOLC_RAD_GEOM'

! Molecular weight of volcanic aerosol species (sulfate) [g mol-1]
real(kind_phys), parameter :: molmass_volcaero = 47.9981995_kind_phys

! WACCM-derived empirical coefficient relating mass concentration
! to wet aerosol geometric-mean radius [m (kg m-3)^(-1/3)]
real(kind_phys), parameter :: radius_conversion = 1.9e-4_kind_phys

! TODO: infrastructure for writing (and reading) tracer_data restart information.
! see CAM/prescribed_volcrad_aero::{init,read,write}_prescribed_volcaero_restart
! !!! Restarts will not be bit-for-bit without this !!!
! TODO when SIMA implements restarts.

contains

! Register prescribed volcanic aerosol constituents.
!> \section arg_table_prescribed_volcanic_aerosol_register Argument Table
!! \htmlinclude prescribed_volcanic_aerosol_register.html
subroutine prescribed_volcanic_aerosol_register( &
amIRoot, iulog, &
prescribed_volcaero_file, &
volcaero_constituents, &
errmsg, errflg)

use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t

! Input arguments:
logical, intent(in) :: amIRoot
integer, intent(in) :: iulog
character(len=*), intent(in) :: prescribed_volcaero_file ! input filename from namelist

! Output arguments:
type(ccpp_constituent_properties_t), allocatable, intent(out) :: volcaero_constituents(:)
character(len=*), intent(out) :: errmsg
integer, intent(out) :: errflg

character(len=*), parameter :: subname = 'prescribed_volcanic_aerosol_register'

errmsg = ''
errflg = 0

! Check if prescribed volcanic aerosols are enabled
if (prescribed_volcaero_file == 'UNSET' .or. &
len_trim(prescribed_volcaero_file) == 0) then
has_prescribed_volcaero = .false.
Comment thread
nusbaume marked this conversation as resolved.
Outdated
if (amIRoot) then
write(iulog,*) subname//': No prescribed volcanic aerosols specified'
Comment thread
cacraigucar marked this conversation as resolved.
end if
return
end if

has_prescribed_volcaero = .true.

! Register two constituents: aerosol MMR and geometric-mean radius
allocate(volcaero_constituents(2), stat=errflg, errmsg=errmsg)
if (errflg /= 0) then
errmsg = subname // ": " // trim(errmsg)
return
end if

! (1) Volcanic aerosol dry mass mixing ratio
call volcaero_constituents(1)%instantiate( &
std_name = volcaero_const_name, &
Comment thread
cacraigucar marked this conversation as resolved.
diag_name = volcaero_const_name, &
long_name = 'prescribed volcanic aerosol dry mass mixing ratio', &
units = 'kg kg-1', &
vertical_dim = 'vertical_layer_dimension', &
min_value = 0.0_kind_phys, &
advected = .false., &
water_species = .false., &
mixing_ratio_type = 'dry', &
errcode = errflg, &
errmsg = errmsg)
if (errflg /= 0) return

! (2) Volcanic aerosol geometric-mean radius (derived quantity)
call volcaero_constituents(2)%instantiate( &
Comment thread
nusbaume marked this conversation as resolved.
std_name = volcrad_const_name, &
diag_name = volcrad_const_name, &
long_name = 'prescribed volcanic aerosol geometric-mean radius derived from mass', &
units = 'm', &
vertical_dim = 'vertical_layer_dimension', &
min_value = 0.0_kind_phys, &
advected = .false., &
water_species = .false., &
mixing_ratio_type = 'dry', &
errcode = errflg, &
errmsg = errmsg)
if (errflg /= 0) return

if (amIRoot) then
write(iulog,*) trim(subname)//': Registered 2 prescribed volcanic aerosol constituents'
Comment thread
cacraigucar marked this conversation as resolved.
Outdated
end if

end subroutine prescribed_volcanic_aerosol_register

! Initialize prescribed volcanic aerosol reading via tracer_data.
!> \section arg_table_prescribed_volcanic_aerosol_init Argument Table
!! \htmlinclude prescribed_volcanic_aerosol_init.html
subroutine prescribed_volcanic_aerosol_init( &
amIRoot, iulog, &
prescribed_volcaero_name, &
prescribed_volcaero_file, &
prescribed_volcaero_filelist, &
prescribed_volcaero_datapath, &
prescribed_volcaero_type, &
prescribed_volcaero_cycle_yr, &
prescribed_volcaero_fixed_ymd, &
prescribed_volcaero_fixed_tod, &
errmsg, errflg)

! host model dependency for tracer_data read utility
use tracer_data, only: trcdata_init

! host model dependency for history output
use cam_history, only: history_add_field
use cam_history_support, only: horiz_only

! Input arguments:
logical, intent(in) :: amIRoot
integer, intent(in) :: iulog
character(len=*), intent(in) :: prescribed_volcaero_name ! netCDF field name for volcanic aerosol
character(len=*), intent(in) :: prescribed_volcaero_file ! input filename from namelist
character(len=*), intent(in) :: prescribed_volcaero_filelist ! input filelist from namelist
character(len=*), intent(in) :: prescribed_volcaero_datapath ! input datapath from namelist
character(len=*), intent(in) :: prescribed_volcaero_type ! data type from namelist
integer, intent(in) :: prescribed_volcaero_cycle_yr ! cycle year from namelist [1]
integer, intent(in) :: prescribed_volcaero_fixed_ymd ! fixed year-month-day from namelist (YYYYMMDD) [1]
integer, intent(in) :: prescribed_volcaero_fixed_tod ! fixed time of day from namelist [s]

! Output arguments:
character(len=*), intent(out) :: errmsg
integer, intent(out) :: errflg

! Local variables:
character(len=64) :: specifier(1)

character(len=*), parameter :: subname = 'prescribed_volcanic_aerosol_init'

errmsg = ''
errflg = 0

if (.not. has_prescribed_volcaero) return

if (amIRoot) then
write(iulog,*) trim(subname)//': volcanic aerosol is prescribed in: '// &
trim(prescribed_volcaero_file)
end if

! Build specifier: constituent_name:ncdf_field_name
specifier(1) = trim(volcaero_const_name) // ':' // trim(prescribed_volcaero_name)

! Initialize tracer_data module with file and field information
call trcdata_init( &
specifier = specifier, &
filename = prescribed_volcaero_file, &
filelist = prescribed_volcaero_filelist, &
datapath = prescribed_volcaero_datapath, &
flds = tracer_data_fields, &
file = tracer_data_file, &
data_cycle_yr = prescribed_volcaero_cycle_yr, &
data_fixed_ymd = prescribed_volcaero_fixed_ymd, &
data_fixed_tod = prescribed_volcaero_fixed_tod, &
data_type = prescribed_volcaero_type)

! Verify tracer_data is correctly initialized
if (.not. associated(tracer_data_fields)) then
errflg = 1
errmsg = subname//': tracer_data_fields not associated after trcdata_init'
return
end if

! Register history fields.
! No longer need history output for the constituents because, well,
! they are constituents.
Comment thread
nusbaume marked this conversation as resolved.
Outdated
call history_add_field('VOLC_MASS', &
'volcanic aerosol vertical mass path in layer', &
'lev', 'inst', 'kg m-2')
call history_add_field('VOLC_MASS_C', &
'volcanic aerosol column mass', &
horiz_only, 'inst', 'kg m-2')

if (amIRoot) then
write(iulog,*) trim(subname)//': Initialized volcanic aerosol field from tracer_data'
end if

end subroutine prescribed_volcanic_aerosol_init

! Advance prescribed volcanic aerosol data, convert units, apply tropopause
! masking, and compute geometric-mean radius.
!> \section arg_table_prescribed_volcanic_aerosol_run Argument Table
!! \htmlinclude prescribed_volcanic_aerosol_run.html
subroutine prescribed_volcanic_aerosol_run( &
ncol, pver, pcnst, &
const_props, &
mwdry, boltz, gravit, &
T, pmiddry, pdel, zm, &
pmid, pint, phis, zi, &
tropLev, &
constituents, &
errmsg, errflg)

! host model dependency for tracer_data
use tracer_data, only: advance_trcdata

! host model dependency for history output
use cam_history, only: history_out_field

! framework dependency for const_props
use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t

! dependency to get constituent index
use ccpp_const_utils, only: ccpp_const_get_idx
Comment thread
nusbaume marked this conversation as resolved.
Outdated

! dependency for unit string handling
use string_utils, only: to_lower, get_last_significant_char

integer, intent(in) :: ncol
integer, intent(in) :: pver
integer, intent(in) :: pcnst
type(ccpp_constituent_prop_ptr_t), &
intent(in) :: const_props(:)
real(kind_phys), intent(in) :: mwdry ! molecular weight of dry air [g mol-1]
real(kind_phys), intent(in) :: boltz ! Boltzmann constant [J K-1 molecule-1]
Comment thread
nusbaume marked this conversation as resolved.
Outdated
real(kind_phys), intent(in) :: gravit ! gravitational acceleration [m s-2]
real(kind_phys), intent(in) :: T(:,:) ! air temperature [K] (layer centers)
real(kind_phys), intent(in) :: pmiddry(:,:) ! dry air pressure [Pa] (layer centers)
real(kind_phys), intent(in) :: pdel(:,:) ! air pressure thickness [Pa] (layer centers)
real(kind_phys), intent(in) :: zm(:,:) ! geopotential height wrt surface [m] (layer centers)
real(kind_phys), intent(in) :: pmid(:,:) ! air pressure [Pa] (layer centers)
real(kind_phys), intent(in) :: pint(:,:) ! air pressure at interfaces [Pa]
real(kind_phys), intent(in) :: phis(:) ! surface geopotential [m2 s-2]
real(kind_phys), intent(in) :: zi(:,:) ! geopotential height wrt surface at interfaces [m]
integer, intent(in) :: tropLev(:) ! tropopause vertical layer index [index]

real(kind_phys), intent(inout) :: constituents(:,:,:)

character(len=*), intent(out) :: errmsg
integer, intent(out) :: errflg

! Local variables
integer :: i, k
integer :: mmr_idx, rad_idx
real(kind_phys) :: to_mmr(ncol, pver) ! unit conversion factor to MMR [1]
real(kind_phys) :: mmrvolc ! volcanic aerosol MMR [kg kg-1]
real(kind_phys) :: concvolc ! mass concentration of volcanic aerosol [kg m-3]
real(kind_phys) :: volcmass(ncol, pver) ! volcanic aerosol mass path in layer [kg m-2]
real(kind_phys) :: columnmass(ncol) ! volcanic aerosol column mass [kg m-2]

character(len=*), parameter :: subname = 'prescribed_volcanic_aerosol_run'

errmsg = ''
errflg = 0

if (.not. has_prescribed_volcaero) return

! Advance tracer_data to current time
call advance_trcdata(tracer_data_fields, tracer_data_file, &
pmid, pint, phis, zi)

! Get constituent indices for MMR and radius
call ccpp_const_get_idx(const_props, volcaero_const_name, &
mmr_idx, errmsg, errflg)
if (errflg /= 0) return

call ccpp_const_get_idx(const_props, volcrad_const_name, &
rad_idx, errmsg, errflg)
if (errflg /= 0) return
Comment thread
nusbaume marked this conversation as resolved.
Outdated

! Determine unit conversion factor based on units in the input file
select case ( to_lower(trim(tracer_data_fields(1)%units(:get_last_significant_char(tracer_data_fields(1)%units)))) )
case ("molec/cm3", "/cm3", "molecules/cm3", "cm^-3", "cm**-3")
! Number density [molecules cm-3] -> MMR [kg kg-1]
! mmr = (M * 1e6 * k_B * T) / (M_air * p_dry)
to_mmr(:ncol,:) = (molmass_volcaero * 1.0e6_kind_phys * boltz * T(:ncol,:)) &
/ (mwdry * pmiddry(:ncol,:))
case ('kg/kg', 'mmr', 'kg kg-1')
to_mmr(:ncol,:) = 1.0_kind_phys
case ('mol/mol', 'mole/mole', 'vmr', 'fraction')
to_mmr(:ncol,:) = molmass_volcaero / mwdry
case default
errflg = 1
errmsg = subname//': unrecognized units: '//trim(tracer_data_fields(1)%units)
return
end select

! Convert tracer_data field to MMR and store in constituent array.
! Apply tropopause masking: zero below tropopause.
! Compute geometric-mean radius where MMR > 0 above tropopause.
constituents(:ncol, :pver, rad_idx) = 0.0_kind_phys

do k = 1, pver
do i = 1, ncol
! Apply unit conversion
mmrvolc = to_mmr(i, k) * tracer_data_fields(1)%data(i, k)

! Zero below tropopause
if (k >= tropLev(i)) then
mmrvolc = 0.0_kind_phys
end if

constituents(i, k, mmr_idx) = mmrvolc

! Compute geometric-mean wet aerosol radius from mass concentration
if (mmrvolc > 0.0_kind_phys) then
! concvolc [kg m-3] = mmr [kg kg-1] * pdel [Pa] / (g [m s-2] * zm [m])
concvolc = (mmrvolc * pdel(i, k)) / (gravit * zm(i, k))
constituents(i, k, rad_idx) = radius_conversion * (concvolc ** (1.0_kind_phys / 3.0_kind_phys))
end if
end do
end do

! Compute volcanic aerosol mass path in each layer [kg m-2]
volcmass(:ncol, :pver) = constituents(:ncol, :pver, mmr_idx) * pdel(:ncol, :pver) / gravit
columnmass(:ncol) = sum(volcmass(:ncol, :pver), 2)

! History output
call history_out_field('VOLC_MASS', volcmass(:, :))
call history_out_field('VOLC_MASS_C', columnmass(:))

end subroutine prescribed_volcanic_aerosol_run

end module prescribed_volcanic_aerosol
Loading
Loading