From f98c63fe4a5da0ae6ffaee74e7e0dab56263370a Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Thu, 12 Mar 2026 13:15:05 -0400 Subject: [PATCH 1/9] Implement prescribed_volcanic_aerosol (CAM4, CAM5); add to tracer_data_test suite --- .../chemistry/prescribed_volcanic_aerosol.F90 | 362 ++++++++++++++++++ .../prescribed_volcanic_aerosol.meta | 239 ++++++++++++ .../prescribed_volcanic_aerosol_namelist.xml | 203 ++++++++++ test/test_suites/suite_tracer_data_test.xml | 4 + 4 files changed, 808 insertions(+) create mode 100644 schemes/chemistry/prescribed_volcanic_aerosol.F90 create mode 100644 schemes/chemistry/prescribed_volcanic_aerosol.meta create mode 100644 schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.F90 b/schemes/chemistry/prescribed_volcanic_aerosol.F90 new file mode 100644 index 00000000..a1a56c9e --- /dev/null +++ b/schemes/chemistry/prescribed_volcanic_aerosol.F90 @@ -0,0 +1,362 @@ +! 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 == 'NONE' .or. & + len_trim(prescribed_volcaero_file) == 0) then + has_prescribed_volcaero = .false. + if (amIRoot) then + write(iulog,*) subname//': No prescribed volcanic aerosols specified' + 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, & + 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( & + 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' + 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 + call history_add_field(volcaero_const_name, & + 'prescribed volcanic aerosol dry mass mixing ratio', & + 'lev', 'inst', 'kg kg-1') + call history_add_field(volcrad_const_name, & + 'volcanic aerosol geometric-mean radius', & + 'lev', 'inst', 'm') + 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 + + ! dependency for unit string handling + use string_utils, only: to_lower, GLC + + 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] + 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 + + ! Determine unit conversion factor based on units in the input file + select case ( to_lower(trim(tracer_data_fields(1)%units(:GLC(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(volcaero_const_name, constituents(:, :, mmr_idx)) + call history_out_field(volcrad_const_name, constituents(:, :, rad_idx)) + 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 diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.meta b/schemes/chemistry/prescribed_volcanic_aerosol.meta new file mode 100644 index 00000000..d56f7c49 --- /dev/null +++ b/schemes/chemistry/prescribed_volcanic_aerosol.meta @@ -0,0 +1,239 @@ +[ccpp-table-properties] + name = prescribed_volcanic_aerosol + type = scheme + +[ccpp-arg-table] + name = prescribed_volcanic_aerosol_register + type = scheme +[ amIRoot ] + standard_name = flag_for_mpi_root + units = flag + type = logical + dimensions = () + intent = in +[ iulog ] + standard_name = log_output_unit + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_volcaero_file ] + standard_name = filename_for_prescribed_volcanic_aerosol + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ volcaero_constituents ] + # or can this just be the ccpp_constituent_properties? + standard_name = prescribed_volcanic_aerosol_constituents + units = none + type = ccpp_constituent_properties_t + allocatable = True + dimensions = (:) + intent = out +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=* + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out + +[ccpp-arg-table] + name = prescribed_volcanic_aerosol_init + type = scheme +[ amIRoot ] + standard_name = flag_for_mpi_root + units = flag + type = logical + dimensions = () + intent = in +[ iulog ] + standard_name = log_output_unit + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_volcaero_name ] + standard_name = variable_name_of_volcanic_aerosol_in_file_for_prescribed_volcanic_aerosol + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_volcaero_file ] + standard_name = filename_for_prescribed_volcanic_aerosol + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_volcaero_filelist ] + standard_name = filename_of_file_list_for_prescribed_volcanic_aerosol + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_volcaero_datapath ] + standard_name = datapath_for_prescribed_volcanic_aerosol + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_volcaero_type ] + standard_name = time_interpolation_method_for_prescribed_volcanic_aerosol + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_volcaero_cycle_yr ] + standard_name = cycle_year_for_prescribed_volcanic_aerosol + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_volcaero_fixed_ymd ] + standard_name = fixed_date_for_prescribed_volcanic_aerosol + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_volcaero_fixed_tod ] + standard_name = fixed_time_of_day_for_prescribed_volcanic_aerosol + units = s + type = integer + dimensions = () + intent = in +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=* + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out + +[ccpp-arg-table] + name = prescribed_volcanic_aerosol_run + type = scheme +[ ncol ] + standard_name = horizontal_loop_extent + units = count + type = integer + dimensions = () + intent = in +[ pver ] + standard_name = vertical_layer_dimension + units = count + type = integer + dimensions = () + intent = in +[ pcnst ] + standard_name = number_of_ccpp_constituents + units = count + type = integer + dimensions = () + intent = in +[ const_props ] + standard_name = ccpp_constituent_properties + units = none + type = ccpp_constituent_prop_ptr_t + dimensions = (number_of_ccpp_constituents) + intent = in +[ mwdry ] + standard_name = molecular_weight_of_dry_air + units = g mol-1 + type = real | kind = kind_phys + dimensions = () + intent = in +[ boltz ] + standard_name = boltzmann_constant + units = J K-1 + type = real | kind = kind_phys + dimensions = () + intent = in +[ gravit ] + standard_name = standard_gravitational_acceleration + units = m s-2 + type = real | kind = kind_phys + dimensions = () + intent = in +[ T ] + standard_name = air_temperature + units = K + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + intent = in +[ pmiddry ] + standard_name = air_pressure_of_dry_air + units = Pa + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + intent = in +[ pdel ] + standard_name = air_pressure_thickness + units = Pa + type = real | kind = kind_phys + dimensions = (horizontal_dimension, vertical_layer_dimension) + intent = in +[ zm ] + standard_name = geopotential_height_wrt_surface + units = m + type = real | kind = kind_phys + dimensions = (horizontal_dimension, vertical_layer_dimension) + intent = in +[ pmid ] + standard_name = air_pressure + units = Pa + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + intent = in +[ pint ] + standard_name = air_pressure_at_interface + units = Pa + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_interface_dimension) + intent = in +[ phis ] + standard_name = surface_geopotential + units = m2 s-2 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = in +[ zi ] + standard_name = geopotential_height_wrt_surface_at_interface + units = m + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_interface_dimension) + intent = in +[ tropLev ] + standard_name = tropopause_vertical_layer_index + units = index + type = integer + dimensions = (horizontal_loop_extent) + intent = in +[ constituents ] + standard_name = ccpp_constituents + units = none + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents) + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=* + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out diff --git a/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml b/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml new file mode 100644 index 00000000..cc2a8e25 --- /dev/null +++ b/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml @@ -0,0 +1,203 @@ + + + + + + + + + char*256 + chemistry + prescribed_volcaero_nl + datapath_for_prescribed_volcanic_aerosol + none + + Full pathname of the directory that contains the files specified in prescribed_volcaero_filelist. + + + + ${DIN_LOC_ROOT}/atm/cam/volc + ${DIN_LOC_ROOT}/atm/cam/volc + + + + + char*256 + chemistry + prescribed_volcaero_nl + filename_for_prescribed_volcanic_aerosol + none + + Filename of dataset for prescribed volcanic aerosols. + + + + CCSM4_volcanic_1850-2008_prototype1.nc + CCSM4_volcanic_1850-2008_prototype1.nc + + + + + char*256 + chemistry + prescribed_volcaero_nl + filename_of_file_list_for_prescribed_volcanic_aerosol + none + + Filename of file that contains a sequence of filenames for prescribed volcanic aerosols. + The filenames in this file are relative to the directory specified by prescribed_volcaero_datapath. + + + + + + + + char*16 + chemistry + prescribed_volcaero_nl + variable_name_of_volcanic_aerosol_in_file_for_prescribed_volcanic_aerosol + none + + Name of variable containing volcanic aerosol data in the prescribed volcanic aerosol datasets. + Default: 'MMRVOLC' + + + MMRVOLC + + + + + char*32 + chemistry + prescribed_volcaero_nl + time_interpolation_method_for_prescribed_volcanic_aerosol + none + CYCLICAL,SERIAL,INTERP_MISSING_MONTHS,FIXED + + Type of time interpolation for data in prescribed_aero files. + Can be set to 'CYCLICAL', 'SERIAL', 'INTERP_MISSING_MONTHS', or 'FIXED'. + Default: 'SERIAL' + + + SERIAL + + + + + integer + chemistry + prescribed_volcaero_nl + cycle_year_for_prescribed_volcanic_aerosol + 1 + + The cycle year of the prescribed volcanic aerosol data if prescribed_volcaero_type is 'CYCLICAL'. + Format: YYYY + Default: 0 + + + 0 + + + + + integer + chemistry + prescribed_volcaero_nl + fixed_date_for_prescribed_volcanic_aerosol + 1 + + The date at which the prescribed volcanic aerosol data is fixed if prescribed_volcaero_type is 'FIXED'. + Format: YYYYMMDD + Default: 0 + + + 0 + + + + + integer + chemistry + prescribed_volcaero_nl + fixed_time_of_day_for_prescribed_volcanic_aerosol + s + + The time of day (seconds) corresponding to prescribed_volcaero_fixed_ymd at which the prescribed volcanic aerosol data is fixed if prescribed_aero_type is 'FIXED'. + Default: 0 seconds + + + 0 + + + diff --git a/test/test_suites/suite_tracer_data_test.xml b/test/test_suites/suite_tracer_data_test.xml index c7f7319d..ca4df619 100644 --- a/test/test_suites/suite_tracer_data_test.xml +++ b/test/test_suites/suite_tracer_data_test.xml @@ -10,5 +10,9 @@ prescribed_aerosol_deposition_flux + + + tropopause_find + prescribed_volcanic_aerosol From 37afbefa1354c77cebc464ae84e4c65f74650f4d Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Thu, 12 Mar 2026 13:39:41 -0400 Subject: [PATCH 2/9] Update UNSET (since paramgen does not like empty values); GLC --- schemes/chemistry/prescribed_volcanic_aerosol.F90 | 6 +++--- schemes/chemistry/prescribed_volcanic_aerosol.meta | 4 ++-- schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.F90 b/schemes/chemistry/prescribed_volcanic_aerosol.F90 index a1a56c9e..0c14cc2b 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.F90 +++ b/schemes/chemistry/prescribed_volcanic_aerosol.F90 @@ -73,7 +73,7 @@ subroutine prescribed_volcanic_aerosol_register( & errflg = 0 ! Check if prescribed volcanic aerosols are enabled - if (prescribed_volcaero_file == 'NONE' .or. & + if (prescribed_volcaero_file == 'UNSET' .or. & len_trim(prescribed_volcaero_file) == 0) then has_prescribed_volcaero = .false. if (amIRoot) then @@ -250,7 +250,7 @@ subroutine prescribed_volcanic_aerosol_run( & use ccpp_const_utils, only: ccpp_const_get_idx ! dependency for unit string handling - use string_utils, only: to_lower, GLC + use string_utils, only: to_lower, get_last_significant_char integer, intent(in) :: ncol integer, intent(in) :: pver @@ -305,7 +305,7 @@ subroutine prescribed_volcanic_aerosol_run( & if (errflg /= 0) return ! Determine unit conversion factor based on units in the input file - select case ( to_lower(trim(tracer_data_fields(1)%units(:GLC(tracer_data_fields(1)%units)))) ) + 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) diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.meta b/schemes/chemistry/prescribed_volcanic_aerosol.meta index d56f7c49..5545b159 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.meta +++ b/schemes/chemistry/prescribed_volcanic_aerosol.meta @@ -181,13 +181,13 @@ standard_name = air_pressure_thickness units = Pa type = real | kind = kind_phys - dimensions = (horizontal_dimension, vertical_layer_dimension) + dimensions = (horizontal_loop_extent, vertical_layer_dimension) intent = in [ zm ] standard_name = geopotential_height_wrt_surface units = m type = real | kind = kind_phys - dimensions = (horizontal_dimension, vertical_layer_dimension) + dimensions = (horizontal_loop_extent, vertical_layer_dimension) intent = in [ pmid ] standard_name = air_pressure diff --git a/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml b/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml index cc2a8e25..ad6c343d 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml +++ b/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml @@ -85,7 +85,7 @@ Full pathname of the directory that contains the files specified in prescribed_volcaero_filelist. - + UNSET ${DIN_LOC_ROOT}/atm/cam/volc ${DIN_LOC_ROOT}/atm/cam/volc @@ -101,7 +101,7 @@ Filename of dataset for prescribed volcanic aerosols. - + UNSET CCSM4_volcanic_1850-2008_prototype1.nc CCSM4_volcanic_1850-2008_prototype1.nc @@ -118,7 +118,7 @@ The filenames in this file are relative to the directory specified by prescribed_volcaero_datapath. - + UNSET From 7c4a0c798ce96c5cab16c6d3a5dac6914035e625 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 13 Mar 2026 14:30:28 -0400 Subject: [PATCH 3/9] Fix boltz physconst units. --- schemes/chemistry/prescribed_ozone.meta | 2 +- schemes/chemistry/prescribed_volcanic_aerosol.meta | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index 3e0b9d84..6db778a7 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -150,7 +150,7 @@ intent = in [ boltz ] standard_name = boltzmann_constant - units = J K-1 + units = J K-1 molecule-1 type = real | kind = kind_phys dimensions = () intent = in diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.meta b/schemes/chemistry/prescribed_volcanic_aerosol.meta index 5545b159..3123859c 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.meta +++ b/schemes/chemistry/prescribed_volcanic_aerosol.meta @@ -155,7 +155,7 @@ intent = in [ boltz ] standard_name = boltzmann_constant - units = J K-1 + units = J K-1 molecule-1 type = real | kind = kind_phys dimensions = () intent = in From df29ac0d9a07c7b0a6635e6ef15ace3cbe992da5 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Sat, 14 Mar 2026 11:54:27 -0400 Subject: [PATCH 4/9] cherry-picked from trunk 175ea9ebb6e40f5a17f082f1df47b71597121791 to cherry-pick: remove constituent diag in prescribe_volcanic_aerosol --- schemes/chemistry/prescribed_volcanic_aerosol.F90 | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.F90 b/schemes/chemistry/prescribed_volcanic_aerosol.F90 index 0c14cc2b..afabe639 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.F90 +++ b/schemes/chemistry/prescribed_volcanic_aerosol.F90 @@ -203,13 +203,9 @@ subroutine prescribed_volcanic_aerosol_init( & return end if - ! Register history fields - call history_add_field(volcaero_const_name, & - 'prescribed volcanic aerosol dry mass mixing ratio', & - 'lev', 'inst', 'kg kg-1') - call history_add_field(volcrad_const_name, & - 'volcanic aerosol geometric-mean radius', & - 'lev', 'inst', 'm') + ! Register history fields. + ! No longer need history output for the constituents because, well, + ! they are constituents. call history_add_field('VOLC_MASS', & 'volcanic aerosol vertical mass path in layer', & 'lev', 'inst', 'kg m-2') @@ -352,8 +348,6 @@ subroutine prescribed_volcanic_aerosol_run( & columnmass(:ncol) = sum(volcmass(:ncol, :pver), 2) ! History output - call history_out_field(volcaero_const_name, constituents(:, :, mmr_idx)) - call history_out_field(volcrad_const_name, constituents(:, :, rad_idx)) call history_out_field('VOLC_MASS', volcmass(:, :)) call history_out_field('VOLC_MASS_C', columnmass(:)) From 9b8852de84a090a7f9b36924740c7c9c40851f68 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 6 Apr 2026 15:20:01 -0400 Subject: [PATCH 5/9] Add sima_state_diagnostics to test SDF to get volc_mmr constituent diag --- test/test_suites/suite_tracer_data_test.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_suites/suite_tracer_data_test.xml b/test/test_suites/suite_tracer_data_test.xml index ca4df619..39f1ec65 100644 --- a/test/test_suites/suite_tracer_data_test.xml +++ b/test/test_suites/suite_tracer_data_test.xml @@ -14,5 +14,8 @@ tropopause_find prescribed_volcanic_aerosol + + + sima_state_diagnostics From e812a6bf6d55502b0fa339f5311c947d1dab13d1 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 11 May 2026 06:32:31 -0400 Subject: [PATCH 6/9] Address review comments --- schemes/chemistry/prescribed_ozone.meta | 2 +- .../chemistry/prescribed_volcanic_aerosol.F90 | 23 ++++++------------- .../prescribed_volcanic_aerosol.meta | 3 +-- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index 6db778a7..3e0b9d84 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -150,7 +150,7 @@ intent = in [ boltz ] standard_name = boltzmann_constant - units = J K-1 molecule-1 + units = J K-1 type = real | kind = kind_phys dimensions = () intent = in diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.F90 b/schemes/chemistry/prescribed_volcanic_aerosol.F90 index afabe639..a7145267 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.F90 +++ b/schemes/chemistry/prescribed_volcanic_aerosol.F90 @@ -75,7 +75,6 @@ subroutine prescribed_volcanic_aerosol_register( & ! Check if prescribed volcanic aerosols are enabled if (prescribed_volcaero_file == 'UNSET' .or. & len_trim(prescribed_volcaero_file) == 0) then - has_prescribed_volcaero = .false. if (amIRoot) then write(iulog,*) subname//': No prescribed volcanic aerosols specified' end if @@ -204,8 +203,6 @@ subroutine prescribed_volcanic_aerosol_init( & end if ! Register history fields. - ! No longer need history output for the constituents because, well, - ! they are constituents. call history_add_field('VOLC_MASS', & 'volcanic aerosol vertical mass path in layer', & 'lev', 'inst', 'kg m-2') @@ -225,7 +222,6 @@ end subroutine prescribed_volcanic_aerosol_init !! \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, & @@ -239,11 +235,8 @@ subroutine prescribed_volcanic_aerosol_run( & ! 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 + ! framework dependency to get constituent index + use ccpp_scheme_utils, only: ccpp_constituent_index ! dependency for unit string handling use string_utils, only: to_lower, get_last_significant_char @@ -251,10 +244,8 @@ subroutine prescribed_volcanic_aerosol_run( & 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] + real(kind_phys), intent(in) :: boltz ! Boltzmann constant [J K-1] 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) @@ -292,12 +283,12 @@ subroutine prescribed_volcanic_aerosol_run( & 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) + call ccpp_constituent_index(volcaero_const_name, & + mmr_idx, errmsg=errmsg, errcode=errflg) if (errflg /= 0) return - call ccpp_const_get_idx(const_props, volcrad_const_name, & - rad_idx, errmsg, errflg) + call ccpp_constituent_index(volcrad_const_name, & + rad_idx, errmsg=errmsg, errcode=errflg) if (errflg /= 0) return ! Determine unit conversion factor based on units in the input file diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.meta b/schemes/chemistry/prescribed_volcanic_aerosol.meta index 3123859c..290e76ad 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.meta +++ b/schemes/chemistry/prescribed_volcanic_aerosol.meta @@ -24,7 +24,6 @@ dimensions = () intent = in [ volcaero_constituents ] - # or can this just be the ccpp_constituent_properties? standard_name = prescribed_volcanic_aerosol_constituents units = none type = ccpp_constituent_properties_t @@ -155,7 +154,7 @@ intent = in [ boltz ] standard_name = boltzmann_constant - units = J K-1 molecule-1 + units = J K-1 type = real | kind = kind_phys dimensions = () intent = in From 1649409b0927d7effd901ed56cc3b31b11835841 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 11 May 2026 23:11:50 -0400 Subject: [PATCH 7/9] Remove extraneous const_props --- schemes/chemistry/prescribed_volcanic_aerosol.meta | 6 ------ 1 file changed, 6 deletions(-) diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.meta b/schemes/chemistry/prescribed_volcanic_aerosol.meta index 290e76ad..a37655b7 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.meta +++ b/schemes/chemistry/prescribed_volcanic_aerosol.meta @@ -140,12 +140,6 @@ type = integer dimensions = () intent = in -[ const_props ] - standard_name = ccpp_constituent_properties - units = none - type = ccpp_constituent_prop_ptr_t - dimensions = (number_of_ccpp_constituents) - intent = in [ mwdry ] standard_name = molecular_weight_of_dry_air units = g mol-1 From 68a09edd99336f2ed17832059879c6308bf5e74b Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Wed, 13 May 2026 14:18:03 -0400 Subject: [PATCH 8/9] Address review comments --- schemes/chemistry/prescribed_volcanic_aerosol.F90 | 4 +++- schemes/chemistry/prescribed_volcanic_aerosol.meta | 4 ++-- schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.F90 b/schemes/chemistry/prescribed_volcanic_aerosol.F90 index a7145267..20e0a7e3 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.F90 +++ b/schemes/chemistry/prescribed_volcanic_aerosol.F90 @@ -121,7 +121,9 @@ subroutine prescribed_volcanic_aerosol_register( & if (errflg /= 0) return if (amIRoot) then - write(iulog,*) trim(subname)//': Registered 2 prescribed volcanic aerosol constituents' + write(iulog,*) trim(subname)//& + ': Registered 2 prescribed volcanic aerosol constituents: '//& + volcaero_const_name//', '//volcrad_const_name end if end subroutine prescribed_volcanic_aerosol_register diff --git a/schemes/chemistry/prescribed_volcanic_aerosol.meta b/schemes/chemistry/prescribed_volcanic_aerosol.meta index a37655b7..8d2f67a0 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol.meta +++ b/schemes/chemistry/prescribed_volcanic_aerosol.meta @@ -18,7 +18,7 @@ dimensions = () intent = in [ prescribed_volcaero_file ] - standard_name = filename_for_prescribed_volcanic_aerosol + standard_name = filename_of_prescribed_volcanic_aerosol units = none type = character | kind = len=* dimensions = () @@ -65,7 +65,7 @@ dimensions = () intent = in [ prescribed_volcaero_file ] - standard_name = filename_for_prescribed_volcanic_aerosol + standard_name = filename_of_prescribed_volcanic_aerosol units = none type = character | kind = len=* dimensions = () diff --git a/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml b/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml index ad6c343d..506733d1 100644 --- a/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml +++ b/schemes/chemistry/prescribed_volcanic_aerosol_namelist.xml @@ -95,7 +95,7 @@ char*256 chemistry prescribed_volcaero_nl - filename_for_prescribed_volcanic_aerosol + filename_of_prescribed_volcanic_aerosol none Filename of dataset for prescribed volcanic aerosols. From 962296b72c986ddfceba07cc23b53b8ad1e675ca Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Wed, 13 May 2026 14:20:29 -0400 Subject: [PATCH 9/9] Update remaining filename_for_ to filename_of_ to match ESMStandardNames rules --- schemes/chemistry/prescribed_aerosol_deposition_flux.meta | 2 +- .../chemistry/prescribed_aerosol_deposition_flux_namelist.xml | 2 +- schemes/chemistry/prescribed_aerosols.meta | 4 ++-- schemes/chemistry/prescribed_aerosols_namelist.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta index 7f932c58..18bc6d01 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta @@ -24,7 +24,7 @@ dimensions = (aerodep_flx_specifier_dimension) intent = in [ filename ] - standard_name = filename_for_prescribed_aerosol_deposition_fluxes + standard_name = filename_of_prescribed_aerosol_deposition_fluxes units = none type = character | kind = len=* dimensions = () diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml b/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml index 3e421355..03de316b 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml @@ -93,7 +93,7 @@ char*256 chemistry aerodep_flx_nl - filename_for_prescribed_aerosol_deposition_fluxes + filename_of_prescribed_aerosol_deposition_fluxes none Filename of dataset for prescribed aerosol deposition fluxes. diff --git a/schemes/chemistry/prescribed_aerosols.meta b/schemes/chemistry/prescribed_aerosols.meta index eec2debe..6e4ba68d 100644 --- a/schemes/chemistry/prescribed_aerosols.meta +++ b/schemes/chemistry/prescribed_aerosols.meta @@ -25,7 +25,7 @@ dimensions = (prescribed_aero_specifier_dimension) intent = in [ prescribed_aero_file ] - standard_name = filename_for_prescribed_aerosols + standard_name = filename_of_prescribed_aerosols units = none type = character | kind = len=* dimensions = () @@ -115,7 +115,7 @@ dimensions = (prescribed_aero_specifier_dimension) intent = in [ prescribed_aero_file ] - standard_name = filename_for_prescribed_aerosols + standard_name = filename_of_prescribed_aerosols units = none type = character | kind = len=* dimensions = () diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index 878043b6..c248eda1 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -117,7 +117,7 @@ char*256 chemistry prescribed_aero_nl - filename_for_prescribed_aerosols + filename_of_prescribed_aerosols none Filename of dataset for prescribed aerosols.