From 6c6ed3249d12ce075a615d661727900953cc322c Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 20 Oct 2025 11:17:42 -0400 Subject: [PATCH 01/19] Draft prescribed_ozone --- schemes/chemistry/prescribed_ozone.F90 | 177 ++++++++++++++++++ schemes/chemistry/prescribed_ozone.meta | 138 ++++++++++++++ .../chemistry/prescribed_ozone_namelist.xml | 118 ++++++++++++ test/test_suites/suite_tracer_data_test.xml | 8 + 4 files changed, 441 insertions(+) create mode 100644 schemes/chemistry/prescribed_ozone.F90 create mode 100644 schemes/chemistry/prescribed_ozone.meta create mode 100644 schemes/chemistry/prescribed_ozone_namelist.xml create mode 100644 test/test_suites/suite_tracer_data_test.xml diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 new file mode 100644 index 00000000..c68b0dfa --- /dev/null +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -0,0 +1,177 @@ +! read and provide prescribed ozone for radiation +! Original Author: Francis Vitt +! CCPP version: Haipeng Lin, October 2025 +module prescribed_ozone + use ccpp_kinds, only: kind_phys + + ! CAM-SIMA host model dependency to read chemistry data. + use tracer_data, only: trfile ! data information and file read state. + use tracer_data, only: trfld ! tracer data container. + + implicit none + private + save + + ! public CCPP-compliant subroutines + public :: prescribed_ozone_init + public :: prescribed_ozone_run + + ! fields to store tracer_data state and information. + type(trfld), pointer :: tracer_data_fields(:) + type(trfile) :: tracer_data_file + + ! parameters for ozone + ! TODO - this should use a centralized chemistry species database + real(kind_phys), parameter :: ozone_mw = 47.9981995_kind_phys + + ! namelist options + logical :: has_prescribed_ozone = .false. + character(len=8), parameter :: ozone_name = 'ozone' ! name of the output field + + character(len=16) :: fld_name = 'ozone' + character(len=256) :: filename = ' ' + character(len=256) :: filelist = ' ' + character(len=256) :: datapath = ' ' + character(len=32) :: data_type = 'SERIAL' + integer :: cycle_yr = 0 + integer :: fixed_ymd = 0 + integer :: fixed_tod = 0 + +contains + +!> \section arg_table_prescribed_ozone_init Argument Table +!! \htmlinclude prescribed_ozone_init.html + subroutine prescribed_ozone_init( & + amIRoot, iulog, & + fld_name_nl, filename_nl, filelist_nl, datapath_nl, & + data_type_nl, & + cycle_yr_nl, fixed_ymd_nl, fixed_tod_nl, & + errmsg, errflg) + + use cam_history, only: history_add_field + use tracer_data, only: trcdata_init + + logical, intent(in) :: amIRoot ! MPI root flag + integer, intent(in) :: iulog ! log output unit + character(len=*), intent(in) :: fld_name_nl ! field name from namelist + character(len=*), intent(in) :: filename_nl ! input filename from namelist + character(len=*), intent(in) :: filelist_nl ! input filelist from namelist + character(len=*), intent(in) :: datapath_nl ! input datapath from namelist + character(len=*), intent(in) :: data_type_nl ! data type from namelist + integer, intent(in) :: cycle_yr_nl ! cycle year from namelist + integer, intent(in) :: fixed_ymd_nl ! fixed year-month-day from namelist (YYYYMMDD) [1] + integer, intent(in) :: fixed_tod_nl ! fixed time of day from namelist [s] + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + character(len=32) :: tracer_data_specifier(1) + + errmsg = '' + errflg = 0 + + fld_name = fld_name_nl + filename = filename_nl + filelist = filelist_nl + datapath = datapath_nl + data_type = data_type_nl + cycle_yr = cycle_yr_nl + fixed_ymd = fixed_ymd_nl + fixed_tod = fixed_tod_nl + + ! check if user has specified an input dataset + if(filename /= 'UNSET' .and. len_trim(filename) > 0) then + has_prescribed_ozone = .true. + + if(amIRoot) then + write(iulog,*) 'prescribed_ozone_init: ozone is prescribed in: ' // trim(filename) + endif + else + return + endif + + ! initialize dataset in tracer_data module. + ! construct field specifier - one field + ! format is (internal field name):(netCDF name) + ! the latter is namelist-configurable together with the file source. + tracer_data_specifier(1) = trim(ozone_name)//':'//trim(fld_name) + + call trcdata_init( & + specifier = tracer_data_specifier(:), & + filename = filename, & + filelist = filelist, & + datapath = datapath, & + fields = tracer_data_fields(:), & + file = tracer_data_file, & + rmv_file = .false., & + cycle_yr = cycle_yr, & + data_fixed_ymd = fixed_ymd, & + data_fixed_tod = fixed_tod, & + data_type = data_type) + + ! add history field for diagnostic purposes + call history_add_field('ozone', 'prescribed_ozone', 'lev', 'inst', 'mol mol-1') + + end subroutine prescribed_ozone_init + +!> \section arg_table_prescribed_ozone_run Argument Table +!! \htmlinclude prescribed_ozone_run.html + subroutine prescribed_ozone_run( & + ncol, pver, & + mwdry, boltz, & + t, pmiddry, & + prescribed_ozone, & + errmsg, errflg) + + use tracer_data, only: advance_trcdata + use cam_history, only: history_out_field + + integer, intent(in) :: ncol + integer, intent(in) :: pver + 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] + real(kind_phys), intent(in) :: t(:,:) ! temperature [K] + real(kind_phys), intent(in) :: pmiddry(:,:) ! dry air pressure [Pa] + real(kind_phys), intent(out) :: prescribed_ozone(:,:) ! prescribed ozone mass mixing ratio [kg kg-1 dry] + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! conversion factor to mass mixing ratio (kg kg-1 dry) + real(kind_phys) :: to_mmr(ncol, pver) + + ! units from file + character(len=32) :: units_str + + errmsg = '' + errflg = 0 + + if(.not. has_prescribed_ozone) then + return + endif + + ! advance data in tracer_data to current time. + call advance_trcdata(tracer_data_fields, tracer_data_file) + units_str = trim(tracer_data_fields(1)%units) + + ! copy field from tracer_data container. + prescribed_ozone(:ncol,:pver) = tracer_data_fields(1)%data(:ncol, :pver) + + ! unit conversions needed for this field to be in kg kg-1 dry? + select case(units_str) + case('kg/kg', 'mmr') + to_mmr = 1._kind_phys + case('mol/mol', 'mole/mole', 'vmr', 'fraction') + to_mmr = ozone_mw/mwdry + case('molec/cm3', '/cm3', 'molecules/cm3', 'cm^-3', 'cm**-3') + to_mmr(:ncol,:pver) = (ozone_mw*1e6_kind_phys*boltz*t(:ncol,:pver))/& + (mwdry*pmiddry(:ncol,:pver)) + case default + errflg = 1 + errmsg = 'prescribed_ozone_run: unit' // units_str //' are not recognized' + end select + + ! convert to mol mol-1 (dry) only for diagnostic output + call history_out_field('ozone', prescribed_ozone(:ncol,:pver)*(mwdry/ozone_mw)) + + end subroutine prescribed_ozone_run + +end module prescribed_ozone diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta new file mode 100644 index 00000000..0f104b5c --- /dev/null +++ b/schemes/chemistry/prescribed_ozone.meta @@ -0,0 +1,138 @@ +[ccpp-table-properties] + name = prescribed_ozone + type = scheme + dependencies = ../../../../utils/cam_history.F90,../../../../utils/tracer_data.F90 + +[ccpp-arg-table] + name = prescribed_ozone_init + type = scheme +[ amIRoot ] + standard_name = flag_for_mpi_root + units = flag + type = logical + dimensions = () + intent = in +[ iulog ] + standard_name = log_output_unit + units = index + type = integer + dimensions = () + intent = in +[ fld_name_nl ] + standard_name = variable_name_of_ozone_in_prescribed_forcing_file + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ filename_nl ] + standard_name = filename_of_prescribed_ozone_forcing + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ filelist_nl ] + standard_name = filename_of_prescribed_ozone_forcing_file_list + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ datapath_nl ] + standard_name = datapath_for_prescribed_ozone_forcing + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ data_type_nl ] + standard_name = time_interpolation_method_for_prescribed_ozone_forcing + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ cycle_yr_nl ] + standard_name = cycle_year_for_prescribed_ozone_forcing + units = count + type = integer + dimensions = () + intent = in +[ fixed_ymd_nl ] + standard_name = fixed_date_for_prescribed_ozone_forcing + units = count + type = integer + dimensions = () + intent = in +[ fixed_tod_nl ] + standard_name = fixed_time_of_day_for_prescribed_ozone_forcing + units = s + type = integer + dimensions = () + intent = in +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out + +[ccpp-arg-table] + name = prescribed_ozone_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 +[ 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 +[ 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 +[ prescribed_ozone ] + standard_name = prescribed_ozone_tbd + units = kg kg-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + intent = out +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out diff --git a/schemes/chemistry/prescribed_ozone_namelist.xml b/schemes/chemistry/prescribed_ozone_namelist.xml new file mode 100644 index 00000000..d0cff2e9 --- /dev/null +++ b/schemes/chemistry/prescribed_ozone_namelist.xml @@ -0,0 +1,118 @@ + + + + + + + char*256 + chemistry + prescribed_ozone_nl + datapath_for_prescribed_ozone_forcing + none + + Full pathname of the directory that contains the files specified in prescribed_ozone_filelist. + + + ${DIN_LOC_ROOT}/atm/cam/ozone + + + + + char*256 + chemistry + prescribed_ozone_nl + filename_of_prescribed_ozone_forcing + none + + Filename of dataset for prescribed ozone. + + + ozone_1.9x2.5_L26_2000clim_c091112.nc + + + + + char*256 + chemistry + prescribed_ozone_nl + filename_of_prescribed_ozone_forcing_file_list + none + + Filename of file that contains a sequence of filenames for prescribed ozone. The filenames in this file are relative to the directory specified by prescribed_ozone_datapath. + + + + + + + + char*16 + chemistry + prescribed_ozone_nl + variable_name_of_ozone_in_prescribed_forcing_file + none + + Name of variable containing ozone data in the prescribed ozone datasets. Default: 'O3' + + + O3 + + + + + char*32 + chemistry + prescribed_ozone_nl + time_interpolation_method_for_prescribed_ozone_forcing + none + CYCLICAL,SERIAL,INTERP_MISSING_MONTHS,FIXED + + Type of time interpolation for data in prescribed_ozone files. Can be set to 'CYCLICAL', 'SERIAL', 'INTERP_MISSING_MONTHS', or 'FIXED'. Default: 'CYCLICAL' + + + CYCLICAL + + + + + integer + chemistry + prescribed_ozone_nl + cycle_year_for_prescribed_ozone_forcing + count + + The cycle year of the prescribed ozone data if prescribed_ozone_type is 'CYCLICAL'. Format: YYYY Default: 2000 + + + 2000 + + + + + integer + chemistry + prescribed_ozone_nl + fixed_date_for_prescribed_ozone_forcing + count + + The date at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. Format: YYYYMMDD Default: 0 + + + 0 + + + + + integer + chemistry + prescribed_ozone_nl + fixed_time_of_day_for_prescribed_ozone_forcing + s + + The time of day (seconds) corresponding to prescribed_ozone_fixed_ymd at which the prescribed ozone data is fixed if prescribed_ozone_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 new file mode 100644 index 00000000..63ae2903 --- /dev/null +++ b/test/test_suites/suite_tracer_data_test.xml @@ -0,0 +1,8 @@ + + + + + + prescribed_ozone + + From 3cf744bdd10bbb30da0572f88ff621c10aca9f90 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Thu, 23 Oct 2025 16:26:00 -0400 Subject: [PATCH 02/19] Draft prescribed_aerosols code --- schemes/chemistry/prescribed_aerosols.F90 | 633 ++++++++++++++++++ schemes/chemistry/prescribed_ozone.F90 | 5 +- schemes/chemistry/prescribed_ozone.meta | 6 +- .../chemistry/prescribed_ozone_namelist.xml | 2 +- test/test_suites/suite_tracer_data_test.xml | 3 + 5 files changed, 642 insertions(+), 7 deletions(-) create mode 100644 schemes/chemistry/prescribed_aerosols.F90 diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 new file mode 100644 index 00000000..c4472979 --- /dev/null +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -0,0 +1,633 @@ +! Manages reading and interpolation of prescribed aerosol concentrations. +! +! This module uses CCPP constituents (non-advected) to store prescribed aerosol fields +! for access by radiation and other schemes. +! +! The prescribed_aero_specifier namelist variable specifies a list of +! variable names of the concentration fields in the netCDF dataset (ncdf_fld_name) +! and the corresponding constituent names, both of which are arbitrary: +! +! prescribed_aero_specifier = 'constituent_name1:ncdf_fld_name1','constituent_name2:ncdf_fld_name2', ... +! +! If there is no ":" then the specified name is used as both the +! constituent_name and ncdf_fld_name +! +! Original Author: Francis Vitt +! CCPP version: Haipeng Lin, October 2025 +module prescribed_aerosols + + 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 + save + + ! public CCPP-compliant subroutines + public :: prescribed_aerosols_register + public :: prescribed_aerosols_init + public :: prescribed_aerosols_run + + ! maximum of specified aerosol fields + ! if need to be expanded, need to change namelist definition as well. + integer, parameter :: N_AERO_MAX = 50 + + ! fields to store tracer_data state and information + type(trfld), pointer :: tracer_data_fields(:) + type(trfile) :: tracer_data_file + + ! local derived type to store constituent name to + ! netCDF field name and tracer_data read index mapping: + type :: aero_constituent_map + character(len=64) :: constituent_name ! CCPP constituent standard name + + ! if modal aerosols and is an interstitial (_a) species, then the species + ! is diagnosed from log-mean (_logm) and log-variance (_logv) with a randomized + ! log-normal distribution; + ! in this case, field_index is used for log-mean, and a separate field for variance + ! is stored here. + logical :: is_modal_aero_interstitial + character(len=64) :: ncdf_field_name_logv ! netCDF variable name in input file (log-variance) + integer :: field_index_logv ! index into tracer_data_fields array (log-variance) + + ! all other cases, including modal log-mean: + character(len=64) :: ncdf_field_name ! netCDF variable name in input file + integer :: field_index ! index into tracer_data_fields array + end type aero_constituent_map + + type(aero_constituent_map), allocatable :: aero_map_list(:) + + ! module state variables + logical :: has_prescribed_aerosols = .false. + logical :: clim_modal_aero = .false. + integer :: aero_cnt ! # of aerosol constituents + integer :: aero_cnt_c ! # of cloud-borne species (for modal aerosols only) + + ! Normal random number which persists from one timestep to the next + ! (used for modal aerosol sampling) + real(kind_phys) :: randn_persists = 0.0_kind_phys + + ! TODO: infrastructure for writing (and reading) randn_persists to persist during restart runs. + ! see CAM/prescribed_aero::{read,write}_prescribed_aero_restart + ! !!! Restarts will not be bit-for-bit without this !!! + ! TODO when SIMA implements restarts. + +contains + + ! Register prescribed aerosols in constituents object. +!> \section arg_table_prescribed_aerosols_register Argument Table +!! \htmlinclude prescribed_aerosols_register.html + subroutine prescribed_aerosols_register( & + amIRoot, iulog, & + prescribed_aero_specifier, & + prescribed_aero_file, & + prescribed_aero_filelist, & + prescribed_aero_datapath, & + prescribed_aero_type, & + prescribed_aero_rmfile, & + prescribed_aero_cycle_yr, & + prescribed_aero_fixed_ymd, & + prescribed_aero_fixed_tod, & + clim_modal_aero_in, & + aerosol_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_aero_specifier(:) ! aerosol specifiers from namelist + character(len=*), intent(in) :: prescribed_aero_file ! input filename from namelist + character(len=*), intent(in) :: prescribed_aero_filelist ! input filelist from namelist + character(len=*), intent(in) :: prescribed_aero_datapath ! input datapath from namelist + character(len=*), intent(in) :: prescribed_aero_type ! data type from namelist + integer, intent(in) :: prescribed_aero_cycle_yr ! cycle year from namelist [1] + integer, intent(in) :: prescribed_aero_fixed_ymd ! fixed year-month-day from namelist (YYYYMMDD) [1] + integer, intent(in) :: prescribed_aero_fixed_tod ! fixed time of day from namelist [s] + logical, intent(in) :: clim_modal_aero_in ! flag for modal aerosol simulation [flag] + + ! Output arguments: + type(ccpp_constituent_properties_t), allocatable, intent(out) :: aerosol_constituents(:) ! prescribed aero CCPP constituents + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Local variables: + character(len=256) :: tmpstr + character(len=64) :: constituent_name + character(len=64) :: ncdf_fld_name + character(len=64) :: unit_name + integer :: strlen + integer :: idx, idx2 + integer :: aero_idx + integer :: i, j + logical :: is_modal_aero_interstitial + logical :: skip_spec + + character(len=*), parameter :: subname = 'prescribed_aerosols_register' + + errmsg = '' + errflg = 0 + + ! Store module-level settings + ! TODO: this matches the CAM implementation but maybe I want this to distinguish + ! between none|bulk|modal|sectional. To revisit when the aerosol abstract interface + ! is implemented in CAM-SIMA. (hplin, 10/22/25) + clim_modal_aero = clim_modal_aero_in + + ! Check if prescribed aerosols are enabled + if (prescribed_aero_file == 'UNSET' .or. & + len_trim(prescribed_aero_file) == 0) then + has_prescribed_aerosols = .false. + if (amIRoot) then + write(iulog,*) subname//': No prescribed aerosols specified' + end if + return + end if + + ! Parse the aerosol format specifier from namelist into mapping ddt. + ! We need two scans. First to determine the count, the second to populate + ! the information into the ddt. + aero_cnt = 0 + aero_cnt_c = 0 ! cloud borne species count + cnt_loop: do i = 1, N_AERO_MAX + if(len_trim(prescribed_aero_specifier(i)) == 0) exit cnt_loop + + skip_spec = .false. + if(clim_modal_aero) then + ! For modal aerosols, interstitial species (*_a) are diagnosed from + ! their *_logm and *_logv counterparts (e.g. soa_a1 is diagnosed from + ! soa_a1_logm and soa_a1_logv). Therefore, only *_logm and *_logv and cloud + ! borne (*_c) species are specified in the build-namelist. + + ! In the following cnt_loop, we will count the cloud borne species and *_logm species + ! (in lieu of *_a species). We will skip *_logv species. + ! This will ensure that aero_cnt variable is the sum of cloud borne and + ! interstitial species (which we will manually add in the names to the ddt later). + ! We are also counting cloud borne (*_c) species which will help + ! adding the same number of interstitial species to the ddt. + ! + ! For modal aerosols, skip counting species ending with *_logv + if(index(prescribed_aero_specifier(i),'_c') >= 1) aero_cnt_c = aero_cnt_c + 1 + if(index(prescribed_aero_specifier(i),'_logv') >= 1) skip_spec = .true. + endif + if(.not. skip_spec) aero_cnt = aero_cnt + 1 + end do cnt_loop + + if(aero_cnt == 0) then + has_prescribed_aerosols = .false. + return + endif + + has_prescribed_aerosols = .true. + + ! Allocate mapping list of ddt + allocate(aero_map_list(aero_cnt), stat=errflg, errmsg=errmsg) + if(errflg /= 0) then + return + end if + + ! Now populate the information into the ddt. + ! + ! In CAM modal aerosols, the interstitial species (_a) are added at the end of the cloud borne (_c) species. + ! TODO hplin 10/22/25 We might not need this? Fields were identified by their pbuf name anyway + + aero_idx = 0 ! pointer for current aero_map_list index. + reg_loop: do i = 1, N_AERO_MAX + ! Parse specifier + tmpstr = trim(adjustl(prescribed_aero_specifier(i))) + idx = index(tmpstr, ':') + if(idx > 0) then + ! format as constituent_name:ncdf_name + constituent_name = tmpstr(:idx-1) + ncdf_fld_name = tmpstr(idx+1:) + else + ! format as single_name_for_both + constituent_name = tmpstr + ncdf_fld_name = tmpstr + end if + + is_modal_aero_interstitial = .false. + + ! For modal aerosols only, + ! skip the _logv species; remove the _logm suffix to directly get the interstitial (_a) name + ! e.g. in loop: num_c1, num_a1_logm, num_a1_logv, ... --> num_c1, num_a1, (skip), ... + if(clim_modal_aero) then + if(index(constituent_name,'_logv') >= 1) exit reg_loop + idx = index(constituent_name,'_logm') + if(idx > 0) then + ! use this as the proxy for interstitial species and initialize it here + is_modal_aero_interstitial = .true. + aero_map_list(aero_idx)%is_modal_aero_interstitial = .true. + + aero_map_list(aero_idx)%constituent_name = constituent_name(:idx-1) ! removed _logm + aero_map_list(aero_idx)%ncdf_field_name = ncdf_fld_name ! log-mean specifier name + + idx2 = index(ncdf_fld_name,'_logm') + if(idx2 == 0) then + ! the netcdf name does not have logm; error out for now + ! if this becomes a problem, we can rescan prescribed_aero_specifier for the actual logv entry. + errflg = 1 + errmsg = subname//': cannot construct ncdf_field_name for interstitial species based on '//ncdf_fld_name + endif + ! for now construct log-variance specifier name by changing _logm with _logv + aero_map_list(aero_idx)%ncdf_field_name_logv = ncdf_fld_name(:idx2) // '_logv' + + ! The tracer data specifier index will be rescanned once tracer_data_fields is initialized + ! at the init phase. + aero_map_list(aero_idx)%field_index_logv = -1 + end if ! logm + end if ! clim_modal_aero + + if(.not. is_modal_aero_interstitial) then + ! Store mapping for all other cases except modal aerosol interstitial species. + aero_map_list(aero_idx)%is_modal_aero_interstitial = .false. + + aero_map_list(aero_idx)%constituent_name = constituent_name + aero_map_list(aero_idx)%ncdf_field_name = ncdf_fld_name + + ! The tracer data specifier index will be rescanned once tracer_data_fields is initialized + ! at the init phase. + aero_map_list(aero_idx)%field_index = -1 + end if + + ! We added a new aero_map_list entry, so advance the pointer. + aero_idx = aero_idx + 1 + end do + + ! Sanity check + if(aero_idx /= aero_cnt) then + errflg = 1 + write(errmsg,*) subname//': consistency check 1 failure; at the end of ddt allocation, aero_idx is not aero_cnt', aero_idx, aero_cnt + return + end if + + ! Allocate CCPP dynamic constituents object for prescribed aerosols. + allocate(aerosol_constituents(aero_cnt), stat=errflg, errmsg=errmsg) + if (errflg /= 0) return + + ! Now register constituents in the CCPP constituent properties object. + do i = 1, aero_cnt + ! check units. at this point, we do not know the units from file + ! because tracer_data has not read any data yet. + ! number concentrations are units of 1 kg-1; all others are kg kg-1 + if(index(aero_map_list(aero_idx)%constituent_name, 'num_') == 1) then + unit_name = '1 kg-1' + else + unit_name = 'kg kg-1' + end if + + call aerosol_constituents(i)%instantiate( & + std_name = trim(aero_map_list(aero_idx)%constituent_name), & + long_name = 'prescribed aerosol '//trim(aero_map_list(aero_idx)%constituent_name), & + units = unit_name, & + vertical_dim = 'vertical_layer_dimension', & + min_value = 0.0_kind_phys, & + advected = .false., & + water_species = .false., & + mixing_ratio_type = 'dry', & + errflg = errflg, & + errmsg = errmsg) + if(errflg /= 0) return + + ! TODO: add history field here. + ! name is ncdf_field_name // '_D' + end do + + if (amIRoot) then + write(iulog,*) trim(subname)//': Registered ', aero_cnt, ' prescribed aerosol constituents' + end if + + end subroutine prescribed_aerosols_register + + ! Initialize prescribed aerosol reading via tracer_data. +!> \section arg_table_prescribed_aerosols_init Argument Table +!! \htmlinclude prescribed_aerosols_init.html + subroutine prescribed_aerosols_init( & + amIRoot, iulog, & + prescribed_aero_specifier, & + prescribed_aero_file, & + prescribed_aero_filelist, & + prescribed_aero_datapath, & + prescribed_aero_type, & + prescribed_aero_cycle_yr, & + prescribed_aero_fixed_ymd, & + prescribed_aero_fixed_tod, & + errmsg, errflg) + + ! + use tracer_data, only: trcdata_init + + ! Input arguments: + logical, intent(in) :: amIRoot + integer, intent(in) :: iulog + character(len=*), intent(in) :: prescribed_aero_specifier(:) ! aerosol specifier from namelist + character(len=*), intent(in) :: prescribed_aero_file ! input filename from namelist + character(len=*), intent(in) :: prescribed_aero_filelist ! input filelist from namelist + character(len=*), intent(in) :: prescribed_aero_datapath ! input datapath from namelist + character(len=*), intent(in) :: prescribed_aero_type ! data type from namelist + integer, intent(in) :: prescribed_aero_cycle_yr ! cycle year from namelist [1] + integer, intent(in) :: prescribed_aero_fixed_ymd ! fixed year-month-day from namelist (YYYYMMDD) [1] + integer, intent(in) :: prescribed_aero_fixed_tod ! fixed time of day from namelist [s] + + ! Output arguments: + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Local variables: + integer :: i + integer :: idx + + ! Local parameters: + character(len=*), parameter :: subname = 'prescribed_aerosols_init' + + ! Initialize output arguments: + errmsg = '' + errflg = 0 + + if (.not. has_prescribed_aerosols) return + + if (amIRoot) then + write(iulog,*) trim(subname)//': aerosols are prescribed in: '//trim(prescribed_aero_file) + end if + + ! Initialize tracer_data module with file and field information + call trcdata_init( & + specifier = prescribed_aero_specifier(:), & + filename = prescribed_aero_file, & + filelist = prescribed_aero_filelist, & + datapath = prescribed_aero_datapath, & + flds = tracer_data_fields, & + file = tracer_data_file, & + data_cycle_yr = prescribed_aero_cycle_yr, & + data_fixed_ymd = prescribed_aero_fixed_ymd, & + data_fixed_tod = prescribed_aero_fixed_tod, & + data_type = prescribed_aero_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 + + ! Note: because in modal aerosols, interstitial fields are derived from the + ! log-mean and log-variance (logm, logv) fields, the number of tracer_data + ! fields is not equal to aero_cnt(= a + c), but (logm + logv) + c. + + ! Based on aero_map_list, scan the tracer data fields to populate correct field_index + do i = 1, aero_cnt + ! Find the matching field in tracer_data_fields for the primary field + do idx = 1, size(tracer_data_fields) + if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%ncdf_field_name)) then + aero_map_list(i)%field_index = idx + exit + end if + end do + + ! For modal aerosol interstitial species, also find the logv field + if (aero_map_list(i)%is_modal_aero_interstitial) then + do idx = 1, size(tracer_data_fields) + if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%ncdf_field_name_logv)) then + aero_map_list(i)%field_index_logv = idx + exit + end if + end do + end if + end do + + ! Check aero_map_list for any unpopulated field indices (consistency check) + ! Print out each aero_map_list field information + do i = 1, aero_cnt + if (aero_map_list(i)%field_index <= 0) then + errflg = 1 + write(errmsg, '(3a)') trim(subname), ': Field not found in tracer_data for constituent: ', & + trim(aero_map_list(i)%constituent_name) + return + end if + + if (amIRoot) then + if (aero_map_list(i)%is_modal_aero_interstitial) then + if (aero_map_list(i)%field_index_logv <= 0) then + errflg = 1 + write(errmsg, '(3a)') trim(subname), ': logv field not found for interstitial constituent: ', & + trim(aero_map_list(i)%constituent_name) + return + end if + + write(iulog, '(a,i3,2a)') ' ', i, ': ', trim(aero_map_list(i)%constituent_name) + write(iulog, '(3a,i3)') ' log-mean field: ', trim(aero_map_list(i)%ncdf_field_name), & + ' (trcdata index ', aero_map_list(i)%field_index, ')' + write(iulog, '(3a,i3)') ' log-variance field: ', trim(aero_map_list(i)%ncdf_field_name_logv), & + ' (trcdata index ', aero_map_list(i)%field_index_logv, ')' + else + write(iulog, '(a,i3,5a,i3,a)') ' ', i, ': ', trim(aero_map_list(i)%constituent_name), & + ' from ', trim(aero_map_list(i)%ncdf_field_name), ' (trcdata index ', aero_map_list(i)%field_index, ')' + end if + end if + end do + + if (amIRoot) then + write(iulog,*) trim(subname)//': Initialized ', aero_cnt, ' aerosol fields' + end if + + end subroutine prescribed_aerosols_init + +!> \section arg_table_prescribed_aerosols_run Argument Table +!! \htmlinclude prescribed_aerosols_run.html + subroutine prescribed_aerosols_run( & + ncol, pver, pcnst, & + const_props, & + pi, & + constituents, & + errmsg, errflg) + + ! + use tracer_data, only: advance_trcdata + + ! 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 + + ! Input arguments: + integer, intent(in) :: ncol + integer, intent(in) :: pver + integer, intent(in) :: pcnst ! Number of CCPP constituents [count] + type(ccpp_constituent_prop_ptr_t), & + intent(in) :: const_props(:) ! CCPP constituent properties pointer + real(kind_phys), intent(in) :: pi + + ! Input/Output arguments: + real(kind_phys), intent(inout) :: constituents(:,:,:) ! constituent array (ncol, pver, pcnst) [kg kg-1 dry] + + ! Output arguments: + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Local variables: + integer :: i, idx + integer :: const_idx + real(kind_phys) :: field_data(ncol, pver) + + ! Local parameters: + character(len=*), parameter :: subname = 'prescribed_aerosols_run' + + ! Initialize output arguments: + errmsg = '' + errflg = 0 + + if (.not. has_prescribed_aerosols) return + + ! Advance tracer_data to current time + call advance_trcdata(tracer_data_fields, tracer_data_file) + + ! Copy the prescribed aerosol data to constituent array + ! Loop over aero_map_list to retrieve data from tracer_data_fields. + ! For most species (non-modal; cloud borne) just retrieve data and save to constituent. + ! For interstitial species (is_modal_aero_interstitial) construct mixing ratio based on + ! the logv and logm values read from tracer_data (port of rand_sample_prescribed_aero) + do i = 1, aero_cnt + ! Get constituent index + call ccpp_const_get_idx(const_props, & + trim(aero_map_list(i)%constituent_name), & + const_idx, errmsg, errflg) + if (errflg /= 0) return + + if (.not. aero_map_list(i)%is_modal_aero_interstitial) then + ! For non-interstitial species (cloud-borne or bulk aerosols), + ! directly copy field data from tracer_data container + constituents(:ncol, :pver, const_idx) = & + tracer_data_fields(aero_map_list(i)%field_index)%data(:ncol, :pver, 1) + else + ! For modal aerosol interstitial species (_a), compute from log-normal distribution + ! using log-mean (logm) and log-variance (logv) + call compute_modal_aero_interstitial( & + ncol, pver, & + pi, & + tracer_data_fields(aero_map_list(i)%field_index)%data(:ncol, :pver, 1), & + tracer_data_fields(aero_map_list(i)%field_index_logv)%data(:ncol, :pver, 1), & + constituents(:ncol, :pver, const_idx)) + end if + end do + + end subroutine prescribed_aerosols_run + + ! Compute modal aerosol interstitial species from log-normal distribution + ! Port of rand_sample_prescribed_aero from CAM prescribed_aero.F90 + ! + ! Original authors: Balwinder Singh (12/14/2012) adapted from Jin-Ho Yoon + subroutine compute_modal_aero_interstitial(ncol, pver, pi, logm_data, logv_data, mixing_ratio) + + ! Input arguments + integer, intent(in) :: ncol + integer, intent(in) :: pver + real(kind_phys), intent(in) :: pi + real(kind_phys), intent(in) :: logm_data(:, :) ! Log-mean of aerosol distribution [ln(kg kg-1)] + real(kind_phys), intent(in) :: logv_data(:, :) ! Log-variance of aerosol distribution [ln(kg kg-1)^2] + + ! Output arguments + real(kind_phys), intent(out) :: mixing_ratio(:, :) ! Computed aerosol mixing ratio [kg kg-1] + + ! Local variables + integer :: i, k + real(kind_phys) :: logm2 ! Log-mean squared [ln(kg kg-1)^2] + real(kind_phys) :: variance ! Variance [(ln(kg kg-1))^2] + real(kind_phys) :: std ! Standard deviation [ln(kg kg-1)] + real(kind_phys) :: mean_max ! Maximum mean value [kg kg-1] + real(kind_phys) :: std_max ! Maximum std value [kg kg-1] + real(kind_phys) :: randn ! Normal random number [1] + + ! Local parameters + real(kind_phys), parameter :: mean_max_val = 5.0_kind_phys + real(kind_phys), parameter :: std_max_val = 3.0_kind_phys + + ! Generate/use persisting random number + randn = randn_prescribed_aero(pi) + + ! This loop logic is a good candidate for making into a pure elemental function + ! despite the impurity of the RNG dependency above. + do k = 1, pver + do i = 1, ncol + logm2 = logm_data(i, k) * logm_data(i, k) + + ! Compute (non-negative) variance + variance = max(0.0_kind_phys, (logv_data(i, k) - logm2)) + + ! Standard deviation + std = sqrt(variance) + + ! Bounds to keep mixing ratios from going unphysical + mean_max = exp(logm_data(i, k)) * mean_max_val + std_max = exp(logm_data(i, k) + std_max_val * std) + + ! Compute mixing ratio with random sampling + mixing_ratio(i, k) = min(exp(logm_data(i, k) + randn * std), mean_max, std_max) + end do + end do + end subroutine compute_modal_aero_interstitial + + ! Generate normally distributed random number that persists for entire day + ! Port of randn_prescribed_aero and boxMuller from CAM prescribed_aero.F90 + ! + ! NOTE: I think this can be moved into the host model to be used as a + ! common RNG for physics schemes to avoid having dependency on time_manager. + function randn_prescribed_aero(pi) result(randn) + use time_manager, only: is_end_curr_day, is_first_step, get_nstep + + real(kind_phys), intent(in) :: pi + + ! Local variables + integer :: i, seed_size, nstep + integer, allocatable :: seed(:) + real(kind_phys) :: randu1, randu2 ! Uniform random numbers [1] + real(kind_phys) :: randn ! Normal random number [1] + real(kind_phys) :: ur, theta ! Box-Muller variables [1] + + ! Local parameters for random seed generation + integer, parameter :: rconst1_1 = 5000000 + integer, parameter :: rconst1_2 = 50 + integer, parameter :: rconst2_1 = 10000000 + integer, parameter :: rconst2_2 = 10 + real(kind_phys), parameter :: pi2 = 2.0_kind_phys * pi + + ! Use same random number for the entire day; generate new one at start of new day + if (is_first_step() .or. is_end_curr_day()) then + ! Generate two uniformly distributed random numbers (between 0 and 1) + call random_seed(size=seed_size) + allocate(seed(seed_size)) + + ! Using nstep as a seed to generate same sequence + nstep = get_nstep() + do i = 1, seed_size + seed(i) = rconst1_1 * nstep + rconst1_2 * (i - 1) + end do + call random_seed(put=seed) + call random_number(randu1) + + do i = 1, seed_size + seed(i) = rconst2_1 * nstep + rconst2_2 * (i - 1) + end do + call random_seed(put=seed) + call random_number(randu2) + deallocate(seed) + + ! Box-Muller method: convert uniform to normal distribution (mean=0, std=1) + ur = sqrt(-2.0_kind_phys * log(randu1)) + theta = pi2 * randu2 + randn = ur * cos(theta) + + ! Store for use throughout the day + randn_persists = randn + else + ! Use the previously generated random number + randn = randn_persists + end if + + end function randn_prescribed_aero + +end module prescribed_aerosols diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index c68b0dfa..edc7c4c6 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -100,10 +100,9 @@ subroutine prescribed_ozone_init( & filename = filename, & filelist = filelist, & datapath = datapath, & - fields = tracer_data_fields(:), & + flds = tracer_data_fields, & ! ptr file = tracer_data_file, & - rmv_file = .false., & - cycle_yr = cycle_yr, & + data_cycle_yr = cycle_yr, & data_fixed_ymd = fixed_ymd, & data_fixed_tod = fixed_tod, & data_type = data_type) diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index 0f104b5c..b5875d7c 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -1,7 +1,7 @@ [ccpp-table-properties] name = prescribed_ozone type = scheme - dependencies = ../../../../utils/cam_history.F90,../../../../utils/tracer_data.F90 + dependencies = ../../../../history/cam_history.F90,../../../../utils/tracer_data.F90 [ccpp-arg-table] name = prescribed_ozone_init @@ -14,7 +14,7 @@ intent = in [ iulog ] standard_name = log_output_unit - units = index + units = 1 type = integer dimensions = () intent = in @@ -119,7 +119,7 @@ dimensions = (horizontal_loop_extent, vertical_layer_dimension) intent = in [ prescribed_ozone ] - standard_name = prescribed_ozone_tbd + standard_name = O3 units = kg kg-1 type = real | kind = kind_phys dimensions = (horizontal_loop_extent, vertical_layer_dimension) diff --git a/schemes/chemistry/prescribed_ozone_namelist.xml b/schemes/chemistry/prescribed_ozone_namelist.xml index d0cff2e9..a97ef94a 100644 --- a/schemes/chemistry/prescribed_ozone_namelist.xml +++ b/schemes/chemistry/prescribed_ozone_namelist.xml @@ -41,7 +41,7 @@ Filename of file that contains a sequence of filenames for prescribed ozone. The filenames in this file are relative to the directory specified by prescribed_ozone_datapath. - + UNSET diff --git a/test/test_suites/suite_tracer_data_test.xml b/test/test_suites/suite_tracer_data_test.xml index 63ae2903..76267737 100644 --- a/test/test_suites/suite_tracer_data_test.xml +++ b/test/test_suites/suite_tracer_data_test.xml @@ -4,5 +4,8 @@ prescribed_ozone + + + prescribed_aerosols From 54d4e85dd42f6396e3f8ec6da3517b74f8ff4c83 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 24 Oct 2025 17:19:17 -0400 Subject: [PATCH 03/19] Populate namelist; meta for aerosols; fix standard names for consistency; misc updates --- schemes/chemistry/prescribed_aerosols.F90 | 12 +- schemes/chemistry/prescribed_aerosols.meta | 222 ++++++++++++++++++ .../prescribed_aerosols_namelist.xml | 157 +++++++++++++ schemes/chemistry/prescribed_ozone.meta | 20 +- .../chemistry/prescribed_ozone_namelist.xml | 20 +- 5 files changed, 403 insertions(+), 28 deletions(-) create mode 100644 schemes/chemistry/prescribed_aerosols.meta create mode 100644 schemes/chemistry/prescribed_aerosols_namelist.xml diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index c4472979..95916c21 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -87,11 +87,10 @@ subroutine prescribed_aerosols_register( & prescribed_aero_filelist, & prescribed_aero_datapath, & prescribed_aero_type, & - prescribed_aero_rmfile, & prescribed_aero_cycle_yr, & prescribed_aero_fixed_ymd, & prescribed_aero_fixed_tod, & - clim_modal_aero_in, & + prescribed_aero_model, & aerosol_constituents, & errmsg, errflg) @@ -108,10 +107,10 @@ subroutine prescribed_aerosols_register( & integer, intent(in) :: prescribed_aero_cycle_yr ! cycle year from namelist [1] integer, intent(in) :: prescribed_aero_fixed_ymd ! fixed year-month-day from namelist (YYYYMMDD) [1] integer, intent(in) :: prescribed_aero_fixed_tod ! fixed time of day from namelist [s] - logical, intent(in) :: clim_modal_aero_in ! flag for modal aerosol simulation [flag] + character(len=*), intent(in) :: prescribed_aero_model ! type of aerosol representation [none] ! Output arguments: - type(ccpp_constituent_properties_t), allocatable, intent(out) :: aerosol_constituents(:) ! prescribed aero CCPP constituents + type(ccpp_constituent_properties_t), allocatable, intent(out) :: aerosol_constituents(:) ! prescribed aero runtime CCPP constituents character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -133,10 +132,7 @@ subroutine prescribed_aerosols_register( & errflg = 0 ! Store module-level settings - ! TODO: this matches the CAM implementation but maybe I want this to distinguish - ! between none|bulk|modal|sectional. To revisit when the aerosol abstract interface - ! is implemented in CAM-SIMA. (hplin, 10/22/25) - clim_modal_aero = clim_modal_aero_in + clim_modal_aero = (trim(prescribed_aero_model) == "modal") ! Check if prescribed aerosols are enabled if (prescribed_aero_file == 'UNSET' .or. & diff --git a/schemes/chemistry/prescribed_aerosols.meta b/schemes/chemistry/prescribed_aerosols.meta new file mode 100644 index 00000000..5b59f345 --- /dev/null +++ b/schemes/chemistry/prescribed_aerosols.meta @@ -0,0 +1,222 @@ +[ccpp-table-properties] + name = prescribed_aerosols + type = scheme + dependencies = ../../../../utils/tracer_data.F90 + +[ccpp-arg-table] + name = prescribed_aerosols_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_aero_specifier ] + standard_name = specifier_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = (prescribed_aero_specifier_dimension) + intent = in +[ prescribed_aero_file ] + standard_name = filename_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_filelist ] + standard_name = filename_of_file_list_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_datapath ] + standard_name = datapath_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_type ] + standard_name = time_interpolation_method_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_cycle_yr ] + standard_name = cycle_year_for_prescribed_aerosols + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_aero_fixed_ymd ] + standard_name = fixed_date_for_prescribed_aerosols + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_aero_fixed_tod ] + standard_name = fixed_time_of_day_for_prescribed_aerosols + units = s + type = integer + dimensions = () + intent = in +[ prescribed_aero_model ] + standard_name = representation_type_for_prescribed_aerosols + units = none + type = character | kind = len=5 + dimensions = () + intent = in +[ aerosol_constituents ] + # or can this just be the ccpp_constituent_properties? + standard_name = prescribed_aerosols_constituents + units = none + type = ccpp_constituent_properties_t + allocatable = True + dimensions = (:) + intent = out +[ errmsg ] + standard_name = enter_standard_name_19 + units = enter_units + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = enter_standard_name_20 + units = enter_units + type = integer + dimensions = () + intent = out + +[ccpp-arg-table] + name = prescribed_aerosols_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_aero_specifier ] + standard_name = specifier_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = (prescribed_aero_specifier_dimension) + intent = in +[ prescribed_aero_file ] + standard_name = filename_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_filelist ] + standard_name = filename_of_file_list_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_datapath ] + standard_name = datapath_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_type ] + standard_name = time_interpolation_method_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ prescribed_aero_cycle_yr ] + standard_name = cycle_year_for_prescribed_aerosols + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_aero_fixed_ymd ] + standard_name = fixed_date_for_prescribed_aerosols + units = 1 + type = integer + dimensions = () + intent = in +[ prescribed_aero_fixed_tod ] + standard_name = fixed_time_of_day_for_prescribed_aerosols + units = s + type = integer + dimensions = () + intent = in +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out + +[ccpp-arg-table] + name = prescribed_aerosols_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 +[ pi ] + standard_name = pi_constant + units = 1 + type = real | kind = kind_phys + dimensions = () + intent = in +[ constituents ] + standard_name = ccpp_constituents + units = none + type = real | kind = kind_phys + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml new file mode 100644 index 00000000..e0c7fab7 --- /dev/null +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -0,0 +1,157 @@ + + + + + + + char*256 + chemistry + prescribed_aero_nl + datapath_for_prescribed_aerosols + none + + Full pathname of the directory that contains the files specified in prescribed_aero_filelist. + + + + + atm/cam/chem/trop_mozart_aero/aero + + + + + + char*5 + chemistry + prescribed_aero_nl + representation_type_for_prescribed_aerosols + none + bulk,modal + + Switch used to indicate which type of aerosols are prescribed -- bulk or modal. + + TODO: This is used to set the default prescribed_aero_specifier and aerodep_flx_specifier namelist variables. + + + bulk + + + + + char*256 + chemistry + prescribed_aero_nl + filename_for_prescribed_aerosols + none + + Filename of dataset for prescribed aerosols. + + + aero_1.9x2.5_L26_2000clim_c091112.nc + + + + + + char*256 + chemistry + prescribed_aero_nl + filename_of_file_list_for_prescribed_aerosols + none + + Filename of file that contains a sequence of filenames for prescribed aerosols. The filenames in this file are relative to the directory specified by prescribed_aero_datapath. + + + aero_1.9x2.5_L26_list_c070514.txt + + + + + + char*32(50) + chemistry + prescribed_aero_nl + specifier_for_prescribed_aerosols + none + + A list of variable names of the concentration fields in the prescribed aerosol datasets and corresponding standard name stored in the constituents properties object and constituent value array seperated by colons. For example: + + prescribed_aero_specifier = 'pconstituent_standard_name1:ncdf_fld_name1','constituent_standard_name2:ncdf_fld_name2', ... + + If there is no colon separator then the specified name is used as both the constituent_standard_name and ncdf_fld_name. + + + 'sulf:SO4','bcar1:CB1','bcar2:CB2','ocar1:OC1','ocar2:OC2','sslt1:SSLT01','sslt2:SSLT02','sslt3:SSLT03','sslt4:SSLT04','dust1:DST01','dust2:DST02','dust3:DST03','dust4:DST04' + + + + + + char*32 + chemistry + prescribed_aero_nl + time_interpolation_method_for_prescribed_aerosols + 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: 'CYCLICAL' + + + CYCLICAL + + + + + integer + chemistry + prescribed_aero_nl + cycle_year_for_prescribed_aerosols + 1 + + The cycle year of the prescribed ozone data if prescribed_aero_type is 'CYCLICAL'. + Format: YYYY + Default: 0 + + + 0 + + + + + integer + chemistry + prescribed_aero_nl + fixed_date_for_prescribed_aerosols + 1 + + The date at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. + Format: YYYYMMDD + Default: 0 + + + 0 + + + + + integer + chemistry + prescribed_aero_nl + fixed_time_of_day_for_prescribed_aerosols + s + + The time of day (seconds) corresponding to prescribed_aero_fixed_ymd at which the prescribed ozone data is fixed if prescribed_aero_type is 'FIXED'. + Default: 0 seconds + + + 0 + + + diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index b5875d7c..d47c9e33 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -19,49 +19,49 @@ dimensions = () intent = in [ fld_name_nl ] - standard_name = variable_name_of_ozone_in_prescribed_forcing_file + standard_name = variable_name_of_ozone_in_file_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in [ filename_nl ] - standard_name = filename_of_prescribed_ozone_forcing + standard_name = filename_of_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in [ filelist_nl ] - standard_name = filename_of_prescribed_ozone_forcing_file_list + standard_name = filename_of_file_list_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in [ datapath_nl ] - standard_name = datapath_for_prescribed_ozone_forcing + standard_name = datapath_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in [ data_type_nl ] - standard_name = time_interpolation_method_for_prescribed_ozone_forcing + standard_name = time_interpolation_method_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in [ cycle_yr_nl ] - standard_name = cycle_year_for_prescribed_ozone_forcing - units = count + standard_name = cycle_year_for_prescribed_ozone + units = 1 type = integer dimensions = () intent = in [ fixed_ymd_nl ] - standard_name = fixed_date_for_prescribed_ozone_forcing - units = count + standard_name = fixed_date_for_prescribed_ozone + units = 1 type = integer dimensions = () intent = in [ fixed_tod_nl ] - standard_name = fixed_time_of_day_for_prescribed_ozone_forcing + standard_name = fixed_time_of_day_for_prescribed_ozone units = s type = integer dimensions = () diff --git a/schemes/chemistry/prescribed_ozone_namelist.xml b/schemes/chemistry/prescribed_ozone_namelist.xml index a97ef94a..dd9a11e5 100644 --- a/schemes/chemistry/prescribed_ozone_namelist.xml +++ b/schemes/chemistry/prescribed_ozone_namelist.xml @@ -7,7 +7,7 @@ char*256 chemistry prescribed_ozone_nl - datapath_for_prescribed_ozone_forcing + datapath_for_prescribed_ozone none Full pathname of the directory that contains the files specified in prescribed_ozone_filelist. @@ -21,7 +21,7 @@ char*256 chemistry prescribed_ozone_nl - filename_of_prescribed_ozone_forcing + filename_of_prescribed_ozone none Filename of dataset for prescribed ozone. @@ -35,7 +35,7 @@ char*256 chemistry prescribed_ozone_nl - filename_of_prescribed_ozone_forcing_file_list + filename_of_file_list_for_prescribed_ozone none Filename of file that contains a sequence of filenames for prescribed ozone. The filenames in this file are relative to the directory specified by prescribed_ozone_datapath. @@ -49,7 +49,7 @@ char*16 chemistry prescribed_ozone_nl - variable_name_of_ozone_in_prescribed_forcing_file + variable_name_of_ozone_in_file_for_prescribed_ozone none Name of variable containing ozone data in the prescribed ozone datasets. Default: 'O3' @@ -63,7 +63,7 @@ char*32 chemistry prescribed_ozone_nl - time_interpolation_method_for_prescribed_ozone_forcing + time_interpolation_method_for_prescribed_ozone none CYCLICAL,SERIAL,INTERP_MISSING_MONTHS,FIXED @@ -78,8 +78,8 @@ integer chemistry prescribed_ozone_nl - cycle_year_for_prescribed_ozone_forcing - count + cycle_year_for_prescribed_ozone + 1 The cycle year of the prescribed ozone data if prescribed_ozone_type is 'CYCLICAL'. Format: YYYY Default: 2000 @@ -92,8 +92,8 @@ integer chemistry prescribed_ozone_nl - fixed_date_for_prescribed_ozone_forcing - count + fixed_date_for_prescribed_ozone + 1 The date at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. Format: YYYYMMDD Default: 0 @@ -106,7 +106,7 @@ integer chemistry prescribed_ozone_nl - fixed_time_of_day_for_prescribed_ozone_forcing + fixed_time_of_day_for_prescribed_ozone s The time of day (seconds) corresponding to prescribed_ozone_fixed_ymd at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. Default: 0 seconds From ada594990023f55ebe290cd58e5b177200323aa2 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 24 Oct 2025 20:05:07 -0400 Subject: [PATCH 04/19] Fix build; fix runtime --- schemes/chemistry/prescribed_aerosols.F90 | 73 +++++++++---------- schemes/chemistry/prescribed_aerosols.meta | 12 +-- .../prescribed_aerosols_namelist.xml | 8 +- 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index 95916c21..2eb69364 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -50,11 +50,11 @@ module prescribed_aerosols ! in this case, field_index is used for log-mean, and a separate field for variance ! is stored here. logical :: is_modal_aero_interstitial - character(len=64) :: ncdf_field_name_logv ! netCDF variable name in input file (log-variance) + character(len=64) :: trcdata_field_name_logv! tracer_data field name (fldnam, not netCDF variable name) (log-variance) integer :: field_index_logv ! index into tracer_data_fields array (log-variance) ! all other cases, including modal log-mean: - character(len=64) :: ncdf_field_name ! netCDF variable name in input file + character(len=64) :: trcdata_field_name ! tracer_data field name (fldnam, not netCDF variable name) integer :: field_index ! index into tracer_data_fields array end type aero_constituent_map @@ -150,7 +150,9 @@ subroutine prescribed_aerosols_register( & aero_cnt = 0 aero_cnt_c = 0 ! cloud borne species count cnt_loop: do i = 1, N_AERO_MAX - if(len_trim(prescribed_aero_specifier(i)) == 0) exit cnt_loop + ! FIXME: should I be responsible for handling this? I feel like I should not handle this + if(prescribed_aero_specifier(i) == 'UNSET' .or. & + len_trim(prescribed_aero_specifier(i)) == 0) exit cnt_loop skip_spec = .false. if(clim_modal_aero) then @@ -170,6 +172,7 @@ subroutine prescribed_aerosols_register( & if(index(prescribed_aero_specifier(i),'_c') >= 1) aero_cnt_c = aero_cnt_c + 1 if(index(prescribed_aero_specifier(i),'_logv') >= 1) skip_spec = .true. endif + if(.not. skip_spec) aero_cnt = aero_cnt + 1 end do cnt_loop @@ -191,10 +194,14 @@ subroutine prescribed_aerosols_register( & ! In CAM modal aerosols, the interstitial species (_a) are added at the end of the cloud borne (_c) species. ! TODO hplin 10/22/25 We might not need this? Fields were identified by their pbuf name anyway - aero_idx = 0 ! pointer for current aero_map_list index. - reg_loop: do i = 1, N_AERO_MAX + aero_idx = 1 ! pointer for current aero_map_list index. + ddt_loop: do i = 1, N_AERO_MAX ! Parse specifier tmpstr = trim(adjustl(prescribed_aero_specifier(i))) + + ! FIXME: should I be responsible for handling this? I feel like I should not handle this + if(tmpstr == 'UNSET' .or. len_trim(tmpstr) == 0) exit ddt_loop + idx = index(tmpstr, ':') if(idx > 0) then ! format as constituent_name:ncdf_name @@ -212,7 +219,7 @@ subroutine prescribed_aerosols_register( & ! skip the _logv species; remove the _logm suffix to directly get the interstitial (_a) name ! e.g. in loop: num_c1, num_a1_logm, num_a1_logv, ... --> num_c1, num_a1, (skip), ... if(clim_modal_aero) then - if(index(constituent_name,'_logv') >= 1) exit reg_loop + if(index(constituent_name,'_logv') >= 1) cycle ddt_loop idx = index(constituent_name,'_logm') if(idx > 0) then ! use this as the proxy for interstitial species and initialize it here @@ -220,17 +227,10 @@ subroutine prescribed_aerosols_register( & aero_map_list(aero_idx)%is_modal_aero_interstitial = .true. aero_map_list(aero_idx)%constituent_name = constituent_name(:idx-1) ! removed _logm - aero_map_list(aero_idx)%ncdf_field_name = ncdf_fld_name ! log-mean specifier name + aero_map_list(aero_idx)%trcdata_field_name = constituent_name ! log-mean specifier name - idx2 = index(ncdf_fld_name,'_logm') - if(idx2 == 0) then - ! the netcdf name does not have logm; error out for now - ! if this becomes a problem, we can rescan prescribed_aero_specifier for the actual logv entry. - errflg = 1 - errmsg = subname//': cannot construct ncdf_field_name for interstitial species based on '//ncdf_fld_name - endif - ! for now construct log-variance specifier name by changing _logm with _logv - aero_map_list(aero_idx)%ncdf_field_name_logv = ncdf_fld_name(:idx2) // '_logv' + ! construct log-variance specifier name by changing _logm with _logv + aero_map_list(aero_idx)%trcdata_field_name_logv = constituent_name(:idx-1) // '_logv' ! The tracer data specifier index will be rescanned once tracer_data_fields is initialized ! at the init phase. @@ -243,7 +243,7 @@ subroutine prescribed_aerosols_register( & aero_map_list(aero_idx)%is_modal_aero_interstitial = .false. aero_map_list(aero_idx)%constituent_name = constituent_name - aero_map_list(aero_idx)%ncdf_field_name = ncdf_fld_name + aero_map_list(aero_idx)%trcdata_field_name = constituent_name ! The tracer data specifier index will be rescanned once tracer_data_fields is initialized ! at the init phase. @@ -252,12 +252,12 @@ subroutine prescribed_aerosols_register( & ! We added a new aero_map_list entry, so advance the pointer. aero_idx = aero_idx + 1 - end do + end do ddt_loop ! Sanity check - if(aero_idx /= aero_cnt) then + if(aero_idx /= aero_cnt+1) then errflg = 1 - write(errmsg,*) subname//': consistency check 1 failure; at the end of ddt allocation, aero_idx is not aero_cnt', aero_idx, aero_cnt + write(errmsg,*) subname//': consistency check 1 failure; at the end of ddt allocation, aero_idx is not aero_cnt+1', aero_idx, aero_cnt return end if @@ -266,32 +266,32 @@ subroutine prescribed_aerosols_register( & if (errflg /= 0) return ! Now register constituents in the CCPP constituent properties object. - do i = 1, aero_cnt + reg_loop: do i = 1, aero_cnt ! check units. at this point, we do not know the units from file ! because tracer_data has not read any data yet. ! number concentrations are units of 1 kg-1; all others are kg kg-1 - if(index(aero_map_list(aero_idx)%constituent_name, 'num_') == 1) then + if(index(aero_map_list(i)%constituent_name, 'num_') == 1) then unit_name = '1 kg-1' else unit_name = 'kg kg-1' end if call aerosol_constituents(i)%instantiate( & - std_name = trim(aero_map_list(aero_idx)%constituent_name), & - long_name = 'prescribed aerosol '//trim(aero_map_list(aero_idx)%constituent_name), & + std_name = trim(aero_map_list(i)%constituent_name), & + long_name = 'prescribed aerosol '//trim(aero_map_list(i)%constituent_name), & units = unit_name, & vertical_dim = 'vertical_layer_dimension', & min_value = 0.0_kind_phys, & advected = .false., & water_species = .false., & mixing_ratio_type = 'dry', & - errflg = errflg, & + errcode = errflg, & errmsg = errmsg) if(errflg /= 0) return ! TODO: add history field here. ! name is ncdf_field_name // '_D' - end do + end do reg_loop if (amIRoot) then write(iulog,*) trim(subname)//': Registered ', aero_cnt, ' prescribed aerosol constituents' @@ -352,7 +352,7 @@ subroutine prescribed_aerosols_init( & ! Initialize tracer_data module with file and field information call trcdata_init( & - specifier = prescribed_aero_specifier(:), & + specifier = prescribed_aero_specifier(:aero_cnt), & filename = prescribed_aero_file, & filelist = prescribed_aero_filelist, & datapath = prescribed_aero_datapath, & @@ -378,7 +378,7 @@ subroutine prescribed_aerosols_init( & do i = 1, aero_cnt ! Find the matching field in tracer_data_fields for the primary field do idx = 1, size(tracer_data_fields) - if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%ncdf_field_name)) then + if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%trcdata_field_name)) then aero_map_list(i)%field_index = idx exit end if @@ -387,7 +387,7 @@ subroutine prescribed_aerosols_init( & ! For modal aerosol interstitial species, also find the logv field if (aero_map_list(i)%is_modal_aero_interstitial) then do idx = 1, size(tracer_data_fields) - if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%ncdf_field_name_logv)) then + if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%trcdata_field_name_logv)) then aero_map_list(i)%field_index_logv = idx exit end if @@ -415,13 +415,13 @@ subroutine prescribed_aerosols_init( & end if write(iulog, '(a,i3,2a)') ' ', i, ': ', trim(aero_map_list(i)%constituent_name) - write(iulog, '(3a,i3)') ' log-mean field: ', trim(aero_map_list(i)%ncdf_field_name), & + write(iulog, '(3a,i3)') ' log-mean field: ', trim(aero_map_list(i)%trcdata_field_name), & ' (trcdata index ', aero_map_list(i)%field_index, ')' - write(iulog, '(3a,i3)') ' log-variance field: ', trim(aero_map_list(i)%ncdf_field_name_logv), & + write(iulog, '(3a,i3)') ' log-variance field: ', trim(aero_map_list(i)%trcdata_field_name_logv), & ' (trcdata index ', aero_map_list(i)%field_index_logv, ')' else write(iulog, '(a,i3,5a,i3,a)') ' ', i, ': ', trim(aero_map_list(i)%constituent_name), & - ' from ', trim(aero_map_list(i)%ncdf_field_name), ' (trcdata index ', aero_map_list(i)%field_index, ')' + ' from ', trim(aero_map_list(i)%trcdata_field_name), ' (trcdata index ', aero_map_list(i)%field_index, ')' end if end if end do @@ -498,15 +498,15 @@ subroutine prescribed_aerosols_run( & ! For non-interstitial species (cloud-borne or bulk aerosols), ! directly copy field data from tracer_data container constituents(:ncol, :pver, const_idx) = & - tracer_data_fields(aero_map_list(i)%field_index)%data(:ncol, :pver, 1) + tracer_data_fields(aero_map_list(i)%field_index)%data(:ncol,:pver) else ! For modal aerosol interstitial species (_a), compute from log-normal distribution ! using log-mean (logm) and log-variance (logv) call compute_modal_aero_interstitial( & ncol, pver, & pi, & - tracer_data_fields(aero_map_list(i)%field_index)%data(:ncol, :pver, 1), & - tracer_data_fields(aero_map_list(i)%field_index_logv)%data(:ncol, :pver, 1), & + tracer_data_fields(aero_map_list(i)%field_index)%data(:ncol,:pver), & + tracer_data_fields(aero_map_list(i)%field_index_logv)%data(:ncol,:pver), & constituents(:ncol, :pver, const_idx)) end if end do @@ -589,7 +589,6 @@ function randn_prescribed_aero(pi) result(randn) integer, parameter :: rconst1_2 = 50 integer, parameter :: rconst2_1 = 10000000 integer, parameter :: rconst2_2 = 10 - real(kind_phys), parameter :: pi2 = 2.0_kind_phys * pi ! Use same random number for the entire day; generate new one at start of new day if (is_first_step() .or. is_end_curr_day()) then @@ -614,7 +613,7 @@ function randn_prescribed_aero(pi) result(randn) ! Box-Muller method: convert uniform to normal distribution (mean=0, std=1) ur = sqrt(-2.0_kind_phys * log(randu1)) - theta = pi2 * randu2 + theta = 2.0_kind_phys * pi * randu2 randn = ur * cos(theta) ! Store for use throughout the day diff --git a/schemes/chemistry/prescribed_aerosols.meta b/schemes/chemistry/prescribed_aerosols.meta index 5b59f345..cfdcebfb 100644 --- a/schemes/chemistry/prescribed_aerosols.meta +++ b/schemes/chemistry/prescribed_aerosols.meta @@ -69,7 +69,7 @@ [ prescribed_aero_model ] standard_name = representation_type_for_prescribed_aerosols units = none - type = character | kind = len=5 + type = character | kind = len=* dimensions = () intent = in [ aerosol_constituents ] @@ -81,14 +81,14 @@ dimensions = (:) intent = out [ errmsg ] - standard_name = enter_standard_name_19 - units = enter_units + standard_name = ccpp_error_message + units = none type = character | kind = len=512 dimensions = () intent = out [ errflg ] - standard_name = enter_standard_name_20 - units = enter_units + standard_name = ccpp_error_code + units = 1 type = integer dimensions = () intent = out @@ -206,7 +206,7 @@ standard_name = ccpp_constituents units = none type = real | kind = kind_phys - dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents) intent = inout [ errmsg ] standard_name = ccpp_error_message diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index e0c7fab7..4f02a24f 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -13,9 +13,9 @@ Full pathname of the directory that contains the files specified in prescribed_aero_filelist. - - - atm/cam/chem/trop_mozart_aero/aero + + + ${DIN_LOC_ROOT}/atm/cam/chem/trop_mozart_aero/aero @@ -120,7 +120,7 @@ Default: 0 - 0 + 2000 From 7b3311515b6265aa0ecd1754eaeb72bd7b189199 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 27 Oct 2025 11:24:48 -0400 Subject: [PATCH 05/19] cleanup --- schemes/chemistry/prescribed_aerosols.F90 | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index 2eb69364..f67953ca 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -288,9 +288,6 @@ subroutine prescribed_aerosols_register( & errcode = errflg, & errmsg = errmsg) if(errflg /= 0) return - - ! TODO: add history field here. - ! name is ncdf_field_name // '_D' end do reg_loop if (amIRoot) then @@ -314,9 +311,12 @@ subroutine prescribed_aerosols_init( & prescribed_aero_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 + ! Input arguments: logical, intent(in) :: amIRoot integer, intent(in) :: iulog @@ -336,6 +336,7 @@ subroutine prescribed_aerosols_init( & ! Local variables: integer :: i integer :: idx + character(len=64) :: unit_name ! Local parameters: character(len=*), parameter :: subname = 'prescribed_aerosols_init' @@ -395,7 +396,8 @@ subroutine prescribed_aerosols_init( & end if end do - ! Check aero_map_list for any unpopulated field indices (consistency check) + ! Check aero_map_list for any unpopulated field indices (consistency check), + ! Register history field, and ! Print out each aero_map_list field information do i = 1, aero_cnt if (aero_map_list(i)%field_index <= 0) then @@ -405,6 +407,20 @@ subroutine prescribed_aerosols_init( & return end if + ! Add history field + ! Check units. at this point, we do not know the units from file + ! because tracer_data has not read any data yet. + ! number concentrations are units of 1 kg-1; all others are kg kg-1 + if(index(aero_map_list(i)%constituent_name, 'num_') == 1) then + unit_name = '1 kg-1' + else + unit_name = 'kg kg-1' + end if + call history_add_field(trim(aero_map_list(i)%constituent_name) // '_D', & + 'prescribed aero ' // trim(aero_map_list(i)%constituent_name), & + 'lev', 'avg', unit_name) + + ! Informational printout if (amIRoot) then if (aero_map_list(i)%is_modal_aero_interstitial) then if (aero_map_list(i)%field_index_logv <= 0) then @@ -450,6 +466,9 @@ subroutine prescribed_aerosols_run( & ! dependency to get constituent index use ccpp_const_utils, only: ccpp_const_get_idx + ! host model dependency for history output + use cam_history, only: history_out_field + ! Input arguments: integer, intent(in) :: ncol integer, intent(in) :: pver @@ -509,6 +528,10 @@ subroutine prescribed_aerosols_run( & tracer_data_fields(aero_map_list(i)%field_index_logv)%data(:ncol,:pver), & constituents(:ncol, :pver, const_idx)) end if + + ! History output + call history_out_field(trim(aero_map_list(i)%constituent_name) // '_D', & + constituents(:ncol, :pver, const_idx)) end do end subroutine prescribed_aerosols_run From f1e390822266936586fc23eb6ea95b38c7247b3b Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Tue, 28 Oct 2025 14:47:05 -0400 Subject: [PATCH 06/19] Bug fixes --- schemes/chemistry/prescribed_aerosols.F90 | 10 +++++++-- schemes/chemistry/prescribed_aerosols.meta | 24 ++++++++++++++++++++++ schemes/chemistry/prescribed_ozone.F90 | 11 +++++++++- schemes/chemistry/prescribed_ozone.meta | 24 ++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index f67953ca..b58bf22c 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -454,6 +454,7 @@ subroutine prescribed_aerosols_run( & ncol, pver, pcnst, & const_props, & pi, & + pmid, pint, phis, zi, & ! necessary fields for trcdata read. constituents, & errmsg, errflg) @@ -472,10 +473,14 @@ subroutine prescribed_aerosols_run( & ! Input arguments: integer, intent(in) :: ncol integer, intent(in) :: pver - integer, intent(in) :: pcnst ! Number of CCPP constituents [count] + integer, intent(in) :: pcnst ! # of CCPP constituents [count] type(ccpp_constituent_prop_ptr_t), & intent(in) :: const_props(:) ! CCPP constituent properties pointer real(kind_phys), intent(in) :: pi + real(kind_phys), intent(in) :: pmid(:,:) ! air pressure [Pa] + 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(:,:) ! height above surface, interfaces [m] ! Input/Output arguments: real(kind_phys), intent(inout) :: constituents(:,:,:) ! constituent array (ncol, pver, pcnst) [kg kg-1 dry] @@ -499,7 +504,8 @@ subroutine prescribed_aerosols_run( & if (.not. has_prescribed_aerosols) return ! Advance tracer_data to current time - call advance_trcdata(tracer_data_fields, tracer_data_file) + call advance_trcdata(tracer_data_fields, tracer_data_file, & + pmid, pint, phis, zi) ! Copy the prescribed aerosol data to constituent array ! Loop over aero_map_list to retrieve data from tracer_data_fields. diff --git a/schemes/chemistry/prescribed_aerosols.meta b/schemes/chemistry/prescribed_aerosols.meta index cfdcebfb..97dc83e2 100644 --- a/schemes/chemistry/prescribed_aerosols.meta +++ b/schemes/chemistry/prescribed_aerosols.meta @@ -202,6 +202,30 @@ type = real | kind = kind_phys dimensions = () 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 [ constituents ] standard_name = ccpp_constituents units = none diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index edc7c4c6..1d3e00cd 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -118,6 +118,7 @@ subroutine prescribed_ozone_run( & ncol, pver, & mwdry, boltz, & t, pmiddry, & + pmid, pint, phis, zi, & ! necessary fields for trcdata read. prescribed_ozone, & errmsg, errflg) @@ -130,6 +131,10 @@ subroutine prescribed_ozone_run( & real(kind_phys), intent(in) :: boltz ! boltzmann_constant [J K-1] real(kind_phys), intent(in) :: t(:,:) ! temperature [K] real(kind_phys), intent(in) :: pmiddry(:,:) ! dry air pressure [Pa] + real(kind_phys), intent(in) :: pmid(:,:) ! air pressure [Pa] + 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(:,:) ! height above surface, interfaces [m] real(kind_phys), intent(out) :: prescribed_ozone(:,:) ! prescribed ozone mass mixing ratio [kg kg-1 dry] character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -148,7 +153,8 @@ subroutine prescribed_ozone_run( & endif ! advance data in tracer_data to current time. - call advance_trcdata(tracer_data_fields, tracer_data_file) + call advance_trcdata(tracer_data_fields, tracer_data_file, & + pmid, pint, phis, zi) units_str = trim(tracer_data_fields(1)%units) ! copy field from tracer_data container. @@ -168,6 +174,9 @@ subroutine prescribed_ozone_run( & errmsg = 'prescribed_ozone_run: unit' // units_str //' are not recognized' end select + ! convert to kg kg-1 (dry) + prescribed_ozone = to_mmr * prescribed_ozone + ! convert to mol mol-1 (dry) only for diagnostic output call history_out_field('ozone', prescribed_ozone(:ncol,:pver)*(mwdry/ozone_mw)) diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index d47c9e33..3bef566a 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -118,6 +118,30 @@ type = real | kind = kind_phys dimensions = (horizontal_loop_extent, 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 [ prescribed_ozone ] standard_name = O3 units = kg kg-1 From be8fd4a5c80183057cdd6715c2a9a1a4b27d2db8 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 31 Oct 2025 12:54:32 -0400 Subject: [PATCH 07/19] CCPPize aerodep_flx (draft) --- .../prescribed_aerosol_deposition_flux.F90 | 342 ++++++++++++++++++ .../prescribed_aerosol_deposition_flux.meta | 215 +++++++++++ ...ribed_aerosol_deposition_flux_namelist.xml | 130 +++++++ schemes/chemistry/prescribed_aerosols.F90 | 4 +- .../prescribed_aerosols_namelist.xml | 2 +- schemes/chemistry/prescribed_ozone.F90 | 2 +- .../chemistry/prescribed_ozone_namelist.xml | 6 +- test/test_suites/suite_tracer_data_test.xml | 3 + 8 files changed, 697 insertions(+), 7 deletions(-) create mode 100644 schemes/chemistry/prescribed_aerosol_deposition_flux.F90 create mode 100644 schemes/chemistry/prescribed_aerosol_deposition_flux.meta create mode 100644 schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 new file mode 100644 index 00000000..8928febd --- /dev/null +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 @@ -0,0 +1,342 @@ +! Manages reading and interpolation of prescribed aerosol deposition +! fluxes. These are the deposition fluxes sent to the surface model. +! +! Note: this module currently only implements bulk aerosol deposition fluxes. +! +! This scheme requires the concurrent use of prescribed_aerosols scheme +! to provide prescribed aerosol concentrations. +! +! Original author: Francis Vitt +! CCPP version: Haipeng Lin, November 2025 +module prescribed_aerosol_deposition_flux + 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_aerosol_deposition_flux_init + public :: prescribed_aerosol_deposition_flux_run + + ! fields to store tracer_data state and information. + type(trfld), pointer :: tracer_data_fields(:) + type(trfile) :: tracer_data_file + + ! is this module active? + logical :: has_aerodep_flx = .false. + + ! for bulk aerosol fluxes + integer, parameter :: N_BULK = 14 + logical :: is_bulk = .false. + + ! list of bulk aerosol flux field names + ! these and the index fields below are explicitly enumerated + ! because they are explicitly enumerated in the cam_out coupler data structure. + ! no runtime configurability is achievable here if cam_out hardcodes these. + character(len=12), parameter :: bulk_names(N_BULK) = [& + 'BCDEPWET ', 'BCPHODRY ', 'BCPHIDRY ', & + 'OCDEPWET ', 'OCPHODRY ', 'OCPHIDRY ', & + 'DSTX01DD ', 'DSTX02DD ', 'DSTX03DD ', 'DSTX04DD ', & + 'DSTX01WD ', 'DSTX02WD ', 'DSTX03WD ', 'DSTX04WD '] + integer :: index_bulk_map(N_BULK) + integer :: ibcphiwet, ibcphidry, ibcphodry + integer :: iocphiwet, iocphidry, iocphodry + integer :: idst1dry, idst2dry, idst3dry, idst4dry + integer :: idst1wet, idst2wet, idst3wet, idst4wet + + ! for modal aerosol fluxes (unavailable) + integer, parameter :: N_MODAL = 22 + logical :: is_modal = .false. + + ! namelist options + character(len=256) :: filename = ' ' + character(len=256) :: filelist = ' ' + character(len=256) :: datapath = ' ' + character(len=32) :: data_type = 'SERIAL' + integer :: cycle_yr = 0 + integer :: fixed_ymd = 0 + integer :: fixed_tod = 0 + +contains +!> \section arg_table_prescribed_aerosol_deposition_flux_init Argument Table +!! \htmlinclude prescribed_aerosol_deposition_flux_init.html + subroutine prescribed_aerosol_deposition_flux_init( & + amIRoot, iulog, & + specifier_nl, & + filename_nl, filelist_nl, datapath_nl, & + data_type_nl, & + cycle_yr_nl, fixed_ymd_nl, fixed_tod_nl, & + prescribed_aero_model, & + errmsg, errflg) + + ! + use tracer_data, only: trcdata_init + + ! host model dependency for history diagnostics + use cam_history, only: history_add_field + use cam_history_support, only: horiz_only + + logical, intent(in) :: amIRoot + integer, intent(in) :: iulog + character(len=*), intent(in) :: specifier_nl(:) ! field specifiers from namelist + character(len=*), intent(in) :: filename_nl ! input filename from namelist + character(len=*), intent(in) :: filelist_nl ! input filelist from namelist + character(len=*), intent(in) :: datapath_nl ! input datapath from namelist + character(len=*), intent(in) :: data_type_nl ! data type from namelist + integer, intent(in) :: cycle_yr_nl ! cycle year from namelist + integer, intent(in) :: fixed_ymd_nl ! fixed year-month-day from namelist (YYYYMMDD) [1] + integer, intent(in) :: fixed_tod_nl ! fixed time of day from namelist [s] + character(len=*), intent(in) :: prescribed_aero_model ! type of aerosol representation + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + integer :: i, ndx, number_flds + character(len=*), parameter :: subname = 'prescribed_aerosol_deposition_flux_init' + + errmsg = '' + errflg = 0 + + filename = filename_nl + filelist = filelist_nl + datapath = datapath_nl + data_type = data_type_nl + cycle_yr = cycle_yr_nl + fixed_ymd = fixed_ymd_nl + fixed_tod = fixed_tod_nl + + ! check if user has specified an input dataset + if (filename /= 'UNSET' .and. len_trim(filename) > 0 .and. filename /= 'NONE') then + has_aerodep_flx = .true. + + if (amIRoot) then + write(iulog,*) subname//': aerosol deposition fluxes prescribed in: '//trim(filename) + write(iulog,*) subname//': aerosol representation is: '//trim(prescribed_aero_model) + end if + else + return + end if + + select case(prescribed_aero_model) + case('bulk') + is_bulk = .true. + case('modal') + is_modal = .true. + errflg = 1 + errmsg = subname//': modal aerosol mode not yet implemented' + return + case default + errflg = 1 + errmsg = subname//': unknown aerosol representation' + end select + + ! count number of specifiers + number_flds = 0 + do i = 1, size(specifier_nl) + if(len_trim(specifier_nl(i)) > 0 .and. trim(specifier_nl(i)) /= 'UNSET') then + number_flds = number_flds + 1 + endif + end do + + ! initialize dataset in tracer_data module. + call trcdata_init( & + specifier = specifier_nl(:number_flds), & + filename = filename, & + filelist = filelist, & + datapath = datapath, & + flds = tracer_data_fields, & ! ptr + file = tracer_data_file, & + data_cycle_yr = cycle_yr, & + data_fixed_ymd = fixed_ymd, & + data_fixed_tod = fixed_tod, & + data_type = data_type) + + number_flds = 0 + if (associated(tracer_data_fields)) number_flds = size(tracer_data_fields) + + if (number_flds < 1) then + has_aerodep_flx = .false. + if (amIRoot) then + write (iulog, *) subname//': no aerosol deposition fluxes have been specified' + end if + return + end if + + ! map field names to indices + index_bulk_map(:) = -1 + + do i = 1, number_flds + ! match specifier field name to known bulk aerosol names + ndx = get_ndx(tracer_data_fields(i)%fldnam, bulk_names) + if (ndx > 0) then + index_bulk_map(ndx) = i + + ! add history field for diagnostics + call history_add_field(trim(tracer_data_fields(i)%fldnam)//'_D', & + 'prescribed aero deposition flux ' // trim(tracer_data_fields(i)%fldnam), & + horiz_only, 'avg', & + tracer_data_fields(i)%units) + else + ! when modal aerosols are available can match against modal names. + + ! if all else fails + errflg = 1 + errmsg = 'prescribed_aerosol_deposition_flux_init: aerosol flux name not recognized: ' & + //trim(tracer_data_fields(i)%fldnam) + return + end if + end do + + ! set up index pointers for bulk fluxes + ! these correspond exactly to the order in bulk_names + ibcphiwet = index_bulk_map(1) + ibcphodry = index_bulk_map(2) + ibcphidry = index_bulk_map(3) + iocphiwet = index_bulk_map(4) + iocphodry = index_bulk_map(5) + iocphidry = index_bulk_map(6) + idst1dry = index_bulk_map(7) + idst2dry = index_bulk_map(8) + idst3dry = index_bulk_map(9) + idst4dry = index_bulk_map(10) + idst1wet = index_bulk_map(11) + idst2wet = index_bulk_map(12) + idst3wet = index_bulk_map(13) + idst4wet = index_bulk_map(14) + + end subroutine prescribed_aerosol_deposition_flux_init + +!> \section arg_table_prescribed_aerosol_deposition_flux_run Argument Table +!! \htmlinclude prescribed_aerosol_deposition_flux_run.html + subroutine prescribed_aerosol_deposition_flux_run( & + ncol, & + pmid, pint, phis, zi, & ! necessary fields for trcdata read. + bcphiwet, bcphidry, bcphodry, & + ocphiwet, ocphidry, ocphodry, & + dst1dry, dst2dry, dst3dry, dst4dry, & + dst1wet, dst2wet, dst3wet, dst4wet, & + errmsg, errflg) + + use tracer_data, only: advance_trcdata + use cam_history, only: history_out_field + + ! Input arguments: + integer, intent(in) :: ncol + real(kind_phys), intent(in) :: pmid(:, :) ! air pressure at centers [Pa] + 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(:, :) ! height above surface, interfaces [m] + + ! Output arguments to coupler: + real(kind_phys), intent(out) :: bcphiwet(:) ! wet deposition of hydrophilic black carbon [kg m-2 s-1] + real(kind_phys), intent(out) :: bcphidry(:) ! dry deposition of hydrophilic black carbon [kg m-2 s-1] + real(kind_phys), intent(out) :: bcphodry(:) ! dry deposition of hydrophobic black carbon [kg m-2 s-1] + real(kind_phys), intent(out) :: ocphiwet(:) ! wet deposition of hydrophilic organic carbon [kg m-2 s-1] + real(kind_phys), intent(out) :: ocphidry(:) ! dry deposition of hydrophilic organic carbon [kg m-2 s-1] + real(kind_phys), intent(out) :: ocphodry(:) ! dry deposition of hydrophobic organic carbon [kg m-2 s-1] + real(kind_phys), intent(out) :: dst1dry(:) ! dry deposition of dust bin 1 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst2dry(:) ! dry deposition of dust bin 2 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst3dry(:) ! dry deposition of dust bin 3 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst4dry(:) ! dry deposition of dust bin 4 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst1wet(:) ! wet deposition of dust bin 1 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst2wet(:) ! wet deposition of dust bin 2 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst3wet(:) ! wet deposition of dust bin 3 [kg m-2 s-1] + real(kind_phys), intent(out) :: dst4wet(:) ! wet deposition of dust bin 4 [kg m-2 s-1] + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + + ! initialize outputs to zero + bcphiwet(:) = 0._kind_phys + bcphidry(:) = 0._kind_phys + bcphodry(:) = 0._kind_phys + ocphiwet(:) = 0._kind_phys + ocphidry(:) = 0._kind_phys + ocphodry(:) = 0._kind_phys + dst1dry (:) = 0._kind_phys + dst2dry (:) = 0._kind_phys + dst3dry (:) = 0._kind_phys + dst4dry (:) = 0._kind_phys + dst1wet (:) = 0._kind_phys + dst2wet (:) = 0._kind_phys + dst3wet (:) = 0._kind_phys + dst4wet (:) = 0._kind_phys + + if (.not. has_aerodep_flx) return + + if (is_modal) then + ! modal aerosol mode not yet implemented + errflg = 1 + errmsg = 'prescribed_aerosol_deposition_flux_run: modal aerosol mode not yet implemented' + return + end if + + ! advance data in tracer_data to current time. + call advance_trcdata(tracer_data_fields, tracer_data_file, & + pmid, pint, phis, zi) + + if(is_bulk) then + ! set bulk aerosol fluxes + call set_fluxes(bcphiwet, ibcphiwet) + call set_fluxes(bcphidry, ibcphidry) + call set_fluxes(bcphodry, ibcphodry) + call set_fluxes(ocphiwet, iocphiwet) + call set_fluxes(ocphidry, iocphidry) + call set_fluxes(ocphodry, iocphodry) + call set_fluxes(dst1dry, idst1dry) + call set_fluxes(dst2dry, idst2dry) + call set_fluxes(dst3dry, idst3dry) + call set_fluxes(dst4dry, idst4dry) + call set_fluxes(dst1wet, idst1wet) + call set_fluxes(dst2wet, idst2wet) + call set_fluxes(dst3wet, idst3wet) + call set_fluxes(dst4wet, idst4wet) + endif + + end subroutine prescribed_aerosol_deposition_flux_run + + subroutine set_fluxes(fluxes, fld_idx) + ! host model dependency for history output + use cam_history, only: history_out_field + + real(kind_phys), intent(inout) :: fluxes(:) + integer, intent(in) :: fld_idx + + integer :: i + + if (fld_idx < 1) return + + ! copy from tracer_data container (surface field only) + fluxes(:) = tracer_data_fields(fld_idx)%data(:, 1) + + ! output diagnostic + call history_out_field(trim(tracer_data_fields(fld_idx)%fldnam)//'_D', & + fluxes(:)) + + end subroutine set_fluxes + + pure integer function get_ndx(name, list) + character(len=*), intent(in) :: name + character(len=*), intent(in) :: list(:) + + integer :: i + integer :: maxnum + + maxnum = size(list) + + get_ndx = -1 + do i = 1, maxnum + if (trim(name) == trim(list(i))) then + get_ndx = i + return + end if + end do + + end function get_ndx +end module prescribed_aerosol_deposition_flux diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta new file mode 100644 index 00000000..5007c0be --- /dev/null +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta @@ -0,0 +1,215 @@ +[ccpp-table-properties] + name = prescribed_aerosol_deposition_flux + type = scheme + +[ccpp-arg-table] + name = prescribed_aerosol_deposition_flux_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 +[ specifier_nl ] + standard_name = specifier_for_prescribed_aerosol_deposition_fluxes + units = none + type = character | kind = len=* + dimensions = (aerodep_flx_specifier_dimension) + intent = in +[ filename_nl ] + standard_name = filename_for_prescribed_aerosol_deposition_fluxes + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ filelist_nl ] + standard_name = filename_of_file_list_for_prescribed_aerosol_deposition_fluxes + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ datapath_nl ] + standard_name = datapath_for_prescribed_aerosol_deposition_fluxes + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ data_type_nl ] + standard_name = time_interpolation_method_for_prescribed_aerosol_deposition_fluxes + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ cycle_yr_nl ] + standard_name = cycle_year_for_prescribed_aerosol_deposition_fluxes + units = 1 + type = integer + dimensions = () + intent = in +[ fixed_ymd_nl ] + standard_name = fixed_date_for_prescribed_aerosol_deposition_fluxes + units = 1 + type = integer + dimensions = () + intent = in +[ fixed_tod_nl ] + standard_name = fixed_time_of_day_for_prescribed_aerosol_deposition_fluxes + units = s + type = integer + dimensions = () + intent = in +[ prescribed_aero_model ] + standard_name = representation_type_for_prescribed_aerosols + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out + +[ccpp-arg-table] + name = prescribed_aerosol_deposition_flux_run + type = scheme +[ ncol ] + standard_name = horizontal_loop_extent + units = count + type = integer + dimensions = () + 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 +[ bcphiwet ] + standard_name = wet_deposition_flux_of_hydrophilic_black_carbon_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ bcphidry ] + standard_name = dry_deposition_flux_of_hydrophilic_black_carbon_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ bcphodry ] + standard_name = dry_deposition_flux_of_hydrophobic_black_carbon_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ ocphiwet ] + standard_name = wet_deposition_flux_of_hydrophilic_organic_carbon_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ ocphidry ] + standard_name = dry_deposition_flux_of_hydrophilic_organic_carbon_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ ocphodry ] + standard_name = dry_deposition_flux_of_hydrophobic_organic_carbon_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst1dry ] + standard_name = dry_deposition_of_dust_bin1_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst2dry ] + standard_name = dry_deposition_of_dust_bin2_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst3dry ] + standard_name = dry_deposition_of_dust_bin3_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst4dry ] + standard_name = dry_deposition_of_dust_bin4_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst1wet ] + standard_name = wet_deposition_of_dust_bin1_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst2wet ] + standard_name = wet_deposition_of_dust_bin2_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst3wet ] + standard_name = wet_deposition_of_dust_bin3_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ dst4wet ] + standard_name = wet_deposition_of_dust_bin4_at_surface_to_coupler + units = kg m-2 s-1 + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent) + intent = out +[ errmsg ] + standard_name = ccpp_error_message + units = none + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = ccpp_error_code + units = 1 + type = integer + dimensions = () + intent = out diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml b/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml new file mode 100644 index 00000000..299bc78e --- /dev/null +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml @@ -0,0 +1,130 @@ + + + + + + + char*256 + chemistry + aerodep_flx_nl + datapath_for_prescribed_aerosol_deposition_fluxes + none + + Full pathname of the directory that contains the files specified in aerodep_flx_filelist. + + + ${DIN_LOC_ROOT}/atm/cam/chem/trop_mozart_aero/aero + + + + + char*256 + chemistry + aerodep_flx_nl + filename_for_prescribed_aerosol_deposition_fluxes + none + + Filename of dataset for prescribed aerosol deposition fluxes. + + + aerosoldep_monthly_1849-2006_1.9x2.5_c090803.nc + + + + + char*256 + chemistry + aerodep_flx_nl + filename_of_file_list_for_prescribed_aerosol_deposition_fluxes + none + + Filename of file that contains a sequence of filenames for prescribed aerosol deposition fluxes. The filenames in this file are relative to the directory specified by aerodep_flx_datapath. + + + UNSET + + + + + char*32(22) + chemistry + aerodep_flx_nl + specifier_for_prescribed_aerosol_deposition_fluxes + none + + A list of variable names of the aerosol deposition flux fields in the prescribed datasets and corresponding internal name separated by colons. For example: + + aerodep_flx_specifier = 'internal_name1:ncdf_fld_name1','internal_name2:ncdf_fld_name2', ... + + If there is no colon separator then the specified name is used as both the internal_name and ncdf_fld_name. + + + 'BCDEPWET','BCPHODRY','BCPHIDRY','OCDEPWET','OCPHODRY', 'OCPHIDRY','DSTX01DD','DSTX02DD','DSTX03DD','DSTX04DD','DSTX01WD','DSTX02WD','DSTX03WD','DSTX04WD' + + + + + char*32 + chemistry + aerodep_flx_nl + time_interpolation_method_for_prescribed_aerosol_deposition_fluxes + none + CYCLICAL,SERIAL,INTERP_MISSING_MONTHS,FIXED + + Type of time interpolation for data in aerodep_flx files. + Can be set to 'CYCLICAL', 'SERIAL', 'INTERP_MISSING_MONTHS', or 'FIXED'. + Default: 'SERIAL' + + + SERIAL + + + + + integer + chemistry + aerodep_flx_nl + cycle_year_for_prescribed_aerosol_deposition_fluxes + 1 + + The cycle year of the prescribed aerosol deposition flux data if aerodep_flx_type is 'CYCLICAL'. + Format: YYYY + Default: 2000 + + + 2000 + + + + + integer + chemistry + aerodep_flx_nl + fixed_date_for_prescribed_aerosol_deposition_fluxes + 1 + + The date at which the prescribed aerosol deposition flux data is fixed if aerodep_flx_type is 'FIXED'. + Format: YYYYMMDD + Default: 0 + + + 0 + + + + + integer + chemistry + aerodep_flx_nl + fixed_time_of_day_for_prescribed_aerosol_deposition_fluxes + s + + The time of day (seconds) corresponding to aerodep_flx_fixed_ymd at which the prescribed aerosol deposition flux data is fixed if aerodep_flx_type is 'FIXED'. + Default: 0 seconds + + + 0 + + + + diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index b58bf22c..2f818d87 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -24,7 +24,6 @@ module prescribed_aerosols implicit none private - save ! public CCPP-compliant subroutines public :: prescribed_aerosols_register @@ -490,9 +489,8 @@ subroutine prescribed_aerosols_run( & integer, intent(out) :: errflg ! Local variables: - integer :: i, idx + integer :: i integer :: const_idx - real(kind_phys) :: field_data(ncol, pver) ! Local parameters: character(len=*), parameter :: subname = 'prescribed_aerosols_run' diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index 4f02a24f..77d940d2 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -117,7 +117,7 @@ The cycle year of the prescribed ozone data if prescribed_aero_type is 'CYCLICAL'. Format: YYYY - Default: 0 + Default: 2000 2000 diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index 1d3e00cd..9fa2acb1 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -10,7 +10,6 @@ module prescribed_ozone implicit none private - save ! public CCPP-compliant subroutines public :: prescribed_ozone_init @@ -172,6 +171,7 @@ subroutine prescribed_ozone_run( & case default errflg = 1 errmsg = 'prescribed_ozone_run: unit' // units_str //' are not recognized' + return end select ! convert to kg kg-1 (dry) diff --git a/schemes/chemistry/prescribed_ozone_namelist.xml b/schemes/chemistry/prescribed_ozone_namelist.xml index dd9a11e5..761f6d22 100644 --- a/schemes/chemistry/prescribed_ozone_namelist.xml +++ b/schemes/chemistry/prescribed_ozone_namelist.xml @@ -81,7 +81,8 @@ cycle_year_for_prescribed_ozone 1 - The cycle year of the prescribed ozone data if prescribed_ozone_type is 'CYCLICAL'. Format: YYYY Default: 2000 + The cycle year of the prescribed ozone data if prescribed_ozone_type is 'CYCLICAL'. Format: YYYY + Default: 2000 2000 @@ -95,7 +96,8 @@ fixed_date_for_prescribed_ozone 1 - The date at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. Format: YYYYMMDD Default: 0 + The date at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. Format: YYYYMMDD + Default: 0 0 diff --git a/test/test_suites/suite_tracer_data_test.xml b/test/test_suites/suite_tracer_data_test.xml index 76267737..c7f7319d 100644 --- a/test/test_suites/suite_tracer_data_test.xml +++ b/test/test_suites/suite_tracer_data_test.xml @@ -7,5 +7,8 @@ prescribed_aerosols + + + prescribed_aerosol_deposition_flux From ae60d1fb075cd55030df57ebacd3441b46bc938d Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Wed, 12 Nov 2025 10:08:28 -0500 Subject: [PATCH 08/19] Propose updates to simplify nl parameters -- not save in module --- .../prescribed_aerosol_deposition_flux.F90 | 52 +++++++------------ .../prescribed_aerosol_deposition_flux.meta | 16 +++--- schemes/chemistry/prescribed_aerosols.F90 | 3 +- schemes/chemistry/prescribed_ozone.F90 | 51 +++++++----------- schemes/chemistry/prescribed_ozone.meta | 16 +++--- 5 files changed, 53 insertions(+), 85 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 index 8928febd..5a2ba6e0 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 @@ -6,8 +6,7 @@ ! This scheme requires the concurrent use of prescribed_aerosols scheme ! to provide prescribed aerosol concentrations. ! -! Original author: Francis Vitt -! CCPP version: Haipeng Lin, November 2025 +! Based on original CAM version from: Francis Vitt module prescribed_aerosol_deposition_flux use ccpp_kinds, only: kind_phys @@ -52,24 +51,15 @@ module prescribed_aerosol_deposition_flux integer, parameter :: N_MODAL = 22 logical :: is_modal = .false. - ! namelist options - character(len=256) :: filename = ' ' - character(len=256) :: filelist = ' ' - character(len=256) :: datapath = ' ' - character(len=32) :: data_type = 'SERIAL' - integer :: cycle_yr = 0 - integer :: fixed_ymd = 0 - integer :: fixed_tod = 0 - contains !> \section arg_table_prescribed_aerosol_deposition_flux_init Argument Table !! \htmlinclude prescribed_aerosol_deposition_flux_init.html subroutine prescribed_aerosol_deposition_flux_init( & amIRoot, iulog, & - specifier_nl, & - filename_nl, filelist_nl, datapath_nl, & - data_type_nl, & - cycle_yr_nl, fixed_ymd_nl, fixed_tod_nl, & + specifier, & + filename, filelist, datapath, & + data_type, & + cycle_yr, fixed_ymd, fixed_tod, & prescribed_aero_model, & errmsg, errflg) @@ -82,14 +72,14 @@ subroutine prescribed_aerosol_deposition_flux_init( & logical, intent(in) :: amIRoot integer, intent(in) :: iulog - character(len=*), intent(in) :: specifier_nl(:) ! field specifiers from namelist - character(len=*), intent(in) :: filename_nl ! input filename from namelist - character(len=*), intent(in) :: filelist_nl ! input filelist from namelist - character(len=*), intent(in) :: datapath_nl ! input datapath from namelist - character(len=*), intent(in) :: data_type_nl ! data type from namelist - integer, intent(in) :: cycle_yr_nl ! cycle year from namelist - integer, intent(in) :: fixed_ymd_nl ! fixed year-month-day from namelist (YYYYMMDD) [1] - integer, intent(in) :: fixed_tod_nl ! fixed time of day from namelist [s] + character(len=*), intent(in) :: specifier(:) ! field specifiers for tracer data + character(len=*), intent(in) :: filename ! input filename + character(len=*), intent(in) :: filelist ! input filelist + character(len=*), intent(in) :: datapath ! input datapath + character(len=*), intent(in) :: data_type ! data type + integer, intent(in) :: cycle_yr ! cycle year + integer, intent(in) :: fixed_ymd ! fixed year-month-day (YYYYMMDD) [1] + integer, intent(in) :: fixed_tod ! fixed time of day [s] character(len=*), intent(in) :: prescribed_aero_model ! type of aerosol representation character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -100,14 +90,6 @@ subroutine prescribed_aerosol_deposition_flux_init( & errmsg = '' errflg = 0 - filename = filename_nl - filelist = filelist_nl - datapath = datapath_nl - data_type = data_type_nl - cycle_yr = cycle_yr_nl - fixed_ymd = fixed_ymd_nl - fixed_tod = fixed_tod_nl - ! check if user has specified an input dataset if (filename /= 'UNSET' .and. len_trim(filename) > 0 .and. filename /= 'NONE') then has_aerodep_flx = .true. @@ -135,15 +117,16 @@ subroutine prescribed_aerosol_deposition_flux_init( & ! count number of specifiers number_flds = 0 - do i = 1, size(specifier_nl) - if(len_trim(specifier_nl(i)) > 0 .and. trim(specifier_nl(i)) /= 'UNSET') then + do i = 1, size(specifier) + ! remove empty or unset specifiers + if(len_trim(specifier(i)) > 0 .and. trim(specifier(i)) /= 'UNSET') then number_flds = number_flds + 1 endif end do ! initialize dataset in tracer_data module. call trcdata_init( & - specifier = specifier_nl(:number_flds), & + specifier = specifier(:number_flds), & filename = filename, & filelist = filelist, & datapath = datapath, & @@ -301,6 +284,7 @@ subroutine prescribed_aerosol_deposition_flux_run( & end subroutine prescribed_aerosol_deposition_flux_run + ! helper subroutine to set fluxes based on target (hardcoded) array and tracer data index subroutine set_fluxes(fluxes, fld_idx) ! host model dependency for history output use cam_history, only: history_out_field diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta index 5007c0be..a7b185be 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta @@ -17,49 +17,49 @@ type = integer dimensions = () intent = in -[ specifier_nl ] +[ specifier ] standard_name = specifier_for_prescribed_aerosol_deposition_fluxes units = none type = character | kind = len=* dimensions = (aerodep_flx_specifier_dimension) intent = in -[ filename_nl ] +[ filename ] standard_name = filename_for_prescribed_aerosol_deposition_fluxes units = none type = character | kind = len=* dimensions = () intent = in -[ filelist_nl ] +[ filelist ] standard_name = filename_of_file_list_for_prescribed_aerosol_deposition_fluxes units = none type = character | kind = len=* dimensions = () intent = in -[ datapath_nl ] +[ datapath ] standard_name = datapath_for_prescribed_aerosol_deposition_fluxes units = none type = character | kind = len=* dimensions = () intent = in -[ data_type_nl ] +[ data_type ] standard_name = time_interpolation_method_for_prescribed_aerosol_deposition_fluxes units = none type = character | kind = len=* dimensions = () intent = in -[ cycle_yr_nl ] +[ cycle_yr ] standard_name = cycle_year_for_prescribed_aerosol_deposition_fluxes units = 1 type = integer dimensions = () intent = in -[ fixed_ymd_nl ] +[ fixed_ymd ] standard_name = fixed_date_for_prescribed_aerosol_deposition_fluxes units = 1 type = integer dimensions = () intent = in -[ fixed_tod_nl ] +[ fixed_tod ] standard_name = fixed_time_of_day_for_prescribed_aerosol_deposition_fluxes units = s type = integer diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index 2f818d87..b7483f14 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -12,8 +12,7 @@ ! If there is no ":" then the specified name is used as both the ! constituent_name and ncdf_fld_name ! -! Original Author: Francis Vitt -! CCPP version: Haipeng Lin, October 2025 +! Based on original CAM version from: Francis Vitt module prescribed_aerosols use ccpp_kinds, only: kind_phys diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index 9fa2acb1..acb1e4d2 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -1,6 +1,7 @@ ! read and provide prescribed ozone for radiation -! Original Author: Francis Vitt -! CCPP version: Haipeng Lin, October 2025 +! this is a simple example of a CCPP scheme using the CAM-SIMA tracer_data utility. +! +! Based on original CAM version from: Francis Vitt module prescribed_ozone use ccpp_kinds, only: kind_phys @@ -27,39 +28,32 @@ module prescribed_ozone logical :: has_prescribed_ozone = .false. character(len=8), parameter :: ozone_name = 'ozone' ! name of the output field - character(len=16) :: fld_name = 'ozone' - character(len=256) :: filename = ' ' - character(len=256) :: filelist = ' ' - character(len=256) :: datapath = ' ' - character(len=32) :: data_type = 'SERIAL' - integer :: cycle_yr = 0 - integer :: fixed_ymd = 0 - integer :: fixed_tod = 0 - contains !> \section arg_table_prescribed_ozone_init Argument Table !! \htmlinclude prescribed_ozone_init.html subroutine prescribed_ozone_init( & amIRoot, iulog, & - fld_name_nl, filename_nl, filelist_nl, datapath_nl, & - data_type_nl, & - cycle_yr_nl, fixed_ymd_nl, fixed_tod_nl, & + fld_name, filename, filelist, datapath, & + data_type, & + cycle_yr, fixed_ymd, fixed_tod, & errmsg, errflg) use cam_history, only: history_add_field use tracer_data, only: trcdata_init - logical, intent(in) :: amIRoot ! MPI root flag - integer, intent(in) :: iulog ! log output unit - character(len=*), intent(in) :: fld_name_nl ! field name from namelist - character(len=*), intent(in) :: filename_nl ! input filename from namelist - character(len=*), intent(in) :: filelist_nl ! input filelist from namelist - character(len=*), intent(in) :: datapath_nl ! input datapath from namelist - character(len=*), intent(in) :: data_type_nl ! data type from namelist - integer, intent(in) :: cycle_yr_nl ! cycle year from namelist - integer, intent(in) :: fixed_ymd_nl ! fixed year-month-day from namelist (YYYYMMDD) [1] - integer, intent(in) :: fixed_tod_nl ! fixed time of day from namelist [s] + logical, intent(in) :: amIRoot + integer, intent(in) :: iulog + + ! input fields from namelist to initialize tracer_data + character(len=*), intent(in) :: fld_name ! field name + character(len=*), intent(in) :: filename ! input filename + character(len=*), intent(in) :: filelist ! input filelist + character(len=*), intent(in) :: datapath ! input datapath + character(len=*), intent(in) :: data_type ! data type + integer, intent(in) :: cycle_yr ! cycle year + integer, intent(in) :: fixed_ymd ! fixed year-month-day (YYYYMMDD) [1] + integer, intent(in) :: fixed_tod ! fixed time of day [s] character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -68,15 +62,6 @@ subroutine prescribed_ozone_init( & errmsg = '' errflg = 0 - fld_name = fld_name_nl - filename = filename_nl - filelist = filelist_nl - datapath = datapath_nl - data_type = data_type_nl - cycle_yr = cycle_yr_nl - fixed_ymd = fixed_ymd_nl - fixed_tod = fixed_tod_nl - ! check if user has specified an input dataset if(filename /= 'UNSET' .and. len_trim(filename) > 0) then has_prescribed_ozone = .true. diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index 3bef566a..0e1e37c2 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -18,49 +18,49 @@ type = integer dimensions = () intent = in -[ fld_name_nl ] +[ fld_name ] standard_name = variable_name_of_ozone_in_file_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in -[ filename_nl ] +[ filename ] standard_name = filename_of_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in -[ filelist_nl ] +[ filelist ] standard_name = filename_of_file_list_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in -[ datapath_nl ] +[ datapath ] standard_name = datapath_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in -[ data_type_nl ] +[ data_type ] standard_name = time_interpolation_method_for_prescribed_ozone units = none type = character | kind = len=* dimensions = () intent = in -[ cycle_yr_nl ] +[ cycle_yr ] standard_name = cycle_year_for_prescribed_ozone units = 1 type = integer dimensions = () intent = in -[ fixed_ymd_nl ] +[ fixed_ymd ] standard_name = fixed_date_for_prescribed_ozone units = 1 type = integer dimensions = () intent = in -[ fixed_tod_nl ] +[ fixed_tod ] standard_name = fixed_time_of_day_for_prescribed_ozone units = s type = integer From 4b4aee9d25fe088a4d26d5965cb56c6fe9bf8334 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 26 Dec 2025 12:10:39 -0500 Subject: [PATCH 09/19] Update schemes/chemistry/prescribed_aerosols_namelist.xml Co-authored-by: Jesse Nusbaumer --- schemes/chemistry/prescribed_aerosols_namelist.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index 77d940d2..6edeb09b 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -115,7 +115,7 @@ cycle_year_for_prescribed_aerosols 1 - The cycle year of the prescribed ozone data if prescribed_aero_type is 'CYCLICAL'. + The cycle year of the prescribed aerosol data if prescribed_aero_type is 'CYCLICAL'. Format: YYYY Default: 2000 From 3e6b793632f9c8d83a2c09c7fe34b20281a1f8da Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 26 Dec 2025 12:10:59 -0500 Subject: [PATCH 10/19] Update schemes/chemistry/prescribed_aerosols_namelist.xml Co-authored-by: Jesse Nusbaumer --- schemes/chemistry/prescribed_aerosols_namelist.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index 6edeb09b..5da25fbb 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -131,7 +131,7 @@ fixed_date_for_prescribed_aerosols 1 - The date at which the prescribed ozone data is fixed if prescribed_ozone_type is 'FIXED'. + The date at which the prescribed aerosol data is fixed if prescribed_aero_type is 'FIXED'. Format: YYYYMMDD Default: 0 From e77053792743c4d8c57f1e1a8e53b19c9c143962 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 26 Dec 2025 12:11:19 -0500 Subject: [PATCH 11/19] Update schemes/chemistry/prescribed_aerosols_namelist.xml Co-authored-by: Jesse Nusbaumer --- schemes/chemistry/prescribed_aerosols_namelist.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index 5da25fbb..e369c05e 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -147,7 +147,7 @@ fixed_time_of_day_for_prescribed_aerosols s - The time of day (seconds) corresponding to prescribed_aero_fixed_ymd at which the prescribed ozone data is fixed if prescribed_aero_type is 'FIXED'. + The time of day (seconds) corresponding to prescribed_aero_fixed_ymd at which the prescribed aerosol data is fixed if prescribed_aero_type is 'FIXED'. Default: 0 seconds From d3fdc5659d8e307ee6da7a553820ab539cf10606 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 26 Dec 2025 12:15:32 -0500 Subject: [PATCH 12/19] Address review comments (1) --- .../prescribed_aerosol_deposition_flux.F90 | 17 +++-- .../prescribed_aerosol_deposition_flux.meta | 4 +- schemes/chemistry/prescribed_aerosols.F90 | 72 ++++++++++--------- schemes/chemistry/prescribed_aerosols.meta | 7 +- schemes/chemistry/prescribed_ozone.F90 | 14 ++-- schemes/chemistry/prescribed_ozone.meta | 5 +- 6 files changed, 62 insertions(+), 57 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 index 5a2ba6e0..0714126a 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 @@ -81,7 +81,7 @@ subroutine prescribed_aerosol_deposition_flux_init( & integer, intent(in) :: fixed_ymd ! fixed year-month-day (YYYYMMDD) [1] integer, intent(in) :: fixed_tod ! fixed time of day [s] character(len=*), intent(in) :: prescribed_aero_model ! type of aerosol representation - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg integer :: i, ndx, number_flds @@ -91,7 +91,7 @@ subroutine prescribed_aerosol_deposition_flux_init( & errflg = 0 ! check if user has specified an input dataset - if (filename /= 'UNSET' .and. len_trim(filename) > 0 .and. filename /= 'NONE') then + if (filename /= 'UNSET' .and. len_trim(filename) > 0) then has_aerodep_flx = .true. if (amIRoot) then @@ -117,12 +117,15 @@ subroutine prescribed_aerosol_deposition_flux_init( & ! count number of specifiers number_flds = 0 - do i = 1, size(specifier) + specifier_count_loop: do i = 1, size(specifier) ! remove empty or unset specifiers if(len_trim(specifier(i)) > 0 .and. trim(specifier(i)) /= 'UNSET') then number_flds = number_flds + 1 - endif - end do + else + ! Assume all remaining specifier entries are also not set. + exit specifier_count_loop + end if + end do specifier_count_loop ! initialize dataset in tracer_data module. call trcdata_init( & @@ -229,7 +232,7 @@ subroutine prescribed_aerosol_deposition_flux_run( & real(kind_phys), intent(out) :: dst3wet(:) ! wet deposition of dust bin 3 [kg m-2 s-1] real(kind_phys), intent(out) :: dst4wet(:) ! wet deposition of dust bin 4 [kg m-2 s-1] - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg errmsg = '' @@ -280,7 +283,7 @@ subroutine prescribed_aerosol_deposition_flux_run( & call set_fluxes(dst2wet, idst2wet) call set_fluxes(dst3wet, idst3wet) call set_fluxes(dst4wet, idst4wet) - endif + end if end subroutine prescribed_aerosol_deposition_flux_run diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta index a7b185be..7f932c58 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.meta +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.meta @@ -74,7 +74,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] @@ -204,7 +204,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index b7483f14..5f298ae5 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -61,8 +61,8 @@ module prescribed_aerosols ! module state variables logical :: has_prescribed_aerosols = .false. logical :: clim_modal_aero = .false. - integer :: aero_cnt ! # of aerosol constituents - integer :: aero_cnt_c ! # of cloud-borne species (for modal aerosols only) + integer :: aero_count ! # of aerosol constituents + integer :: aero_count_c ! # of cloud-borne species (for modal aerosols only) ! Normal random number which persists from one timestep to the next ! (used for modal aerosol sampling) @@ -109,7 +109,7 @@ subroutine prescribed_aerosols_register( & ! Output arguments: type(ccpp_constituent_properties_t), allocatable, intent(out) :: aerosol_constituents(:) ! prescribed aero runtime CCPP constituents - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg ! Local variables: @@ -145,12 +145,12 @@ subroutine prescribed_aerosols_register( & ! Parse the aerosol format specifier from namelist into mapping ddt. ! We need two scans. First to determine the count, the second to populate ! the information into the ddt. - aero_cnt = 0 - aero_cnt_c = 0 ! cloud borne species count - cnt_loop: do i = 1, N_AERO_MAX + aero_count = 0 + aero_count_c = 0 ! cloud borne species count + count_loop: do i = 1, N_AERO_MAX ! FIXME: should I be responsible for handling this? I feel like I should not handle this if(prescribed_aero_specifier(i) == 'UNSET' .or. & - len_trim(prescribed_aero_specifier(i)) == 0) exit cnt_loop + len_trim(prescribed_aero_specifier(i)) == 0) exit count_loop skip_spec = .false. if(clim_modal_aero) then @@ -159,31 +159,32 @@ subroutine prescribed_aerosols_register( & ! soa_a1_logm and soa_a1_logv). Therefore, only *_logm and *_logv and cloud ! borne (*_c) species are specified in the build-namelist. - ! In the following cnt_loop, we will count the cloud borne species and *_logm species + ! In the following count_loop, we will count the cloud borne species and *_logm species ! (in lieu of *_a species). We will skip *_logv species. - ! This will ensure that aero_cnt variable is the sum of cloud borne and + ! This will ensure that aero_count variable is the sum of cloud borne and ! interstitial species (which we will manually add in the names to the ddt later). ! We are also counting cloud borne (*_c) species which will help ! adding the same number of interstitial species to the ddt. ! ! For modal aerosols, skip counting species ending with *_logv - if(index(prescribed_aero_specifier(i),'_c') >= 1) aero_cnt_c = aero_cnt_c + 1 + if(index(prescribed_aero_specifier(i),'_c') >= 1) aero_count_c = aero_count_c + 1 if(index(prescribed_aero_specifier(i),'_logv') >= 1) skip_spec = .true. - endif + end if - if(.not. skip_spec) aero_cnt = aero_cnt + 1 - end do cnt_loop + if(.not. skip_spec) aero_count = aero_count + 1 + end do count_loop - if(aero_cnt == 0) then + if(aero_count == 0) then has_prescribed_aerosols = .false. return - endif + end if has_prescribed_aerosols = .true. ! Allocate mapping list of ddt - allocate(aero_map_list(aero_cnt), stat=errflg, errmsg=errmsg) + allocate(aero_map_list(aero_count), stat=errflg, errmsg=errmsg) if(errflg /= 0) then + errmsg = subname // ": " // trim(errmsg) return end if @@ -253,23 +254,26 @@ subroutine prescribed_aerosols_register( & end do ddt_loop ! Sanity check - if(aero_idx /= aero_cnt+1) then + if(aero_idx /= aero_count+1) then errflg = 1 - write(errmsg,*) subname//': consistency check 1 failure; at the end of ddt allocation, aero_idx is not aero_cnt+1', aero_idx, aero_cnt + write(errmsg,*) subname//': consistency check 1 failure; at the end of ddt allocation, aero_idx is not aero_count+1', aero_idx, aero_count return end if ! Allocate CCPP dynamic constituents object for prescribed aerosols. - allocate(aerosol_constituents(aero_cnt), stat=errflg, errmsg=errmsg) - if (errflg /= 0) return + allocate(aerosol_constituents(aero_count), stat=errflg, errmsg=errmsg) + if (errflg /= 0) then + errmsg = subname // ": " // trim(errmsg) + return + end if ! Now register constituents in the CCPP constituent properties object. - reg_loop: do i = 1, aero_cnt + reg_loop: do i = 1, aero_count ! check units. at this point, we do not know the units from file ! because tracer_data has not read any data yet. - ! number concentrations are units of 1 kg-1; all others are kg kg-1 + ! number concentrations are units of (1) kg-1; all others are kg kg-1 if(index(aero_map_list(i)%constituent_name, 'num_') == 1) then - unit_name = '1 kg-1' + unit_name = 'kg-1' else unit_name = 'kg kg-1' end if @@ -289,7 +293,7 @@ subroutine prescribed_aerosols_register( & end do reg_loop if (amIRoot) then - write(iulog,*) trim(subname)//': Registered ', aero_cnt, ' prescribed aerosol constituents' + write(iulog,*) trim(subname)//': Registered ', aero_count, ' prescribed aerosol constituents' end if end subroutine prescribed_aerosols_register @@ -328,7 +332,7 @@ subroutine prescribed_aerosols_init( & integer, intent(in) :: prescribed_aero_fixed_tod ! fixed time of day from namelist [s] ! Output arguments: - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg ! Local variables: @@ -351,7 +355,7 @@ subroutine prescribed_aerosols_init( & ! Initialize tracer_data module with file and field information call trcdata_init( & - specifier = prescribed_aero_specifier(:aero_cnt), & + specifier = prescribed_aero_specifier(:aero_count), & filename = prescribed_aero_file, & filelist = prescribed_aero_filelist, & datapath = prescribed_aero_datapath, & @@ -371,10 +375,10 @@ subroutine prescribed_aerosols_init( & ! Note: because in modal aerosols, interstitial fields are derived from the ! log-mean and log-variance (logm, logv) fields, the number of tracer_data - ! fields is not equal to aero_cnt(= a + c), but (logm + logv) + c. + ! fields is not equal to aero_count(= a + c), but (logm + logv) + c. ! Based on aero_map_list, scan the tracer data fields to populate correct field_index - do i = 1, aero_cnt + do i = 1, aero_count ! Find the matching field in tracer_data_fields for the primary field do idx = 1, size(tracer_data_fields) if (trim(tracer_data_fields(idx)%fldnam) == trim(aero_map_list(i)%trcdata_field_name)) then @@ -397,7 +401,7 @@ subroutine prescribed_aerosols_init( & ! Check aero_map_list for any unpopulated field indices (consistency check), ! Register history field, and ! Print out each aero_map_list field information - do i = 1, aero_cnt + do i = 1, aero_count if (aero_map_list(i)%field_index <= 0) then errflg = 1 write(errmsg, '(3a)') trim(subname), ': Field not found in tracer_data for constituent: ', & @@ -408,9 +412,9 @@ subroutine prescribed_aerosols_init( & ! Add history field ! Check units. at this point, we do not know the units from file ! because tracer_data has not read any data yet. - ! number concentrations are units of 1 kg-1; all others are kg kg-1 + ! number concentrations are units of (1) kg-1; all others are kg kg-1 if(index(aero_map_list(i)%constituent_name, 'num_') == 1) then - unit_name = '1 kg-1' + unit_name = 'kg-1' else unit_name = 'kg kg-1' end if @@ -441,7 +445,7 @@ subroutine prescribed_aerosols_init( & end do if (amIRoot) then - write(iulog,*) trim(subname)//': Initialized ', aero_cnt, ' aerosol fields' + write(iulog,*) trim(subname)//': Initialized ', aero_count, ' aerosol fields' end if end subroutine prescribed_aerosols_init @@ -484,7 +488,7 @@ subroutine prescribed_aerosols_run( & real(kind_phys), intent(inout) :: constituents(:,:,:) ! constituent array (ncol, pver, pcnst) [kg kg-1 dry] ! Output arguments: - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg ! Local variables: @@ -509,7 +513,7 @@ subroutine prescribed_aerosols_run( & ! For most species (non-modal; cloud borne) just retrieve data and save to constituent. ! For interstitial species (is_modal_aero_interstitial) construct mixing ratio based on ! the logv and logm values read from tracer_data (port of rand_sample_prescribed_aero) - do i = 1, aero_cnt + do i = 1, aero_count ! Get constituent index call ccpp_const_get_idx(const_props, & trim(aero_map_list(i)%constituent_name), & diff --git a/schemes/chemistry/prescribed_aerosols.meta b/schemes/chemistry/prescribed_aerosols.meta index 97dc83e2..f313484d 100644 --- a/schemes/chemistry/prescribed_aerosols.meta +++ b/schemes/chemistry/prescribed_aerosols.meta @@ -1,7 +1,6 @@ [ccpp-table-properties] name = prescribed_aerosols type = scheme - dependencies = ../../../../utils/tracer_data.F90 [ccpp-arg-table] name = prescribed_aerosols_register @@ -83,7 +82,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] @@ -159,7 +158,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] @@ -235,7 +234,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index acb1e4d2..baa73c2f 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -54,7 +54,7 @@ subroutine prescribed_ozone_init( & integer, intent(in) :: cycle_yr ! cycle year integer, intent(in) :: fixed_ymd ! fixed year-month-day (YYYYMMDD) [1] integer, intent(in) :: fixed_tod ! fixed time of day [s] - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg character(len=32) :: tracer_data_specifier(1) @@ -68,10 +68,10 @@ subroutine prescribed_ozone_init( & if(amIRoot) then write(iulog,*) 'prescribed_ozone_init: ozone is prescribed in: ' // trim(filename) - endif + end if else return - endif + end if ! initialize dataset in tracer_data module. ! construct field specifier - one field @@ -113,14 +113,14 @@ subroutine prescribed_ozone_run( & integer, intent(in) :: pver 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] - real(kind_phys), intent(in) :: t(:,:) ! temperature [K] + real(kind_phys), intent(in) :: t(:,:) ! air temperature [K] real(kind_phys), intent(in) :: pmiddry(:,:) ! dry air pressure [Pa] real(kind_phys), intent(in) :: pmid(:,:) ! air pressure [Pa] 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(:,:) ! height above surface, interfaces [m] + real(kind_phys), intent(in) :: zi(:,:) ! geopotential height above surface, interfaces [m] real(kind_phys), intent(out) :: prescribed_ozone(:,:) ! prescribed ozone mass mixing ratio [kg kg-1 dry] - character(len=512), intent(out) :: errmsg + character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg ! conversion factor to mass mixing ratio (kg kg-1 dry) @@ -134,7 +134,7 @@ subroutine prescribed_ozone_run( & if(.not. has_prescribed_ozone) then return - endif + end if ! advance data in tracer_data to current time. call advance_trcdata(tracer_data_fields, tracer_data_file, & diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index 0e1e37c2..92505ceb 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -1,7 +1,6 @@ [ccpp-table-properties] name = prescribed_ozone type = scheme - dependencies = ../../../../history/cam_history.F90,../../../../utils/tracer_data.F90 [ccpp-arg-table] name = prescribed_ozone_init @@ -69,7 +68,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] @@ -151,7 +150,7 @@ [ errmsg ] standard_name = ccpp_error_message units = none - type = character | kind = len=512 + type = character | kind = len=* dimensions = () intent = out [ errflg ] From 4e68697c1ccf7b79e3ddbf56fdf5f46cd0125b92 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 29 Dec 2025 12:11:20 -0500 Subject: [PATCH 13/19] Address review comments (2): change prescribed_ozone to use constituent --- .../prescribed_aerosol_deposition_flux.F90 | 2 +- schemes/chemistry/prescribed_aerosols.F90 | 15 ++--- schemes/chemistry/prescribed_ozone.F90 | 64 ++++++++++++++----- schemes/chemistry/prescribed_ozone.meta | 16 +++-- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 index 0714126a..f437c1f6 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 @@ -112,7 +112,7 @@ subroutine prescribed_aerosol_deposition_flux_init( & return case default errflg = 1 - errmsg = subname//': unknown aerosol representation' + errmsg = subname//': ERROR: unknown aerosol representation: "'//trim(prescribed_aero_model)//'"' end select ! count number of specifiers diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index 5f298ae5..019fc4eb 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -460,19 +460,18 @@ subroutine prescribed_aerosols_run( & 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 - ! host model dependency for history output - use cam_history, only: history_out_field - - ! Input arguments: integer, intent(in) :: ncol integer, intent(in) :: pver integer, intent(in) :: pcnst ! # of CCPP constituents [count] @@ -484,18 +483,14 @@ subroutine prescribed_aerosols_run( & real(kind_phys), intent(in) :: phis(:) ! surface geopotential [m2 s-2] real(kind_phys), intent(in) :: zi(:,:) ! height above surface, interfaces [m] - ! Input/Output arguments: - real(kind_phys), intent(inout) :: constituents(:,:,:) ! constituent array (ncol, pver, pcnst) [kg kg-1 dry] + real(kind_phys), intent(inout) :: constituents(:,:,:) ! constituent array (ncol, pver, pcnst) - ! Output arguments: character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg - ! Local variables: integer :: i integer :: const_idx - ! Local parameters: character(len=*), parameter :: subname = 'prescribed_aerosols_run' ! Initialize output arguments: diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index baa73c2f..814acf5e 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -26,7 +26,7 @@ module prescribed_ozone ! namelist options logical :: has_prescribed_ozone = .false. - character(len=8), parameter :: ozone_name = 'ozone' ! name of the output field + character(len=8), parameter :: ozone_name = 'O3' ! standard name of the output field contains @@ -100,35 +100,54 @@ end subroutine prescribed_ozone_init !! \htmlinclude prescribed_ozone_run.html subroutine prescribed_ozone_run( & ncol, pver, & + const_props, & mwdry, boltz, & t, pmiddry, & pmid, pint, phis, zi, & ! necessary fields for trcdata read. - prescribed_ozone, & + 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 - integer, intent(in) :: ncol - integer, intent(in) :: pver - 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] - real(kind_phys), intent(in) :: t(:,:) ! air temperature [K] - real(kind_phys), intent(in) :: pmiddry(:,:) ! dry air pressure [Pa] - real(kind_phys), intent(in) :: pmid(:,:) ! air pressure [Pa] - 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 above surface, interfaces [m] - real(kind_phys), intent(out) :: prescribed_ozone(:,:) ! prescribed ozone mass mixing ratio [kg kg-1 dry] - character(len=*), intent(out) :: errmsg - integer, intent(out) :: errflg + ! 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 + + integer, intent(in) :: ncol + integer, intent(in) :: pver + type(ccpp_constituent_prop_ptr_t), & + intent(in) :: const_props(:) ! CCPP constituent properties pointer + 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] + real(kind_phys), intent(in) :: t(:,:) ! air temperature [K] + real(kind_phys), intent(in) :: pmiddry(:,:) ! dry air pressure [Pa] + real(kind_phys), intent(in) :: pmid(:,:) ! air pressure [Pa] + 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 above surface, interfaces [m] + + real(kind_phys), intent(inout) :: constituents(:,:,:) ! constituent array (ncol, pver, pcnst) + + character(len=*), intent(out) :: errmsg + integer, intent(out) :: errflg ! conversion factor to mass mixing ratio (kg kg-1 dry) real(kind_phys) :: to_mmr(ncol, pver) + ! prescribed ozone mass mixing ratio [kg kg-1 dry] + real(kind_phys) :: prescribed_ozone(:,:) + ! units from file character(len=32) :: units_str + integer :: id_o3 + errmsg = '' errflg = 0 @@ -136,6 +155,18 @@ subroutine prescribed_ozone_run( & return end if + ! check for 'O3' constituent where prescribed ozone will be written to + ! which will be read by radiation. + call ccpp_const_get_idx(const_props, & + trim(ozone_name), & + id_o3, errmsg, errflg) + if (errflg /= 0) return + + ! could not find the constituent. + if (id_o3 < 0) then + return + end if + ! advance data in tracer_data to current time. call advance_trcdata(tracer_data_fields, tracer_data_file, & pmid, pint, phis, zi) @@ -162,6 +193,9 @@ subroutine prescribed_ozone_run( & ! convert to kg kg-1 (dry) prescribed_ozone = to_mmr * prescribed_ozone + ! write to constituent array + constituents(:ncol, :pver, id_o3) = prescribed_ozone + ! convert to mol mol-1 (dry) only for diagnostic output call history_out_field('ozone', prescribed_ozone(:ncol,:pver)*(mwdry/ozone_mw)) diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index 92505ceb..b0e404c8 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -93,6 +93,12 @@ 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 @@ -141,12 +147,12 @@ type = real | kind = kind_phys dimensions = (horizontal_loop_extent, vertical_interface_dimension) intent = in -[ prescribed_ozone ] - standard_name = O3 - units = kg kg-1 +[ constituents ] + standard_name = ccpp_constituents + units = none type = real | kind = kind_phys - dimensions = (horizontal_loop_extent, vertical_layer_dimension) - intent = out + dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents) + intent = inout [ errmsg ] standard_name = ccpp_error_message units = none From b758bc234293935edc90260eb76f6ff51ac83be1 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 29 Dec 2025 12:30:33 -0500 Subject: [PATCH 14/19] Fix deferred shape --- schemes/chemistry/prescribed_ozone.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index 814acf5e..f38f7c0e 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -26,7 +26,7 @@ module prescribed_ozone ! namelist options logical :: has_prescribed_ozone = .false. - character(len=8), parameter :: ozone_name = 'O3' ! standard name of the output field + character(len=8), parameter :: ozone_name = 'o3' ! standard name of the output field contains @@ -141,7 +141,7 @@ subroutine prescribed_ozone_run( & real(kind_phys) :: to_mmr(ncol, pver) ! prescribed ozone mass mixing ratio [kg kg-1 dry] - real(kind_phys) :: prescribed_ozone(:,:) + real(kind_phys) :: prescribed_ozone(ncol, pver) ! units from file character(len=32) :: units_str From 784bdcce5efe1b89d3180379d73ba712682570cb Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 29 Dec 2025 15:19:02 -0500 Subject: [PATCH 15/19] Address review comments (3) --- .../prescribed_aerosol_deposition_flux.F90 | 6 +- ...ribed_aerosol_deposition_flux_namelist.xml | 72 +++++++++++++++++++ schemes/chemistry/prescribed_aerosols.F90 | 27 ++++--- .../prescribed_aerosols_namelist.xml | 72 +++++++++++++++++++ .../chemistry/prescribed_ozone_namelist.xml | 72 +++++++++++++++++++ 5 files changed, 232 insertions(+), 17 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 index f437c1f6..6868ee1b 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 @@ -8,8 +8,6 @@ ! ! Based on original CAM version from: Francis Vitt module prescribed_aerosol_deposition_flux - 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. @@ -206,6 +204,8 @@ subroutine prescribed_aerosol_deposition_flux_run( & dst1wet, dst2wet, dst3wet, dst4wet, & errmsg, errflg) + use ccpp_kinds, only: kind_phys + use tracer_data, only: advance_trcdata use cam_history, only: history_out_field @@ -289,6 +289,8 @@ end subroutine prescribed_aerosol_deposition_flux_run ! helper subroutine to set fluxes based on target (hardcoded) array and tracer data index subroutine set_fluxes(fluxes, fld_idx) + use ccpp_kinds, only: kind_phys + ! host model dependency for history output use cam_history, only: history_out_field diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml b/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml index 299bc78e..3e421355 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux_namelist.xml @@ -3,6 +3,78 @@ + + char*256 chemistry diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index 019fc4eb..d39e882c 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -29,10 +29,6 @@ module prescribed_aerosols public :: prescribed_aerosols_init public :: prescribed_aerosols_run - ! maximum of specified aerosol fields - ! if need to be expanded, need to change namelist definition as well. - integer, parameter :: N_AERO_MAX = 50 - ! fields to store tracer_data state and information type(trfld), pointer :: tracer_data_fields(:) type(trfile) :: tracer_data_file @@ -64,6 +60,9 @@ module prescribed_aerosols integer :: aero_count ! # of aerosol constituents integer :: aero_count_c ! # of cloud-borne species (for modal aerosols only) + ! maximum of specified aerosol fields (as counted from namelist entries) + integer :: n_aero_max = -1 + ! Normal random number which persists from one timestep to the next ! (used for modal aerosol sampling) real(kind_phys) :: randn_persists = 0.0_kind_phys @@ -145,13 +144,14 @@ subroutine prescribed_aerosols_register( & ! Parse the aerosol format specifier from namelist into mapping ddt. ! We need two scans. First to determine the count, the second to populate ! the information into the ddt. + ! + ! Determine number of non-empty/non-UNSET elements in the namelist specifier + n_aero_max = count((prescribed_aero_specifier /= 'UNSET') .and. & + (len_trim(prescribed_aero_specifier) > 0)) + aero_count = 0 aero_count_c = 0 ! cloud borne species count - count_loop: do i = 1, N_AERO_MAX - ! FIXME: should I be responsible for handling this? I feel like I should not handle this - if(prescribed_aero_specifier(i) == 'UNSET' .or. & - len_trim(prescribed_aero_specifier(i)) == 0) exit count_loop - + count_loop: do i = 1, n_aero_max skip_spec = .false. if(clim_modal_aero) then ! For modal aerosols, interstitial species (*_a) are diagnosed from @@ -190,17 +190,13 @@ subroutine prescribed_aerosols_register( & ! Now populate the information into the ddt. ! - ! In CAM modal aerosols, the interstitial species (_a) are added at the end of the cloud borne (_c) species. - ! TODO hplin 10/22/25 We might not need this? Fields were identified by their pbuf name anyway + ! In CAM modal aerosols, the interstitial species (_a) are added at the end of the cloud borne (_c) species. This ordering is not needed in SIMA. aero_idx = 1 ! pointer for current aero_map_list index. - ddt_loop: do i = 1, N_AERO_MAX + ddt_loop: do i = 1, n_aero_max ! Parse specifier tmpstr = trim(adjustl(prescribed_aero_specifier(i))) - ! FIXME: should I be responsible for handling this? I feel like I should not handle this - if(tmpstr == 'UNSET' .or. len_trim(tmpstr) == 0) exit ddt_loop - idx = index(tmpstr, ':') if(idx > 0) then ! format as constituent_name:ncdf_name @@ -233,6 +229,7 @@ subroutine prescribed_aerosols_register( & ! The tracer data specifier index will be rescanned once tracer_data_fields is initialized ! at the init phase. + aero_map_list(aero_idx)%field_index = -1 aero_map_list(aero_idx)%field_index_logv = -1 end if ! logm end if ! clim_modal_aero diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index e369c05e..2a6ac8e9 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -3,6 +3,78 @@ + + char*256 chemistry diff --git a/schemes/chemistry/prescribed_ozone_namelist.xml b/schemes/chemistry/prescribed_ozone_namelist.xml index 761f6d22..835c0123 100644 --- a/schemes/chemistry/prescribed_ozone_namelist.xml +++ b/schemes/chemistry/prescribed_ozone_namelist.xml @@ -3,6 +3,78 @@ + + char*256 chemistry From 39668cafb6f7a50dd5a67100682a3af55ccba119 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 5 Jan 2026 17:39:44 -0500 Subject: [PATCH 16/19] Change o3 to O3 now that it is supported --- schemes/chemistry/prescribed_ozone.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index f38f7c0e..82f0411b 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -26,7 +26,7 @@ module prescribed_ozone ! namelist options logical :: has_prescribed_ozone = .false. - character(len=8), parameter :: ozone_name = 'o3' ! standard name of the output field + character(len=8), parameter :: ozone_name = 'O3' ! standard name of the output field contains From 6c3f3fa4dcdb4fbd687e2d8fe30170e04164b147 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Tue, 6 Jan 2026 12:40:52 -0500 Subject: [PATCH 17/19] Register ozone in register phase for prescribed_ozone; add ccpp_chem_utils for qmin --- schemes/chemistry/prescribed_aerosols.F90 | 7 +- schemes/chemistry/prescribed_aerosols.meta | 1 + schemes/chemistry/prescribed_ozone.F90 | 77 ++++++++++++++++++---- schemes/chemistry/prescribed_ozone.meta | 43 ++++++++++++ to_be_ccppized/ccpp_chem_utils.F90 | 45 +++++++++++++ 5 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 to_be_ccppized/ccpp_chem_utils.F90 diff --git a/schemes/chemistry/prescribed_aerosols.F90 b/schemes/chemistry/prescribed_aerosols.F90 index d39e882c..4c474b6d 100644 --- a/schemes/chemistry/prescribed_aerosols.F90 +++ b/schemes/chemistry/prescribed_aerosols.F90 @@ -93,6 +93,8 @@ subroutine prescribed_aerosols_register( & use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + use ccpp_chem_utils, only: chem_constituent_qmin + ! Input arguments: logical, intent(in) :: amIRoot integer, intent(in) :: iulog @@ -122,6 +124,7 @@ subroutine prescribed_aerosols_register( & integer :: i, j logical :: is_modal_aero_interstitial logical :: skip_spec + real(kind_phys) :: qmin character(len=*), parameter :: subname = 'prescribed_aerosols_register' @@ -275,12 +278,14 @@ subroutine prescribed_aerosols_register( & unit_name = 'kg kg-1' end if + qmin = chem_constituent_qmin(aero_map_list(i)%constituent_name) + call aerosol_constituents(i)%instantiate( & std_name = trim(aero_map_list(i)%constituent_name), & long_name = 'prescribed aerosol '//trim(aero_map_list(i)%constituent_name), & units = unit_name, & vertical_dim = 'vertical_layer_dimension', & - min_value = 0.0_kind_phys, & + min_value = qmin, & advected = .false., & water_species = .false., & mixing_ratio_type = 'dry', & diff --git a/schemes/chemistry/prescribed_aerosols.meta b/schemes/chemistry/prescribed_aerosols.meta index f313484d..eec2debe 100644 --- a/schemes/chemistry/prescribed_aerosols.meta +++ b/schemes/chemistry/prescribed_aerosols.meta @@ -1,6 +1,7 @@ [ccpp-table-properties] name = prescribed_aerosols type = scheme + dependencies = ../../to_be_ccppized/ccpp_chem_utils.F90 [ccpp-arg-table] name = prescribed_aerosols_register diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index 82f0411b..7bd58f62 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -13,6 +13,7 @@ module prescribed_ozone private ! public CCPP-compliant subroutines + public :: prescribed_ozone_register public :: prescribed_ozone_init public :: prescribed_ozone_run @@ -30,6 +31,66 @@ module prescribed_ozone contains + ! Register the prescribed ozone constituent in the CCPP constituent properties object. +!> \section arg_table_prescribed_ozone_register Argument Table +!! \htmlinclude prescribed_ozone_register.html + subroutine prescribed_ozone_register( & + amIRoot, iulog, & + filename, & + ozone_constituents, & + errmsg, errflg) + + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + + use ccpp_chem_utils, only: chem_constituent_qmin + + logical, intent(in) :: amIRoot + integer, intent(in) :: iulog + character(len=*), intent(in) :: filename ! input filename + + ! prescribed ozone runtime CCPP constituent + type(ccpp_constituent_properties_t), allocatable, intent(out) :: ozone_constituents(:) + + character(len=*), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + + ! check if user has specified an input dataset + if(filename /= 'UNSET' .and. len_trim(filename) > 0) then + has_prescribed_ozone = .true. + + if(amIRoot) then + write(iulog,*) 'prescribed_ozone_init: ozone is prescribed in: ' // trim(filename) + end if + else + return + end if + + ! allocate CCPP dynamic constituents object for prescribed ozone. + ! if we are prescribing ozone, this module is responsible for registering the constituent. + allocate(ozone_constituents(1), stat=errflg, errmsg=errmsg) + if (errflg /= 0) then + errmsg = "prescribed_ozone_register: " // trim(errmsg) + return + end if + + call ozone_constituents(i)%instantiate( & + std_name = trim(ozone_name), & + long_name = 'prescribed ozone (O3)', & + units = 'kg kg-1', & + vertical_dim = 'vertical_layer_dimension', & + min_value = chem_constituent_qmin(trim(ozone_name)), & + advected = .false., & + water_species = .false., & + mixing_ratio_type = 'dry', & + errcode = errflg, & + errmsg = errmsg) + if(errflg /= 0) return + + end subroutine prescribed_ozone_register + !> \section arg_table_prescribed_ozone_init Argument Table !! \htmlinclude prescribed_ozone_init.html subroutine prescribed_ozone_init( & @@ -62,16 +123,7 @@ subroutine prescribed_ozone_init( & errmsg = '' errflg = 0 - ! check if user has specified an input dataset - if(filename /= 'UNSET' .and. len_trim(filename) > 0) then - has_prescribed_ozone = .true. - - if(amIRoot) then - write(iulog,*) 'prescribed_ozone_init: ozone is prescribed in: ' // trim(filename) - end if - else - return - end if + if(.not. has_prescribed_ozone) return ! initialize dataset in tracer_data module. ! construct field specifier - one field @@ -162,8 +214,11 @@ subroutine prescribed_ozone_run( & id_o3, errmsg, errflg) if (errflg /= 0) return - ! could not find the constituent. + ! could not find the constituent, but the specifier is active. + ! throw an error. if (id_o3 < 0) then + errmsg = 'prescribed_ozone: could not find constituent ' // trim(ozone_name) + errflg = 1 return end if diff --git a/schemes/chemistry/prescribed_ozone.meta b/schemes/chemistry/prescribed_ozone.meta index b0e404c8..3e0b9d84 100644 --- a/schemes/chemistry/prescribed_ozone.meta +++ b/schemes/chemistry/prescribed_ozone.meta @@ -1,6 +1,49 @@ [ccpp-table-properties] name = prescribed_ozone type = scheme + dependencies = ../../to_be_ccppized/ccpp_chem_utils.F90 + +[ccpp-arg-table] + name = prescribed_ozone_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 +[ filename ] + standard_name = filename_of_prescribed_ozone + units = none + type = character | kind = len=* + dimensions = () + intent = in +[ ozone_constituents ] + # or can this just be the ccpp_constituent_properties? + standard_name = prescribed_ozone_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_ozone_init diff --git a/to_be_ccppized/ccpp_chem_utils.F90 b/to_be_ccppized/ccpp_chem_utils.F90 new file mode 100644 index 00000000..0b2ebec4 --- /dev/null +++ b/to_be_ccppized/ccpp_chem_utils.F90 @@ -0,0 +1,45 @@ +! Various utilities used in CAM-SIMA chemistry. +module ccpp_chem_utils + + implicit none + private + + public :: chem_constituent_qmin + +contains + + ! Returns the minimum mixing ratio for a given constituent + ! Used to set appropriate minimum value for various chemical species at register phase. + pure function chem_constituent_qmin(constituent_name) result(qmin) + use ccpp_kinds, only: kind_phys + + use string_utils, only: to_lower + + character(len=*), intent(in) :: constituent_name ! Name of the chemical constituent + real(kind_phys) :: qmin ! Minimum mixing ratio + + character(len=len(constituent_name)) :: name_lower + integer :: i + + ! Convert to lowercase for case-insensitive comparison + name_lower = to_lower(constituent_name) + + ! Default minimum mixing ratio for chemistry species. + qmin = 1.e-36_kind_phys + + if (index(name_lower, 'num_a') == 1) then + ! Aerosol number density. + qmin = 1.e-5_kind_phys + else if (trim(name_lower) == 'o3') then + qmin = 1.e-12_kind_phys + else if (trim(name_lower) == 'ch4') then + qmin = 1.e-12_kind_phys + else if (trim(name_lower) == 'n2o') then + qmin = 1.e-15_kind_phys + else if (trim(name_lower) == 'cfc11' .or. trim(name_lower) == 'cfc12') then + qmin = 1.e-20_kind_phys + end if + + end function chem_constituent_qmin + +end module ccpp_chem_utils From e9b5bfd30659a7197d6cb86e35c4daa5d3fad86a Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 12 Jan 2026 12:37:41 -0500 Subject: [PATCH 18/19] Address review comments --- schemes/chemistry/prescribed_aerosol_deposition_flux.F90 | 4 ++-- schemes/chemistry/prescribed_aerosols_namelist.xml | 5 +++-- schemes/chemistry/prescribed_ozone.F90 | 2 +- to_be_ccppized/ccpp_chem_utils.F90 | 5 ++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 index 6868ee1b..181aa283 100644 --- a/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 +++ b/schemes/chemistry/prescribed_aerosol_deposition_flux.F90 @@ -168,8 +168,8 @@ subroutine prescribed_aerosol_deposition_flux_init( & ! if all else fails errflg = 1 - errmsg = 'prescribed_aerosol_deposition_flux_init: aerosol flux name not recognized: ' & - //trim(tracer_data_fields(i)%fldnam) + errmsg = 'prescribed_aerosol_deposition_flux_init: aerosol flux name not recognized: "' & + //trim(tracer_data_fields(i)%fldnam)//'"' return end if end do diff --git a/schemes/chemistry/prescribed_aerosols_namelist.xml b/schemes/chemistry/prescribed_aerosols_namelist.xml index 2a6ac8e9..878043b6 100644 --- a/schemes/chemistry/prescribed_aerosols_namelist.xml +++ b/schemes/chemistry/prescribed_aerosols_namelist.xml @@ -93,8 +93,9 @@ + + Modifications on the host model side are necessary to set specifier values + based on the value in this namelist option. For now they are set manually. --> char*5 chemistry diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index 7bd58f62..be21a461 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -76,7 +76,7 @@ subroutine prescribed_ozone_register( & return end if - call ozone_constituents(i)%instantiate( & + call ozone_constituents(1)%instantiate( & std_name = trim(ozone_name), & long_name = 'prescribed ozone (O3)', & units = 'kg kg-1', & diff --git a/to_be_ccppized/ccpp_chem_utils.F90 b/to_be_ccppized/ccpp_chem_utils.F90 index 0b2ebec4..74a1024b 100644 --- a/to_be_ccppized/ccpp_chem_utils.F90 +++ b/to_be_ccppized/ccpp_chem_utils.F90 @@ -10,7 +10,7 @@ module ccpp_chem_utils ! Returns the minimum mixing ratio for a given constituent ! Used to set appropriate minimum value for various chemical species at register phase. - pure function chem_constituent_qmin(constituent_name) result(qmin) + function chem_constituent_qmin(constituent_name) result(qmin) use ccpp_kinds, only: kind_phys use string_utils, only: to_lower @@ -19,10 +19,9 @@ pure function chem_constituent_qmin(constituent_name) result(qmin) real(kind_phys) :: qmin ! Minimum mixing ratio character(len=len(constituent_name)) :: name_lower - integer :: i ! Convert to lowercase for case-insensitive comparison - name_lower = to_lower(constituent_name) + name_lower = to_lower(constituent_name) ! impure ! Default minimum mixing ratio for chemistry species. qmin = 1.e-36_kind_phys From b927f9f73143abd8d2749aba1df006b01305ad9e Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Tue, 13 Jan 2026 15:43:36 -0500 Subject: [PATCH 19/19] Update schemes/chemistry/prescribed_ozone.F90 Co-authored-by: Jesse Nusbaumer --- schemes/chemistry/prescribed_ozone.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemes/chemistry/prescribed_ozone.F90 b/schemes/chemistry/prescribed_ozone.F90 index be21a461..21c4fd7d 100644 --- a/schemes/chemistry/prescribed_ozone.F90 +++ b/schemes/chemistry/prescribed_ozone.F90 @@ -62,7 +62,7 @@ subroutine prescribed_ozone_register( & has_prescribed_ozone = .true. if(amIRoot) then - write(iulog,*) 'prescribed_ozone_init: ozone is prescribed in: ' // trim(filename) + write(iulog,*) 'prescribed_ozone_register: ozone is prescribed in: ' // trim(filename) end if else return